Creating a Kendo UI Radio Button Group Widget

11 Apr 2016 20:41
Tags kendo plugin radio widget

Back to list of posts

Overview

I needed to create radio buttons in a web application and wanted them to have a look and feel that matched the Kendo UI theme that I had selected. Although there isn't a radio button widget for Kendo UI, the Kendo UI CSS has styles for a radio button. I wanted to make it easier to apply the Kendo UI style to radio buttons in my web application, so I created a Kendo UI widget for a radio button group.

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

radiobuttons_before.png

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

radiobuttons_after.png

Defining the API

The first step I took to creating my radio button group widget was 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 the radio button group I would have chosen to use the DropDownList, so I took a look at the DropDownList API documentation to figure out what the API should look like for my radio button group 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. groupName (string) - the name property defined for each radio button within a group.
  5. name (string) - the name of the widget.
  6. 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. dataItem - returns the data item for the selected radio button.
  2. refresh - renders all radio buttons using the current data items.
  3. setDataSource - sets the data source of the widget.
  4. text - gets or sets the text of the radio button group.
  5. value - gets or sets the value of the radio button group.

Events

  1. change - fired when the user selects a radio button.
  2. dataBound - fired when the widget is bound to data from its data source.

Defining the ExtRadioButtonGroup

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 ExtRadioButtonGroup = kendo.ui.Widget.extend({
    options: {
        dataTextField: "",
        dataValueField: "",
        groupName: "",
        name: "ExtRadioButtonGroup",
        orientation: "vertical"
    },
 
    events: [
        "change",
        "dataBound"
    ],
 
    dataSource: null,
 
    init: function (element, options) {},
 
    destroy: function () {},
 
    _dataSource: function () {},
 
    _template: function () {},
 
    _onRadioButtonSelected: function () {},
 
    setDataSource: function (dataSource) {},
 
    refresh: function (e) {},
 
    dataItem: function () {},
 
    text: function () {},
 
    value: function () {}
});

Rendering the ExtRadioButtonGroup

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 DropDownList init function, the _dataSource function is called to initialize the DataSource from the Configuration options. The DropDownList also 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 the DropDownList uses a _template function to get the template for each item in the list, so I implement the same for the ExtRadioButtonGroup.

Digging through the CSS for Kendo UI, I discovered the styles k-radio and k-radio-label. The label for the radio button is expected to be rendered after the radio button and have the k-radio-label CSS class applied to it. The radio button is expected to have the k-radio CSS class applied to it. The k-radio CSS class sets the width of the radio button to zero and the k-radio-label CSS class draws a circle before the label text using the colors for the specified Kendo UI Theme. When the "checked" property is assigned to the radio button, then k-radio-label CSS class applies the appropriate background color to the circle that is drawn before the label text. In the _template function, I define the radio button and label (span) with the appropriate CSS classes. I also wrap each radio button 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 radio buttons 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 ExtRadioButtonGroup 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 radio button.</summary>
 
    var html = kendo.format("<div class='k-ext-radiobutton-item' data-uid='#: uid #' data-value='#: {1} #' data-text='#: {2} #' style='margin: 5px 10px 5px 0px;display:{3};'><input type='radio' name='{0}' value='#: {1} #' class='k-radio' /><span class='k-radio-label'>#: {2} #</span></div>", 
        this.options.groupName.length === 0 ? kendo.guid() : this.options.groupName, 
        this.options.dataValueField, this.options.dataTextField,
        this.options.orientation === "vertical" ? "block" : "inline-block");
 
    return kendo.template(html);
},
 
refresh: function (e) {
    /// <summary>Renders all radio buttons 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 Radio Button

To handle the selection of a radio button, I added a click event handler for the radio button 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 _onRadioButtonSelected function to handle the click event and in the function I set the checked property for the selected radio button. I then get the data item associated with the selected radio button and trigger the change event passing the data item as a parameter to any event handlers that have subscribed to the change event. I get the data item by calling the dataItem function.

The dataItem function locates the div that wraps the radio button 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 radio button.
    this.element.on("click" + NS, ".k-radio-label", { sender: this }, this._onRadioButtonSelected);
 
    this.element.css({ "display": "inline-block" });
},
 
destroy: function () {
    /// <summary>Destroy the widget.</summary>
 
    $(this.element).off(NS);
 
    kendo.ui.Widget.fn.destroy.call(this);
},
 
_onRadioButtonSelected: function (e) {
    /// <summary>Handle the selection of a radio button.</summary>
 
    var $target = $(this),
        that = e.data.sender;
 
    that.element.find(".k-radio").prop("checked", false).removeClass("k-state-selected");
 
    $target.prev(".k-radio").prop("checked", true).addClass("k-state-selected");
 
    var dataItem = that.dataItem();
 
    that.trigger("change", { dataItem: dataItem });
},
 
dataItem: function () {
    /// <summary>Gets the dataItem for the selected radio button.</summary>
 
    var uid = this.element.find(".k-radio:checked").closest(".k-ext-radiobutton-item").attr("data-uid");
 
    return this.dataSource.getByUid(uid);
}

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 text Method

The text method determines whether a parameter has been passed in. If there is, then it attempts to find the div with the data-text attribute set to the text passed in and then calling the click function on the child radio button. If a parameter was not passed in, it gets the text of the radio button that is selected. Here is the code:

text: function () {
    /// <summary>Gets or sets the text of the radio button group.</summary>
 
    if (arguments.length === 0) {
        return this.element.find(".k-state-selected").closest(".k-ext-radiobutton-item").attr("data-text");
    } else {
        this.element.find(kendo.format(".k-ext-radiobutton-item[data-text='{0}']", arguments[0])).find(".k-radio-label").click();
    }
}

The value Method

The value method determines whether a parameter has been passed in. If there is, then it attempts to find the div with the data-text attribute set to the value passed in and then calling the click function on the child radio button. If a parameter was not passed in, it gets the value of the radio button that is selected. Here is the code:

value: function () {
    /// <summary>Gets or sets the value of the radio button group.</summary>
 
    if (arguments.length === 0) {
        return this.element.find(".k-state-selected").closest(".k-ext-radiobutton-item").attr("data-value");
    } else {
        this.element.find(kendo.format(".k-ext-radiobutton-item[data-value='{0}']", arguments[0])).find(".k-radio-label").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(ExtRadioButtonGroup);

Implementing the ExtRadioButtonGroup Widget

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

<div id="radioButtonGroup"></div>

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

var data = [{
    id: 1, name: "Cat"
},{
    id: 2, name: "Dog",
},{
    id: 3, name: "Bird"
}];
 
// Initialize the kendoExtRadioButtonGroup.
var radioButtonGroup = $("#radioButtonGroup").kendoExtRadioButtonGroup({
    dataSource: data,
    dataValueField: "id",
    dataTextField: "name",
    groupName: "animal",
    orientation: "horizontal",
    change: function (e) {
        console.log("Event: change", kendo.format("id: {0}, value: {1}", e.dataItem.id, e.dataItem.name));
    },
    dataBound: function () {
        console.log("Event: dataBound");
    }
}).data("kendoExtRadioButtonGroup");
 
// Set the selected radio button.
radioButtonGroup.value(1);

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 radioButtonGroup = $("#radioButtonGroup").kendoExtRadioButtonGroup({
    dataValueField: "id",
    dataTextField: "name",
    groupName: "animal",
    orientation: "horizontal"
}).data("kendoExtRadioButtonGroup");
 
radioButtonGroup.bind("change", function (e) {
    console.log("Event: change", kendo.format("id: {0}, value: {1}", e.dataItem.id, e.dataItem.name));
});
 
radioButtonGroup.bind("dataBound", function () {
    console.log("Event: dataBound");
});
 
radioButtonGroup.setDataSource(data);

jsFiddle

Downloads

kendo.web.plugins.radiobuttongroup.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