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
by the 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 Supplier
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:
DetailTableDataBind
,
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.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.
With the markup all set, let us dig into the javascript required to make the detail
tables bind. We use 2 javascript event handlers - one for the OnHierarchyExpanding
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:
A test page with the above RadGrid and all the required javascript is attached to this post. The javascript comments will help you understand what's going on. Detail 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 __deferred
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
the 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 GridTableView.getDataServiceQuery()
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
in the
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 $callback
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. OnHierarchyExpanding
is used to bind a detail table by the time its parent item is expanding. OnCommand
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.
Veli Pehlivanov is a Technical Lead at one of Telerik’s ASP.NET AJAX teams, where he primarily works on RadGrid and a few other data- and input-centric controls, including RadListView, RadCalendar and RadInput. Veli's interests lie in the area of web development, C#, .NET, JavaScript and agile project management. He likes being on the cutting edge of technology and is keen on delivering efficient software and a greater value for the user.