You can create custom components based on any existing one.
The library allows extending functionality of any component by adding building modules to it as well as creating a totally custom component that inherits from the basic view.
The technique uses the protoUI method to create a new component with its own name, properties, methods, and events. All the new components must be based on the components from Webix library. Custom components, as well as the built-in ones are created according to common rules.
For example, let's make a list editable by extending it with the EditAbility mixin:
Editable List
webix.protoUI({
name:"editlist"
}, webix.ui.list, webix.EditAbility);
// use the component
webix.ui({
view:"editlist",
// ...configuration and data
});
Give your component a name that will serve as a view type. In general, it should be unique. However, you can also create a component with the name that has already been given to some other component. In this case, all components with this name used in the app will receive the new functionality. Be very careful if you give an already existing name to the new widget, because Webix reuses a lot of widgets in other widgets, and something can break quite unexpectedly.
If you create a component based on several mixins/modules, keep in mind that their order matters. Depending on the order, mixins override functionality of other mixins. Check the following example:
webix.protoUI({
name:"mytext",
// your logic here
}, webix.ui.text, mixin2, mixin1);
mixin1 and mixin2 are some arbitrary mixins. This is the order of inheritance, from least important to most important that overrides the same functionality of the previous mixin:
That's why if you redefine a method of webix.ui.text in each of the mixins and in "mytext", it is the method of "mytext" that will be called.
Related sample: Custom Widget: The Order of Inheritance
The structure of a custom component resembles the one of a built-in component. A custom component can have some obligatory and optional fields:
webix.protoUI({
name:"some",
$init:function(){}, //logic on init
$cssName // topmost class for controls
defaults:{ } // default values for configuration settings
'property'_setter // function to process configuration settings
$getSize // how widget calculates the needed sizes
$setSize // how widget applies sizes
// any properties
}, webix.ui.list);
Components extra API may include methods for lifetime handlers ($init, $ready) and other methods and properties. Remember that the properties of a component prototype and configuration settings are different things.
These are configuration settings:
webix.ui({
view:"button",
id:"b1",
value:"Save"
});
var value = $$("b1").config.value;
And this is a view property:
$$("b1").flag = true;
The $init method of a view acts as a constructor. You should define $init for some logic that should run before view initialization (e.g. changing its config, dynamic or calculated default values for configuration settings, providing event handlers). Note that during the initialization of a particular view, Webix calls all the $init methods of its parents.
Let's set the configuration property of the editlist, e.g. editable:true:
webix.protoUI({
name:"editlist",
$init:function(config){
config.editable = true;
}
}, webix.ui.list, webix.EditAbility);
For instance, if you want to provide columns or rows for a layout-based component, provide them in $init as:
$init:function(config){
config.cols = [
{ template:"Column 1" }, { template:"Column 2"}
]
}
and not with addView() afterwards. Using $init is more performant, because a view instance will not be refreshed (repainted in the DOM) like it does when you use addView().
Define $ready for something that should happen right after the view initialization. $ready is an array of callbacks, so you can push/unshift any custom callback there.
Let's add a method that will initialize a context menu for the menuList:
webix.protoUI({
name:"menuList",
$init:function(config){
this.$ready.push(this.initMenu);
},
initMenu:function(){
this.menu = webix.ui({
view:"contextMenu"
});
}
}, webix.ui.list);
You can also define the default values for some configuration settings in the defaults field of a component. Any constant configurations are appropriate for this purpose.
Defaults can be overwritten when you create an instance or use define.
We don't recommend to define data properties (url, data, etc.) as default values.
Let's create a new component named menuList based on List and set select:true:
webix.protoUI({
name:"menuList",
defaults:{
select:true
}
}, webix.ui.list);
If you need to modify default or custom settings before they are applied, use setters.
Setters can unify different setting variations. For example, some setting can take both array and string from configuration. You can define a setter that will convert everything to an array, so that inner logic does not have to check the type. The name of a setter should be like {setting name}_setter (e.g. *menu_setter*).
webix.protoUI({
name:"menuList",
menu_setter:function(value){
if (!webix.isArray(value))
value = [value];
return value;
}
}, webix.ui.list);
// now both string and array values are valid
webix.ui({ view:"menuList", menu:"One" });
webix.ui({ view:"menuList", menu:["One", "Two"]});
You can define custom methods and redefine the methods of the parent class, completely or partially.
For instance, if you want to modify the List.getVisibleCount() method for the custom component and retain the basic logic, you can call the method of the parent List as:
name:"menuList",
getVisibleCount:function(){
var count = webix.ui.list.prototype.getVisibleCount.apply(this, arguments);
return count + 10;
}
If you redefine methods, keep in mind that:
You can add events to custom widgets in the $init handler via the attachEvent() method:
$init:function(config){
this.attachEvent("onItemClick", function(id){
webix.message("Item clicked "+id);
});
}
There are two methods in the view prototype to provide view sizes during initialization: $setSize() and $getSize(). They are not intended for direct calls.
$getSize is called by a view to calculate its desired size. If you want to modify its logic for the custom view, you will typically add some custom logic and call the $getSize of the parent view (normally view or layout, depending on the inheritance chain) to ensure that the sizes are applied correctly.
$getSize:function(x, y){
// own logic
// ...
// parent logic
return webix.ui.view.prototype.$getSize.call(this, x, y);
}
$setSize is called to set the calculated values to the HTML elements of the view. If you want to modify its logic for the custom view, you will typically check whether the $setSize of the parent class resizes the view, then you can apply additional logic for the current view.
$setSize:function(x,y){
// if parent has changed its size
if (webix.ui.view.prototype.$setSize.call(this, x, y)){
// custom logic
}
}
All Webix widgets get the CSS class name of the widget they are based on. They can also inherit CSS class name from their parent class.
You can also add your custom CSS class to the component in $init. When an instance of the component is created, the class will be added in the view HTML alongside with default CSS classes.
webix.protoUI({
name:"menuList",
$init:function(config){
this.$view.className += " menu_list";
}
}, webix.ui.list);
// the resulting HTML
<div class="webix_view webix_list menu_list">...</div>
Webix controls behave in a different way. They do not inherit the CSS class of their parent view. Their CSS class name consists of "webix_el_" and the name of the custom control. To inherit the CSS class of the parent view, you must provide one more property - $cssName. The name passed to it will be put into the name of the CSS class.
webix.protoUI({
name:"selector",
$cssName:"combo"
}, webix.ui.combo);
// the resulting HTML
<div class="webix_view webix_control webix_el_combo">...</div>
You should rely on layouts rather than on placing components in HTML containers. It is good practice to create even simple HTML layouts for custom components using Webix widgets: spacers and templates will do.
For instance, if your view needs to wait until its config is fetched from the server side, use the webix.ajax helper for that. As soon as the config is received, webix.ui will take it and initialize the view with it.
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);
Same is true for layout-based components but with a difference. The data for webix.ui should contain not only a configuration object of the parent view, but also an array of child objects.
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);
If you need to embed a third-party tool into a Webix view, you should put it into an HTML container created by Webix custom component and handle its responsiveness with the $setSize method of this custom component.
Related sample: Custom Widget: MenuList
Back to top