Last week I wrote an article showing you how to create your own custom Kendo UI plugin. I gave you some boilerplate code and went over the basics of creating a simple plugin. I also provided a sample plugin for you that was a composite of the AutoComplete, ListView and Pager.

This week I'm in Sofia with the Kendo UI team, and I've sat down and taken a much closer look at the anatomy and lifecycle of a widget. While the code that I gave you last time gives you enough to create your own Widget, I wanted to follow that up with a post about how to create a widget that is DataSource aware. One that is closer to how an actual Kendo UI widget is built and will give you a solid understanding of what you need to do to create your own DataSource aware widgets, as well as more best practices so your code is maintainable, scalable and pretty. Well, the pretty part is up to you but hopefully this will put you on the right track.

Recap

As a quick recap of the last post, I gave you some boilerplate code that is the least amount of code that you would need to write a widget. For the sake of a good starting point, here is that boilerplate code again.

Basic Widget Boilerplate

(function($) {

    // shorten references to variables. this is better for uglification
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget

    var MyWidget = Widget.extend({

        init: function(element, options) {

            // base call to widget initialization
            Widget.fn.init.call(this, element, options);

        },

        options: {    
             // the name is what it will appear as off the kendo namespace (i.e. kendo.ui.MyWidget). 
             name: "MyWidget",
            // other options go here
            ....
        }

    });

    ui.plugin(MyWidget);

})(jQuery);

 

That gives you a basic widget that you can initialize either declaratively, or directly.

Now what if you wanted to make this Widget DataSource or MVVM aware? There are some additional items that you need to implement. Instead of looking at all that here, I will go over how to create a DataSource aware widget, and I'll tackle the MVVM part next time by building on what I show you here. As usual, I'll give you a finished widget. It's going to be a very simple widget that just repeats the data in the DataSource and also allows you to specify your own custom template. You can think of this as an extremely dumbed down ListView. I'm going to call it the Repeater for this post.

DataSource Necessities

We'll build of the boilerplate code from the last post. This boilerplate simply initializes the widget and adds it the the kendo namespace.

To make our widget aware of a DataSource, the first thing that we need to do is to use the create convenience method on the DataSource base object.

Create Or Initialize DataSource

that.dataSource = kendo.data.DataSource.create(that.options.dataSource);

 

What this line does is offer you quite a bit of flexibility in the way that you initialize the DataSource for your widget. Should you actually create a new DataSource either outside your widget initialization or inline, that DataSource will be returned. But you don't have to create a new DataSource to bind a widget. You could simply set it's DataSource to an array. Something like this.

DataSource As Array

$("#div").kendoRepeater({
    dataSource: ["Item 1", "Item 2", "Item 3"]
});

 

If you pass this simple array, the kendo.data.DataSource.create method will create a new DataSource for you based upon the data in this array and return it to that.dataSource. But you can also create a DataSource just by specifying it's configuration values inline.

DataSource As Configuration Object

$("#div").kendoRepeater({
    dataSource: {
        transport: {
            read: {
                url: "http://mydomain/customers"
            }
        }
    }
});

 

Here I am specifying a DataSource configuration, but not actually creating an instance of one. The kendo.data.DataSource.create(that.options.dataSource) will take this configuration object and return you a new DataSource instance with the specified configuration.

Now you have provided the same flexibility that we do in our own widgets as far as the variety of ways that you can specify the dataSource for this repeater.

Change Is Good

The next thing that you need to do is to bind to your DataSource change event and handle it. This is where you will mutate your DOM based on the Data read from the DataSource. We typically do this in a refresh method. We usually make the refresh method public, because there is a high probability that you or someone else may want to call that method on the widget at some point after initialization.

Binding To The Change Event

// bind to the change event to refresh the widget
that.dataSource.bind("change", function() {
    that.refresh();
});

 

The boilerplate code now looks like this.

Boilerplate Code

(function($) {
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        CHANGE = "change";

    var Repeater = kendo.ui.Widget.extend({

        init: function(element, options) {
            var that = this;

            kendo.ui.Widget.fn.init.call(that, element, options);

            // initialize or create dataSource
            that._dataSource();
        },

        options: {
            name: "Repeater"
        },

        _dataSource: function() {
            // returns the datasource OR creates one if using array or configuration
            that.dataSource = kendo.data.DataSource.create(that.options.dataSource);

            // bind to the change event to refresh the widget
            that.dataSource.bind(CHANGE, function() {
                that.refresh();
            });
        }

    });

    kendo.ui.plugin(Repeater);

})(jQuery);

 

Notice that when you bind to the change event on the DataSource, you are really binding to the string value of "change". As a best practice, we assign these as constants at the top of the widget and then refer to the constant. I also moved the entire DataSource configuration into it's own method which I just execute. This is because that will be the widget since it is the calling object. I can reference all of the widget properties off of the that object after assigning that to this.

We need to add one more thing to the _dataSource method, and this will be to fetch from the DataSource if it's necessary. We do that by checking for the autoBind configuration value off of that.options. Then we call that.dataSource.fetch(). It's important to note that a fetch is different from a read in that it will only populate the DataSource if the DataSource has not yet been read from. If a read has previously been called on the DataSource before the widget was initialized, we will not be causing the DataSource to read again.

Fetch On AutoBind

_dataSource: function() {

    var that = this;

    // returns the datasource OR creates one if using array or configuration
    that.dataSource = kendo.data.DataSource.create(that.options.dataSource);

    // bind to the change event to refresh the widget
    that.dataSource.bind(CHANGE, function() {
        that.refresh();
    });

    // trigger a read on the dataSource if one hasn't happened yet
    if (that.options.autoBind) {
        that.dataSource.fetch();
    }
}

 

The autoBind configuration option doesn't exist yet, so lets add it to the options object on the widget and give it a default value of true. All DataBound widgets in Kendo UI do autoBind by default.

Add AutoBind To Options

options: {
    name: "Repeater",
    autoBind: true
}

 

Rendering The Widget With Templates

HTML output by widgets is rendered using Kendo UI Templates. Kendo UI Templates allow you to precompile HTML and inject data or expressions into the HTML which are evaluated and a DOM fragment is returned as an HTML string.  Nearly all widgets in Kendo UI allow you to specify some kind of template in addition to the default template that a widget uses.  To do this, we need to first add the template to the options object and set it's value to an empty string. Contrary to other configuration settings, we won't set its default value here.

Add Template To Options

options: {
    name: "Repeater",
    autoBind: true,
    template: ""
}

To set the default value, add a line directly under the call to the base widget initialization. This will precompile the template passed in by the user, or use a default template. In the case of this simple repeater, I'm just going to write out strong tags wrapped in a paragraphs and then reference the data object, which will be a string if we pass an array of strings. If I pass objects to the DataSource, the default template will render [object Object].

Setting The Template Default

that.template = kendo.template(that.options.template || "<p><strong>#= data #</strong></p>")

 

Here Is Your Change

Since we bound to the change method, we need to implement the refresh public function that will be called when the DataSource changes or when it's called directly. Inside the refresh method is where I am going to mutate the DOM. The first thing to do is to call that.dataSource.view() which gives us the data from the DataSource. Next, we use kendoRender and pass in a template along with the DataSource data (AKA view). This is how Kendo UI widgets mutate the DOM. The render method applies the data to the template and returns the html string.

Public Refresh Function

refresh: function() {
	var that = this,
        view = that.dataSource.view(),
        html = kendo.render(that.template, view);
}

 

Lastly we simply set the HTML of that.element, which is the element on which we are initializing our widget. In the case that you are handling initialization on an input and you want to translate or wrap that input with a container, you would need to add that logic here before setting it's html. The that.element is a jQuery wrapped element, so we can simply call the html method directly off of it. The final refresh method looks like so:

Mutate DOM Element HTML

refresh: function() {
	var that = this,
        view = that.dataSource.view(),
        html = kendo.render(that.template, view);

	that.element.html(html);
}

 

And with that final touch, we officially have a fully DataBound widget. Here is the complete code for the Repeater Widget.

Full Code For DataSource Aware Widget

(function() {
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
	CHANGE = "change";
    
    var Repeater = Widget.extend({

        init: function(element, options) {
            var that = this;

            kendo.ui.Widget.fn.init.call(that, element, options);

            that.template = kendo.template(that.options.template || "<p><strong>#= data #</strong></p>");

            that._dataSource();

        },

        options: {
            name: "Repeater",
            autoBind: true,
            template: ""
        },

        refresh: function() {
            var that = this,
                view = that.dataSource.view(),
                html = kendo.render(that.template, view);
                
            that.element.html(html);
        },

        _dataSource: function() {

            var that = this;

            // returns the datasource OR creates one if using array or configuration object
            that.dataSource = kendo.data.DataSource.create(that.options.dataSource);

            // bind to the change event to refresh the widget
            that.dataSource.bind(CHANGE, function() {
                that.refresh();
            });

            if (that.options.autoBind) {    
                that.dataSource.fetch();
            }
        }

    });

    ui.plugin(Repeater);
    
})(jQuery);

 

Here is demonstration of it in action. There are two widgets initialized here. The first one takes uses a simple array as a DataSource. The second uses a remote endpoint, a template, and declarative initialization.

Download Kendo UI and you can begin building your own custom DataBound Widgets. As usual, you can find this code and examples on the Kendo UI Labs GitHub repo.

Next time I'll expand upon this widget and make the repeater MVVM aware.


Burke Holland is the Director of Developer Relations at Telerik
About the Author

Burke Holland

Burke Holland is a web developer living in Nashville, TN and the Director of Developer Relations at Telerik. He enjoys working with and meeting developers who are building mobile apps with jQuery / HTML5 and loves to hack on social API's. Burke works for Telerik as a Developer Advocate focusing on Kendo UI.

Related Posts

Comments

Comments are disabled in preview mode.