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")
   .ActionMode(ActionMode.JSON)
   .AllowKeyboardNavigation(true)
   .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.
   .OnActionComplete("ActionComplete")
   .OnToolbarClickEvent("ToolbarClicked"))
   .ToolBar(tools =>
   {
   tools.Add(GridToolBarItems.Update)
  .Add(GridToolBarItems.Cancel);
  })
  .Mappers(m => m.SaveAction("BulkSave"))
  .Editing(edit =>
  {
  edit.AllowEdit(true);
  edit.EditMode(GridEditMode.ManualExcel);//Specify the grid edit mode.
  edit.PrimaryKey(key => key.Add(p => p.OrderID));// Add primary key to primary key collections
  }).Render();
   }

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.
   this.data = 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(options.model.data[options.name]);
  target.subscribe(function (newValue) {
 // Notifying the model that it has been changed.
 if (!options.model.isDirty())
     options.model.isDirty(true);
    // Updating changed values to original JSON data.
   options.model.data[options.name] = 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.
   grid.gridtoolbar.Enable_All();
   // Clearing previous binding, if any.
   ko.cleanNode($(grid.get_element()).find(".GridContent").children().get(0));
   // 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()
        .filter(
  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.push(koChanges[i].data);
  }
  }
  else 
      grid._edit._updatedRecords = ko.utils.arrayMap(koChanges, function (obj) { return obj.data; });
 }

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:

clip_image002

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.
   page.dataSource()[rowIndex][columnName](value);
  });

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

clip_image004

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.

clip_image006

You can find a downloadable version of this sample here: http://www.syncfusion.com/downloads/Support/DirectTrac/General/JsonCollection%20Change576454545.zip

Pingbacks and trackbacks (1)+

Loading