IgniteUI jQuery Grid Unbound Columns: Updating and Hierarchy

Damyan Petev / Friday, October 26, 2012

igniteui-jquery-grid-unbound-columns-updating-hierarchyUnbound Columns bring you the freedom of flexible layouts in the IgniteUI Grid and neat support for features to make your grid behave like nothing’s that different.  After you’ve seen some of the capabilities (getting started blog) and inner workings(configure and make the most of it) it’s only natural to tackle some not too basic cases. We’ll throw in some editing action – in all fairness, something very basic as far as the user or desired end-result are concerned, however nothing simple in the eyes of the developer. Following that some curious scenarios when using the Hierarchical Grid with unbound columns. So let’s jump right on topic.

To Save or not to Save

Custom Deserialization

Adding Updating should be fairly straight-forward, but there’s a catch.. or rather a few. One thing you are extremely likely to face is handling updates to the server. The grid will behave as expected and kindly include unbound values in the transactions. For that reason, as we already mentioned. the client-side model that includes Unbound columns would not match the one the grid was bound to on the server. That means direct deserialization using the standard .NET serializators won't go very well(more like at all) and the same goes for using the Grid model's LoadTransactions method(which is the same thing really). The most reasonable way to go about this is custom deserialization. Don’t let that fool you – it’s actually very easy with something like Json.NET. Plus, it even comes pre-packed with some (all?) new MVC4 templates anyway. It’s fast and, as far as my experience goes, it handles missing or extra values easily. Let’s have the following Grid configuration with AdventureWorks (AdventureWorks Sample Databases (MSDN)) Employees similar to so far used examples:

  1. $('#grid').igGrid({
  2.     updateUrl : '/Home/UpdateUrl',
  3.     localSchemaTransform : false,
  4.     autoGenerateColumns : false,
  5.     autoGenerateLayouts : false,        
  6.     primaryKey: "BusinessEntityID",
  7.     columns : [{
  8.             key : 'BusinessEntityID',
  9.             dataType : 'number',
  10.             headerText : 'B.E. Id'
  11.         }, {
  12.             unbound : true,
  13.             key : 'name',
  14.             headerText : 'Name',
  15.             dataType : 'string'
  16.         }, {
  17.             key : 'LoginID',
  18.             dataType : 'string',
  19.             headerText : 'Login Id'
  20.         }, {
  21.             key : 'SickLeaveHours',
  22.             dataType : 'number',
  23.             headerText : 'Sick Leave Hours'
  24.         }, {
  25.             key : 'VacationHours',
  26.             dataType : 'number',
  27.             headerText : 'Vacation Hours'
  28.         }, {
  29.             unbound : true,
  30.             key : 'total',
  31.             headerText : 'Total',
  32.             dataType : 'number',
  33.             formula : calcTotal,
  34.             template : '<span {{if parseInt(${total})/100 > 1 }} class=\'red\' {{/if}}> ${total} / 100 </span>'
  35.         }
  36.     ],
  37.     features : [{
  38.             name : 'Updating',
  39.             enableAddRow : false,
  40.             columnSettings : [{
  41.                     columnKey : 'total',
  42.                     readOnly : true
  43.                 }, {
  44.                     columnKey : 'BusinessEntityID',
  45.                     readOnly : true
  46.                 }]
  47.         }
  48.     ],
  49.     height : '600px',
  50.     dataSource : "@Url.Action("HrcEmployees")",
  51.     rendered: function (evt, ui){
  52.         ui.owner.setUnboundValues(peopleNames , "name");
  53.     }
  54. });

Or the equivalent in ASP.NET MVC using the helper with chaining:

  1. @(Html.Infragistics().Grid(Model).AutoGenerateColumns(false).AutoGenerateLayouts(true)
  2.     .PrimaryKey("BusinessEntityID")
  3.     .Columns(column =>
  4.     {
  5.         column.For(x => x.BusinessEntityID).HeaderText("B.E. Id").DataType("number");
  6.         column.Unbound("name").HeaderText("Name").DataType("string");
  7.         column.For(x => x.LoginID).HeaderText("Login Id").DataType("string");
  8.         column.For(x => x.SickLeaveHours).HeaderText("Sick Leave Hours").DataType("number");
  9.         column.For(x => x.VacationHours).HeaderText("Vacation Hours").DataType("number");
  10.         column.Unbound("total").HeaderText("Total").DataType("number")
  11.             .Formula("calcTotal").Template("<span {{if parseInt(${total})/100 > 1 }} class='red' {{/if}}> ${total} / 100 </span>");
  12.     })
  13.     .Height("600px")
  14.     .Features(feature =>
  15.         {
  16.             feature.Updating().EnableAddRow(false).ColumnSettings(colSetting =>
  17.             {
  18.                 colSetting.ColumnSetting().ColumnKey("total").ReadOnly(true);
  19.                 colSetting.ColumnSetting().ColumnKey("BusinessEntityID").ReadOnly(true);
  20.             }).EditMode(GridEditMode.Row);
  21.         })
  22.     .UpdateUrl(Url.Action("UpdateUrl"))
  23.     .SetUnboundValues("name", (ViewBag.people as List<string>).Cast<object>().ToList())
  24.     .DataBind()
  25.     .Render()
  26. )

As you can see the ID is not editable for obvious reasons and so is the formula field. What you would get from the grid as a transaction would contain all of the above properties. One approach would be to take advantage of the flexibility of Json.Net and go about it as you normally would:

  1. var transactions = JsonConvert.DeserializeObject<List<Transaction<EmployeeDTO>>>(Request.Form["ig_transactions"]);

Form there on it is identical to what you can find in the documentation on Batch Updating. Another take would be to go for LINQ to JSON instead and deserialize to JObject:

  1. var transactions = JsonConvert.DeserializeObject<List<Transaction<JObject>>>(Request.Form["ig_transactions"]);

An advantage you get is that if you like me don’t have the whole model displayed in the grid and therefore the transactions don’t contain quite the whole records and assigning it blindly will just put nulls in the ones I’m missing. Besides picking separate properties from the record this approach is also the easiest way to access unbound values if you need them:

  1. AdventureWorksEntities context = new AdventureWorksEntities();
  2. foreach (Transaction<JObject> transaction in transactions)
  3. {
  4.     int id = (int)transaction.row["BusinessEntityID"];
  5.     // take id in advance as accessing it is not a canonical method and then find the data records
  6.     var employee = context.Employees.Where(x => x.BusinessEntityID == id).Single();
  7.     var person = context.People.Where(x => x.BusinessEntityID == id).Single();
  8.     if (transaction.type == "row") //edit!
  9.     {
  10.         employee.VacationHours = (short)transaction.row["VacationHours"];
  11.         employee.SickLeaveHours = (short)transaction.row["SickLeaveHours"];
  12.         employee.LoginID = (string)transaction.row["LoginID"];
  13.         person.FirstName = transaction.row["name"].ToString().Split(' ').First();
  14.         person.LastName = transaction.row["name"].ToString().Split(' ').Last();
  15.     }
  16.     if (transaction.type == "delete") //delete!
  17.     {
  18.         context.Employees.DeleteObject(employee);
  19.     }
  20. }
  21. context.SaveChanges();

“My Unbound values don’t reach the server”

So you use the client-side ‘setUnboundValues()’ method like you are supposed to and attempt to save the changes with the method above but they never appear to persist. What gives? Well, for starters “you are supposed to” in the last sentence is truly wrong – the method does set values but since it’s designed to work without updating on the grid, the changes it makes are “client-side only” type and are completely unrelated with Updating and transactions. That means that you are setting a value but it is never sent to the server or persisted in other way. So to achieve the desired result you should turn to the Updating API instead and use either ‘setCellValue’ or ‘updateRow’. Yes, they work work with Unbound columns! Frankly, the methods have very subtle differences with  the main one being the type of transaction they generate and I usually pick the one that matches the edit mode:

  1. <button onclick="$('#grid').data('igGridUpdating').setCellValue(2, 'name', 'John Doe')"> Update names [cell]</button>
  2. <button onclick="$('#grid').data('igGridUpdating').updateRow(2, { name : 'John Doe'})"> Update names [row]</button>

There basically produce the same result with different type of transaction. In the demo and the JFiddle versions linked below I’ve put up a simple demonstration of this with a log you can use to list the current transactions on the grid in order to see that there are indeed none after a ‘setUnboundValues()’ call but the Updating methods behave as expected.

Formula Trouble

So you save  your changes and everything obviously goes well with the server, but ..

igniteui-jquery-grid-unbound-columns-updating-formula

Find something wrong? This is a row in which I’ve edited the Vacation hours value, however the total value calculated from a formula stays the same and moreover, it’s the same after the actual submit of transaction to the server passes too. Because of the type of functionality formula property – it is neither a template nor a formatting function, it’s supposed to provide the initial value. Therefore, it’s called on binding and from there on the grid has data to work with as if it is part of the data source and formula is no longer needed. But hey, I want my value correct and I don’t feel like reloading the page for sure. So how do you get this to work? Consider this, the save changes method does not offer a callback and in case you are wondering neither does the original in the igDataSource. But it does say AJAX and it should have a callback, right? And it does, lets just say it’s well hidden. It’s called a success handler and the grid maintains an array of them to call when it handles the AJAX success (only!). Also the problem is I want to refresh my view in order to update the formula calculations. However, simply calling ‘dataBind()’ on the data source won’t do in my case as I have additional unbound values(the names) that are still the same and they won’t update until a refresh of the page. Plus, it's a lot of overhead I don't really need(goodness forbid using the grid's data bind method that does a whole lot more work even). Thankfully you have grid operations that cause a re-bind such as paging and sorting without messing with the actual data in the source. What if we could use that? Well we can! A slight hack if you will would be to trick the grid into changing page without actually changing it which would essentially be a page re-fresh. This can be done using the ‘pageIndex()’ data source method you'd normally use to programmatically change page or get current index:

  1. $("#grid").live("iggridrendered", function (event, ui) {
  2.     //rendered event only fired once so add success update handler here
  3.     ui.owner.dataSource._addChangesSuccessHandler(updateSuccess);
  4. });
  5. function updateSuccess(data, textStatus, jqXHR) {
  6.     if (data.Success === true && textStatus == "success") {
  7.         //'re-bind' to force formula calculation with new verified values.
  8.         var gridDS = $("#grid").data("igGrid").dataSource;
  9.         gridDS.pageIndex(gridDS.pageIndex());
  10.     }
  11. }

This registers your own handler that gets the very same parameters as a normal AXAJ callback (and this can be useful in many other scenarios!) and in it we access the grid’s data source now that we know that the save has gone through and do our little refresh inception-style. It’s not perfect, but it kind of works; another very plausible solution would be to just go ahead and set the right value.

NOTE: Keep in mind that if you use template to display value in unbound column that is built with values from other columns it will update once they change. There’s a demo available for that in both MVC and JFiddle below.

Hierarchical Grid with Unbound Columns

Now here’s another good one. By design the hierarchical grid uses flat igGrids and for that reason we consider most of the functionality  to be shared and used in the same way with the same results. However, there are a few logical, yet important differences to consider when using hierarchy with Unbound Columns.

Setting values and child column layouts

Of course, all the methods to do that are still available, but consider this – you now have not just one grid, but rather a whole bunch of them. Using the ‘unboundValues’ is mostly logical as they are tied to their column definitions. But when you decide to set some unbound values on the client but nothing happens? For reference here’s how the grid definition looks:

  1. $('#grid').igHierarchicalGrid({
  2.     dataSource: @Html.Raw(Json.Encode(Model)),
  3.     dataSourceType: 'json',
  4.     autoGenerateColumns: false,
  5.     autoGenerateLayouts: false,
  6.     columns: [{
  7.         key: 'DepartmentID',
  8.         headerText: 'DEP Id',
  9.         dataType: 'number'
  10.     }, {
  11.         key: 'Name',
  12.         headerText: 'Name',
  13.         dataType: 'string'
  14.     }, {
  15.         key: 'GroupName',
  16.         headerText: 'Group Name',
  17.         dataType: 'string'
  18.     }, {
  19.         key: 'ModifiedDate',
  20.         headerText: 'Modified Date',
  21.         dataType: 'date'
  22.     }, {
  23.         unbound: true,
  24.         key: 'employees',
  25.         headerText: 'Employees',
  26.         dataType: 'number',
  27.         unboundValues: [7, 3, 18, 9, 12, 4, 180, 5, 6]
  28.     }
  29. ],
  30.     columnLayouts: [{
  31.         autoGenerateColumns: false,
  32.         autoGenerateLayouts: false,
  33.         mergeUnboundColumns: true,
  34.         columns: [{
  35.             key: 'BusinessEntityID',
  36.             headerText: 'B.E. Id',
  37.             dataType: 'number'
  38.         }, {
  39.             key: 'DepartmentID',
  40.             headerText: 'Dep id',
  41.             dataType: 'number',
  42.             hidden: true
  43.         }, {
  44.             unbound: true,
  45.             key: 'name',
  46.             headerText: 'name',
  47.             dataType: 'string'
  48.         }, {
  49.             key: 'VacationHours',
  50.             headerText: 'Vacation Hours',
  51.             dataType: 'number'
  52.         }, {
  53.             key: 'SickLeaveHours',
  54.             headerText: 'Sick Leave Hours',
  55.             dataType: 'number'
  56.         }, {
  57.             unbound: true,
  58.             key: 'counter',
  59.             headerText: 'Counter',
  60.             dataType: 'number'
  61.         }
  62.         ],
  63.         key: 'Employees',
  64.         primaryKey: 'BusinessEntityID',
  65.         foreignKey: 'DepartmentID'
  66.     }
  67. ],
  68.     height: '500px',
  69.     autoCommit: false,
  70.     primaryKey: 'DepartmentID',
  71.     localSchemaTransform: true
  72. });

igniteui-jquery-hierarchical-grid-unbound-columns

And to sill some of the missing values you attempt calling something like:

  1. $('#grid').igHierarchicalGrid('setUnboundValues', [ 52, 522, 368, 42635], 'counter');

However nothing happens. If you had attempted the same with say ‘employees’ it would’ve. That is because as we explained there are multiple grids here and set value methods are to be called for the respective layout. So in order to add values to the child layout you need to access it first. The hierarchical grid provides a method to do that:

  1. $.each($('#grid').data('igHierarchicalGrid').allChildrenWidgets(), function (i, grid) {
  2.     grid.setUnboundValues([12.2, 12.2, 12.2, 12.2], 'counter');
  3. });

You could pick one, but above I’m targeting all available child grids and setting values and there’s a good reason. To be able to target specific layout you need to be aware it is there – the Hierarchical grid creates the child grids only when expanding for performance reasons, therefore you cannot set values to something that doesn’t exist yet. Furthermore, unless you provide you unbound values in advance ( set them on the column property or merge them on the server) even after calling the above, newly expanded(created) layouts will still be with empty unbound columns. My best advice is to assign even temporary initial values to spare the user the sight of empty columns and/or handle a suitable event such as 'childGridCreated'.

ASP.NET MVC

MVC offers some additional challenges such as the relevant Merge Unbound Columns property and others. First and foremost let me remind again the SetUnboundValues’s Dictionary method overload is still your best bet to get things going as expected. Also the client-side explanations above still holds true although this time you set values to column layouts of the main grid, but it has to be done for the respective layout.

  1. childLayout.MergeUnboundColumns = true;
  2. childLayout.Columns.Add(new UnboundColumn()
  3. {
  4.     Key = "counter",
  5.     HeaderText = "Counter",
  6.     DataType = "number",
  7.     UnboundValues = new List<object>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 }
  8. }); //This will not work!

Using the Unbound values column property which if used in child layout definition will be applied to all layouts by default. However when you enable merging the values inside the column definition are not supported and pretty much ignored. So in their place you get the default null values and empty columns on the client.

Then you have the SetUnboundValues’s List overload that suspiciously looks very much like the property, but you will be surprised when it actually works with merging. But there’s also a catch – since the method’s purpose is to provide all values for for a grid in one go, when used it will still map values to ID’s ..of the first child layout only. This is as far as the grid is willing to do the guessing for you I guess. However if the child layout doesn’t have primary key another default behavior kicks in and those values are applied separately to each child. I’m only explaining all of this as I think in some situations child layouts could be sharing values or just need an example values for one layout and these can turn out just fine.

Once again, assigning with the Dictionary overload applies the required mapping so values will properly be added throughout children  based on key matches.

Load On Demand and Merge Unbound columns don’t go well…

These really don’t go well at all! And it’s really not that surprising – LoadOnDemand assures the grid that data for child layouts will be provided later on and merging demands they are present in the data source, so even though you may have Unbound Values set using the property itself and you can see it on the client code it will still be ignored and data for the unbound columns will be required from the data received when creating the child. That mean that you need to send it along with the original grid model and this makes the Unbound Columns redundant or if the source is oData nearly absurd to implement. SO better don’t try this at home. The best part is that if you don’t provide those values and the grid starts looking for them it will end up badly..with exceptions :) This is true for SetUnboundValue methods as well, however this is not hopeless combination of functionality:

GetData() to the rescue

Of course, this is the Grid models’ GetData() method and it’s extremely helpful when using load on demand. In fact, so helpful, that when used to generate responses for the child layouts it will look at your model and properly merge unbound values applied with SetUnboundValue methods again following the default  behavior – with list values are added to each generated response essentially added to very layout and with dictionary overload just the matching ones. So this (lines 73/74 in the model show the setting of values with both List and Dictionary):

  1. public JsonResult BindChild(string path, string layout)
  2. {
  3.     GridModel viewModel = GenerateGridModel();
  4.     AdventureWorksEntities context = new AdventureWorksEntities();
  5.     viewModel.DataSource = context.Employees.ToList().ConvertAll(eh => eh.ToDTO()).AsQueryable();
  6.     return viewModel.GetData(path, layout);
  7. }   
  8.  
  9. private GridModel GenerateGridModel()
  10. {
  11.     GridModel grid = new GridModel();
  12.     grid.Height = "500px";
  13.     grid.AutoCommit = false;
  14.     grid.PrimaryKey = "DepartmentID";
  15.    
  16.     grid.AutoGenerateColumns = false;
  17.     grid.AutoGenerateLayouts = false;
  18.     GridColumnLayoutModel childLayout = new GridColumnLayoutModel();
  19.  
  20.     //settings
  21.     grid.LoadOnDemand = true;
  22.     childLayout.Key = "Employees";
  23.     childLayout.PrimaryKey = "BusinessEntityID";
  24.     childLayout.ForeignKey = "DepartmentID";
  25.     childLayout.MergeUnboundColumns = true;
  26.     
  27.     childLayout.AutoGenerateColumns = false;
  28.     childLayout.AutoGenerateLayouts = false;
  29.  
  30.     childLayout.DataSourceUrl = Url.Action("BindChild");
  31.  
  32.     childLayout.Columns = new List<GridColumn>();
  33.     childLayout.Columns.Add(new GridColumn()
  34.     {
  35.         Key = "BusinessEntityID",
  36.         HeaderText = "B.E. Id",
  37.         DataType = "number"
  38.     });
  39.  
  40.     childLayout.Columns.Add(new GridColumn()
  41.     {
  42.         Key = "DepartmentID",
  43.         HeaderText = "Dep id",
  44.         DataType = "number",
  45.         Hidden = true
  46.     });
  47.  
  48.     childLayout.Columns.Add(new UnboundColumn()
  49.     {
  50.         Key = "name",
  51.         HeaderText = "name",
  52.         DataType = "string",
  53.     });
  54.  
  55.     childLayout.Columns.Add(new GridColumn()
  56.     {
  57.         Key = "VacationHours",
  58.         HeaderText = "Vacation Hours",
  59.         DataType = "number"
  60.     });
  61.     childLayout.Columns.Add(new GridColumn()
  62.     {
  63.         Key = "SickLeaveHours",
  64.         HeaderText = "Sick Leave Hours",
  65.         DataType = "number"
  66.     });
  67.     childLayout.Columns.Add(new UnboundColumn()
  68.     {
  69.         Key = "counter",
  70.         HeaderText = "Counter",
  71.         DataType = "number"
  72.     });
  73.     childLayout.SetUnboundValues("counter", new List<object>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
  74.     childLayout.SetUnboundValues("name", peopleNames);
  75.  
  76.     grid.Columns = new List<GridColumn>();
  77.     grid.Columns.Add(new GridColumn()
  78.     {
  79.         Key = "DepartmentID",
  80.         HeaderText = "DEP Id",
  81.         DataType = "number"
  82.     });
  83.     grid.Columns.Add(new GridColumn()
  84.     {
  85.         Key = "Name",
  86.         HeaderText = "Name",
  87.         DataType = "string"
  88.     });
  89.  
  90.     grid.Columns.Add(new GridColumn()
  91.     {
  92.         Key = "GroupName",
  93.         HeaderText = "Group Name",
  94.         DataType = "string"
  95.     });
  96.  
  97.     grid.Columns.Add(new GridColumn()
  98.     {
  99.         Key = "ModifiedDate",
  100.         HeaderText = "Modified Date",
  101.         DataType = "date"
  102.     });
  103.     grid.Columns.Add(new UnboundColumn()
  104.     {
  105.         Key = "employees",
  106.         HeaderText = "Employees",
  107.         DataType = "number",
  108.         UnboundValues = new List<object>() { 7, 3, 18, 9, 12, 4, 180, 5, 6 }
  109.     });
  110.  
  111.     grid.ColumnLayouts.Add(childLayout);
  112.     return grid;
  113. }

Will produce the following response to the client:

  1. {
  2.     "Records" : [{
  3.             "BusinessEntityID" : 11,
  4.             "DepartmentID" : 2,
  5.             "name" : "Ovidiu Cracium",
  6.             "VacationHours" : 7,
  7.             "SickLeaveHours" : 23,
  8.             "counter" : 1
  9.         }, {
  10.             "BusinessEntityID" : 12,
  11.             "DepartmentID" : 2,
  12.             "name" : "Thierry D\u0027Hers",
  13.             "VacationHours" : 9,
  14.             "SickLeaveHours" : 24,
  15.             "counter" : 2
  16.         }, {
  17.             "BusinessEntityID" : 13,
  18.             "DepartmentID" : 2,
  19.             "name" : "Janice Galvin",
  20.             "VacationHours" : 8,
  21.             "SickLeaveHours" : 24,
  22.             "counter" : 3
  23.         }
  24.     ],
  25.     "TotalRecordsCount" : 0,
  26.     "Metadata" : {
  27.         "timezoneOffset" : 10800000
  28.     }
  29. }

Even when merging is not in use, this method will be kind enough to include the said values as metadata instead and achieve the same results. One thing to note is that the method does require the presence of primary and foreign key in the child layout in order to perform proper mapping, which if not needed can be hidden but still must be present. Despite that,  using the Grid model with Load On demand is the best way to have child data generated properly and it allows Unbound Columns to be merged along with all else!

Bonus scene (formula + template)

Or more like bonus scenario. Since unbound columns are not even tied with values what happens if as above and examples before you got that Unbound Column with both formula to calculate total and template. Now that template uses a static value for the max…how about we add yet another column for that, provide some values and use that instead in the template?

  1. @(Html.Infragistics().Grid(Model).AutoGenerateColumns(false).AutoGenerateLayouts(false).LocalSchemaTransform(false)
  2.     .ColumnLayouts(layout => layout.For(x => x.Employees).AutoGenerateColumns(false).Columns(column =>
  3.         {
  4.             column.For(x => x.BusinessEntityID).HeaderText("B.E. Id").DataType("number");
  5.             column.For(x => x.SickLeaveHours).HeaderText("Sick Leave Hours").DataType("number");
  6.             column.For(x => x.VacationHours).HeaderText("Vacation Hours").DataType("number");
  7.             column.Unbound("total").HeaderText("Total / Max").DataType("number").Formula("calcTotal").Template("<span {{if ${total}/parseInt(${max}) > 1 }} class='red' {{/if}}> ${total} / ${max} </span>");
  8.             column.Unbound("max").HeaderText("Max").DataType("string").Hidden(true);
  9.         })
  10.         .LocalSchemaTransform(false)
  11.         .SetUnboundValues("max", new Dictionary<object, object>() { { 2, 50 }, { 3, 100 }, { 5, 80 }, { 6, 100 }, { 11, 25 }, { 12, 66 }, { 13, 76 }, { 4, 32 }, { 15, 18 } })
  12.         .PrimaryKey("BusinessEntityID")
  13.     )
  14.     .PrimaryKey("DepartmentID")
  15.     .Columns(column =>
  16.     {
  17.         column.For(x => x.DepartmentID).HeaderText("DEP Id").DataType("number");
  18.         column.For(x => x.Name).HeaderText("Name").DataType("string");
  19.         column.For(x => x.GroupName).HeaderText("Group Name").DataType("string");
  20.         column.For(x => x.ModifiedDate).HeaderText("Modified Date").DataType("date");
  21.         column.Unbound("employees").HeaderText("Employees").DataType("number").UnboundValues(new List<object>() { 7, 3, 18, 9, 12, 4, 180, 5, 6 });
  22.     })
  23.     .Height("600px")
  24.     .Features(feature =>
  25.         {
  26.             feature.Paging().Type(OpType.Local).PageSize(10);
  27.             feature.Sorting().Type(OpType.Local);
  28.             feature.Filtering().Type(OpType.Local);
  29.         })
  30.     .DataSource(Model)
  31.     .ClientDataSourceType(ClientDataSourceType.JSON)
  32.     .DataBind()
  33.     .Render()
  34. )

Would it work as expected? For a moment, it might. See there’s no merging enabled, therefore it happens on the client and since the schema doesn't include the Unbound columns the grid isn’t as kind as to go about and apply null-s to every record missing unbound value. So in essence unless you provide all of them, records will not even have a ‘max’ property and guess what happens when the templating engine tries to get a value for it :) But no worries, a solution is possible using yet another formula:

  1. column.Unbound("max").HeaderText("Max").DataType("string").Hidden(true).Formula("maxFix");

And in it you can assign values or in this case keep the ones available and place defaults. It doesn’t even have to be null-s anymore:

  1. function maxFix(row, grid) {
  2.     return row.max ?   row.max : "???";
  3. };

And you get quite lovely results:

igniteui-jquery-grid-unbound-columns-formulas-templates

Resources and demos

Donwload your Ignite UI Free Trial now!

As always, you can follow us on Twitter @DamyanPetev and @Infragistics and stay in touch on Facebook, Google+ and LinkedIn!

TL;DR | Summary

In this article we covered the usage the Updating feature with Unbound Columns in the IgniteUI Grid – how to deserialize the now non-conforming model of data the client side jQuery widget is sending. We also looked into the proper methods of setting values so they can be persisted and how to deal with editing and formula updating upon dependency field change.

In addition to those, some light was shone upon how the Hierarchical version of the grid ever so slightly differs from the flat one and the unique challenges it poses – setting values and the dynamic creation of child widgets. The odd interaction between options such as Merge Unbound columns and Load on Demand and just how bad can things so and how useful the ASP.NET MVC Grid model ‘GetData()’ method can be to prevent that. Lastly, a small demo just to give head up when using Unbound Column values in templates without merging values on the server.