Grid State
The Grid lets you read, save, load, and change its state through code. The state includes the Grid features that are controlled by the user, such as the current sorting, page number, applied grouping, column widths, and many others.
This article describes:
-
The properties of the
GridState
object. -
How to set initial Grid configuration programmatically in
OnStateInit
. -
How to detect user changes in the Grid state with
OnStateChanged
. - How to use Grid methods to get and set the Grid state.
-
Why you may need to override the
Equals
method of the Grid model class.
Information in the Grid State
The Grid state is a generic class GridState<TItem>
. The type depends on the type of the Grid model. The GridState<TItem>
object exposes the following properties:
Property | Type | Description |
---|---|---|
CollapsedGroups |
ICollection<int> |
The indexes of all collapsed groups when LoadGroupsOnDemand="false" , i.e. when not loading groups on demand. |
ColumnStates |
ICollection<GridColumnState> |
Information about each column's reorder index, width, visibility, locked state, Id parameter value and Field . The column order in the collection matches the column order in the Grid declaration. On the other hand, the Index property matches the current column's position in the UI. Id and Field are always null after deserialization, because these properties have no public setters.
|
EditField |
string |
The currently edited data item property in Incell edit mode. |
EditItem |
TItem * |
The currently edited data item in any edit mode. |
ExpandedItems |
ICollection<TItem> |
The expanded data items, when using <DetailTemplate> (hierarchy). |
FilterDescriptors |
ICollection<IFilterDescriptor> |
A collection of CompositeFilterDescriptor , except the ones that relate to the GridSearchBox . |
GroupDescriptors |
ICollection<GroupDescriptor> |
Information about currently applied grouping. |
InsertedItem |
TItem * |
The data item that is being added in Inline or Popup edit mode. Not applicable for Incell editing. |
OriginalEditItem |
TItem * |
The original copy of the data item that is currently in edit mode. This GridState property holds the unmodified data item values. |
Page |
int? |
The current page index. Some user actions reset the page index to 1, such as filtering or changing the page size. |
SearchFilter |
IFilterDescriptor |
The CompositeFilterDescriptor that holds the filter descriptors for the GridSearchBox . |
SelectedItems |
ICollection<TItem> |
The currently selected data item(s). |
Skip |
int? |
The number of scrolled data items when using virtual row scrolling. In other words, this is the number of rows above the currently visible ones. |
SortDescriptors |
ICollection<SortDescriptor> |
The currently applied sorts. |
TableWidth |
string |
The sum of all visible column widths. This property changes together with ColumnStates . The OnStateChanged event does not fire separately for it. |
* TItem
is the Grid model type.
Events
The Grid features two events, which are related to its state.
OnStateInit
The OnStateInit
event fires when the Grid is initializing. Use this event to:
- Define initial state, for example default initial sorting;
- Load and apply state that was previously saved in a database or in
localStorage
.
The generic event argument is of type GridStateEventArgs<TItem>
and has a GridState
property. See Information in the Grid State for details.
If you change the column order or number of columns in the Grid declaration, this can break state restore. In such cases, either ignore the stored column state, or implement custom logic to restore only the columns that still exist in the Grid.
To set the initial visibility of columns, better use the
Visible
parameter, rather than conditional markup for the whole column. TheVisible
parameter values will be present in the Grid state and the columns collection count will remain the same. This makes it easier to reconcile changes.
The example below shows how to apply initial sorting, filtering and grouping.
@using Telerik.DataSource
<TelerikGrid Data="@GridData"
Pageable="true"
PageSize="5"
Sortable="true"
FilterMode="@GridFilterMode.FilterMenu"
Groupable="true"
OnStateInit="@( (GridStateEventArgs<Product> args) => OnGridStateInit(args) )">
<GridColumns>
<GridColumn Field="@nameof(Product.Name)" />
<GridColumn Field="@nameof(Product.Category)" />
<GridColumn Field="@nameof(Product.Stock)" />
<GridColumn Field="@nameof(Product.Discontinued)" />
</GridColumns>
</TelerikGrid>
@code {
private List<Product> GridData { get; set; }
private async Task OnGridStateInit(GridStateEventArgs<Product> args)
{
// Sort by Stock
args.GridState.SortDescriptors.Add(new SortDescriptor()
{
Member = nameof(Product.Stock),
SortDirection = ListSortDirection.Descending
});
// Filter Discontinued products
var discontinuedColumnFilter = new CompositeFilterDescriptor()
{
FilterDescriptors = new FilterDescriptorCollection() {
new FilterDescriptor()
{
Member = nameof(Product.Discontinued),
MemberType = typeof(bool),
Operator = FilterOperator.IsEqualTo,
Value = false
}
}
};
args.GridState.FilterDescriptors.Add(discontinuedColumnFilter);
// Group by Category
args.GridState.GroupDescriptors.Add(new GroupDescriptor()
{
Member = nameof(Product.Category),
MemberType = typeof(string)
});
}
protected override void OnInitialized()
{
GridData = new List<Product>();
var rnd = new Random();
for (int i = 1; i <= 12; i++)
{
GridData.Add(new Product()
{
Id = i,
Name = $"Product {i}",
Category = $"Category {i % 2 + 1}",
Stock = rnd.Next(0, 100),
Discontinued = i % 3 == 0
});
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public int Stock { get; set; }
public bool Discontinued { get; set; }
}
}
OnStateChanged
OnStateChanged
fires when the user performs an action that changes the value of a property in the Grid state. The event argument is of type GridStateEventArgs<TItem>
and exposes these properties:
Property | Type | Description |
---|---|---|
PropertyName |
string |
Information about what changed in the Grid state. The possible values match the property names of the GridState object. The possible values for the PropertyName are SortDescriptors , FilterDescriptors , SearchFilter , GroupDescriptors , Page , Skip , CollapsedGroups , ColumnStates , ExpandedItems , InsertedItem , OriginalEditItem , EditItem . |
GridState |
GridState<TItem> |
The current (up-to-date) Grid state object. |
Here is some additional information about certain PropertyName
values:
-
EditItem
is used when the user starts editing an existing item. -
InsertedItem
signifies the user adding a new item in inline or popup edit mode. It's not applicable forIncell
editing. -
OriginalEditItem
is used when the user exits edit or insert mode via save or cancel. -
ColumnStates
is used for several column actions such as hiding, showing, locking, reordering and resizing.
Some user actions will trigger two
OnStateChanged
events with a differentPropertyName
each time. These include filtering, searching and grouping. For example, filtering resets the current page to 1. First, the event will fire withPropertyName
equal to"FilterDescriptors"
, and thenPropertyName
will be"Page"
. However, theGridState
property of the event argument will provide correct information about the overall Grid state in both event handler executions.
We recommend using an
async Task
handler for theOnStateChanged
event, in order to reduce re-rendering and avoid blocking UI updates if the handler will wait for a service to save the Grid state somewhere.
To observe the changes in the Grid state more easily, copy and run the following example in a local app and at full screen.
Find out how to get the applied filtering, sorting and grouping criteria.
@using System.Text.Json
<div id="demo-container">
<TelerikGrid Data="@GridData"
EditMode="@GridEditMode.Inline"
FilterMode="@GridFilterMode.FilterMenu"
Groupable="true"
Pageable="true"
@bind-PageSize="@GridPageSize"
Reorderable="true"
Resizable="true"
@bind-SelectedItems="@GridSelectedItems"
SelectionMode="@GridSelectionMode.Multiple"
ShowColumnMenu="true"
Sortable="true"
OnCreate="@OnGridCreate"
OnUpdate="@OnGridUpdate"
OnStateChanged="@( (GridStateEventArgs<Product> args) => OnGridStateChanged(args) )">
<GridSettings>
<GridPagerSettings PageSizes="@( new List<int?>() { null, 5, 10 } )" />
</GridSettings>
<GridToolBarTemplate>
<GridCommandButton Command="Add">Add</GridCommandButton>
<GridSearchBox />
</GridToolBarTemplate>
<GridColumns>
<GridCheckboxColumn SelectAll="true" />
<GridColumn Field="@nameof(Product.Name)" />
<GridColumn Field="@nameof(Product.Category)" />
<GridColumn Field="@nameof(Product.Stock)" />
<GridColumn Field="@nameof(Product.Discontinued)" />
<GridCommandColumn>
<GridCommandButton Command="Edit">Edit</GridCommandButton>
<GridCommandButton Command="Save" ShowInEdit="true">Save</GridCommandButton>
<GridCommandButton Command="Cancel" ShowInEdit="true">Cancel</GridCommandButton>
</GridCommandColumn>
</GridColumns>
<DetailTemplate>
Detail Template for product <strong>@context.Name</strong>.
</DetailTemplate>
</TelerikGrid>
<div id="console">
<code class="@GridStateChangedPropertyClass">OnStateChanged</code> count:
@OnStateChangedCount
<TelerikButton OnClick="@( () => OnStateChangedCount = 0 )">Reset</TelerikButton>
<br /><br />
Last <code>OnStateChanged</code> event:
<br />
<strong class="@GridStateChangedPropertyClass">PropertyName</strong>:
<code>"@GridStateChangedProperty"</code>
<br />
<strong>GridState</strong>:
<pre>
@( new MarkupString(GridStateString) )
</pre>
</div>
</div>
<style>
.first-of-two {
color: #f00;
}
.latest-changed-property {
color: #00f;
}
@@media (min-width: 800px) {
#demo-container {
display: flex;
align-items: flex-start;
gap: 1em;
}
#demo-container > .k-grid {
flex: 2 2 800px;
}
#console {
height: 90vh;
overflow: auto;
flex: 1 0 300px;
border: 1px solid rgba(128, 128, 128, .3);
padding: 1em;
}
}
</style>
@code {
private List<Product> GridData { get; set; } = new List<Product>();
private int GridPageSize { get; set; } = 5;
private IEnumerable<Product> GridSelectedItems { get; set; } = new List<Product>();
private int OnStateChangedCount { get; set; }
private string GridStateChangedProperty { get; set; } = string.Empty;
private string GridStateChangedPropertyClass { get; set; } = string.Empty;
private string GridStateString { get; set; } = string.Empty;
private bool _doubleStateChanged { get; set; }
private List<string> _operationsWithMultipleStateChanged = new List<string>() {
"FilterDescriptors",
"GroupDescriptors",
"SearchFilter"
};
private async Task OnGridStateChanged(GridStateEventArgs<Product> args)
{
if (_doubleStateChanged)
{
_doubleStateChanged = false;
await Task.Delay(1500);
GridStateChangedPropertyClass = string.Empty;
}
++OnStateChangedCount;
GridStateChangedProperty = args.PropertyName;
// serialize the GridState and highlight the changed property
GridStateString = JsonSerializer.Serialize(args.GridState, new JsonSerializerOptions() { WriteIndented = true })
.Replace($"\"{GridStateChangedProperty}\"", $"\"<strong class='latest-changed-property'>{GridStateChangedProperty}</strong>\"");
// highlight first GridStateChangedProperty during filtering, grouping and search
if (_operationsWithMultipleStateChanged.Contains(GridStateChangedProperty))
{
_doubleStateChanged = true;
GridStateChangedPropertyClass = "first-of-two";
}
}
private void OnGridUpdate(GridCommandEventArgs args)
{
var updatedItem = (Product)args.Item;
var originalItemIndex = GridData.FindIndex(x => x.Id == updatedItem.Id);
GridData[originalItemIndex] = updatedItem;
}
private void OnGridCreate(GridCommandEventArgs args)
{
var createdItem = (Product)args.Item;
GridData.Insert(0, createdItem);
}
protected override void OnInitialized()
{
var rnd = new Random();
for (int i = 1; i <= 12; i++)
{
GridData.Add(new Product()
{
Id = i,
Name = $"Product {i}",
Category = $"Category {i % 4 + 1}",
Stock = rnd.Next(0, 100),
Discontinued = i % 3 == 0
});
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Category { get; set; } = string.Empty;
public int Stock { get; set; }
public bool Discontinued { get; set; }
}
}
Methods
The GetState
and SetStateAsync
methods of the Grid instance let you get and set the current Grid state on demand at any time after OnStateInit
.
GetState
returns the current Grid state, so you can save it or retrieve specific information. For example, you can useGetState
to get the current filters, sorts, and page number. Or, you can get the current Grid column properties like order index, width, and others).SetStateAsync
receives an instance of aGridState<TItem>
object and applies it to the Grid. For example, you can have a button that puts the Grid in a certain configuration programmatically, for example sort or filter the data, enter or exit edit mode, expand or collapse groups or detail Grids, etc.
If you want to make changes to the current Grid state:
- First, get the current state with the
GetState
method. - Apply the desired modifications to the obtained
GridState
object. - Set the modified state object via the
SetStateAsync
method.
Do not use
GetState()
in theOnStateInit
orOnStateChanged
events. Do not useSetStateAsync()
inOnStateInit
. Instead, get or set theGridState
property of the event argument.Avoid calling
SetStateAsync
in the Grid CRUD methods (such asOnRead
,OnUpdate
,OnEdit
,OnCreate
,OnCancel
). Doing so may lead to unexpected results because the Grid has more logic to execute after these events. Setting the Grid state firesOnRead
, so callingSetStateAsync()
in this handler can lead to an endless loop.
To reset the Grid state to its initial markup configuration, call
SetStateAsync(null)
.To reset the Grid state to a completely new configuration, create a
new GridState<T>()
and apply the settings there. Then pass the state object toSetStateAsync()
.
SetStateAsync Examples
The tabs below show how to set the Grid state and control filtering, sorting and other Grid features.
If you want to set an initial state to the Grid, use a similar snippet, but in the
OnStateInit event
@using Telerik.DataSource
<TelerikGrid @ref="@GridRef"
Data="@GridData"
Pageable="true"
Sortable="true"
SortMode="@SortMode.Multiple"
Height="400px">
<GridToolBarTemplate>
<TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
OnClick="@SetGridSort">Sort Grid by HireDate</TelerikButton>
<label>
<TelerikCheckBox @bind-Value="@ShouldResetSortState" />
Reset Existing Sort State
</label>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@(nameof(Employee.Name))" Title="Employee Name" />
<GridColumn Field="@(nameof(Employee.Team))" Title="Team" />
<GridColumn Field="@(nameof(Employee.HireDate))" Title="Hire Date" DisplayFormat="{0:d}" />
<GridColumn Field="@(nameof(Employee.IsOnLeave))" Title="Is On Leave" />
</GridColumns>
</TelerikGrid>
@code {
private TelerikGrid<Employee>? GridRef { get; set; }
private List<Employee> GridData { get; set; } = new();
private bool ShouldResetSortState { get; set; } = true;
private async Task SetGridSort()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
if (ShouldResetSortState)
{
// Remove any existing sorts.
gridState.SortDescriptors.Clear();
}
SortDescriptor? hireDateSortDescriptor = gridState.SortDescriptors
.Where(x => x.Member == nameof(Employee.HireDate)).FirstOrDefault();
if (hireDateSortDescriptor != null)
{
// Update the existing HireDate sort if it exists.
hireDateSortDescriptor.SortDirection = ListSortDirection.Descending;
}
else
{
// Add a new sort descriptor.
// In multi-column sorting scenarios
// you can also insert the new SortDescriptor
// before the existing ones to control the sort priority.
gridState.SortDescriptors.Add(new SortDescriptor()
{
Member = nameof(Employee.HireDate),
SortDirection = ListSortDirection.Descending
});
}
await GridRef.SetStateAsync(gridState);
}
}
protected override void OnInitialized()
{
for (int i = 1; i <= 30; i++)
{
GridData.Add(new Employee()
{
Id = i,
Name = $"Name {i}",
Team = $"Team {i % 5 + 1}",
HireDate = DateTime.Today.AddDays(-Random.Shared.Next(1, 3000)),
IsOnLeave = i % 4 == 0 ? true : false
});
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Team { get; set; } = string.Empty;
public DateTime HireDate { get; set; }
public bool IsOnLeave { get; set; }
}
}
@using Telerik.DataSource
<TelerikGrid @ref="@GridRef"
Data="@GridData"
Pageable="true"
FilterMode="@GridFilterMode.FilterRow"
Height="400px">
<GridToolBarTemplate>
<TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
OnClick="@( () => SetGridFilters(false) )">Filter Grid by Team</TelerikButton>
<TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
OnClick="@( () => SetGridFilters(true) )">Filter Grid by Team and HireDate</TelerikButton>
<span class="k-separator"></span>
<TelerikButton OnClick="@RemoveGridFilters">Remove All Filters</TelerikButton>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@(nameof(Employee.Name))" Title="Employee Name" />
<GridColumn Field="@(nameof(Employee.Team))" Title="Team" />
<GridColumn Field="@(nameof(Employee.HireDate))" Title="Hire Date" DisplayFormat="{0:d}" />
<GridColumn Field="@(nameof(Employee.IsOnLeave))" Title="Is On Leave" />
</GridColumns>
</TelerikGrid>
@code {
private TelerikGrid<Employee>? GridRef { get; set; }
private List<Employee> GridData { get; set; } = new();
private async Task SetGridFilters(bool shouldFilterSecondColumn)
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
// Find the Team CompositeFilterDescriptor if it exists.
CompositeFilterDescriptor? teamCFD = gridState.FilterDescriptors.Cast<CompositeFilterDescriptor>()
.Where(x => x.FilterDescriptors.Cast<FilterDescriptor>().First().Member == nameof(Employee.Team))
.FirstOrDefault();
if (teamCFD != null)
{
// Update the existing Team CompositeFilterDescriptor.
var teamFilterDescriptors = teamCFD.FilterDescriptors.Cast<FilterDescriptor>();
// When using a filter row, the column's CompositeFilterDescriptor
// always contains one FilterDescriptor.
FilterDescriptor firstTeamFD = teamFilterDescriptors.First();
firstTeamFD.Operator = FilterOperator.IsEqualTo;
firstTeamFD.Value = "Team 1";
}
else
{
// Create a new Team CompositeFilterDescriptor.
var teamFdCollection = new FilterDescriptorCollection();
teamFdCollection.Add(new FilterDescriptor()
{
Member = nameof(Employee.Team),
MemberType = typeof(string),
Operator = FilterOperator.IsEqualTo,
Value = "Team 1"
});
// Add one CompositeFilterDescriptor per column.
gridState.FilterDescriptors.Add(new CompositeFilterDescriptor()
{
// The LogicalOperator property doesn't matter, because
// there is only one FilterDescritor in the CompositeFilterDescriptor.
FilterDescriptors = teamFdCollection
});
}
// Find the HireDate CompositeFilterDescriptor if it exists.
CompositeFilterDescriptor? hireDateCFD = gridState.FilterDescriptors.Cast<CompositeFilterDescriptor>()
.Where(x => x.FilterDescriptors.Cast<FilterDescriptor>().First().Member == nameof(Employee.HireDate))
.FirstOrDefault();
if (hireDateCFD != null)
{
// Instead of changing the existing CompositeFilterDescriptor,
// you can also remove it and create a new one.
gridState.FilterDescriptors.Remove(hireDateCFD);
}
if (shouldFilterSecondColumn)
{
var hireDateFdCollection = new FilterDescriptorCollection();
hireDateFdCollection.Add(new FilterDescriptor()
{
Member = nameof(Employee.HireDate),
MemberType = typeof(DateTime),
Operator = FilterOperator.IsGreaterThanOrEqualTo,
Value = DateTime.Today.AddYears(-3)
});
gridState.FilterDescriptors.Add(new CompositeFilterDescriptor()
{
FilterDescriptors = hireDateFdCollection
});
}
await GridRef.SetStateAsync(gridState);
}
}
private async Task RemoveGridFilters()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
gridState.FilterDescriptors.Clear();
await GridRef.SetStateAsync(gridState);
}
}
protected override void OnInitialized()
{
for (int i = 1; i <= 30; i++)
{
GridData.Add(new Employee()
{
Id = i,
Name = $"Name {i}",
Team = $"Team {i % 5 + 1}",
HireDate = DateTime.Today.AddDays(-Random.Shared.Next(1, 3000)),
IsOnLeave = i % 4 == 0 ? true : false
});
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Team { get; set; } = string.Empty;
public DateTime HireDate { get; set; }
public bool IsOnLeave { get; set; }
}
}
@using Telerik.DataSource
<TelerikGrid @ref="@GridRef"
Data="@GridData"
Pageable="true"
FilterMode="@GridFilterMode.FilterMenu"
Height="400px">
<GridToolBarTemplate>
<TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
OnClick="@( () => SetTeamFilter(false) )">Filter Grid by Team 1</TelerikButton>
<TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
OnClick="@( () => SetTeamFilter(true) )">Filter Grid by Team 1 or 3</TelerikButton>
<span class="k-separator"></span>
<TelerikButton OnClick="@RemoveGridFilters">Remove All Filters</TelerikButton>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@(nameof(Employee.Name))" Title="Employee Name" />
<GridColumn Field="@(nameof(Employee.Team))" Title="Team" />
<GridColumn Field="@(nameof(Employee.HireDate))" Title="Hire Date" DisplayFormat="{0:d}" />
<GridColumn Field="@(nameof(Employee.IsOnLeave))" Title="Is On Leave" />
</GridColumns>
</TelerikGrid>
@code {
private TelerikGrid<Employee>? GridRef { get; set; }
private List<Employee> GridData { get; set; } = new();
private async Task SetTeamFilter(bool shouldSetSecondFilter)
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
// Find the Team CompositeFilterDescriptor if it exists.
CompositeFilterDescriptor? teamCFD = gridState.FilterDescriptors.Cast<CompositeFilterDescriptor>()
.Where(x => x.FilterDescriptors.Cast<FilterDescriptor>().First().Member == nameof(Employee.Team))
.FirstOrDefault();
if (teamCFD != null)
{
// Update the existing Team CompositeFilterDescriptor.
teamCFD.LogicalOperator = FilterCompositionLogicalOperator.Or;
var teamFilterDescriptors = teamCFD.FilterDescriptors.Cast<FilterDescriptor>();
// When using a filter menu, the column's CompositeFilterDescriptor
// always contains two FilterDescriptors.
FilterDescriptor firstTeamFD = teamFilterDescriptors.First();
firstTeamFD.Operator = FilterOperator.IsEqualTo;
firstTeamFD.Value = "Team 1";
// Set a null FilterDescriptor Value and IsEqualTo Operator
// to disable a filter.
FilterDescriptor secondTeamFD = teamFilterDescriptors.Last();
secondTeamFD.Operator = FilterOperator.IsEqualTo;
secondTeamFD.Value = shouldSetSecondFilter ? "Team 3" : null;
}
else
{
// Create a new Team CompositeFilterDescriptor.
var fdCollection = new FilterDescriptorCollection();
fdCollection.Add(new FilterDescriptor()
{
Member = nameof(Employee.Team),
MemberType = typeof(string),
Operator = FilterOperator.IsEqualTo,
Value = "Team 1"
});
fdCollection.Add(new FilterDescriptor()
{
Member = nameof(Employee.Team),
MemberType = typeof(string),
Operator = FilterOperator.IsEqualTo,
Value = shouldSetSecondFilter ? "Team 3" : null
});
// Add one CompositeFilterDescriptor per column.
gridState.FilterDescriptors.Add(new CompositeFilterDescriptor()
{
LogicalOperator = FilterCompositionLogicalOperator.Or,
FilterDescriptors = fdCollection
});
}
await GridRef.SetStateAsync(gridState);
}
}
private async Task RemoveGridFilters()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
gridState.FilterDescriptors.Clear();
await GridRef.SetStateAsync(gridState);
}
}
protected override void OnInitialized()
{
for (int i = 1; i <= 30; i++)
{
GridData.Add(new Employee()
{
Id = i,
Name = $"Name {i}",
Team = $"Team {i % 5 + 1}",
HireDate = DateTime.Today.AddDays(-Random.Shared.Next(1, 3000)),
IsOnLeave = i % 4 == 0 ? true : false
});
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Team { get; set; } = string.Empty;
public DateTime HireDate { get; set; }
public bool IsOnLeave { get; set; }
}
}
@using Telerik.DataSource
<TelerikGrid @ref="@GridRef"
Data="@GridData"
Pageable="true"
Sortable="true">
<GridToolBarTemplate>
<GridSearchBox />
<TelerikButton Icon="@SvgIcon.Search"
ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
OnClick="@OnSearchButtonClick">Search Programmatically</TelerikButton>
<TelerikButton Icon="@SvgIcon.X"
OnClick="@OnClearButtonClick">Clear Search</TelerikButton>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@nameof(GridModel.Name)" />
<GridColumn Field="@nameof(GridModel.Description)" />
</GridColumns>
</TelerikGrid>
@code {
private TelerikGrid<GridModel>? GridRef { get; set; }
private List<GridModel> GridData { get; set; } = new();
private async Task OnSearchButtonClick()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
var searchString = $"{(char)Random.Shared.Next(97, 123)}{(char)Random.Shared.Next(97, 123)}";
var cfd = new CompositeFilterDescriptor();
cfd.LogicalOperator = FilterCompositionLogicalOperator.Or;
cfd.FilterDescriptors = new FilterDescriptorCollection();
// Add one FilterDesccriptor for each string column
cfd.FilterDescriptors.Add(new FilterDescriptor()
{
Member = nameof(GridModel.Name),
MemberType = typeof(string),
Operator = FilterOperator.Contains,
Value = searchString
});
cfd.FilterDescriptors.Add(new FilterDescriptor()
{
Member = nameof(GridModel.Description),
MemberType = typeof(string),
Operator = FilterOperator.Contains,
Value = searchString
});
gridState.SearchFilter = cfd;
await GridRef.SetStateAsync(gridState);
}
}
private async Task OnClearButtonClick()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
(gridState.SearchFilter as CompositeFilterDescriptor)?.FilterDescriptors.Clear();
await GridRef.SetStateAsync(gridState);
}
}
protected override void OnInitialized()
{
for (int i = 1; i <= 500; i++)
{
GridData.Add(new GridModel()
{
Id = i,
Name = $"{(char)Random.Shared.Next(65, 91)}{(char)Random.Shared.Next(65, 91)} " +
$"{(char)Random.Shared.Next(65, 91)}{(char)Random.Shared.Next(65, 91)} {i}",
Description = $"{(char)Random.Shared.Next(97, 123)}{(char)Random.Shared.Next(97, 123)} " +
$"{(char)Random.Shared.Next(97, 123)}{(char)Random.Shared.Next(97, 123)} {i}"
});
}
}
public class GridModel
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
}
}
@using Telerik.DataSource
<TelerikGrid @ref="@GridRef"
Data="@GridData"
Pageable="true"
Groupable="true">
<GridToolBarTemplate>
<TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
OnClick="@( () => SetGridGroups(false) )">Group Grid by Team</TelerikButton>
<TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
OnClick="@( () => SetGridGroups(true) )">Group Grid by Team and IsOnLeave</TelerikButton>
<span class="k-separator"></span>
<TelerikButton OnClick="@RemoveGridGroups">Remove All Groups</TelerikButton>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@(nameof(Employee.Name))" Title="Employee Name" />
<GridColumn Field="@(nameof(Employee.Team))" />
<GridColumn Field="@(nameof(Employee.HireDate))" Title="Hire Date" DisplayFormat="{0:d}" />
<GridColumn Field="@(nameof(Employee.IsOnLeave))" Title="Is On Leave" />
</GridColumns>
</TelerikGrid>
@code {
private TelerikGrid<Employee>? GridRef { get; set; }
private List<Employee> GridData { get; set; } = new();
private async Task SetGridGroups(bool shouldGroupBySecondColumn)
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
// Remove any existing Grid groups
// You can also modify or reorder existing GroupDescriptors.
gridState.GroupDescriptors.Clear();
gridState.GroupDescriptors.Add(new GroupDescriptor()
{
Member = nameof(Employee.Team),
MemberType = typeof(string),
// https://feedback.telerik.com/blazor/1544196-allow-sorting-the-grouped-column
SortDirection = ListSortDirection.Ascending
});
if (shouldGroupBySecondColumn)
{
gridState.GroupDescriptors.Add(new GroupDescriptor()
{
Member = nameof(Employee.IsOnLeave),
MemberType = typeof(bool),
// https://feedback.telerik.com/blazor/1544196-allow-sorting-the-grouped-column
SortDirection = ListSortDirection.Descending
});
}
await GridRef.SetStateAsync(gridState);
}
}
private async Task RemoveGridGroups()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
gridState.GroupDescriptors.Clear();
await GridRef.SetStateAsync(gridState);
}
}
protected override void OnInitialized()
{
for (int i = 1; i <= 30; i++)
{
GridData.Add(new Employee()
{
Id = i,
Name = $"Name {i}",
Team = $"Team {i % 5 + 1}",
HireDate = DateTime.Today.AddDays(-Random.Shared.Next(1, 3000)),
IsOnLeave = i % 4 == 0 ? true : false
});
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Team { get; set; } = string.Empty;
public DateTime HireDate { get; set; }
public bool IsOnLeave { get; set; }
}
}
<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; }
}
}
<TelerikGrid @ref="@GridRef"
Data="@GridData"
Pageable="true"
Reorderable="true">
<GridToolBarTemplate>
<TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
OnClick="@ReorderPriceAndQuantity">Reorder Price and Quantity</TelerikButton>
<TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
OnClick="@MakeIdColumnLast">Make Id Column Last</TelerikButton>
<span class="k-separator"></span>
<TelerikButton OnClick="@ResetColumnOrder">Reset Column Order</TelerikButton>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@(nameof(Product.Id))" Width="80px" />
<GridColumn Field="@(nameof(Product.Name))" Title="Product Name" />
<GridColumn Field="@(nameof(Product.Price))" DisplayFormat="{0:c2}" />
<GridColumn Field="@(nameof(Product.Quantity))" />
<GridColumn Field="@(nameof(Product.ReleaseDate))" DisplayFormat="{0:d}" />
</GridColumns>
</TelerikGrid>
@code {
private TelerikGrid<Product>? GridRef { get; set; }
private List<Product> GridData { get; set; } = new();
private async Task ReorderPriceAndQuantity()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
// Get column by its index in the Grid markup.
var priceColumnState = gridState.ColumnStates.ElementAt(2);
var priceColumnIndex = priceColumnState.Index;
// Get column by a parameter such as Field or Id.
var quantityColumnState = gridState.ColumnStates.First(x => x.Field == nameof(Product.Quantity));
var quantityColumnIndex = quantityColumnState.Index;
priceColumnState.Index = quantityColumnIndex;
quantityColumnState.Index = priceColumnIndex;
await GridRef.SetStateAsync(gridState);
}
}
private async Task MakeIdColumnLast()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
var idColumnState = gridState.ColumnStates.First(x => x.Field == nameof(Product.Id));
var oldIdIndex = idColumnState.Index;
idColumnState.Index = gridState.ColumnStates.Count - 1;
foreach (var columnState in gridState.ColumnStates)
{
// Decrement the indexes of all columns that were after Id.
if (columnState.Field != nameof(Product.Id) && columnState.Index > oldIdIndex)
{
--columnState.Index;
}
}
await GridRef.SetStateAsync(gridState);
}
}
private async Task ResetColumnOrder()
{
if (GridRef != null)
{
var gridState = GridRef.GetState();
gridState.ColumnStates = new List<GridColumnState>();
await GridRef.SetStateAsync(gridState);
}
}
protected override void OnInitialized()
{
for (int i = 1; i <= 5; i++)
{
GridData.Add(new Product()
{
Id = i,
Name = $"Product {i}",
Price = Random.Shared.Next(1, 100) * 1.23m,
Quantity = Random.Shared.Next(0, 100),
ReleaseDate = DateTime.Today.AddDays(-Random.Shared.Next(150, 3000))
});
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Quantity { get; set; }
public DateTime ReleaseDate { get; set; }
}
}
If you want to alter the filters for a specific column, do not use more than one
FilterDescriptor
inFilterRow
mode, and more than twoFilterDescriptors
inFilterMenu
mode. Otherwise additional descriptors will not show up in the UI. This means that you may need to replace or modify an existing descriptor, rather than add a new one.Inactive filter descriptors in
FilterMenu
mode are distinguished by theirnull
Value
.
Equals Comparison
State properties that pertain to data items (for example, edited item or selected items) are typed according to the Grid model. If you restore such data, make sure to implement appropriate comparison checks - by default the .Equals()
check for a class (object) is a reference check and the reference from the restored state is very unlikely to match the current reference in the Grid data. Thus, you may want to override the .Equals()
method of the Grid model class, so that it compares by ID, or otherwise re-populate the models in the state object with the new model references from the Grid data.
Examples
You can find multiple examples for using the Grid state in the following Knowledge Base articles:
- Save and load the Grid state from
localStorage
- Save the Grid state in a WebAssembly app
- Override a user action that changes the Grid state, for example, sort descending first
- Initiate programmatic editing or inserting of a Grid row
- Get current Grid column state (order index, width, and others)