Telerik blogs

I had the privilege this week of attending the DevReach conference in Sofia, Bulgaria. For those of you who are not already familiar, Telerik (the company which makes Kendo UI) originated in Bulgaria. There are offices here where you will find engineers, product managers, marketing, sales, R&D and virtually any other function you can think of.

Sofia is a beautiful city and I always enjoy coming here - especially for DevReach. I was able to talk to quite a few folks during the time of the conference and I always get some great feedback on Kendo UI. One developer that I talked to asked me how to write "controls" using Kendo UI that have features that can be expanded on or used as is. This would be what is commonly known as having a base class where you can define some default properties, methods, etc. and then override those as desired.

I've written several articles about creating composite widgets with Kendo UI, A DataSource aware Kendo UI widget and MVVM aware Kendo UI Widgets.

The process for creating your own widget with encapsulated logic is the same as the first post that I wrote, but I had a fun time putting together a proof of concept for this blog post, so lets go over how to do this and create a unique widget that contains some pre-defined logic that you can then either use, or override as you see fit.

The Bootstrap

Everyone loves bootstraps these days, including myself. I love code that gets me up and running. I've listed this before, but lets go over again the fundamentals of what it takes to create a basic Widget that will be available off of the Kendo UI namespace.

Widget Bootrap

(function($) {

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

    var CustomWidget = Widget.extend({

        init: function(element, options) {

            // assign that to this
            var that = this;

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

        },

        options: {

            // the name is what it will appear as off the kendo namespace(i.e. kendo.ui.CustomWidget). 
            // The jQuery plugin would be jQuery.fn.CustomWidget.
            name: "CustomWidget",
            // other options go here
            ...
        }

    });

    ui.plugin(CustomWidget);

})(jQuery);

Create The Widget

The widget that I create here will be VERY loosely based on what the developer who I spoke to was asking me about.

It's a textbox, a label and a checkbox. The textbox has a default change event and so does the checkbox. These might get used over and over and you wouldn't want to have to specify the base functionality for these events every time you used one. In this tutorial, we will specify some base events and then expose the ability to expand on those features by inheriting from this widget. Here is what the basic widget looks like. Whatever you type in the textbox becomes the text of the label. Clicking the checkbox will toggle the background color.

Not terribly impressive, but we need a starting point. Here is the code that makes the complete widget. I'll be going through what all of this does.

The first step to recreating this is to build the UI. To do that, I specify a _templates object which contains the templates needed to create the 3 HTML components that make up this control.

Specifying Templates

_templates: {
    textbox: "<input class='k-textbox' placeholder='#: placeholder #' type='text' id='#: inputId #' value='#: labelId #'>",
    label: "<label for='#: inputId #' id='#: labelId #' style='display: inline-block; width: 200px; margin-left: 20px;'>#: labelText # </label>",
    checkbox: "<input type='checkbox' # if (data.checked) { # checked='checked' # } #>"
}

You might have noticed that the templates are assuming we are going to pass some data in. This will ultimately be the options object. Lets add the necessary properties to the options object that our template will be expecting. Even if we aren't going to use it, we still need to specify it in the options object and at least give it a default value.

Default Options Values

 options: {
    name: "CustomInput",
    labelText: "Please Enter Your Name...",
    inputText: "",
    checked: false,
    placeholder: "Enter Name",
    toggleColor: "#9BF49D"
},

Here's the breakdown of what they are all going to tell us.

  • name Used by Kendo UI to know what the Widget is called so we can use $("#someSelector").kendoCustomInput();
  • labelText The default text to be shown in each Label.
  • inputText The default text to have in the TextBox (or input for you HTML purists out there).
  • checked Whether or not the CheckBox portion of this control will be checked.
  • placeholder This is something that is only supported in more current browsers, but we can add it in as a bonus for browsers where it will work. This is the watermark text that appears in the textbox before you type something.
  • toggleColor The color to change the background to when the checkbox is changed. That default hex value is a light green.

The next thing that we need to do is to turn all of these templates into actual HTML UI components by passing the objects object through them. I extract all of this logic into a private _create function. We try to keep the init functions as skinny as possible, so extract all of your logic into private methods that are prefixed with an underscore.

We are also going to add default change events for the textbox and the checkbox.

Create Function

// this function creates each of the UI elements and appends them to the element
// that was selected out of the DOM for this widget
_create: function() {

    // cache a reference to this
    var that = this;

    // set the initial toggled state
    that.toggle = true;

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

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

    // setup the textbox change event. wrap it in a closure so that
    // "this" will be equal to the widget and not the HTML element that
    // the change function passes.
    that.textbox.change(function(e) { 
      that._inputChange(); 
    });

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

    // setup the checkbox change event. wrap it in a closure to preserve
    // the context of "this"
    that.checkbox.change(function(e) { 
      that._checkChange(); 
    });

    // append all elements to the DOM
    that.element.append(that.textbox)
                .append(that.label)
                .append(that.checkbox);

}

A Note On Lexical Scoping

Lexical scoping means that a function remembers its values even after it executes. In JavaScript, you can reset a variable's value outside of a function and the value of the variable will be changed inside the function as well. This can be really frustrating. The same laws apply to the this object. There are certain tricks that developers have come up with to make sure that the variable values are preserved. One of these ways is to use a closure.

Notice that each of the change events is wrapped in a function (or closure). This is so that I can pass that as the context of this in the function. There are several tricks for setting the context of this. Using closures is one way, but be sure to check out the jQuery $.proxy method and the JavaScript call and apply functions.

Widget Creation

Now that this much is done, we can create the widget by calling the _create method in the init function.

Calling _create In init

init: function(element, options) {

    // assign that to this to reduce scope confusion
    var that = this;

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

    // creates the UI and appends it to the element selected from the DOM
    that._create();
}

Add default a event for the CheckBox which will change the background color. This is a private method, so prefix it with an _.

Default CheckBox Change Event

// the event that fires when the checkbox changes
_checkChange: function() {
  var that = this;

  // check the toggle value
  if (that.toggle) {
    // change the background color to the specified one on the
    // options object
    that.element.css("background", that.options.toggleColor);
  }
  else {
    // toggle it back to white
    that.element.css("background", "#fff");
  }
  // flip the toggle
  that.toggle = !that.toggle;
}

Now the control will toggle the background color when you select and de-select the checkbox. Add a method for the textbox (or input) to change the label to whatever value is in the textbox.

Change Value Of Label To TextBox Value

// the event that fires when the textbox changes
_inputChange: function() {
    var that = this;
    that.label.text(that.textbox.val());
}

At this point, you would have a basic widget that you could initialize by selecting a DOM element and calling kendoCustomInput

Custom Input Widget

<div id="customInput"></div>
<script>
    $("#customInput").kendoCustomInput();
</script>

Overridding The Defaults

When I initially wrote this widget, I was using options objects to pass in overriden events. In talking with the team, this is a bad idea. You should instead do one of two things.

  1. Add option properties that you can respond to (i.e. background color)
  2. Override this widget with a new one to change/inherit the default events

We might want to toggle the background color AND disable the textbox when the checkbox changes. To do this, we need to create a new widget that extends the first one.

Create the new widget just like the first, but this time extend the CustomInput class. You will see that in order to call the base _checkChange method, you need to reference it off the prototype - also known as the base object.

Create CustomInputDisabled Widget

// create a new widget which extends the first custom widget
var CustomInputDisable = CustomInput.extend({

  // every widget has an init function
  init: function (element, options) {

      // cache this
      var that = this;

      // initialize the widget by calling init on the extended class
      CustomInput.fn.init.call(that, element, options);

  },

  // handle the _checkChange event.  This is overriding the base event.
  _checkChange: function() {

     // cache the value of this
     var that = this;

    // disable the textbox first
    if (!that.textbox.is(":disabled")) {
        that.textbox.attr("disabled", "disabled");
    } else {
      that.textbox.removeAttr("disabled");
    }

    // this calls the base method from the CustomInput widget.  If you didn't want
    // to call this, you would omit this line.  then the textbox would be disabled, but
    // the background color would not change.
    CustomInput.prototype._checkChange.call(this); // call the base method
  },
  // all options are inherited from the CustomInput widget. This just sets a new name
  // for this widget
  options: {
    name: "CustomInputDisable"
  }
});

// add this new widget to the UI namespace.
ui.plugin(CustomInputDisable);

More Functionality

You are probably going to want to take this further and look at using DataSources and MVVM. For that, I suggest reading my prior blog posts on how to make your widget DataSource Aware and MVVM Aware.

You can build your own widgets using Kendo UI, or simply encapsulate settings for a single widget. You may have a grid with some very extensive settings. Simply extend the grid base widget and set the default options. Now you have your own grid with your settings that you can re-use over and over without having to reconfigure.

Checkout the code from today's tutorial in the Plugins GitHub Repo.


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.