Saturday, March 24, 2012

How to build a grid with knockout and ASP.Net MVC


Fairly recently the team at CBS decided to adopt knockout to help us build our UIs. One of our major undertakings was to replace the JQGrid with a knockout equivalent, we haven't regretted it for a minute. The ability to quickly adapt the functionality to meet the different demands of our various clients plus the ability to style easily and effectively (a big gripe with the JQGrid)  has paid off handsomely.

This tutorial is broken into 3 parts:
Part 1 - building the basic HTML table using knockout and loading data from the server
Part 2 - implementing paging
Part 3 - implementing sorting


In this tutorial I'm going to take you through building a basic html table using knockout and retrieving data from a controller method on the server.

Step 1 - Install the knockout and knockoutMapping js files
You can either download these from the knockout website or use NuGet - if you're using NuGet then you need to install the knockoutjs package and the knockout.mapping package this will add a couple of js files to the scripts folder in your solution.

Once you have the js files added you need to reference them, I normally add them to my _Layout.cshtml file as I use them on virtually every page in my applications. Your _layout.cshtml file should look as follows:

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/knockout.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/knockout.mapping-latest.js")" type="text/javascript"></script>
</head>
<body>
    <div class="page">
        <div id="header">
            <div id="title">
                <h1>My MVC Application</h1>
            </div>
            <div id="logindisplay">
                @Html.Partial("_LogOnPartial")
            </div>
            <div id="menucontainer">
                <ul id="menu">
                    <li>@Html.ActionLink("Home""Index""Home")</li>
                    <li>@Html.ActionLink("About""About""Home")</li>
                    <li>@Html.ActionLink("People""Index""People")</li>
                </ul>
            </div>
        </div>
        <div id="main">
            @RenderBody()
        </div>
        <div id="footer">
        </div>
    </div>
</body>
</html>
 
Step 2 - Build a ViewThe view I added for this demo is called Index.cshtml and lives in a Views/People folder. The code looks as follows:

@{
    ViewBag.Title = "Index";
}
 
<h2>Index</h2>
 
<table id="people">
    <thead>
    <tr>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Age</th>
    </tr>
    </thead>
    <tbody data-bind="foreach: people">
        <tr>
            <td><span data-bind="text: FirstName"></span></td>
            <td><span data-bind="text: LastName"></span></td>
            <td><span data-bind="text: Age"></span></td>
        </tr>
    </tbody>
    
</table>
 
<script type="text/javascript">
    function peopleViewModel() {
        var _this = {};
 
        _this.people = ko.observableArray();
        ko.applyBindings(_this, $("#people").get(0));
 
        function LoadPeopleFromServer() {
            $.post(
                '/people/data',
                function (data) {
                    var results = ko.observableArray();
                    ko.mapping.fromJS(data, {}, results);
                    for (var i = 0; i < results().length; i++) {
                        _this.people.push(results()[i]);
                    };
                },
                'json'
            )
        }
 
        LoadPeopleFromServer();
 
        return _this;
    }
 
    var viewModel = peopleViewModel();
 
</script>
 
 
I've kept the HTML and Javascript in one file in order to make this easy to follow. If you have no experience of knockout I would strongly suggest you follow the excellent interactive tutorial on the knockout site. The HTML is vanilla knockout, it uses the data-bind attribute to specify how knockout binds values on the javascript model to DOM elements.

The javascript is more interesting: you'll notice that the peopleViewModel() object has a property _this.people which is an observableArray, this means that knockout will take notice when elements are added and removed from it and update the DOM accordingly. The LoadPeopleFromServer() method makes use of the jQuery ($.post) method to retrive data from the server in a JSON format, this JSON is returned to us as an array of javascript objects by JQuery, this is then taken by the knockout mapping utility and turned into an ko.observableArray of javascript objects. The beauty of using the mapping utlity is that we don't have to create any javascript objects to represent people, this is all done for us! This also has the advatage that if we add a new property to a person object on the server we don't need to change any javascript, only the HTML where we want the new value shown.

You may wonder why _this.people has been declared and we then push objects into it once they have returned from the server rather than just passing it into the mapping method. The reason for this is that the mapping method creates a new observableArray and assigs it to the variable you passed in. This ruins your knockout bindings as they were bound to the original array that you declared. You can get around this by only binding once you have got data back from the server but this gets more complicated once you introduce paging, etc. This pattern works and is easy to follow and implement.

Step 3 - The Controller Method
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace knockoutGrid.Controllers
{
    public class PeopleController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
 
 
        public JsonResult Data()
        {
            List<Person> people = new List<Person>();
            people.Add(new Person("Richie""McCaw", 33));
            people.Add(new Person("Dan""Carter", 32));
            people.Add(new Person("Owen""Franks", 23));
 
            return Json(people);
        }
 
    }
 
    public class Person
    {
        public string FirstName { getset; }
        public string LastName { getset; }
        public int Age { getset; }
 
        public Person(string firstName, string lastName, int age)
        {
            this.FirstName = firstName;
            this.LastName = lastName;
            this.Age = age;
        }
    }
 
}

The controller code is relatively simple, normally I would be retrieving data from a database but here I've hard coded it. The only things you really need to take heed of are the use of the JsonResult type that is being returned and the Json method that is needed to turn the list of people into valid JSON.

In Part 2 I will show you how to implement paging!

I have put the source code up on bitbucket, you can download it here.

6 comments:

  1. Hi David,
    While i am running this , i got error results()in undefine ..can u please expalin this error??????????

    ReplyDelete
  2. I've put the source code up on bitbucket: https://bitbucket.org/dwcarter/knockoutgrid/get/af0fa4f38c8b.zip . Please try downloading and running it and see if that solves your problem.

    ReplyDelete
  3. Wow, brilliant, I've been looking everywhere to do something similar Tnx!

    ReplyDelete
  4. I had to add JsonRequestBehavior.AllowGet in Json(people, JsonRequestBehavior.AllowGet) in order to make it workable.

    ReplyDelete
  5. Hi David,
    Excellent job, well done. This project is perfect to do sorting and pagination, but it is difficult to select a grid row and display it on the popup. would you be able to help me to find a way to do this? Thanks,
    px

    ReplyDelete
  6. Very well formatted tutorial. Many thanks for sharing.

    ReplyDelete