Advanced

Как создать свой компонент

Вы можете создать свой компонент на основе любого из существующих.

Библиотека позволяет расширить любой компонент определенными миксинами, или создать кастомный виджет на основе исходного компонента view.

Метод protoUI позволяет создать новый компонент с отдельным названием, свойствами, методами и событиями. Он принимает несколько объектов Webix в качестве параметров, объединяет их и возвращает новый компонент на их основе. Пользовательские компоненты, также как и встроенные, создаются в соответствии с общими правилами.

Пример создания компонента

Для примера, давайте создадим новый компонент на основе виджета list и сделаем его редактируемым с помощью миксина EditAbility:

Редактируемый компонент List

webix.protoUI({
    name:"editlist"
}, webix.ui.list, webix.EditAbility);
 
// используем новый компонент
webix.ui({
    view:"editlist",
    // … другие настройки и данные
});

Компоненту следует присвоит соответствующее имя, с помощью которого вы сможете его инициализировать. Имя должно быть уникальным. Если ваше название будет совпадать с названием уже существующего компонента, то новый компонент перезапишет конфигурации уже существующего. В дальнейшем это может привести к ошибке, поскольку некоторые компоненты используются в других более сложных виджетах.

Related sample:  List: Editing

Порядок наследования

Если вы создаете компонент на основе нескольких миксинов/модулей, то вам стоит учитывать порядок их наследования. Дело в том, что конфигурации одного миксина могут переопределять конфигурации другого. Смотрите пример ниже:

webix.protoUI({
    name:"mytext",
    // ваша логика
}, webix.ui.text, mixin2, mixin1);

Порядок наследования идет от наименее важного миксина/модуля в самом начале списка аргументов, до наиболее значимого в самом конце. Функционал первого элемента может быть перезаписан следующими в порядке наследования миксинами/модулями:

  • webix.ui.text
  • mixin2
  • mixin1
  • ваша логика в "mytext".

Вы можете переопределить какой либо метод модуля webix.ui.text в каждом из миксинов и в кастомной логике компонента "mytext". В конечном итоге, вы получите кастомный метод, поскольку его приоритет наследования выше остальных.

Related sample:  Custom Widget: The Order of Inheritance

Структура компонента

У кастомных и встроенных компонентов очень похожая структура. Пользовательский компонент может включать как обязательные так и необязательные поля:

webix.protoUI({
    name:"some",
    $init:function(){}, // действия при первой загрузке
    $cssName, // верхний класс 
    defaults:{}, // настройки по умолчанию
    'property'_setter, // функция для обработки параметров конфигурации
    $getSize, // как виджет вычисляет нужные размеры
    $setSize, // как виджет устанавливает размеры
    // другие свойства
}, webix.ui.list);

Пользовательское API

Дополнительно API компонента может включать методы для управления жизненным циклом ($init, $ready), а также другие методы и свойства. Стоит учитывать, что свойства прототипа компонента и параметры его конфигурации — это не одно и то же.

Это параметры конфигурации:

webix.ui({
    view:"button",
    id:"b1",
    value:"Save"
});
 
var value = $$("b1").config.value;

Это свойство прототипа компонента:

$$("b1").flag = true;

$init

Метод $init выполняет роль конструктора компонента. Внутри этого метода вы можете определить логику, которая будет выполнена до инициализации компонента (например, изменить значения настроек по умолчанию или задать обработчики событий).

Стоит учитывать, что во время инициализации конкретного компонента, Webix вызывает метод $init у всех его родителей. Давайте добавим конфигурацию editable:true новому компоненту editlist:

webix.protoUI({
    name:"editlist",
    $init:function(config){
       config.editable = true;
    }
}, webix.ui.list, webix.EditAbility);

Меняйте конфигурации через метод $init, а не через динамические методы

Например, если вы хотите добавить несколько рядов или столбцов для компонента на основе лейаута, лучше определить их через метод $init при инициализации:

$init:function(config){
    config.cols = [
        {  template:"Column 1" }, { template:"Column 2"}
    ]
}

а не через метод addView() после нее. Метод $init более эффективный, поскольку не перерисовывает компонент в DOM, как это делает метод addView().

$ready

Если какие либо действия должны произойти сразу после инициализации компонента, их нужно определить в массиве свойства $ready. Создайте свой коллбэк и добавьте его в массив с помощью соответствующих методов push/unshift.

Давайте добавим метод, который будет инициализировать контекстное меню для компонента menuList:

webix.protoUI({
    name:"menuList",
    $init:function(config){
        this.$ready.push(this.initMenu);
    },
    initMenu:function(){
        this.menu = webix.ui({
            view:"contextMenu"
        });
    }
}, webix.ui.list);

Настройки по умолчанию

Для некоторых настроек вы можете определить значения по умолчанию. Сделать это можно в объекте defaults. Здесь лучше всего указывать конфигурации, которые не будут меняться в дальнейшем.

Настройки по умолчанию можно переопределить при создании компонента (его экземпляра), или используя метод define.

Мы не рекомендуем определять значения по умолчанию для свойств, которые работают с данными (url, data, etc.) Давайте создадим новый компонент menuList на основе компонента List и зададим ему конфигурацию select:true по умолчанию:

webix.protoUI({
    name:"menuList",
    defaults:{
        select:true
    }
}, webix.ui.list);

Сеттер для конфигураций

Если вам нужно изменить настройки по умолчанию или кастомные настройки перед их непосредственным применением, вы можете использовать специальные сеттеры.

Сеттеры могут объединять разные варианты настроек. Для примера, определенным параметрам можно присвоить как строковое значение так и массив значений. Вы же можете определить сеттер, который будет возвращать все значения в виде массива, а внутренняя логика будет игнорировать их тип. Название сеттера должно быть таким: {setting name}_setter (например *menu_setter*).

webix.protoUI({
    name:"menuList",
    menu_setter:function(value){
        if (!webix.isArray(value))
            value = [value];
        return value;
    }
}, webix.ui.list);
 
// теперь значения можно задавать и строкой и массивом
webix.ui({ view:"menuList", menu:"One" });
webix.ui({ view:"menuList", menu:["One", "Two"]);

Определение и переопределение методов

Вы можете определять свои методы, а также полностью или частично переопределять методы родительских классов.

Допустим, вы хотите использовать метод List.getVisibleCount() в своем компоненте. Но вам нужно его изменить, не затрагивая основную логику. В этом случае, вы можете вызвать нужный метод у родительского класса List следующим образом:

name:"menuList",
getVisibleCount:function(){
    var count = webix.ui.list.prototype.getVisibleCount.apply(this, arguments);
    return count + 10;
}

Важное замечание

Если вы переопределяете методы, то вам следует помнить о том, что:

  • приватные методы работают только в версии для отладки, поэтому вам не следует переопределять или вызывать их напрямую
  • приватные методы могут вызываться многими публичными методами.

Как установить обработчик на событие

Внутри метода $init кастомного компонента вы можете установить обработчик на любое событие через метод attachEvent():

$init:function(config){
    this.attachEvent("onItemClick", function(id){
        webix.message("Item clicked "+id);
    });
}

Как управлять размерами компонента

У прототипа компонента есть 2 специальных метода $setSize() и $getSize(), которые определяют его размер во время инициализации. Они не предназначены для прямого вызова.

$getSize

Метод $getSize вызывается компонентом для расчета необходимых размеров. Допустим, вы хотите изменить логику расчета размеров для своего компонента. В этом случае, вам следует определить нужную логику и вызвать метод $getSize у родительского компонента (или лейаута, в зависимости от уровня наследования), чтобы убедиться в том, что размеры применяются правильно.

$getSize:function(x, y){
    // кастомная логика
    // ...
    // логика родителя
    return webix.ui.view.prototype.$getSize.call(this, x, y);
}

$setSize

Метод $setSize устанавливает рассчитанные размеры для HTML элементов компонента. Допустим, вы хотите изменить логику этого метода для своего компонента. В этом случае, вам следует проверить, меняет ли размеры метод $setSize родительского класса, и применить кастомную логику.

$setSize:function(x,y){
    // если родитель меняет свой размер
    if (webix.ui.view.prototype.$setSize.call(this, x, y)){
        // кастомная логика
    }
}

Стилизация компонента

1. Как добавить дополнительное название CSS класса

Все виджеты получают CSS класс от виджета, на основе которого они построены. Виджеты также могут наследовать CSS класс от их родительского класса.

Метод $init позволяет добавить свой CSS класс к нужному компоненту. При создании экземпляра такого компонента, его новый класс добавится к HTML элементам вместе с CSS классами по умолчанию.

webix.protoUI({
    name:"menuList",
    $init:function(config){
        this.$view.className += " menu_list";
    }
}, webix.ui.list);
 
// полученный результат
<div class="webix_view webix_list menu_list">...</div>

2. Как добавить CSS класс для контрола

Поведение контролов немного отличается от обычных компонентов. Они не наследуют CSS класс от своих родителей. Названия их классов должно начинаться как "webix_el_" + название самого контрола. Чтобы наследовать CSS класс родителя, вам необходимо указать этот класс через дополнительное свойство $cssName.

webix.protoUI({
    name:"selector",
    $cssName:"combo"
}, webix.ui.combo);
 
// полученный результат
<div class="webix_view webix_control webix_el_combo">...</div>

Полезные советы для работы с HTML контейнерами

В первую очередь, вам нужно ориентироваться на расположение компонентов в лейауте, а не в HTML контейнерах. С помощью Webix виджетов вы можете создать даже самый простой HTML лейаут для своего компонента (спейсеры, шаблоны и другие).

Допустим, ваш компонент должен получить параметры конфигурации от сервера. Для того чтобы дождаться ответа, вы можете использовать специальный помощник webix.ajax. Как только компонент получит необходимые настройки, webix.ui незамедлительно его инициализирует.

webix.protoUI({
    name:"myView",
    $init:function(config){
        webix.ajax(url).then(webix.bind(function(text, data){
            webix.ui(data.json(), this);
        }, this));
    }
}, webix.ui.view);

Такой же подход работает и для компонентов на основе лейаута, но с незначительными отличиями. Данные для webix.ui должны содержать не только объект с настройками, но и массив с объектами.

webix.protoUI({
    name:"myLayout",
    $init:function(config){
        config.cols = [];
        webix.ajax(url).then(webix.bind(function(text, data){
            webix.ui(data.json(), this);
        }, this));
    }
}, webix.ui.layout);

Допустим, вы хотите интегрировать сторонний инструмент в компонент Webix. Для этого вам необходимо поместить его в HTML контейнер, созданный вашим компонентом, и управлять его размерами через метод $setSize этого компонента.

Related sample:  Custom Widget: MenuList

Наверх