New to Telerik UI for ASP.NET AJAX? Start a free 30-day trial
Programmatic Data Binding with Hierarchy on Client-side
Updated over 6 months ago
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
DetailTableand bind them to data.
Solution
Steps to implement this solution:
- Add a
RadGridcomponent to the page and set itsAllowPaging,AllowSorting,AllowFilteringByColumnproperties totrue. - Set the
PageSizeproperty to the maximum number you can choose from thePageSizeComboBox 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
CssClassproperty tohidden. - Add a
CSSstyle to the page targeting the.hiddenclass, and sets thedisplayattribute tonone. - After the initial data binding, you can remove the
hiddenclass from the Grid.
- Set the
- Attach the OnGridCreated and OnCommand client-side events.
- Add the
MasterTableViewelement and define itsColumns.- Set the
DataTypeproperty for each column.
- Set the
- Add a
GridTableViewelement to theDetailTablescollection of theMasterTableViewand define itsColumns.- Set the
DataTypeproperty for each column.
- Set the
- Set the
Nameproperty on everyMasterTable,GridTableViewto the name of the data source Table (in this example, theMasterTableViewwill be theOrdersand its child table will be theOrderDetails). - Set the
ClientDataKeyNamesproperty on all Parent Tables (in this caseMasterTableViewis the only parent table). - Set the
HierarchyLoadModeproperty toClienton all Parent Tables (in this caseMasterTableViewis 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/Pastethe 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>