Creating a Kendo UI CheckBox List Widget

12 Apr 2016 20:12
Tags checkbox kendo plugin widget

Back to list of posts

Overview

I wrote an article about Creating a Kendo UI Radio Button Group Widget and while working on the article, I saw that the Kendo UI CSS has styles for a checkbox. I decided it would be nice to create a widget that displayed a list of checkboxes that uses the Kendo UI theme. Although I was able to easily adapt the ExtRadioButtonGroup widget to create the CheckBox List widget, I'll walk you through the steps that would have been done if I had started from scratch.

Here is what the checkboxes looked like before I created a Kendo UI widget for them:

checkboxlist_before.png

Here is what the radio buttons looked like after I created a Kendo UI widget for them (with Default theme):

checkboxlist_after.png

Defining the API

The first step to creating a checkbox list widget is to define the configuration, fields, methods and events that I want to have for the widget. If I were to use an existing Kendo UI widget instead of creating a list of check boxes I would have chosen to use the MultiSelect, so I took a look at the MultiSelect API documentation to figure out what the API should look like for my checkbox list widget. This is what I came up with:

Configuration

  1. dataSource: (kendo.data.DataSource) - the data source of the widget.
  2. dataTextField (string) - the field of the data item that provides the text content of the radio buttons.
  3. dataValueField (string) - the field of the data item that provides the value of the widget.
  4. name (string) - the name of the widget.
  5. orientation (string) default: "vertical" - the orientation of the radio buttons in the group. Supported values are "horizontal" and "vertical".

Fields

  1. dataSource (kendo.data.DataSource) - the data source of the widget. Configured via the dataSource option.

Methods

  1. dataItems - returns the data items for the selected checkboxes.
  2. refresh - renders all checkboxes using the current data items.
  3. setDataSource - sets the data source of the widget.
  4. value - gets or sets the values of the checked checkboxes.

Events

  1. dataBound - fired when the widget is bound to data from its data source.
  2. select - fired when the user selects a checkbox.

Defining the ExtCheckBoxList

Since I am not extending an existing Kendo UI widget, I derive from the kendo.ui.Widget class. The Configuration options are defined as a JSON object and the names of the events are defined as an array of strings. The fields are assigned to null and then I define the init and destroy functions, private functions (beginning with an underscore) and then public methods. Here is what the outline of what the widget looks like:

var ExtCheckBoxList = kendo.ui.Widget.extend({
    options: {
        dataTextField: "",
        dataValueField: "",
        groupName: "",
        name: "ExtCheckBoxList",
        orientation: "vertical"
    },
 
    events: [
        "dataBound",
        "select"
    ],
 
    dataSource: null,
 
    init: function (element, options) {},
 
    destroy: function () {},
 
    _dataSource: function () {},
 
    _template: function () {},
 
    _onCheckBoxSelected: function (e) {},
 
    setDataSource: function (dataSource) {},
 
    refresh: function (e) {},
 
    dataItems: function () {},
 
    value: function () {}
});

Rendering the ExtCheckBoxList

When a Kendo UI Widget is created, the widget's init function is called to initialize the widget. When I look at the Kendo UI source code, I see that in the MultiSelect init function, the _dataSource function is called to initialize the DataSource from the Configuration options. In some of the Kendo widgets, the _dataSource function binds to the DataSource's change event. When the change event is raised, the refresh function is called to populate the widget with the data found in the DataSource. When the widget is populated, the dataBound event is raised. I also found that some of the Kendo widgets use a _template function to get the template for each item in the list. I implement the same for the ExtCheckBoxList.

Digging through the CSS for Kendo UI, I discovered the styles k-checkbox and k-checkbox-label. The label for the checkbox is expected to be rendered after the checkbox and have the k-checkbox-label CSS class applied to it. The checkbox is expected to have the k-checkbox CSS class applied to it. The k-checkbox CSS class sets the width of the checkbox to zero and the k-checkbox-label CSS class draws a square before the label text using the colors for the specified Kendo UI Theme. When the "checked" property is assigned to the checkbox, then k-checkbox-label CSS class draws a check inside the square. In the _template function, I define the checkbox and label (span) with the appropriate CSS classes. I also wrap each checkbox and label in a div so that I can define data-uid, data-value and data-text attributes as well as control the orientation of the checkboxes by setting the display for the div to "block" for vertical orientation and "inline-block" for horizontal orientation.

I decided that the root element for the ExtCheckBoxList should be a div. I didn't want the div to span the entire width of the page, so in the init function, I added the display style of "inline-block" to the div element.

Here is the code:

init: function (element, options) {
    /// <summary>Initialize the widget.</summary>
 
    kendo.ui.Widget.fn.init.call(this, element, options);
 
    this._dataSource();
 
    // Read the data from the data source.
    this.dataSource.fetch();
 
    this.element.css({ "display": "inline-block" });
},
 
_dataSource: function () {
    /// <summary>Initialize the data source.</summary>
 
    var dataSource = this.options.dataSource;
 
    // If the data source is an array, then define an object and set the array to the data attribute.
    dataSource = $.isArray(dataSource) ? { data: dataSource } : dataSource;
 
    // If there is a data source defined already. 
    if (this.dataSource && this._refreshHandler) {
        // Unbind from the change event.
        this.dataSource.unbind("change", this._refreshHandler);
    } else {
        // Create the refresh event handler for the data source change event.
        this._refreshHandler = $.proxy(this.refresh, this);
    }
 
    // Initialize the data source.
    this.dataSource = kendo.data.DataSource.create(dataSource).bind("change", this._refreshHandler);
},
 
_template: function () {
    /// <summary>Get the template for a checkbox.</summary>
 
    var html = kendo.format("<div class='k-ext-checkbox-item' data-uid='#: uid #' data-value='#: {0} #' data-text='#: {1} #' style='margin: 5px 10px 5px 0px;display:{2};'><input type='checkbox' value='#: {0} #' class='k-checkbox' /><span class='k-checkbox-label'>#: {1} #</span></div>", 
        this.options.dataValueField, this.options.dataTextField,
        this.options.orientation === "vertical" ? "block" : "inline-block");
 
    return kendo.template(html);
},
 
refresh: function (e) {
    /// <summary>Renders all checkboxes using the current data items.</summary>
 
    var template = this._template();
 
    // Remove all the existing items.
    this.element.empty();
 
    // Add each of the radio buttons.
    for (var idx = 0; idx < e.items.length; idx++) {
        this.element.append(template(e.items[idx]));
    }
 
    // Fire the dataBound event.
    this.trigger("dataBound");
}

Handling the Selection of a CheckBox

To handle the selection of a checkbox, I added a click event handler for the checkbox labels in the init function. I found that the Kendo UI Widgets use namespaces when defining events. This makes it easier to remove all event handlers from an element and all it's child elements by using the jQuery off function and passing in just the namespace. I created a _onCheckBoxSelected function to handle the click event and in the function I set the checked property for the selected checkbox. I then get the data item associated with the selected checkbox and trigger the select event passing the data item as a parameter to any event handlers that have subscribed to the select event. I get the data item by locating the div that wraps the checkbox that is checked and gets the data-uid attribute. The uid is used to look up the item in the DataSource using the DataSource.getByUid function.

I also implemented the destroy method, that gets called when widgets are destroyed, to remove all event handlers for the widget.

Here is the code:

init: function (element, options) {
    /// <summary>Initialize the widget.</summary>
 
    kendo.ui.Widget.fn.init.call(this, element, options);
 
    this._dataSource();
 
    // Read the data from the data source.
    this.dataSource.fetch();
 
    // Attach an event handler to the selection of a checkbox.
    this.element.on("click" + NS, ".k-radio-label", { sender: this }, this._onCheckBoxSelected);
 
    this.element.css({ "display": "inline-block" });
},
 
destroy: function () {
    /// <summary>Destroy the widget.</summary>
 
    $(this.element).off(NS);
 
    kendo.ui.Widget.fn.destroy.call(this);
},
 
_onCheckBoxSelected: function (e) {
    /// <summary>Handle the selection of a checkbox.</summary>
 
    var $target = $(this),
        $checkBoxItem = $target.closest(".k-ext-checkbox-item"),
        that = e.data.sender,
        isChecked = $checkBoxItem.find(".k-checkbox").is(":checked");
 
    $target.prev(".k-checkbox").prop("checked", !isChecked).addClass("k-state-selected");
 
    var selectedUid = $checkBoxItem.attr("data-uid");
    that.trigger("select", { item: that.dataSource.getByUid(selectedUid), checked: !isChecked });
},
 
dataItems: function () {
    /// <summary>Gets the dataItems for the selected checkboxes.</summary>
 
    var dataSource = this.dataSource,
        list = [],
        $items = this.element.find(".k-checkbox:checked").closest(".k-ext-checkbox-item");
 
    $.each($items, function () {
        var uid = $(this).attr("data-uid");
        list.push(dataSource.getByUid(uid));
    });
 
    return list;
}

Defining the remaining Methods

The setDataSource Method

The setDataSource method sets the value passed in to the options.dataSource configuration option then calls the _dataSource function. Here is the code:

setDataSource: function (dataSource) {
    /// <summary>Sets the data source of the widget.</summary>
 
    this.options.dataSource = dataSource;
    this._dataSource();
    this.dataSource.fetch();
}

The value Method

The value method determines whether a parameter has been passed in. If there is, then it loops over all the values in the array and attempts to find the div with the data-value attribute set to the value in the array and then calling the click function on the child checkbox. If a parameter was not passed in, it gets the value for each of the checkboxes that are checked. Here is the code:

value: function () {
    /// <summary>Gets or sets the value of the checkboxlist.</summary>
 
    if (arguments.length === 0) {
        var list = [];
        var $items = this.element.find(".k-checkbox:checked").closest(".k-ext-checkbox-item");
 
        // Get the value of all selected checkboxes.
        $.each($items, function () {
            var value = $(this).attr("data-value");
            list.push(value);
        });
        return list;
    } else {
        var that = this,
            list = $.isArray(arguments[0]) ? arguments[0] : (typeof arguments[0] === "string" ? [arguments[0]] : []);
 
        // Clear any selected checkboxes.
        this.element.find(".k-checkbox").prop("checked", false).removeClass("k-state-selected");
 
        // Check each checkbox.
        $.each (list, function () {
            var value = this;
            that.element.find(kendo.format(".k-ext-checkbox-item[data-value='{0}'] .k-checkbox", value)).click();
        });
    }
}

Adding the Widget to Kendo UI

Finally, the widget needs to be added as a plugin to Kendo UI. This is done using the kendo.ui.plugin function like this:

kendo.ui.plugin(ExtCheckBoxList);

Implementing the ExtCheckBoxList Widget

Now that I have a working plugin, I can implement it. I create an div element in the HTML:

<div id="checkBoxList"></div>

I create an array with some data and then initialize the ExtCheckBoxList with the desired options. After the widget is initialized, I can set the selected value using the value function:

var data = [{
    id: 1, name: "Cat"
},{
    id: 2, name: "Dog",
},{
    id: 3, name: "Bird"
}];
 
// Initialize the kendoExtRadioButtonGroup.
var checkBoxList = $("#checkBoxList").kendoExtCheckBoxList({
    dataSource: data,
    dataValueField: "id",
    dataTextField: "name",
    orientation: "horizontal",
    dataBound: function () {
        console.log("Event: dataBound");
    },
    select: function (e) {
        console.log("Event: select", kendo.format("id: {0}, value: {1}, is checked: {2}<br/>", e.item.id, e.item.name, e.checked));
    }
}).data("kendoExtCheckBoxList");
 
// Set the selected checkboxes.
checkBoxList.value(["1", "3"]);

I can set the data source and define the event handlers in the initialization of the widget as shown above, or I can set the data source and bind to the events after the widget is initialized like this:

var checkBoxList = $("#checkBoxList").kendoExtCheckBoxList({
    dataValueField: "id",
    dataTextField: "name",
    orientation: "horizontal"
}).data("kendoExtRadioButtonGroup");
 
checkBoxList.bind("select", function (e) {
    console.log("Event: select", kendo.format("id: {0}, value: {1}, is checked: {2}<br/>", e.item.id, e.item.name, e.checked));
});
 
checkBoxList.bind("dataBound", function () {
    console.log("Event: dataBound");
});
 
checkBoxList.setDataSource(data);

jsFiddle

Downloads

kendo.web.plugins.checkboxlist.js

Comments: 0

Add a New Comment

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License