MVVM Support with KnockoutJS in NetAdvantage for jQuery 2012 Vol.1

[Infragistics] Mihail Mateev / Tuesday, April 24, 2012

Probably many developers who have experience with XAML applications are familiar with Model View ViewModel (MVVM) design pattern. Specialists who work on WEB projects now have the opportunity to implement this pattern in the client portion of the applications using KnockoutJS.

Knockout is a JavaScript library that helps you to create rich, responsive display and editor user interfaces with a clean underlying data model. Any time you have sections of UI that update dynamically (e.g., changing depending on the user’s actions or when an external data source changes), KO can help you implement it more simply and maintainable.

This article is about how to  integrate KnockoutJS with the Infragistics jQuery Grids in 12.1 release. Now with NetAdvantage for jQuery 12.1 it is possible to  take advantage of the MVVM pattern within your web applications and full two-way data binding support , both for cell/row updates, as well as add row/delete row. This post also could help XAML programmers to transit to HTML + JavaScript world, while carrying best practices such as MVVM pattern over to the new world.

With Knockout you will have:

  • Elegant dependency tracking - automatically updates the right parts of your UI whenever your data model changes.
  • Declarative bindings - a simple and obvious way to connect parts of your UI to your data model. You can construct a complex dynamic UIs easily using arbitrarily nested binding contexts.

Knockout is a pure JavaScript library - works with any server or client-side technology. It can be added on top of your existing web application without requiring major architectural changes. KO is a compact - around 13kb after gzipping and works on any mainstream browser (IE 6+, Firefox 2+, Chrome, Safari, others).

It is a challenge for each company, that has jQuery components to put together jQuery and Knockout and offer a real MVVM support for WEB components. To be possible to support notifications when the data in ViewModel is changed, components have Knockout extensions. In NetAdvantage for jQuery 2012 Vol.1 you could find KO extensions from Infragistics jQuery Grid, Hierarchical Grid and Data Source.

You will learn how to use KnockoutJS  bindings with the igGrid widget via simple ASP.Net MVC3 application. This application that provides information about the Customers from Northwind sample database. Data is stored in Microsoft SQL Server. Business objects are created via Entity Framework

Prerequisites

You need to add in the project:

  • Infragistics 12.1 release scripts(under Scripts folder) and styles (under Content folder):

It  is important to not to forget Infragistics knockout extensions:

  1. infragistics.datasource.knockoutjs.js
  2. infragistics.ui.grid.knockout-extensions.js
  • Knockout library: knockout.js (current sample uses version 2.0.0)
  • Knockout mapping: knockout.mapping-latest.js (current sample uses version 2.0) : this file is used from Infragistics knockout extensions.
  • Northwind sample data base

How to start:

You need to have data: in this sample is used Entity Framework to create business objects from Northwind database

Knockout Extensions

Knockout extensions should be loaded as resources in your page.

   1: <script type="text/javascript">
   2:     $.ig.loader({
   3:         scriptPath: "../../Scripts/Infragistics/js/", 
   4:         cssPath: "../../Content/Infragistics/css/",
   5:         resources: "igGrid.*,extensions/infragistics.datasource.knockoutjs.js,extensions/infragistics.ui.grid.knockout-extensions.js",
   6:         theme: "metro"
   7:     });
   8:  
   9: </script>

 

MVC Controller:

When you want to use Infragistics jQuery components and Knockout you have no need to use ASP.Net MVC, but MVC framework helps developers to create quickly line of business applications. It also helps Windows developers to create easily WEB applications. 

In this sample MVC controller is used to maintain data.

The method “Customers” is used to serialize data in JSON

   1: #region Customers
   2:  public JsonResult Customers()
   3:  {
   4:      return Json(nw.Customers, JsonRequestBehavior.AllowGet);
   5:  }
   6:  #endregion //Customers

 

Northwind data is a helper class used to deserialize JSON where data is a response data key

   1: public class NorthwindData
   2: {
   3:     public IEnumerable<Customer> data { get; set; }
   4: }

 

UpdateCustomers action is used to update Northwind database. View can use use this method to pass updated data from views to the controller.

   1: #region UpdateCustomers
   2: [HttpPost]
   3: public ActionResult UpdateCustomers(NorthwindData customers)
   4: {
   5:     var x = customers.data;
   6:  
   7:     foreach (Customer cust in x)
   8:     {
   9:         Customer oldCust = nw.Customers.Where(y => y.CustomerID.Equals(cust.CustomerID)).FirstOrDefault();
  10:         if (oldCust == null)
  11:         {
  12:             nw.Customers.AddObject(cust);
  13:         }
  14:         else
  15:         {
  16:             oldCust.Country = cust.Country;
  17:             oldCust.City = cust.City;
  18:             oldCust.ContactName = cust.ContactName;
  19:             oldCust.Address = cust.Address;
  20:             oldCust.Phone = cust.Phone;
  21:         }
  22:     }
  23:  
  24:     nw.SaveChanges();
  25:     return null;
  26: }
  27: #endregion //UpdateCustomers

 

Item is a class, used to represent customer in the ViewModel.

   1: function Item(CustomerID, Country, ContactName, CompanyName, City, Address, Phone) {
   2:         return {
   3:             CustomerID: ko.observable(CustomerID),
   4:             Country: ko.observable(Country),
   5:             ContactName: ko.observable(ContactName),
   6:             CompanyName: ko.observable(CompanyName),
   7:             City: ko.observable(City),                
   8:             Address: ko.observable(Address),
   9:             Phone: ko.observable(Phone)
  10:         };
  11:     };

 

ViewModel

The ViewModel contains observableArray to present a list with customers. It also has a computed field, named recordsCount, used to display the total number of customers.

   1: function ItemsViewModel() {
   2:     var self = this;
   3:  
   4:     self.data = ko.observableArray([]);
   5:  
   6:     self.recordsCount = ko.computed(function () {
   7:         var count = 0;
   8:         count = self.data().length;
   9:         return count;
  10:     });
  11:  
  12:     for (var i = 0; i < db.length; i++) {
  13:         self.data.push(new Item(db[i].CustomerID, db[i].Country, db[i].ContactName, db[i].CompanyName, db[i].City, db[i].Address, db[i].Phone));
  14:     }
  15: };

 

Data Maintenance:

  • Load data using the MVC Controller:

You could use jQuery.ajax() to get data as JSON from the Controller. In the sample this approach is used to load data about the customers.

   1: $.ajax({ url: url, success: function (data) {
   2:                db = data;
   3:                itemsModel = new ItemsViewModel();
   4:                ko.applyBindings(itemsModel);
   5:            },
   6:                type: 'POST', dataType: 'json'
   7:            });

  

  • Update the data source:

The same approach with jQuery.ajax() is used to update the Northwind database. In this case is used action “UpdateCustomers” in the our MVC controller.

   1: function UpdateCustomers() {
   2:     $("#grid").igGrid("commit");  
   3:    var updatedData = ko.toJSON(itemsModel);
   4:    $.ajax({
   5:        url: updateUrl,
   6:        type: "POST",
   7:        data: updatedData,
   8:        dataType: "json",
   9:        contentType: "application/json; charset=utf-8",
  10:        success: function () {
  11:            console.log('Update succeeded!');
  12:        },
  13:        error: function () {
  14:            console.log('Update failed!');
  15:        }
  16:    });
  17: };

 

JavaScript method “UpdateCustomers”  is called when an update button is clicked.

   1: <div style="position: relative; width: 180px; height: 30px; top: 80px; left: 20px">
   2:     <button id="updateBtn" style="height: 30px; width: 180px;"  onclick="UpdateCustomers()">Update Customers</button>
   3: </div>

 

Data Binding and Infragistics jQuery Grid

You need “data-bind” clause to create igGrid and bind it to the data source. In this case data source is the option “data” from our ViewModel.

   1: <table id="grid" data-bind="igGrid: {dataSource: data, width: 1100, height: 500, primaryKey: 'CustomerID', autoCommit: false, >
   2:                             features: [ {
   3:                                 name: 'Updating', editMode: 'row',
   4:                                 }
   5:                             ],
   6:                             autoGenerateColumns: false, 
   7:                             columns: [
   8:                                 {key: 'CustomerID', headerText: 'Customer ID', width: 80, dataType: 'string'},
   9:                                 {key: 'Country', headerText: 'Country', width: 150, dataType: 'string'},
  10:                                 {key: 'City', headerText: 'City', width: 100, dataType: 'string'},
  11:                                 {key: 'ContactName', headerText: 'Contact Name', width: 270, dataType: 'string'},
  12:                                 {key: 'CompanyName', headerText: 'Company Name', width: 200, dataType: 'string'},
  13:                                 {key: 'Address', headerText: 'Address', width: 200, dataType: 'string'},
  14:                                 {key: 'Phone', headerText: 'Phone', width: 100, dataType: 'string'}
  15:                             ]
  16:  
  17:                          }"
  18:         </table>

 

  • Calculated properties:

Calculated property (option) recordsCount is used to represent the total number of customers.

   1: self.recordsCount = ko.computed(function () {
   2:                     var count = 0;
   3:                     count = self.data().length;
   4:                     return count;
   5:                 });

 

You could bind this property to some HTML element (span in this case).

   1: <div style="position: relative; width: 180px; height: 30px; top: 20px; left: 20px">
   2:      <label><b>Customers count:</b> </label><span  data-bind="text: recordsCount" ></span>
   3:  </div>

 

 

To gather all in one

Here is the whole page with igGird, Knockout and MVVM implementation.

   1: @{
   2:     ViewBag.Title = "CustomersNorthwind";
   3: }
   4: <script type="text/javascript">
   1:  
   2:     $.ig.loader({
   3:         scriptPath: "../../Scripts/Infragistics/js/", 
   4:         cssPath: "../../Content/Infragistics/css/",
   5:         resources: "igGrid.*,extensions/infragistics.datasource.knockoutjs.js,extensions/infragistics.ui.grid.knockout-extensions.js",
   6:         theme: "metro"
   7:     });
   8:  
</script>
   1:  
   2: <script type="text/javascript">
   3:     var url = "Home/Customers/";
   4:     var updateUrl = "Home/UpdateCustomers/"
   5:     var db, itemsModel;
   6:  
   7:  
   8:     function Item(CustomerID, Country, ContactName, CompanyName, City, Address, Phone) {
   9:             return {
  10:                 CustomerID: ko.observable(CustomerID),
  11:                 Country: ko.observable(Country),
  12:                 ContactName: ko.observable(ContactName),
  13:                 CompanyName: ko.observable(CompanyName),
  14:                 City: ko.observable(City),                
  15:                 Address: ko.observable(Address),
  16:                 Phone: ko.observable(Phone)
  17:             };
  18:         };
  19:  
  20:          function UpdateCustomers() {
  21:              $("#grid").igGrid("commit");  
  22:             var updatedData = ko.toJSON(itemsModel);
  23:             $.ajax({
  24:                 url: updateUrl,
  25:                 type: "POST",
  26:                 data: updatedData,
  27:                 dataType: "json",
  28:                 contentType: "application/json; charset=utf-8",
  29:                 success: function () {
  30:                     console.log('Update succeeded!');
  31:                 },
  32:                 error: function () {
  33:                     console.log('Update failed!');
  34:                 }
  35:             });
  36:         };
  37:  
  38:         $.ig.loader(function () {
  39:  
  40:             $.ajax({ url: url, success: function (data) {
  41:                 db = data;
  42:                 itemsModel = new ItemsViewModel();
  43:                 ko.applyBindings(itemsModel);
  44:             },
  45:                 type: 'POST', dataType: 'json'
  46:             });
  47:             function ItemsViewModel() {
  48:                 var self = this;
  49:  
  50:                 self.data = ko.observableArray([]);
  51:  
  52:                 self.recordsCount = ko.computed(function () {
  53:                     var count = 0;
  54:                     count = self.data().length;
  55:                     return count;
  56:                 });
  57:  
  58:                 for (var i = 0; i < db.length; i++) {
  59:                     self.data.push(new Item(db[i].CustomerID, db[i].Country, db[i].ContactName, db[i].CompanyName, db[i].City, db[i].Address, db[i].Phone));
  60:                 }
  61:             };
  62:  
  63:  
  64:         });
</script>
   5: <h2>Customers Northwind</h2>
   6:  
   7: <br />
   8: <div style="display: block; height: 600px; width: 1180px;">
   9:     <div style="float: left; width: 1150px; height: 500px; margin-right: 5px;">
  10:         <table id="grid" data-bind="igGrid: {dataSource: data, width: 1100, height: 500, primaryKey: 'CustomerID', autoCommit: false, >
  11:                             features: [ {
  12:                                 name: 'Updating', editMode: 'row',
  13:                                 }
  14:                             ],
  15:                             autoGenerateColumns: false, 
  16:                             columns: [
  17:                                 {key: 'CustomerID', headerText: 'Customer ID', width: 80, dataType: 'string'},
  18:                                 {key: 'Country', headerText: 'Country', width: 150, dataType: 'string'},
  19:                                 {key: 'City', headerText: 'City', width: 100, dataType: 'string'},
  20:                                 {key: 'ContactName', headerText: 'Contact Name', width: 270, dataType: 'string'},
  21:                                 {key: 'CompanyName', headerText: 'Company Name', width: 200, dataType: 'string'},
  22:                                 {key: 'Address', headerText: 'Address', width: 200, dataType: 'string'},
  23:                                 {key: 'Phone', headerText: 'Phone', width: 100, dataType: 'string'}
  24:                             ]
  25:  
  26:                          }"
  27:         </table>
  28:     </div>
  29:     <br />
  30:     <div style="position: relative; width: 180px; height: 30px; top: 20px; left: 20px">
  31:         <label><b>Customers count:</b> </label><span  data-bind="text: recordsCount" ></span>
  32:     </div>
  33:     <div style="position: relative; width: 180px; height: 30px; top: 80px; left: 20px">
  34:         <button id="updateBtn" style="height: 30px; width: 180px;"  onclick="UpdateCustomers()">Update Customers</button>
  35:     </div>
  36:     
  37: </div>

 

How it works

Here is the default data in the Customers table.

Start the application

 

  • Update records:

Change the address in the first row.

Updated filed is still not committed.

When you click “Update Customers” button data is committed our ViewModel (ItemsViewModel class instance) is updated and data is send as JSON to the Controller using jQuery.ajax()

  • Insert records

You could use the standard “Insert row” functionality of the igGrid.

Data in the jQuery Grid is updated and you could use the same approach like in update records: commit data and send it to the controller to be possible to save changes in SQL Server.

  • Delete records

Delete records works in the same way like update and insert.

MVVM implementation with Knockout and igGrid is a pretty easy. You could use Infragistics Hierarchical Grid in similar scenarios when you have hierarchical data. Knockout support in NetAdvantage for jQuery is CTP. Expect in the next releases (after 12.1) more new features for MVVM support.

Demo application will be available for download after NetAdvantage 2012 Vol.1 RTM release date.

You can try this product very soon. Follow news from Infragistics in https://www.infragistics.com/ and twitter: @infragistics for more information about new Infragistics products.