Friday, January 10, 2014

Create a simple KendoUI MVVM widget

KendoUI is a nice framework for building HTML based applications.  One aspect of KendoUI that is not clearly documented is the creation of custom widgets.

There is an article at the kendoui web site that covers creation of a widget that uses a datasource to manage an internal list and in that article it shows how to connect the widget to their MVVM framework.

http://docs.kendoui.com/howto/create-custom-kendo-widget

However, this article and it's related blog posts are too complicated, working with widgets that are bound to lists of items.  A simple, straightforward example that simply binds to a single value in our view model (like a numeric textbox for instance) is not provided.

To begin, let's layout the requirements for this new, fictional widget.  It must have a text box that is MVVM bound.  We should be able to use a markup such as:
<input data-role="simplewidget" data-bind="value: simpleValue" />
To accomplish this, we need to create a kendo widget by extending the kendo widget framework. The first section of the article linked above details this well. I'll repeat the full boilerplate code here.
(function($) {
   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). 
        // The jQuery plugin would be jQuery.fn.kendoMyWidget.
        name: "MyWidget",
       // other options go here
       ....
   }
});
ui.plugin(MyWidget);

})(jQuery);
So far, this is quite straightforward. Using Widget.extend, we extend the Widget function and add init and options. The kendo framework will call init and examine the collection of options. Now we want to make the widget MVVM aware with a property from the viewmodel update the UI with the value provided.  In order for MVVM to support a binding of 'value' we have to implement a value method.
  //MVVM framework calls 'value' when the viewmodel 'value' binding changes
  value: function(value) {
      var that = this;

      if (value === undefined) {
          return that._value;
      }
      that._update(value);
      that._old = that._value;
  },
  //Update the internals of 'value'
  _update: function (value) {
      var that = this;
      that._value = value;
      that.element.val(value);
  }
This method will be called by MVVM with the current viewmodel value supplied.  It will also be called anytime the bound propery changes. We'll need to keep track of that, so we keep it in a local variable called _value, as well as creating a copy of it in a local variable called _old. We will want to know the original value for change tracking purposes later. If we simply add the above two functions to our widget boiler plate, we will see MVVM calling the value function anytime the viewmodel value we bound to changes. We also set the element value with a call to element.val(value) to get the UI displaying the viewmodel value.

Our widget is updated with the data from the view model with this one simple function.  Next we will want to support notifying the MVVM framework when our user updates the value using the UI.  First we need to alert the MVVM framework that we are going to be firing an event that can be bound to. This is done by declaring an events property.
events: ["change"]
This line tells MVVM that a CHANGE event is available from this widget. Now that MVVM is aware that we might trigger an event called 'change' we need to detect the UI updates in our input box and when the user changes the value of our input we need to notify MVVM.  To detect changes by the user, let's bind a blur event to our  textbox.

   //Create a blur event handler.
   element = that.element.on(".kendoButtonTextBox", $.proxy(that._blur, that));
A key component here is the user of $.proxy to ensure that the scope of the call to "that._blur" is set to the widget's scope. Without resetting the scope the event handler function will not have the widget's scope and won't be able to access any of the properties or methods on the widget.

Now an implementation for the _blur function must be provided.
  //blur event handler - primary UI change detection entry point
  _blur: function () {           
      var that = this;            
      that._change(that.element.val());
  },
  //Update the internals of 'value'       
  _change: function (value) {
      var that = this;
      //Determine if the value is different than it was before
      if (that._old != value) {
          //It is different, update the value
          that._update(value);
          //Capture the new value for future change detection
          that._old = value;
          // trigger the external change
          that.trigger("change");
      }
  }
In this code, the _blur event handler function is calling the _change event trigger function. This function uses the _update function to update the internal value, and triggers the change event from the provided scope. The MVVM framework is listening for this change and has subscribed our viewmodel to listen for the change event.

In this simple example, we created a textbox that binds to a single viewmodel property.  In my next example, I'll expand on this widget to allow the widget to create and trigger custom events that can also be MVVM bound.  The full source code for a working, simple MVVM widget is at:

http://jsfiddle.net/cn172/XE2Qg/

No comments:

Post a Comment