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
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
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
- There are no server-side databinding event handlers whatsoever. No
ParentTableRelations. This is all client-side databinding.
HierarchyLoadMode for both the master and first child table are set
to Client. All detail tables will be loaded and available on the client.
- Filtering, paging and sorting is disabled for the innermost detail table, as it
will be bound to a single related entity only
RadGrid.ClientSettings.DataBinding settings 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.ClientEvents specify 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
- Find the GridDataItem component that is associated with the expanding item.
- Find the detail table of the item.
- Bind the detail table if it is not already databound.
gridCommand - intercept commands and rebind detail tables
- Check if the command originates from a detail table
- Bind the detail table
- Collapse any expanded items and clear data from detail tables
The result is a 3-level hierarchical client-bound RadGrid:
tables are bound when parent items are expanded and when a page, sort or filter
command is fired.
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
$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
$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.
Download the test page
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