Tuesday, February 7, 2012

Using knockout.mapping Plugin with Asp .Net MVC - Part 1

Knockout is a JavaScript library that makes it easier to create rich, desktop-like user interfaces with JavaScript and HTML, using observers to make your UI automatically stay in sync with an underlying data model.

In this post I am going to show how ASP .Net MVC and Knockout can compliment each other to allow for a rich client side user experience.

Starting with an Empty MVC 3 application in Visual Studio let's start with some dependencies:
PM> install-package knockoutjs

PM> install-package Knockout.Mapping
PM> update-package //running this last command will update bundled packages that MVC 3 comes with (jQuery etc).

Installing the above packages will give you the javascript files required for Knockout and the Knockout Mapping plugin.

I highly recommend reading the documentation on the Knockout js site for a solid background of what Knockout can do to help solve client side functionality problems.

Now that we have the pre-requisites let's build a model to interact with:


Code Snippet

public class Workout
    {
        public long Id { get; set; }
        public string DateTime { get; set; }
        public string WorkoutName { get; set; }
    }



Now that we have a model we need to present it to the user.

Let's start with a view that shows all workouts in a tabular format. The html output I want is as such:

Html Layout for Knockout

To achieve that I need to replicate the html code as follows:


Code Snippet



  1. <table class="gridtable">

  2.     <thead>

  3.         <tr>

  4.             <th>

  5.                 Date Of Workout

  6.             </th>

  7.             <th>

  8.                 Name Of Workout

  9.             </th>

  10.         </tr>

  11.     </thead>

  12.     <tbody>

  13.         <tr>

  14.             <td>

  15.                 24/12/2011

  16.             </td>

  17.             <td>

  18.                 Full Body Workout 1a

  19.             </td>

  20.         </tr>

  21.         <tr>

  22.             <td>

  23.                 25/12/2011

  24.             </td>

  25.             <td>

  26.                 Full Body Workout 2a

  27.             </td>

  28.         </tr>

  29.     </tbody>

  30. </table>





Let's set up a Action on the Home controller to return a JsonResult.




Code Snippet



  1. public JsonResult GetWorkouts()

  2.         {

  3.             var model = new List<Workout>

  4.                             {

  5.                                 new Workout

  6.                                     {DateTime = DateTime.Now.AddDays(-7).ToShortDateString(), Id = 1, WorkoutName = "Full Body Workout 1a"},

  7.                                 new Workout

  8.                                     {DateTime = DateTime.Now.AddDays(-6).ToShortDateString(), Id = 2, WorkoutName = "Full Body Workout 2a"}

  9.                             };

  10.  

  11.             return Json(model);

  12.         }





Now for the javascript to initalise knockout and set up the inital data for the view model.



Code Snippet

(function (getworkouts, $, undefined) {

    //create view model and initialise with default data.
    getworkouts.viewModel = function() {
        this.workouts = ko.mapping.fromJS(
                    [],
                    {
                        key: function (workout) { return ko.utils.unwrapObservable(workout.Id); }
                    });
                };

    //use ajax call to populate the view model from the Action and also bootstraps knockout
    getworkouts.getDataFromSource = function () {
        $.ajax({
            type: 'POST',
            url: '/Home/GetWorkouts',
            dataType: 'json',
            success: function (data) {
                var model = new getworkouts.viewModel();
                model.workouts = ko.mapping.fromJS(data);
                ko.applyBindings(model);
            }
        });
    };
} (window.getworkouts = window.getworkouts || {}, jQuery));

$(document).ready(function () {
    //bootstrap call
    getworkouts.getDataFromSource();
});



As you can see I use a Self-Executing Anonymous Function to help protect the global namespace.

Let's dissect the javascript.

First we create a public property on the View Model called workouts, this will store the array of workouts to be displayed to the client. Next we create a bootstrapping function, see the function named getDataFromSource. This makes a jQuery ajax call to /Home/GetWorkouts, an action on the Home controller that returns a JsonResult which represents the data to be presented. The line model.workouts = ko.mapping.fromJS(data); is particularly interesting. The mapping plugin for Knockout gives you a straightforward way to map a plain JavaScript object into a view model with the appropriate observables. This provides an alternative to manually writing your own JavaScript code to construct the view model. That is instead of writing a lot of boilerplate code you can write one line and get on with writing the rest of your application. Finally calling ko.applyBindings(model); initialises knockout.

All that is needed now is the UI set up.




Code Snippet

<table class="gridtable">
    <thead>
        <tr>
            <th>
                Date Of Workout
            </th>
            <th>
                Name Of Workout
            </th>
        </tr>
    </thead>
    <tbody data-bind="foreach: workouts">
        <tr>
            <td><span data-bind="text: DateTime"></span></td>
            <td><span data-bind="text: WorkoutName"></span></td>
        </tr>
    </tbody>
</table>



This will iterate over the workouts property and produce the relevant tr elements and text to produce Html as we were expecting.

The source code is available here at github

My next post in this series will add Edit/Delete functionality to the application until then I hope that this post has presented the reader with an overview of a possible approach for a real life knockout and MVC 3 based application.

2 comments:

  1. I have been looking for something like this in the web that could explain in a simple example Knockout and Knockout Mapping.

    Hope you can make the next part for the edit and delete functionality.

    Regards,

    ReplyDelete
  2. @Jean Part 2 has been on my to do list for awhile unfortunately work has been busy, you've inspired me though...

    ReplyDelete