Introduction to Kendo MVVM

This website is no longer actively supported

Written by John DeVightJohn DeVight on 24 Oct 2012 12:23

Overview

When I first started developing web applications, JavaScript libraries where still in it's infancy. Rarely did anyone think of implementing business logic in JavaScript. Instead, pages were posted back to the server and XMLHTTPRequests were made to the server using technologies such as the UpdatePanel in ASP.NET AJAX. The business logic was on the server and frustrated users would have to wait for servers to evaluate data, apply business rules and respond to the web page. JavaScript libraries have matured significantly and developers are able to provide a "rich and interactive client experience" using these JavaScript libraries. Additionally, developers have put a good deal more business logic into JavaScript. This has presented a new challange: separating UI code from business logic in JavaScript. Libraries such as Knockout.js and Kendo MVVM have provided solutions to this problem by providing an implementation of the MVVM pattern for separating the UI from the business logic.

Model-View-ViewModel (MVVM) is an architecture pattern used to develop a clear separation of the UI from the business logic. While similar to the Model-View-Presenter (MVP) pattern, what differentiates MVVM from MVP is that MVVM uses declarative bindings to associate UI elements with Model data to handle the synchronization of the data between the View and the ViewModel.

The MVVM pattern consists of 4 elements: Model, View, ViewModel and declarative data binding. Each is defined as follows:

  1. Model: represents the domain specific data or the data access layer.
  2. View: the UI implements displayed to the user.
  3. ViewModel: an abstraction of the view that serves as a mediator between the View and the Model. The ViewModel sits behind the view and exposes data needed by the View from the Model.
  4. Declarative data binding: declarative data binding frees the developer from having to write code to synchronize the data between the View and the ViewModel that creates a clear separation of the UI from the busines logic.

Why Use Kendo's Implementation of MVVM?

Knockout.js is a fantastic library for implementing the MVVM pattern, and I would recommend it to anyone. However, Knockout.js isn't 100% compatible with Kendo UI. Todd Anglin posted an article that talks about the limitations of integrating Knockout.js with Kendo UI. I really like Kendo UI and if I am going to use Kendo UI, then I might as well use an implementation of MVVM that is fully compatible with Kendo UI. Additionally, for fans of Knockout.js, the Kendo MVVM framework is very similar to Knockout.js and I think would be easy for a developer who has worked with Knockout.js to transition into using Kendo MVVM.

Getting Started with Kendo MVVM

Kendo MVVM is part of the Kendo UI Framework which is included in all of their products: Web, Mobile and DataViz. To get started, you can either download the Open Source or 30-Day Free Trial from the Kendo UI Download Page, or you can link to the CDN files. Which ever route you choose, you will need to reference the following files:

File Path in Downloaded Zip File CDN Path
kendo.all.min.js ./js/kendo.web.min.js http://cdn.kendostatic.com/2012.2.710/js/kendo.web.min.js
kendo.common.min.css ./styles/kendo.common.min.css http://cdn.kendostatic.com/2012.2.710/styles/kendo.common.min.css
kendo.default.min.css ./styles/kendo.default.min.css http://cdn.kendostatic.com/2012.2.710/styles/kendo.default.min.css
jQuery ./js/jquery.min.js http://code.jquery.com/jquery-1.7.1.min.js

ObservableObject

A Simple Example using the ObservableObject

The Kendo MVVM framework creates an instance of the ObservableObject using the kendo.observable method. The ViewModel is implemented with the kendo.observable method by passing an object that contains properties and methods.

I can define a person ViewModel object as follows:

var person = kendo.observable({
    firstName: "John",
    lastName: "DeVight"
});

In the HTML that will display the person's information, I create an input field for each of the person's properties. Each input has a "data-bind" attribute. This is how the Kendo MVVM framework implements declarative data binding to bind the property of a ViewModel to the HTML UI element. In the following example, the first input field has a data-bind attribute to set the "value" of the input field to the "firstName" property. The second input field has a data-bind attribute to set the "value" of the input field to the "lastName" property.

Here is the HTML that will display the first name and last name of the person:

<div id="personFields">
    <div class="field">
        <div>First Name:</div><input data-bind="value: firstName"/>
    </div>
    <div class="field">
        <div>Last Name:</div><input data-bind="value: lastName"/>
    </div>
</div>

The last thing that needs to be done is to bind the ViewModel to the View (or HTML). The ViewModel is bound to the UI using the kendo.bind method which takes two parameters, the HTML element to bind the ViewModel and the ViewModel.

I could bind the ViewModel to the entire page by passing $('body') as the first parameter to the kendo.bind method, but I may have more than one View on a page that will require that I bind a separate ViewModel to each View. So, instead of binding to the HTML body, I defined a "container" div element called personFields that I can bind to the ViewModel. Here is the code:

kendo.bind($('#personFields'), person);

Here is the example on jsFiddle:

2 Way Data Binding

In the first example, I demonstrated binding the the ViewModel to the View. The Kendo MVVM framework supports 2 way data binding; what is updated in the ViewModel is reflected in the View and vice-versa. However, this is difficult to see in the first example. Let's take a look at each scenerio and see how it is implemented.

Changes to the View reflected in the ViewModel using a Dependent Method

Let's start with seeing how a change to the View is reflected in the ViewModel. I can demonstrate this using a dependent method. Using the example above, I can combine the person's first and last name together to display the person's full name. For the Kendo MVVM framework to create a dependency the ObservableObject.get function must be used. The use of the get function within the fullName dependent method will tell the Kendo MVVM framework that the dependant methods depends on the firstName and lastName properties. Here is how to create the fullName method in person:

// Create an observable view model object.
var person = kendo.observable({
    firstName: "John",
    lastName: "DeVight",
 
    // Create a dependent method (calculated field).
    fullName: function() {
        // The "get" function must be used so that changes to any
        // dependencies will be reflected in the dependent method.
        return this.get("firstName") + " " + this.get("lastName");
    }
});

To bind the fullName to the View, the HTML is updated with a span that contains an attribute called "data-bind" that binds the "text" of the span element to the "fullName" dependent method. Here is the HTML:

<div id="personFields">
    <div class="field">
        <div>First Name:</div><input data-bind="value: firstName"/>
    </div>
    <div class="field">
        <div>Last Name:</div><input data-bind="value: lastName"/>
    </div>
    <div class="field">
        <div>Full Name:</div><span data-bind="text: fullName"/>
    </div>
</div>

Here is the example on jsFiddle:

Changes to the ViewModel reflected in the View

Now let's go the other way and see how a change to the ViewModel is reflected in the View. To do this, the ObservableObject.set function is used. Continuing with our example, let's say I want to be able to reset the first and last name for the person. To do that, I'll add a method to the person object called reset. In the reset method I'll set the firstName and lastName. Here is the code:

// Create an observable view model object.
var person = kendo.observable({
    firstName: "John",
    lastName: "DeVight",
 
    // Create a dependent method (calculated field).
    fullName: function() {
        // The "get" function must be used so that changes to any
        // dependencies will be reflected in the dependent method.
        return this.get("firstName") + " " + this.get("lastName");
    },
 
    reset: function() {
        this.set("firstName", "John");
        this.set("lastName", "DeVight");
    }
});

Next I want to create a button that calls the reset function. In addition to using declarative binding to bind a property of a ViewModel to a View, a method of a ViewModel can be bound to an event of an HTML element in the View. Instead of defining the onclick attribute in the button and having it call the person's reset method, I can use the "data-bind" attribute to bind the "click" event to the "reset" method. Here is the HTML:

<div id="personFields">
    <div class="field">
        <div>First Name:</div><input data-bind="value: firstName"/>
    </div>
    <div class="field">
        <div>Last Name:</div><input data-bind="value: lastName"/>
    </div>
    <div class="field">
        <div>Full Name:</div><span data-bind="text: fullName"/>
    </div>
 
    <button data-bind="click: reset">Reset</button>
</div>

Here is the example on jsFiddle:

ObservableArray

A Simple Example using the ObservableArray

With the Kendo MVVM framework there are two ways of creating a new ObservableArray. The first is to create an instance of ObservableArray and pass in the array of objects. Here is an example:

var people = new kendo.data.ObservableArray([
    {
        firstName: "John",
        lastName: "DeVight"},
    {
        firstName: "Wendy",
        lastName: "Parry"
    }
]);

The second way is to use the kendo.observable method to create an instance of an ObservableObject and any arrays defined in the ObservableObject will be wrapped in an instance of an ObservableArray. Here is an example:

var vm = kendo.observable({
    people: [
        {
        firstName: "John",
        lastName: "DeVight"},
    {
        firstName: "Wendy",
        lastName: "Parry"}
    ]
});

In this example, I am going to take the second approach of creating an ObserableObject that contains an ObservableArray.

To display the people in a table, I define a Kendo Template that defines how each item in the array will be rendered as a row the table. Here is the template:

<script type="text/x-kendo-template" id="personTemplate">
    <tr>
        <td>#= firstName #</td>
        <td>#= lastName #</td>
    </tr>
</script>

Now I define my table and in the tbody element for my table, I bind to the ViewModel using the "data-bind" attribute and set the source to the property of the ViewModel that contains the array. In the ViewModel above, the ObservableObject contains an array called people. Therefore, I will set the "data-bind" attribute to have a source of people. Here is the HTML:

<table>
<thead>
    <tr>
        <th>First Name</th>
        <th>Last Name</th>
    </tr>
</thead>
<tbody id="peopleRows" data-bind="source: people" data-template="personTemplate">
</tbody>   
</table>

Finally, I need to bind the ObservableObject to the tbody of the table:

kendo.bind($("#peopleRows"), vm);

Here is the example on jsFiddle:

Using a Kendo Model to define a Dependent Method

In the example of the Observable object, when I demonstrated the use of a dependent method to show the user's full name, I simply added a function called fullName to the person object that used the ObserableObject.get function to get the firstName and lastName properties to return the user's full name. Now with an array of ObservableObjects, I don't want to add a fullName function as I create each instance of a person. Instead of doing this, I can create a Kendo Model called Person that has the fullName function. Here is the definition of the Person model:

var Person = kendo.data.Model.define({
    fields: {
        "firstName": {
            type: "string"
        },
        "lastName": {
            type: "string"
        }
    },
    fullName : function() {
        return this.get("firstName") + " " + this.get("lastName");
    }
});

Now when I create a ViewModel that contains a property of people, I'll create instances of the Person object as I define each person in the array:

var vm = kendo.observable({
    people: [
        new Person({
            firstName: "John",
            lastName: "DeVight"
        }),
        new Person({
            firstName: "Wendy",
            lastName: "Parry"
        })
    ]
});

The last thing I need to do is change the template so that the firstName and lastName of each person is displayed as an input field, and when changed, the fullName is changed as well. Here is the template:

<script type="text/x-kendo-template" id="personTemplate">
    <tr>
        <td><input data-bind="value: firstName" /></td>
        <td><input data-bind="value: lastName" /></td>
        <td><span data-bind="text: fullName" /></td>
    </tr>
</script>

Here is the example on jsFiddle:

Binding Methods to Add and Delete

The ObservableArray has several methods for working with arrays that you can find in thier documentation on kendo.data.ObservableArray. To implement the add and delete methods to the array of people, I am going to use the push method to add a new item to the end of the array and a splice method for removing an item from an array based on the index of the item. I can accomplish this by adding add and delete methods to my ViewModel. Binding to add and delete methods is the same as what was done to bind the reset method in the ObservableObject example above. Here is the code:

// Create an observable object with an obserable array where each item
// in the array is an instance of a Person model.
var vm = kendo.observable({
    people: [
        new Person({
            firstName: "John",
            lastName: "DeVight"
        }),
        new Person({
            firstName: "Wendy",
            lastName: "Parry"
        })
    ],
 
    // Add a new person to the array.
    add: function() {
        this.people.push(new Person());
    },
 
    // Delete the person from the array.
    delete: function (e) {
        var that = this;        
        $.each(that.people, function(idx, person) {
            if (e.data.uid === person.uid) {
                that.people.splice(idx, 1);
                return true;
            }
        });
    }
});

In the previous example, I bound the ViewModel to the table's tbody element. Since I am going to implement a delete button on each row, this would work just fine. However, I want to define the add button outside of the table. Therefore, I created a div called peopleList that contains the add button and the table of people. The add button implements the data-bind attribute to bind the "click" event to the "add" method. Here is the HTML:

<div id="peopleList">
    <div id="commands">
        <button data-bind="click: add" class="t-button">Add</button>
    </div>
 
    <table>
    <thead>
        <tr>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Full Name</th>
            <th></th>
        </tr>
    </thead>
    <tbody data-bind="source: people" data-template="personTemplate">
    </tbody>   
    </table>
</div>

To add a delete button to each row, I modified the template to include another table cell that contains a delete button. The delete button implements the data-bind attribute to bind the "click" event to the "delete" method. Here is the template:

<script type="text/x-kendo-template" id="personTemplate">
    <tr>
        <td><input data-bind="value: firstName" /></td>
        <td><input data-bind="value: lastName" /></td>
        <td><span data-bind="text: fullName" /></td>
        <td><button data-bind="click: delete">X</button></td>
    </tr>
</script>

Finally, instead of binding the ViewModel to the table's tbody element, I bind the ViewModel to the peopleList div element. Here is the code:

kendo.bind($("#peopleList"), vm);

Here is the example on jsFiddle:

Adding a Role DropDownList For Each Person

Now I want to add a role dropdownlist for each person in the table. To start, I add a new field to the Person model called roleId. Here is the updated Person model:

// Define a Person model.
var Person = kendo.data.Model.define({
    fields: {
        "firstName": {
            type: "string"
        },
        "lastName": {
            type: "string"
        },
        "roleId": {
            type: "number"
        }
    },
 
    // Define a function for fullName to get the firstName and lastName
    // and concatenate them together.
    fullName: function() {
        return this.get("firstName") + " " + this.get("lastName");
    }
});

Then in the ViewModel I initialize each person with a roleId. I add another array to the ViewModel called roles that has an id and a name. Here is the updated ViewModel:

var vm = kendo.observable({
    people: [
        new Person({
        firstName: "John",
        lastName: "DeVight",
        roleId: 2
    }),
        new Person({
        firstName: "Wendy",
        lastName: "Parry",
        roleId: 1
    })
        ],
 
    roles: [
        { id: 1, name: "CEO" },
        { id: 2, name: "Developer" },
        { id: 3, name: "Tester" }
    ],
 
    // Add a new person to the array.
    add: function() {
        this.people.push(new Person());
    },
 
    // Delete the person from the array.
    delete: function(e) {
        var that = this;
        $.each(that.people, function(idx, person) {
            if (e.data.uid === person.uid) {
                that.people.splice(idx, 1);
                return true;
            }
        });
    }
});

In the View, I update the personTemplate to have a new column with a select element in it. The select element implements the data-bind attribute to bind the "source" to the "roles" array and also binds the "value" to the "roleId" for each person. The select element also implements the data-template attribute that is set to a template called selectRoleTemplate that is used to format each option in the select element. Here is the updated personTemplate:

<script type="text/x-kendo-template" id="personTemplate">
    <tr>
        <td><input data-bind="value: firstName" /></td>
        <td><input data-bind="value: lastName" /></td>
        <td><span data-bind="text: fullName" /></td>
        <td><select data-bind="source: roles, value: roleId" data-template="selectRoleTemplate"></select></td>
        <td><button data-bind="click: delete">X</button></td>
    </tr>
</script>

Finally, I add a selectRoleTemplate that defines what each option element in the select element. Here is the template:

<script type="text/x-kendo-template" id="selectRoleTemplate">
    <option data-bind="value: id, text: name" />
</script>

Here is the example on jsFiddle:

Conclusion

The tutorial on the Kendo website is good, but brief; and while the documentation for Kendo MVVM on the Kendo UI website, is thorough in defining the configuration, methods and events I felt like I needed more information to get started. The Kendo MVVM reminds me a lot of Knockout.js, and combining my experience with Knockout.js and the documentation that Kendo provides, it was easy enough for me to figure out. Both frameworks are great, but since I work with the Kendo UI Web and Mobile controls, the Kendo MVVM framework has been my choice simply from the standpoint of integrating the controls with an MVVM framework.

References

  1. Kendo UI
  2. Knockout.js
  3. Kendo UI, MVVM and KnockoutJS
  4. jsFiddles : jddevight's fiddles - jsFiddle

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