JSON Binding to MVC Grid and Monitoring Changes with Knockout.js

Bharath M.

This is another post on the flexibility of Syncfusion Grid’s client-side functionality. Here are the steps involved:

Grid preparation

In this post, we are going to play with JSON functionalities of the grid. I chose the JSON mode for the grid and Excel-like editing to ensure that the grid is responding to JSON changes.

@{ Html.Grid("Grid1")
   .Column(column =>
    column.Add(p => p.OrderID).HeaderText("Order ID").TextAlign(TextAlignment.Right).Width(100);
    column.Add(p => p.CustomerID).HeaderText("Customer ID").Width(100);
    column.Add(p => p.EmployeeID).Width(100);
    column.Add(p => p.Freight).Width(100);
   .ClientSideEvents(c => 
   c.QueryCellInfo("Grid1QueryCell") // This adds the Data-bind attribute to a grid cell.
   .ToolBar(tools =>
  .Mappers(m => m.SaveAction("BulkSave"))
  .Editing(edit =>
  edit.EditMode(GridEditMode.ManualExcel);//Specify the grid edit mode.
  edit.PrimaryKey(key => key.Add(p => p.OrderID));// Add primary key to primary key collections

Knockout’s ViewModel

Knockout.js is the better choice for dynamic UI updates without any glue-code or events. Here, the grid is bound with the “Editable Order” model from the server side. Since Knockout.js is model-based, we have to create a model on the client side also. The code for that is as follows:

 function OrderViewModel(data) {

   // Actual JSON from grid. = data;
   // This is to detect changes in the model.
  this.isDirty = ko.observable(false);
  // Model properties.
  this.OrderID = ko.observable().extend({ AutoUpdate: { "model": this, "name": "OrderID" } });
  this.CustomerID = ko.observable().extend({ AutoUpdate: { "model": this, "name": "CustomerID" } });
  this.EmployeeID = ko.observable().extend({ AutoUpdate: { "model": this, "name": "EmployeeID" } });
  this.Freight = ko.observable().extend({ AutoUpdate: { "model": this, "name": "Freight" } });
 // Created custom extender for auto-updating JSON data as well as the model.
  ko.extenders.AutoUpdate = function (target, options) {
  target.subscribe(function (newValue) {
 // Notifying the model that it has been changed.
 if (!options.model.isDirty())
    // Updating changed values to original JSON data.[] = newValue;
   return target;

AutoUpdate is the custom extender for observables; it will take care of syncing a JSON data source between Knockout’s model and the grid’s data source.

We also need another model for storing the list of an OrderViewModel collection and binding it to a page.

function PageViewModel() {
this.dataSource = ko.observableArray([]);

Integrating the Grid and Knockout’s ViewModel

For the integration, we are going to write code for setting the data-bind attributes to a grid cell and binding the grid’s HTML elements to Knockout.js.

The QueryCellInfo code adding the data-bind attribute is as follows:

function Grid1QueryCell(sender, args) {
   if (args.Column.Name) {
  var currentData = "dataSource()[" + args.Element.parentNode.rowIndex + "].";
  // Here we set the Knockout.js data-binding attribute.
  $(args.Element).attr("data-bind", "text: " + currentData + args.Column.Name + ",css: {updatedCell : " + currentData + "isDirty}");

  // text: is for updating cell values and
  // css: is for notifying the user that cells have been updated.

For binding the model, we have to use the grid’s OnActionComplete event, since during initial rendering the grid didn’t have data in it. Let me show you the code:

 // The jQuery ready event has been used to define the page view-model.

 $(function () {
    page = new PageViewModel();
   // Knockout.js binding can be applied to the whole page here.
    // ko.applyBindings(page);
  // The grid's OnActionComplete event has been used to update Knockout’s data source.
  function ActionComplete(grid, args) {
  // Here we are setting the page data source.
    page.dataSource(ko.syncJsMap(grid.get_jsonModeMgr().get_dataSource(), OrderViewModel));
   // I'm going to enable all toolbar items.
   // Clearing previous binding, if any.
   // Applying bindings to newly created content.
    ko.applyBindings(page, $(grid.get_element()).find(".GridContent").children().get(0));
   // We need to set updatedRecords on the ToolbarClicked event.
   function ToolbarClicked(grid, args) {
   // Getting the changed JSON data.
   var koChanges = page.dataSource()
  function (obj) { return obj.isDirty(); });
   // Marking the grid as Editing.
   grid._edit._isEdit = true;
   // Updating the changed records to the grid.
   if (grid._edit._updatedRecords) {
    // Merging the changes if there are any updates from the grid.
   for (var i = 0; i < koChanges.length; i++) {
   if (grid._edit._updatedRecords.indexOf(koChanges[i].data) == -1)
      grid._edit._updatedRecords = ko.utils.arrayMap(koChanges, function (obj) { return; });

syncJsMap is the Knockout.js extension I created for converting the JSON data source to Knockout’s model-based data source. Here is the code I used:

ko.syncJsMap = function (obj, viewModel) {
 if (obj.length) {
  var mapArray = [];
  for (var i = 0; i < obj.length; i++) {
       mapArray.push(new viewModel(obj[i]));
   return mapArray;

Playing with the JSON Collection

Now it’s time to play with the results of the previous processes. I have written some HTML and JavaScript to produce the following functionality:


Upon clicking Change, the concerned JSON data will be updated using Knockout’s ViewModel. The code for that is as follows:

$("#butChange").click(function () {
  var rowIndex = parseInt($("#rowIndex").val(), 10);
  var columnName = $("#selectColumn").val()
  var value = $("#change").val();
   // The JSON collection can be changed like this. This will update the grid's UI as well as the data source.

Once Change is clicked, you’ll see the updates in the grid as well. Please refer to the following screenshot:


You can see in the previous screenshot that the updated row will have a red mark. Click on the save icon in the grid’s toolbar to store data in the database and clear all dirty items.


You can find a downloadable version of this sample here:

Leave a comment