Telerik blogs

Following up on last weeks post about creating custom DataBound Kendo UI Widgets, I wanted to cover how to make these widgets MVVM aware. The simple widget that I built in the last post is DataSource aware and can also be bound via declarative initialization. The declarative initialization comes along for free as part of the Kendo UI Framework when you create a plugin and define it’s options. For instance, you can set the DataSource of the simple repeater widget by setting its data-source property.

This works fine and dandy and is quite a handy way to bind widgets when you have several on a page. However we can improve this experience by allowing the user to encapsulate the initialization dependencies in their ViewModel, which is really a better home for them than just hanging out in a script block.

Hello MVVM

I suggest that you take a look at last week’s post before proceeding to familiarize yourself with what it takes to create a base widget and how to make that widget DataSource aware. From here on out, I am simply going to build on the code that I finished with in the last post.

Events

The first thing that we need to do is to define some events for our widget. Specifically, we need to expose the dataBinding event and the dataBound event. The dataBinding event will be what we call before we mutate the DOM with our widget. This gives MVVM a chance to traverse the fragment that we are about to mutate and unbind anything that is currently bound. The second event is the dataBound event, which allows MVVM to go back through the fragment and re-bind what is necessary. These events are exposed via the events object on the widget. These events are strings, so we define them as constants in the head of the widget as part of the pattern we use when developing all widgets.

Example

var DATABINDING = "dataBinding",
    DATABOUND = "dataBound",
    CHANGE = "change"    

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

    init: function(element, options) {
        ...
    },
    options{
        ...
    },

    // events are used by other widgets / developers - API for other purposes
    // these events support MVVM bound items in the template. for loose coupling with MVVM.
    events: [
        // call before mutating DOM.
        // mvvm will traverse DOM, unbind any bound elements or widgets
        DATABINDING,
        // call after mutating DOM
        // traverses DOM and binds ALL THE THINGS
        DATABOUND
    ]
});

 

By exposing these as events for MVVM to listen to, we have loose coupling between our widget and the MVVM core engine. This means that if we don’t expose these events, MVVM simply won’t know about our widget’s lifecycle. This is a very good architecture as it ensures that your widget won’t break other MVVM bindings that it should have no knowledge of.

Items

MVVM will be expecting us to expose the DOM fragments from our widget which represent each row or each repeated data element. We should be returning the outermost element for MVVM to work with. While is varies, this is typically just this.element.children. Since each template item rendered is a DOM Fragment that is attached to the bound element, this is all we need. We can expose it for MVVM by making it available off of the items object.

Example

var DATABINDING = "dataBinding",
    DATABOUND = "dataBound",
    CHANGE = "change"    

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

    init: function(element, options) {
        ...
    },
    options{
        ...
    },

    // events are used by other widgets / developers - API for other purposes
    // these events support MVVM bound items in the template. for loose coupling with MVVM.
    events: [
        // call before mutating DOM.
        // mvvm will traverse DOM, unbind any bound elements or widgets
        DATABINDING,
        // call after mutating DOM
        // traverses DOM and binds ALL THE THINGS
        DATABOUND
    ],

    // mvvm expects an array of dom elements that represent each item of the datasource.
    // should be the outermost element
    items: function() {
        return this.element.children();
    }
});

 

Changing The DataSource

Since it is possible to change the DataSource with MVVM, we need to implement the setDataSource function. MVVM will call this when the DataSource is set inside of a ViewModel. It’s a pretty simple implementation. We are simply going to set our internal DataSource reference equal to the one passed in by MVVM, and then rebuild the DataSource using the already defined _dataSource() function.

Example

        
// for supporting changing the datasource via MVVM
setDataSource: function(dataSource) {
    // set the internal datasource equal to the one passed in by MVVM
    this.options.dataSource = dataSource;
    // rebuild the datasource if necessary, or just reassign
    this._dataSource();
}

 

Tweak The _dataSource

We do need to make some small tweaks to our method which assigns or builds the DataSource. If you remember from the last post, the _dataSource method that we call in init does 3 things.

  1. Assign the DataSource, or build on from an array or configuration object.
  2. Read from the DataSource if autoBind is enabled and the DataSource has not yet been read from.
  3. Binds the change event on the DataSource to an internal refresh method that we handle manually.

Since we have already bound the change event on the DataSource possibly once, we need to make sure we unbind it if neccessary. If this is not done, the widget will retain a list of all the bindings and will execute the refresh function numerous times - which is not what we want. Also, MVVM will be listening to the internal _refreshHandler function which we have not yet defined. We simply need to point that internal _refreshHandler to our publicly exposes refresh method. First though, we need to check and see if there is an existing connection between the public refresh (which is bound to the change event on the DataSource) and the internal _refreshHandler. If there is, we need to remove just the binding to the change event. If there is no connection between our internal _refreshHandler and the public refresh function, we need to create it. This is done by the $.proxy jQuery method which simply calls the public refresh with the correct context, which is the widget itself. Finally, we rebind to the change event of the DataSource.

Example

        
_dataSource: function() {

    var that = this;

    // if the DataSource is defined and the _refreshHandler is wired up, unbind because
    // we need to rebuild the DataSource
    if ( that.dataSource && that._refreshHandler ) {
        that.dataSource.unbind(CHANGE, that._refreshHandler);
    }
    else {
        that._refreshHandler = $.proxy(that.refresh, that);
    }
 
    // 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, that._refreshHandler );
 
    if (that.options.autoBind) {    
        that.dataSource.fetch();
    }
}

 

This can be a bit confusing if haven’t used the proxy jQuery function before, but all it’s doing is saying that when the _refreshHandler is called, it should execute the public refresh widget function and inside that refresh function, this will be a reference to the widget itself, and not something else (like window for instance). Due to the fact that the keyword this is always changing in JavaScript, this is just a good way to ensure that the scope is correct when the refresh function executes.

We Are Almost Done!

Finally, we simply need to trigger the dataBinding and dataBound events. We do this in the public refresh and remember, dataBinding happens before we mutate the DOM and dataBound happens directly after.

Example

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

    // trigger the dataBinding event
    that.trigger(DATABINDING);

    // mutate the DOM (AKA build the widget UI)
    that.element.html(html);

    // trigger the dataBound event
    that.trigger(DATABOUND);
}

 

And with that, we have now fully enabled MVVM in our widget. This means we can now define the widget like so:

Example

	
<div data-role="repeater" data-bind="source: dataSource">
<script>
    var viewModel = kendo.observable({
        dataSource:  new kendo.data.DataSource({
                        transport: {
                            read: "Customers/Orders",
                            dataType: "json"
                        }
                    })  
    })

    kendo.bind(document.body.children, viewModel);
</script>

 

Notice that the widget is now bound to the dataSource variable inside of the ViewModel via data-bind. This means that if we add an item client side to the DataSource, our widget will reflect the change immediately, without us having to re-render anything.

I have attached a fiddle below for demonstration. Notice that when you add an item to the DataSource, it is immediately reflected in the Repeater widget. Very nice.

That’s A Wrap

That’s a wrap on how to create a custom Kendo UI Widget from scratch. For reference, here are the other two posts that you might want to refer back to to get a complete understanding of what the widget lifecycle is, and how to make a widget DataSource aware.

Download Kendo UI today and get started building your own widgets with the powerful core that we have exposed for you to use as well.


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 was the Director of Developer Relations at Progress. He enjoys working with and meeting developers who are building mobile apps with jQuery / HTML5 and loves to hack on social API's. Burke worked for Progress as a Developer Advocate focusing on Kendo UI.

Comments

Comments are disabled in preview mode.