Using Third-party Libraries with Webix UI

Webix UI provides a lot of widgets, but it is just a tiny bit, as compared to the massive amount of different Open Source libraries. Fortunately, it is quite easy to wrap any custom library in Webix UI control.

Konva.js

As an example, I will use Konva javascript library.

Konva is a javascript library that adds interactivity to HTML5 canvas. You can draw things on the stage, add event listeners to them, move them, scale them, and rotate them independently of other shapes. Thus, high performance animations are supported, even if your application uses thousands of shapes.

Check the image below. It is an active Canvas object and you can try to drag-n-drop stars.



You can check full source code of the demo at //konvajs.github.io/docs/sandbox/Elastic_Stars.html.

Custom view

Let’s start from creating a basic custom view. The syntax of any view looks as follows:

webix.protoUI({
    name:"view name",
    $init:function(){
        //constructor
    }
}, webix.ui.view);

The code is quite simple. The protoUI method takes two parameters. The first one is the configuration object. It contains the name of the new view, the constructor and any optional methods. The second parameter is the view that will be used as a base for creating a custom component.

In most cases, you will want to use webix.ui.view as the second parameter, because it is a most basic Webix view and it works best for third party libraries.

A base wrapper for Konva library will look like this:

webix.protoUI({
    name:"konva",
    $init:function(){
        this.stage = new Konva.Stage({
            container: this.$view
        });
    },
    $setSize:function(x,y){
        if (webix.ui.view.prototype.$setSize.call(this, x,y)){
            // resize the stage, if the view size was changed  
            this.stage.size({ width:this.$width, height:this.$height });
        }
    },
    getStage:function(){
        return this.stage;
    }
}, webix.ui.view);

Here we have a new view with a constructor and 2 methods. In the constructor we are creating the Konva’s base object.

The $setSize method is called each time when a custom view is resized. So it is a good place to attach the sizing logic of the custom library. In the above code the $setSize method contains a call to the base $setSize method of Webix view. Only after that a call to custom Konva sizing method goes. It is organized in such a way that Konva’s sizing method will be called only when our view really needs to be repainted.

The getStage method is an accessor method that allows accessing the wrapped third-party for any further customization.

It this state a custom component is fully ready and can be used in an app. Check the next snippet that creates a set of boxes on the canvas, for example.



You can check full code of the sample.

Automatic script loading

The above code expects that konva.js file will be added to the page manually. It is fine, but in a big app it’s better to minimize file loading and load assets only when they are necessary.

We can rewrite the custom view so it will load konva.js file itself when the related view is initialized.

webix.protoUI({
    name:"konva",
    $init:function(){
        webix.require("konva/konva.js");
        /*the rest of code stays untouched*/

This code will make a sync request to the js file and continue executing after that. We needn’t include konva.js on the page anymore, as it will be loaded automatically.

When loading files with webix.require, paths are specified in relation to location of webix.js file. So if you are loading ./codebase/webix.js on the page, webix.require(“konva.js”) will try to load ./codebase/konva.js file. This root path can be specified directly as webix.codebase = “./some/folder/”;

While solving part of our problems, the above code uses sync data loading. It simplifies logic, but results in temporary UI freezing (a browser will block all operations during the konva.js file loading). We can do better and use async data loading instead. Unfortunately, as any other async logic, it will complicate code a lot. In case of Webix, we can use a bundled promise library to minimize code cluttering.

webix.protoUI({
    name:"konva",
    $init:function(){
        //creating promise
        this.stage = webix.promise.defer().then(webix.bind(function(){
            //init lib when it is ready
            var stage = new Konva.Stage({
                container: this.$view
            });

            //call an optional callback code
            if (this.config.ready)
                this.config.ready.call(this, stage);

            return stage;
        }, this));

        //if file is already loaded, resolve promise
        if (window.Konva)
            this.stage.resolve();
        else
            //wait for data loading and resolve promise after it
            webix.require("konva/konva.js", function(){ this.stage.resolve(); }, this);
    },
    $setSize:function(x,y){
        if (webix.ui.view.prototype.$setSize.call(this, x,y)){
            //call resize when lib is ready
            this.stage.then(function(stage){
                stage.size({ width:x, height:y });
            });
        }
    },
    getStage:function(){
        return this.stage;
    }
}, webix.ui.view);

Let’s have a look at the made changes:

  • instead of storing the stage object in this.stage we are storing promise of that object;
  • webix.require now has a callback parameter in which initialization of Konva library occurs
  • the $setSize method was updated to work with promise object

When we configure konva.js, the code needs to be changed a bit as well. Instead of using

var layer = $$("k1").getStage.addLayer();

we need to use

$$("k1").getStage.then(function(stage){
    var layer = stage.clip({ width:100 });
});

or the “ready” callback

webix.ui({
    view:"konva", ready:function(stage){
        stage.clip({ width:100 });
    }}
})

You can check the live example.

Final thoughts

It is quite easy to wrap a third-party library in a Webix View. Adding automatic dependency loading requires a bit more logic and handling, especially if you want to handle all operations in a nice async way. Still, it can be solved in a few lines of code.

So if you need to use a nice open source library with Webix UI, you can create your own custom component by repeating the above steps. You can share the result components by making pull request to our components repo.

Good luck!