New to Telerik UI for ASP.NET AJAX? Start a free 30-day trial
Programmatic Data Binding with Hierarchy on Client-side
Description
Instructions to implement programmatic data binding with Hierarchical RadGrid on client-side that supports paging, sorting, filtering and 2 level hierarchy.
This example works with the following functionalities:
- 2 Level Hierarchy
- Paging
- Sorting
- Filtering
You can customize the code to make it work with 3 or more level hierarchies. You just need to access the Nested Views in each data item of every
DetailTable
and bind them to data.
Solution
Steps to implement this solution:
- Add a
RadGrid
component to the page and set itsAllowPaging
,AllowSorting
,AllowFilteringByColumn
properties totrue
. - Set the
PageSize
property to the maximum number you can choose from thePageSize
ComboBox in the Pager (in this case it is50
).To avoid the Grid flickering, you will need to hide it on initial binding and unhide it after:
- Set the
CssClass
property tohidden
. - Add a
CSS
style to the page targeting the.hidden
class, and sets thedisplay
attribute tonone
. - After the initial data binding, you can remove the
hidden
class from the Grid.
- Set the
- Attach the OnGridCreated and OnCommand client-side events.
- Add the
MasterTableView
element and define itsColumns
.- Set the
DataType
property for each column.
- Set the
- Add a
GridTableView
element to theDetailTables
collection of theMasterTableView
and define itsColumns
.- Set the
DataType
property for each column.
- Set the
- Set the
Name
property on everyMasterTable
,GridTableView
to the name of the data source Table (in this example, theMasterTableView
will be theOrders
and its child table will be theOrderDetails
). - Set the
ClientDataKeyNames
property on all Parent Tables (in this caseMasterTableView
is the only parent table). - Set the
HierarchyLoadMode
property toClient
on all Parent Tables (in this caseMasterTableView
is the only parent table). - Finally, add your JavaScript code that will handle binding data to this Grid and its
DetailTables
. Check out the Example below for a runnable sample.
You can
Copy/Paste
the following example code as is, and test it out. Feel free to customize it according to your needs.
Example
ASP.NET
<style>
.hidden {
display: none;
}
</style>
<telerik:RadGrid ID="RadGrid1" runat="server" AllowPaging="true" AllowSorting="true" AllowFilteringByColumn="true" PageSize="50" CssClass="hidden" Width="800px">
<MasterTableView Name="Orders" ClientDataKeyNames="OrderID" HierarchyLoadMode="Client">
<DetailTables>
<telerik:GridTableView Name="OrderDetails">
<Columns>
<telerik:GridNumericColumn DataField="OrderID" HeaderText="OrderID" UniqueName="OrderID" DataType="System.Int32" ReadOnly="true"></telerik:GridNumericColumn>
<telerik:GridNumericColumn DataField="UnitsInStock" HeaderText="UnitsInStock" UniqueName="UnitsInStock" DataType="System.Int32"></telerik:GridNumericColumn>
<telerik:GridNumericColumn DataField="Discount" HeaderText="Discount" UniqueName="Discount" DataType="System.Decimal" DataFormatString="{0:P0}"></telerik:GridNumericColumn>
<telerik:GridBoundColumn DataField="Description" HeaderText="Description" UniqueName="Description"></telerik:GridBoundColumn>
</Columns>
</telerik:GridTableView>
</DetailTables>
<Columns>
<telerik:GridNumericColumn DataField="OrderID" HeaderText="OrderID" UniqueName="OrderID" ReadOnly="true" DataType="System.Int32"></telerik:GridNumericColumn>
<telerik:GridDateTimeColumn DataField="OrderDate" HeaderText="OrderDate" UniqueName="OrderDate" DataType="System.DateTime"></telerik:GridDateTimeColumn>
<telerik:GridNumericColumn DataField="Freight" HeaderText="Freight" UniqueName="Freight" DataType="System.Decimal" NumericType="Number">
</telerik:GridNumericColumn>
<telerik:GridBoundColumn DataField="ShipName" HeaderText="ShipName" UniqueName="ShipName"></telerik:GridBoundColumn>
<telerik:GridBoundColumn DataField="ShipCountry" HeaderText="ShipCountry" UniqueName="ShipCountry"></telerik:GridBoundColumn>
</Columns>
</MasterTableView>
<ClientSettings>
<ClientEvents OnGridCreated="RadGrid1_OnGridCreated" OnCommand="RadGrid1_OnCommand" />
</ClientSettings>
</telerik:RadGrid>
<script>
(function () {
// Generates the dummy data for Orders and OrderDetails tables. You might as well fetch the data from server using XMLHttpRequests.
let db = window.db = {
Orders: [],
OrderDetails: [],
temp: {}
};
for (var i = 1; i <= 100; i++) {
var nextDay = Date.now() + i * 1000 * 60 * 60 * 24;
db.Orders.push({
OrderID: i,
OrderDate: `/Date(${nextDay})/`,
Freight: parseFloat((i * 0.1).toFixed(2)),
ShipName: `Name ${i}`,
ShipCountry: `Country ${i}`
});
for (var j = 1; j <= 20; j++) {
db.OrderDetails.push({
OrderID: i,
UnitsInStock: j * 3,
Discount: parseFloat((j / 100).toFixed(2)),
Description: `Details ${j} of Order #${i}`
});
}
}
})();
/** RadGrid Client Events */
function RadGrid1_OnGridCreated(sender, args) {
/** Initial Data binding */
let masterTable = sender.get_masterTableView();
masterTable.PageSize = 10;
bindTableView(masterTable);
$(sender.get_element()).removeClass('hidden');
}
/** Data Binding on User action (e.g. Sorting, Filtering, Paging) */
function RadGrid1_OnCommand(sender, args) {
args.set_cancel(true);
let tableView = args.get_tableView();
bindTableView(tableView);
}
/** Helper Functions */
function bindTableView(tableView) {
let dataSource = getDataSource(tableView);
tableView.set_dataSource(dataSource);
tableView.dataBind();
let dataItems = tableView.get_dataItems();
dataItems.forEach(function (dataItem) {
let detailTables = dataItem.get_nestedViews();
detailTables.forEach(function (tableView) {
tableView.PageSize = 10;
let dataSource = getDataSource(tableView);
tableView.set_dataSource(dataSource);
tableView.dataBind();
});
});
}
function getDataSource(tableView) {
if (tableView) {
let name = tableView.get_name();
let data = $.extend([], db[name]);
if (!data) return null;
if (!(tableView.get_parent() instanceof Telerik.Web.UI.RadGrid)) {
var parentRowElement = tableView.get_parentRow();
if (!parentRowElement.control) { tableView.get_dataItems(); }
let parentDataKeyName = tableView.get_parentView().get_clientDataKeyNames()[0];
let parentItem = parentRowElement.control;
let parentId = parentItem.getDataKeyValue(parentDataKeyName);
data = data.filter(item => item[parentDataKeyName] == parentId);
}
let filteredData = window.db.temp[name] = $.extend(true, [], data);
let virtualItemCount = data.length;
let filterExpressions = tableView.get_filterExpressions().toList();
if (filterExpressions.length > 0) {
filteredData = filterData(data, filterExpressions);
virtualItemCount = filteredData.length;
}
let sortExpressions = tableView.get_sortExpressions().toList();
if (sortExpressions.length > 0) {
filteredData = sortData(filteredData, sortExpressions);
}
tableView.set_virtualItemCount(virtualItemCount);
var skip = tableView.get_pageSize() * tableView.get_currentPageIndex();
var take = tableView.get_pageSize();
filteredData = filteredData.splice(skip, take);
return filteredData;
}
}
function sortData(data, sortExpressions) {
const sortExpression = sortExpressions[0];
const sortOrder = sortExpression.SortOrder;
const fieldName = sortExpression.FieldName;
if (sortOrder == 1) {
return data.sort(item => item[fieldName]);
} else if (sortOrder == 2) {
return data.reverse(item => item[fieldName]);
} else {
return data;
}
}
function filterData(data, filterExpressions) {
const filterExpressionsMap = filterExpressions.map(createFilterExpressionsMap);
return data.filter(item => filterExpressionsMap.every(fn => fn(item)));
}
function createFilterExpressionsMap(filterExpression) {
const dataTypeName = filterExpression.get_dataTypeName();
const fieldName = filterExpression.get_fieldName();
let fieldValue = filterExpression.get_fieldValue();
const filterFunction = filterExpression.get_filterFunction();
const filterFunctions = {
Contains: item => item[fieldName]?.includes(fieldValue),
DoesNotContain: item => !item[fieldName]?.includes(fieldValue),
StartsWith: item => item[fieldName]?.startsWith(fieldValue),
EndsWith: item => item[fieldName]?.endsWith(fieldValue),
EqualTo: item => equalTo(item[fieldName], fieldValue, dataTypeName),
NotEqualTo: item => !equalTo(item[fieldName], fieldValue, dataTypeName),
GreaterThan: item => greaterThan(item[fieldName], fieldValue, dataTypeName),
LessThan: item => lessThan(item[fieldName], fieldValue, dataTypeName),
GreaterThanOrEqualTo: item => greaterThanOrEqualTo(item[fieldName], fieldValue, dataTypeName),
LessThanOrEqualTo: item => lessThanOrEqualTo(item[fieldName], fieldValue, dataTypeName),
IsEmpty: item => item[fieldName] === '',
NotIsEmpty: item => item[fieldName] !== '',
IsNull: item => item[fieldName] == null,
NotIsNull: item => item[fieldName] != null
};
const equalTo = function (dbValue, userValue, dataTypeName) {
if (dataTypeName === 'System.Decimal') {
return parseFloat(dbValue.toFixed(2)) == parseFloat(userValue.toFixed(2));
}
else if (dataTypeName == 'System.DateTime') {
return parseInt(dbValue.match(/\d+/)[0]) == Date.parse(userValue);
}
else {
return dbValue === userValue;
}
}
const greaterThan = function (dbValue, userValue, dataTypeName) {
if (dataTypeName === 'System.Decimal') {
return parseFloat(dbValue.toFixed(2)) > parseFloat(userValue.toFixed(2));
}
else if (dataTypeName == 'System.DateTime') {
return parseInt(dbValue.match(/\d+/)[0]) > Date.parse(userValue);
}
else {
return dbValue > userValue;
}
}
const lessThan = function (dbValue, userValue, dataTypeName) {
if (dataTypeName === 'System.Decimal') {
return parseFloat(dbValue.toFixed(2)) < parseFloat(userValue.toFixed(2));
}
else if (dataTypeName == 'System.DateTime') {
return parseInt(dbValue.match(/\d+/)[0]) < Date.parse(userValue);
}
else {
return dbValue < userValue;
}
}
const greaterThanOrEqualTo = function (dbValue, userValue, dataTypeName) {
return greaterThan(dbValue, userValue, dataTypeName) || equalTo(dbValue, userValue, dataTypeName);
}
const lessThanOrEqualTo = function (dbValue, userValue, dataTypeName) {
return lessThan(dbValue, userValue, dataTypeName) || equalTo(dbValue, userValue, dataTypeName);
}
const filterFn = filterFunctions[filterFunction];
if (!filterFn) {
throw new DOMException(`Unsupported filter function: ${filterFunction}`);
}
return filterFn;
}
</script>