jQuery Hierarchical Grid - Load On Demand

Damyan Petev / Wednesday, February 15, 2012

The Hierarchical Grid (a fairly recent addition to our NetAdvatage for jQuery) is a client-side control that can display hierarchical ( or nested data if you will, the kind of stuff you would use an Org chart or a tree of some sorts to visualize). Now while in some cases other controls just may do, when presented with large amount of information and it requires a table-like representation then the igHierarchicalGrid should definitely be your go-to control. Using a hierarchical grid representation is a amazing reduction in terms of space required per data unit as it allows for not only child layouts to be collapsed and expanded but also provides paging and all the things you’d expect from a grid. And while this is all great, it kind of makes you wonder where else you can save up some. There is, in fact, a very obvious route to take – collapsing and expanding layouts sounds like a great target – why have them while they are not visible? That is indeed the right question. You can say we have thought about that as such way of thinking is in the very design of the control – the hierarchical grid is relying on the flat igGrid control to display child layouts and it creates an instance only when a row is actually expanded. What this means is that all the markup (HTML elements) that might represent a 50 rows worth of child entries would not be created and sit around until and if the user decides to expand their parent row. This reduces the total resources required and at the end of the day it equals performance boost. “Good!”, you might say, yet there is .. more! The Hierarchical Grid is already taking care of its child UI elements for you, but what about the data? Of course that essential side has not been forgotten and the igHierarchicalGrid comes with a ‘Load On Demand’ support. What this brings is the ability to load data only when it is required making that control that much more lightweight and while enabling it, you also lay the foundations for some other nifty features such a remote paging, filtering and sorting. You are, however, required to give it a little push, so keep reading to see how to do just that!

Also here’s and inspirational screenshot of that all-time favourite spinning indicator users would and should see when they must be informed something in the background is happening and that is pretty much the only indication something like Load On Demand is active. Keep in mind this took several attempts to nail the split-second the indicator is visible, so bear with me on its aesthetics :)

jQuery Hierarchical Grid Loading on Demand

Load on Demand with jQuery

Now if you’ve been examining the jQuery API of the Hierarchical Grid you might be wondering where is that ‘loadOnDemand’ property? Simply put, the jQuery widget doesn’t really have one. To be completely thorough… the ASP.NET MVC helper does have one (as it sets up some behind-the-scenes server magic) but more on that later on. The widget doesn’t need that as it is a client control completely independent from server technology and as such in no way does it implement loading data on demand, but rather it should be made aware of such opportunity and if the proper setting are in place the grid will take advantage of it. With that said, the control can consume OData (Open Data Protocol) and with the very nature of the protocol that spells querying data (including paging, ordering and filtering) what follows seems completely natural – when presented with OData the grid will use it to request data only when needed – yes, loading it demand. That means that if you have your information exposed with OData protocol you are almost ready! All that is left to do to get the feature is three properties:

  1. $("#hierarchicalGrid").igHierarchicalGrid({
  2.  
  3.     //--Set up for Load on demand follows:
  4.     odata: true,
  5.     initialDataBindDepth: 0,
  6.     dataSource: oDataInJson,
  7.     responseDataKey: 'd',
  8.     //--end--
  9.  
  10.     autoGenerateColumns: true,
  11.     primaryKey: "ID",
  12.     columnLayouts: [
  13.         {
  14.             name: "Products",
  15.             responseDataKey: 'd',
  16.             childrenDataProperty: "Products",
  17.             autoGenerateColumns: true,
  18.             primaryKey: "ID"
  19.         }
  20.     ],
  21.     width: "700px",
  22. });

As seen in lines 4 through 7 there isn’t all too much complication about it – make the grid aware OData will be used (setting the respective property to ‘true’) and provide a source.

The initial data bind depth is obligatory and it tells the grid just how much in the nested data structure should it dig right from the start and while usually that means setting it to as many levels your data has or even –1 for the “I don’t know! Can’t you just figure it out and load everything?” case, when the goal is loading on demand you want that to be zero – as in just the first(parent) tier of entries will be loaded.

The response key is telling the grid how to look at your data and is specific to it as well. It is only needed when that data is wrapped and it points to where the array of records is. Let’s have a more descriptive example – as seen above is is set to ‘d’ and that is because in that demo the sample OData service response looks like..

   1: ?({
   2: "d" : [
   3: {
   4: "__metadata": {
   5: "uri": "http://services.odata.org/OData/OData.svc/Categories(0)", "type": "ODataDemo.Category"
   6: }, "ID": 0, "Name": "Food", "Products": {
   7: //rest is omitted

Now take the NorthWind sample service for example:

   1: ?({
   2: "d" : {
   3: "results": [
   4: {
   5: "__metadata": {
   6: "uri": "http://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')", "type": "NorthwindModel.Customer"
   7: }, "CustomerID": "ALFKI", "CompanyName": "Alfreds Futterkiste", "ContactName": "Maria Anders", "ContactTitle": "Sales Representative", "Address": "Obere Str. 57", "City": "Berlin", "Region": null, "PostalCode": "12209", "Country": "Germany", "Phone": "030-0074321", "Fax": "030-0076545", "Orders": {
   8: //rest is omitted

In this case the response key would be “d.results”. Simple enough and again specific to how you present your data.

In the light of all this you can check out Taz’s blog on Using OData with jQuery Hierarchical Grid where you can find not only a full guide but also a link to a live demo!

If you take a look at the example in that blog you might notice the datasource is set to the service URL. This is because the grid itself will use a igDataSource control to bind to data and as such it is available for you as well. If you want to tweak some of its setting you can define your data source like so:

  1. var oDataInJson = new $.ig.JSONPDataSource(
  2.     { dataSource: 'http://services.odata.org/OData/OData.svc/Categories?$format=json&$callback=?',
  3.     responseDataKey: "d"
  4.     });

You can read more about this control in our  Help Topics.

Load On Demand with ASP.NET MVC

Unlike the majority of our jQuery products where the helpers mostly mirror the widget’s settings and add some comforts for those used to writing in a managed environment, this time it’s different. As said above the igHierarchicalGrid MVC wrapper does have a Load On Demand property. When that is set to true the wrapper provides utility to send  responses to the client in JSON format. To be more specific it is the grid’s model that provides the support for that. There’s a catch though – the responses use the model’s GetData() method and  that means the model is needed wherever those responses are created, which makes setting up the grid in the View with chaining syntax something you might want to avoid. The model of the grid along with all the properties should be then defined in either the Controller of the Model. There is also one more thing to note – the model creating is best done in a separate method and here’s the full code for one:

  1. using Infragistics.Web.Mvc;
  1. private GridModel CreateGridModel()
  2. {
  3.     GridModel grid = new GridModel();
  4.  
  5.     // Set up properties and columns:
  6.     grid.AutoGenerateColumns = false;
  7.     grid.Columns = new List<GridColumn>();
  8.     grid.Columns.Add(new GridColumn("Customer", "CustomerID", "string", "300px"));
  9.     grid.Columns.Add(new GridColumn("Company Name", "CompanyName", "string", "150px"));
  10.     grid.Columns.Add(new GridColumn("Country", "Country", "string", "150px"));
  11.  
  12.     grid.PrimaryKey = "CustomerID";
  13.     grid.AutoGenerateLayouts = false;
  14.  
  15.     // Create child layout
  16.     GridColumnLayoutModel layout = new GridColumnLayoutModel();
  17.     layout.Key = "Orders";
  18.     layout.ForeignKey = "CustomerID";
  19.     layout.PrimaryKey = "OrderID";
  20.     layout.AutoGenerateColumns = false;
  21.  
  22.     layout.Columns = new List<GridColumn>();
  23.     layout.Columns.Add(new GridColumn("Order ID", "OrderID", "number", "100px"));
  24.     layout.Columns.Add(new GridColumn("Customer", "CustomerID", "string", "100px"));
  25.     layout.Columns.Add(new GridColumn("Order Date", "OrderDate", "date", "150px"));
  26.     layout.Columns.Add(new GridColumn("Shipped Date", "ShippedDate", "date", "150px"));
  27.  
  28.     // Add paging to child layouts (different from the parent's)
  29.     GridPaging layoutPaging = new GridPaging();
  30.     layoutPaging.VisiblePageCount = 2;
  31.     layoutPaging.PageSize = 5;
  32.     layoutPaging.Type = OpType.Remote;
  33.     layout.Features.Add(layoutPaging);
  34.  
  35.     //add that layout to the grid
  36.     grid.ColumnLayouts.Add(layout);
  37.  
  38.     //most importantly turn Load On demand on and define response Urls:
  39.     grid.LoadOnDemand = true;
  40.     grid.DataSourceUrl = this.Url.Action("BindParent");
  41.     grid.ColumnLayouts[0].DataSourceUrl = this.Url.Action("BindChild");
  42.  
  43.     //Also add paging to the parent layout
  44.     GridPaging paging = new GridPaging();
  45.     paging.VisiblePageCount = 2;
  46.     paging.PageSize = 10;
  47.     paging.Type = OpType.Remote;
  48.     grid.Features.Add(paging);
  49.     
  50.     // Return the finished model
  51.     return grid;
  52. }

You will have to scroll down a bit to get to the part we are interested in – starting at line 39 where the LoadOnDemand property is set to true. The layouts had to be created before that so data source URL-s can now be assigned to them.

Now back to the subject of JSON responses as seen above we have defined a grid model with two levels of hierarchy – a parent entries with child entries. Therefore we have the “BindParent” and “BindChild” as seen above. A very important note to take is that such a response is required for each layout, so one method returning requested data for each level in your data. As mentioned those methods would use the model’s GetData(), this is why we moved its creation to a separate method, because it is used to pass the model to the view and in all of those methods as well. Their return type should be JsonResult as that is what the grid would expect, but you don’t have to worry about converting data yourself. This is how those look:

  1. public JsonResult BindParent()
  2. {
  3.     //create a model
  4.     GridModel grid = CreateGridModel();
  5.     //set its datasource
  6.     var dataContext = new NorthwindDataContext();
  7.     var dataSource = dataContext.Customers;
  8.     grid.DataSource = dataSource.Take(30);
  9.     //and use the GetData() method to return the results
  10.     return grid.GetData();
  11. }

And then you have the one for the child layout:

  1. public JsonResult BindChild(string path, string layout)
  2. {
  3.     //create a model
  4.     GridModel grid = CreateGridModel();
  5.     //set its datasource
  6.     var dataContext = new NorthwindDataContext();
  7.     var dataSource = dataContext.Orders;
  8.     grid.DataSource = dataSource;
  9.     //and use the GetData() method to return the results
  10.     return grid.GetData(path, layout);
  11. }

Notice we never defined data source in the model creation method and also notice the response methods set that to the proper table for each layout. Notice as well that the parent requires no parameters – this is based on the way the grid performs internal requests. Parameters guide the method on which layout should the data be set and also its path in the hierarchy. The general path format would describe the sequence in which rows were expanded like so:

PrimaryKeyID/ChildPrimaryKeyID/AnotherChildKey[layout name].

Based on how your grid/data is set up any of the expanded rows’ keys in the path can go with a layout name if it is specific to it. In the case above that would mean the PrimaryKeyID would be the guide for the children under that specific row in the parent, then from them the children of the ChildPrimaryKeyID row and then the specific child layout under AnotherChildKey from the latter. This might seem somewhat confusing but take it like this – it is true to its name – you start from the root (notice it is not mentioned in the path) move down the keys following the hierarchy to get to required data. Now the path above is for 4 levels of hierarchy and since the example so far is just two we only get the first part in the path – the PrimaryKeyID which would be the id of the expanded parent row and look like:

jQuery Hierarchical Grid Load on demand request path.

And as you may have already guessed an request with empty path would mean the parent level, thus the BindParent takes zero arguments.

Events

The jQuery Hierarchical Grid offers two events related to Load On Demand. You can start by reading a blog written by Jordan Tsankov on just that – Handling Events in the igHierarchicalGrid. The basic principals on using the LoadOnDemand events is just the same as using rowExpanding or rowCollapsed you can see in that blog. The widget will fire two events consequently upon loading requested data – first ‘childrenPopulating’ and then the standard ‘childrenPopulated’. As always the ‘-ing’ even fires as child layout is just about to be populated with data and is cancellable, while the other event is fired after the population is complete and is not cancellable. Following the convention you can hook up an event handler by just using the name of the control plus the name of the event without further complications like so:

  1. $("#hierarchicalGrid").live("ighierarchicalgridchildrenpopulating", function (evt, args) {
  2.     //you can decline loading data for certain rows, like some that may not even have child items
  3.      //calcel out if calling row with key ID of 0
  4.     if (args.id == "ID:0")
  5.         {return false;}
  6.     //define you own logic to hanle the event
  7. });

As you can find in our API and in Jordan’s blog above you have access to plenty of information about the event and the participants and for the sake of keeping it simple the example above just uses the special ID formed by the row’s primary key and its value to check if the event has been fired while activating load on demand from the very first row. The demo for this project includes event logging for both MVC and OData implementations, so you can check that one out.

The Added benefits

If you remember from the introduction enabling load on demand would also lay foundations for other remote functionalities. You probably have figured it out the moment OData was mentioned – the protocol providers support those features and the grid will make the appropriate requests when remote paging, filtering, sorting and even summaries!

Then there is even more! The MVC wrapper being witty and all will not only parse and execute hierarchical data requests. It is also capable of querying data based on other options it recognizes in the grid’s request – which means that you simply instruct the grid to perform remote those actions and it will create the appropriate requests and the getData() method will return the correct rows. It really is that simple, you define options, set them to remote and don’t even bother setting up URL key(s) for filtering or sorting like you are normally required to. Here’s what you can simply add to the CreateModel method from above:

  1. //Add features:
  2.  GridSorting sorting = new GridSorting();
  3.  sorting.Type = OpType.Remote;
  4.  grid.Features.Add(sorting);
  5.  GridPaging paging = new GridPaging();
  6.  paging.PageSize = 10;
  7.  paging.Type = OpType.Remote;
  8.  grid.Features.Add(paging);
  9.  GridFiltering filtering = new GridFiltering();
  10.  filtering.Type = OpType.Remote;
  11.  filtering.Mode = FilterMode.Simple;
  12.  grid.Features.Add(filtering);

Conclusion

The Load on Demand functionality of the NetAdvantage for jQuery Hierarchical Grid will keep it simple and reward with great results. Should you choose to expose your data via the Open Data Protocol (OData), your client-side implementation of the control will readily take advantage of all it can offer and handle the requests for you with little to no extra effort. And if you prefer different approach the wrapper will assist you greatly in enabling such functionality on your server-side as well as once more managing requests and generating proper responses. And while doing that Load On Demand would also become available for a number of features that can be performed remotely such as paging, summaries, sorting and filtering.

A read you might find interesting in our help topics would be the Getting started with igGrid, oData and WCF Data Services guide in our Help Topics which is another way you can consume OData with your grid.

Also head over to our Samples to try it out and you can also download the Demo project accompanying this blog in which you will find implemented ASP.NET MVC using the wrapper and a pure jQuery / JavaScript implementation consuming OData.