Friday, January 10, 2014

Extending an MVVM KendoUI widget to support custom event bindings

In my previous post, I put together a simple Kendo widget that is MVVM compatible within the KendoUI framework. In this post, I will add custom event bindings to the widget to allow the MVVM bindings to establish event bindings to dependent methods within your viewmodel. This will allow the widget to trigger custom events when the user clicks on the icon in our custom text box. Starting with the previous widget, we now need some DOM elements for the user to interact with. I had found a posting that explained a way to make simple KendoUI widget that was not MVVM aware that added a magnifying glass icon to a standard textbox. This seemed good for this example. It uses and existing Kendo style for the magnifying glass.

        _create: function () {
            // cache a reference to this
            var that = this;

            // setup the icon
            var template = kendo.template(that._templates.icon);
            that.icon = $(template(that.options));

            // setup the textbox
            template = kendo.template(that._templates.textbox);
            that.textbox = $(template(that.options));
            
            // append all elements to the DOM
            that.element.attr("name", that.options.name);
            that.element.addClass("k-input");
            that.element.css("width", "100%");
            that.element.wrap(that.textbox);
            
            that.element.after(that.icon);
        },
        //HTML for the templates that comprise the widget
        _templates: {
            textbox: "<span style='width: #: width #px;' class='k-widget k-datepicker k-header tb'><span class='k-picker-wrap k-state-default'></span></span>",
            icon: "<span unselectable='on' class='k-select' role='button'><span unselectable='on' class='k-icon #: iconclass #'>select</span></span>"        },
The _create method adds additional dom elements as well as some kendo styles to create the effect. Width is used from the options collection and the icon class also comes from options, so we can override it if we choose to. Now that we have something to click on ,we need to add a line to _create to wire up a click event on the icon.
  that.icon.on("click", $.proxy(that._buttonclick, that));
A simple jquery click event is added via a proxy. From the previous example, we use a proxy to ensure that the scope of the event fired contains the widget when fired. We pass the widget in as scope for the proxy. The handler is tied to a new internal function in our widget, _buttonclick.
        _buttonclick: function (element) {
            var that = this;
            that.trigger("buttonclick", { element: element });
            return that;
        },
The handler for the widgets icon click event fires the custom event BUTTONCLICK to alert any listeners that the event has occured. The widget will be the scope and the element will be the icon DOM element. Now we have to declare the event in our events collection so the MVVM framework knows that the binding is available.
events: ["change", "buttonclick"],
By exporting the event via the events collection, the MVVM framework is aware that our widget can fire an event called 'buttonclick'. We can consume the event in MVVM bindings with the same binding syntax as other kendo widgets.
<input data-role="buttontextbox" data-bind="value: simpleValue, events: { buttonclick: onSomeButtonClick }" />

The buttonclick event binding will call into the dependent method specified as onSomeButtonClick. Our widget can now export custom events from events that are internal to the widget itself.
A functional fiddle with all the code is at:

http://jsfiddle.net/cn172/s7RF3/

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/