If you haven't heard, Odata is a Web protocol that exposes your data to the Web, allowing consumers to make queries through a set of URI parameters. RadGrid, Telerik's ASP.NET AJAX Grid control features a rich client-side API that enables you to easily bind to OData services and have all the paging, sorting and filtering done without ever posting the page to the server. That is all fine if your data is flat. Binding to hierarchical data, however, is another story. RadGrid officially supports hierarchical databinding on the server only. But let's go unofficial for a while. In this blog post we'll demonstrate an approach for detail table databinding to an OData service right in the client. The example uses 3 levels if hierarchical data, but the approach is general enough to work with an arbitrary number of detail tables.
How would we go about that? To demonstrate with an example, we will use one of the sample OData services exposing the Northwind database. We will bind to the following hierarchical data:
Categories (data set) Products (data subset) Supplier (related sub-entity)
Each Category entity has a subset of related Product entities. When a JSON request
is made to the OData service, the related Product entities are referenced
Products field in each Category entity. I say referenced,
because they are not directly available in the response containing the Categories.
In an OData request, related entities and entity sets are deferred by default.
We'll see what this means in the upcoming section The Internals.
Similarly, the supplier that is related to each Product is referenced by the
field and is also deferred. Note that in the Category - Products relation we have
a related entity set, while in the relation Product - Supplier only a single Supplier
entity is related. Our databinding approach needs to be general enough to accommodate
both a response containing a related entity set, as well as a single returned entity.
Let's start with the markup. We have a RadGrid with enabled paging, sorting and filtering. It has a hierarchy of 3 detail tables - the master table to show Categories, a second level detail table to show Products and a third level detail table to show the Supplier associated with each product. Here is the complete markup:
Nothing really fancy here, this is all required for setting up detail tables with server-side databinding too. Highlights here are:
ParentTableRelations. This is all client-side databinding.
HierarchyLoadModefor both the master and first child table are set to Client. All detail tables will be loaded and available on the client.
RadGrid.ClientSettings.DataBindingsettings specify OData binding to the Categories table. This is the data source for the MasterTable only! We will be binding detail tables additionally.
RadGrid.ClientSettings.ClientEventsspecify 2 event handlers - this is the meat of our detail table databinding logic.
event and one for the
OnCommand event. Here is what each event handlers
does, and how it does it:
gridHierarchyExpanding- find the detail table when expanding a parent item and bind it
gridCommand- intercept commands and rebind detail tables
The result is a 3-level hierarchical client-bound RadGrid:
Probably the most interesting part of the code is how the detail data is retrieved.
When an OData service call returns a result set in JSON format, any related entities
or entity sets are excluded. Instead, they are identified by a field named
in the related entity object. Here is an example Category entity returned from the
OData service we use:
As shown, the
Products.__deferred.uri field tells us the URI from which
we can retrieve the Products subset for this Category entity. This is exactly the
data we need for the Products detail table. Each parent grid item provides us with
the URI from where the data for its detail table can be retrieved.
Once we have the URI of the detail table, we can build an OData request URL that
will fetch the Products for every Category item that is expanded. This is done in
bindDetailTable function. It accepts the detail table that is to
be bound and the URI of the data for the table. Inside, we use a private grid method:
RadGrid._getDataServiceData(onSuccess, onFail, uri)
We usually do not recommend using private methods and fields in client components.
But we also usually do not recommend going with an unsupported scenario. And this
is exactly what we are doing in this blog post, so go ahead and use that method.
It simply wraps a
jQuery.ajax() call for you. You do not worry about
the internals, you just have to pass a success and a failure callback, as well as
the URI of the request.
The URL of the OData request is formed of 2 parts - the URI of the data set and
a collection of URI parameters specifying data operations and a return format. For
data sets that need to support paging, sorting and filtering, the
method we use conveniently returns a query string with the paging, sorting and filtering
state for the current detail table. You can read about supported URI parameters
Odata URI Conventions topic. Here is a list of URI parameters RadGrid uses:
$format=json- used to specify the result of the query should be in JSON format
$skip- used to specify how many records to skip when paging (equals [page size] * [current page index])
$take- used to specify how many records to take when paging (equals page size)
$inlinecount=allpages- used to request the total row count in the database when paging
$order- used to specify a sort expression when sorting
$filter- used to specify a filter expression when filtering
$callback- used by jQuery for a JSONP request
When we request a single entity and no paging, sorting or filtering is supported,
the need to specify
$format=json as a minimum. The
parameter is automatically appended by jQuery.
Once we have our query string built, we concatenate it with the data URI and thus
form a URL string to pass to
RadGrid._getDataService(). If the request
is successful, the result is then passed to the detail table and the latter is databound.
Using this approach, you get an all singing, all dancing client-bound RadGrid with
3 levels of hierarchical tables. Paging, sorting and filtering is supported accross
all hierarchical tables. The approach is general enough to be used with as many
detail tables as you need. We use 2 client events for that.
is used to bind a detail table by the time its parent item is expanding.
is used to intercept any grid commands originating from a detail table and bind
the detail table. Again, this is not officially supported, but it works. A test
page is attached if you want to try it on your machine. Any feedback is welcome.
Note: If you still haven’t tried Telerik’s ASP.NET AJAX Grid control (as well as the others in the stack), there’s no better time to do it. Take a look at all the features it supports out of the box and download a free 60-day trial with dedicated support here.
Subscribe to be the first to get our expert-written articles and tutorials for developers!