Grid Hierarchy
The Grid component provides options for visualizing the relations between parent and child records by displaying data in a hierarchical manner through a detail template.
In this article:
Basics
To implement hierarchy in the Grid, define a DetailTemplate
under the main tag of the grid. In this template, you can access the model for the concrete row through the context
, and use other components to show detailed data from it (for example, another grid, or any other set of components and HTML).
When a detail template is defined, an expand/collapse button is rendered at the beginning of the row that the user can click to show and hide the detailed data.
Click the + icon to expand the row details
<TelerikGrid Data="salesTeamMembers">
<DetailTemplate>
@{
var employee = context as MainModel;
<TelerikGrid Data="employee.Orders" Pageable="true" PageSize="5">
<GridColumns>
<GridColumn Field="OrderId"></GridColumn>
<GridColumn Field="DealSize"></GridColumn>
</GridColumns>
</TelerikGrid>
}
</DetailTemplate>
<GridColumns>
<GridColumn Field="Id"></GridColumn>
<GridColumn Field="Name"></GridColumn>
</GridColumns>
</TelerikGrid>
@code {
List<MainModel> salesTeamMembers { get; set; }
protected override void OnInitialized()
{
salesTeamMembers = GenerateData();
}
private List<MainModel> GenerateData()
{
List<MainModel> data = new List<MainModel>();
for (int i = 0; i < 5; i++)
{
MainModel mdl = new MainModel { Id = i, Name = $"Name {i}" };
mdl.Orders = Enumerable.Range(1, 15).Select(x => new DetailsModel { OrderId = x, DealSize = x^i }).ToList();
data.Add(mdl);
}
return data;
}
public class MainModel
{
public int Id { get; set; }
public string Name { get;set; }
public List<DetailsModel> Orders { get; set; }
}
public class DetailsModel
{
public int OrderId { get; set; }
public double DealSize { get; set; }
}
}
To have more levels, simply nest more grids and name the
context
variables. You can find an example in the Multi-Level Hierarchy KB article.
Expand Rows From Code
You can choose which detail templates will be expanded from your code through the grid state. Its ExpandedItems
field contains a collection of the expanded Grid items (all detail templates are collapsed by default).
The ExpandedItems
collection is compared against the Grid Data collection in order to determine which rows will be expanded. The default behavior of the framework is to compare objects by their reference.
When the ExpandedItems
are obtained from a different data source to the Grid (e.g., from a separate service method and not from the view-model), the references may not match and so there will be no expanded items. In such cases, you have to override the Equals
method of the underlying model class so that it matches them, for example, by a unique identifier rather than by reference so that two objects can be equal regardless of their origin, but according to their contents. When you are overriding the Equals
method, it is also recommended to override the GetHashCode
method as well. A similar example is available at Save and Load Grid State from Browser LocalStorage.
If you want to set an initial state to the Grid, use a similar snippet, but in the
OnStateInit event
<TelerikGrid @ref="@GridRef"
Data="@CategoryData"
Pageable="true"
PageSize="2">
<GridToolBarTemplate>
<TelerikDropDownList Data="@CategoryData"
@bind-Value="@DropDownListCategoryId"
TextField="@nameof(Category.Name)"
ValueField="@nameof(Category.Id)">
</TelerikDropDownList>
<TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
OnClick="@ExpandCategory">Expand Category</TelerikButton>
<span class="k-separator"></span>
<TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
OnClick="@ExpandAll">Expand All Categories</TelerikButton>
<span class="k-separator"></span>
<TelerikButton OnClick="@CollapseAll">Collapse All Categories</TelerikButton>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@(nameof(Category.Id))" Width="80px" />
<GridColumn Field="@(nameof(Category.Name))" Title="Category Name" />
</GridColumns>
<DetailTemplate Context="category">
<TelerikGrid Data="@ProductData.Where(x => x.CategoryId == category.Id)">
<GridColumns>
<GridColumn Field="@(nameof(Product.Name))" Title="Product Name" />
<GridColumn Field="@(nameof(Product.Price))" DisplayFormat="{0:c2}" />
<GridColumn Field="@(nameof(Product.Quantity))" />
</GridColumns>
</TelerikGrid>
</DetailTemplate>
</TelerikGrid>
@code {
private TelerikGrid<Category>? GridRef { get; set; }
private List<Category> CategoryData { get; set; } = new();
private List<Product> ProductData { get; set; } = new();
private int DropDownListCategoryId { get; set; } = 1;
private async Task ExpandCategory()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
var categoryToExpand = CategoryData.First(x => x.Id == DropDownListCategoryId);
gridState.ExpandedItems.Add(categoryToExpand);
await GridRef.SetStateAsync(gridState);
}
}
private async Task ExpandAll()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
gridState.ExpandedItems = CategoryData;
await GridRef.SetStateAsync(gridState);
}
}
private async Task CollapseAll()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
gridState.ExpandedItems.Clear();
await GridRef.SetStateAsync(gridState);
}
}
protected override void OnInitialized()
{
var categoryCount = 3;
for (int i = 1; i <= categoryCount; i++)
{
CategoryData.Add(new Category()
{
Id = i,
Name = $"Category {i}"
});
}
for (int i = 1; i <= 12; i++)
{
ProductData.Add(new Product()
{
Id = i,
CategoryId = i % categoryCount + 1,
Name = $"Product {i}",
Price = Random.Shared.Next(1, 100) * 1.23m,
Quantity = Random.Shared.Next(0, 100)
});
}
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
public class Product
{
public int Id { get; set; }
public int CategoryId { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Quantity { get; set; }
}
}
More Examples
The following articles and sample projects can be helpful when implementing hierarchy: