TreeList State
The TreeList lets you read, save, load, and change its state through code. The state includes the TreeList 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
TreeListState
object. - How to set initial TreeList configuration programmatically in
OnStateInit
. - How to detect user changes in the TreeList state with
OnStateChanged
. - How to use TreeList methods to get and set the TreeList state.
- Why you may need to override the
Equals
method of the TreeList model class.
Information in the TreeList State
The TreeList state is a generic class TreeListState<TItem>
. The type depends on the type of the TreeList model. The TreeListState<TItem>
object exposes the following properties:
Property | Type | Description |
---|---|---|
ColumnStates | ICollection<TreeListColumnState> | 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 TreeList 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. |
FilterDescriptors | ICollection<IFilterDescriptor> | A collection of CompositeFilterDescriptor , except the ones that relate to the TreeListSearchBox . |
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 TreeListState 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. |
ParentItem | TItem? * | The parent item of the current InsertedItem in Inline or Popup edit mode. The value is null is the new item is being added at root level. |
SearchFilter | IFilterDescriptor | The CompositeFilterDescriptor that holds the filter descriptors for the TreeListSearchBox . |
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 TreeList model type.
Events
The TreeList features two events, which are related to its state.
OnStateInit
The OnStateInit
event fires when the TreeList 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 TreeListStateEventArgs<TItem>
and has a TreeListState
property. See Information in the TreeList State for details.
If you change the column order or number of columns in the TreeList 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 TreeList.
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 TreeList 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 TreeList OnStateInit
@using System.ComponentModel.DataAnnotations
@using Telerik.DataSource
@using Telerik.DataSource.Extensions
<TelerikTreeList Data="@TreeListData"
IdField="@nameof(Employee.Id)"
ParentIdField="@nameof(Employee.ParentId)"
FilterMode="@TreeListFilterMode.FilterMenu"
OnStateInit="@( (TreeListStateEventArgs<Employee> args) => OnTreeListStateInit(args) )"
Pageable="true"
Sortable="true"
Height="400px">
<TreeListColumns>
<TreeListColumn Field="@nameof(Employee.Name)" Expandable="true" />
<TreeListColumn Field="@nameof(Employee.Salary)" DisplayFormat="{0:C2}" Width="130px" />
<TreeListColumn Field="@nameof(Employee.HireDate)" DisplayFormat="{0:d}" Width="140px" />
<TreeListColumn Field="@nameof(Employee.IsDriver)" Width="120px" />
</TreeListColumns>
</TelerikTreeList>
@code {
private IEnumerable<Employee>? TreeListData { get; set; }
private EmployeeService TreeListEmployeeService { get; set; } = new();
private void OnTreeListStateInit(TreeListStateEventArgs<Employee> args)
{
// Sort sibling items by Salary
args.TreeListState.SortDescriptors.Add(new SortDescriptor()
{
Member = nameof(Employee.Salary),
SortDirection = ListSortDirection.Descending
});
// Filter by IsDriver
var driverColumnFilter = new CompositeFilterDescriptor()
{
FilterDescriptors = new FilterDescriptorCollection() {
new FilterDescriptor()
{
Member = nameof(Employee.IsDriver),
MemberType = typeof(bool),
Operator = FilterOperator.IsEqualTo,
Value = true
}
}
};
args.TreeListState.FilterDescriptors.Add(driverColumnFilter);
}
protected override async Task OnInitializedAsync()
{
TreeListData = await TreeListEmployeeService.Read();
}
public class Employee
{
public int Id { get; set; }
public int? ParentId { get; set; }
public bool HasChildren { get; set; }
[Required]
public string Name { get; set; } = string.Empty;
public string Notes { get; set; } = string.Empty;
[Required]
public decimal? Salary { get; set; }
[Required]
public DateTime? HireDate { get; set; }
public bool IsDriver { get; set; }
public override bool Equals(object? obj)
{
return obj is Employee && ((Employee)obj).Id == Id;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
#region Data Service
public class EmployeeService
{
private List<Employee> Items { get; set; } = new();
private readonly int TreeLevelCount;
private readonly int RootItemCount;
private readonly int ChildItemCount;
private int LastId { get; set; }
private Random Rnd { get; set; } = Random.Shared;
public async Task<int> Create(Employee createdEmployee, Employee? parentEmployee)
{
await SimulateAsyncOperation();
createdEmployee.Id = ++LastId;
createdEmployee.ParentId = parentEmployee?.Id;
Items.Insert(0, createdEmployee);
if (parentEmployee != null)
{
parentEmployee.HasChildren = true;
}
return LastId;
}
public async Task<bool> Delete(Employee deletedEmployee)
{
await SimulateAsyncOperation();
if (Items.Contains(deletedEmployee))
{
DeleteChildren(deletedEmployee.Id);
Items.Remove(deletedEmployee);
if (deletedEmployee.ParentId.HasValue && !Items.Any(x => x.ParentId == deletedEmployee.ParentId.Value))
{
Items.First(x => x.Id == deletedEmployee.ParentId.Value).HasChildren = false;
}
return true;
}
return false;
}
public async Task<List<Employee>> Read()
{
await SimulateAsyncOperation();
return Items;
}
public async Task<DataSourceResult> Read(DataSourceRequest request)
{
return await Items.ToDataSourceResultAsync(request);
}
public async Task<bool> Update(Employee updatedEmployee)
{
await SimulateAsyncOperation();
int originalItemIndex = Items.FindIndex(x => x.Id == updatedEmployee.Id);
if (originalItemIndex != -1)
{
Items[originalItemIndex] = updatedEmployee;
return true;
}
return false;
}
private async Task SimulateAsyncOperation()
{
await Task.Delay(100);
}
private void DeleteChildren(int parentId)
{
List<Employee> children = Items.Where(x => x.ParentId == parentId).ToList();
foreach (Employee child in children)
{
DeleteChildren(child.Id);
}
Items.RemoveAll(x => x.ParentId == parentId);
}
private void PopulateChildren(List<Employee> items, int? parentId, int level)
{
int itemCount = level == 1 ? RootItemCount : ChildItemCount;
for (int i = 1; i <= itemCount; i++)
{
int itemId = ++LastId;
items.Add(new Employee()
{
Id = itemId,
ParentId = parentId,
HasChildren = level < TreeLevelCount,
Name = $"Employee Name {itemId}", // {level}-{i}
Notes = $"Multi-line\nnotes {itemId}",
Salary = Rnd.Next(1_000, 10_000) * 1.23m,
HireDate = DateTime.Today.AddDays(-Rnd.Next(365, 3650)),
IsDriver = itemId % 2 == 0
});
if (level < TreeLevelCount)
{
PopulateChildren(items, itemId, level + 1);
}
}
}
public EmployeeService(int treeLevelCount = 3, int rootItemCount = 3, int childItemCount = 2)
{
TreeLevelCount = treeLevelCount;
RootItemCount = rootItemCount;
ChildItemCount = childItemCount;
List<Employee> items = new();
PopulateChildren(items, null, 1);
Items = items;
}
}
#endregion Data Service
}
OnStateChanged
OnStateChanged
fires when the user performs an action that changes the value of a property in the TreeList state. The event argument is of type TreeListStateEventArgs<TItem>
and exposes these properties:
Property | Type | Description |
---|---|---|
PropertyName | string | Information about what changed in the TreeList state. The possible values match the property names of the TreeListState object. The possible values for the PropertyName are SortDescriptors , FilterDescriptors , SearchFilter , Page , Skip , ColumnStates , ExpandedItems , InsertedItem , OriginalEditItem , EditItem . |
TreeListState | TreeListState<TItem> | The current (up-to-date) TreeList 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 and searching. For example, filtering resets the current page to 1. First, the event will fire withPropertyName
equal to"FilterDescriptors"
, and thenPropertyName
will be"Page"
. However, theTreeListState
property of the event argument will provide correct information about the overall TreeList 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 TreeList state somewhere.
To observe the changes in the TreeList 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 and sorting criteria.
Using TreeList OnStateChanged
@using System.ComponentModel.DataAnnotations
@using System.Text.Json
@using Telerik.DataSource
@using Telerik.DataSource.Extensions
<div id="demo-container">
<TelerikTreeList Data="@TreeListData"
IdField="@nameof(Employee.Id)"
ParentIdField="@nameof(Employee.ParentId)"
ConfirmDelete="true"
EditMode="@TreeListEditMode.Inline"
FilterMode="@TreeListFilterMode.FilterMenu"
OnCreate="@OnTreeListCreate"
OnUpdate="@OnTreeListUpdate"
OnStateChanged="@( (TreeListStateEventArgs<Employee> args) => OnTreeListStateChanged(args) )"
Pageable="true"
@bind-PageSize="@TreeListPageSize"
Reorderable="true"
Resizable="true"
@bind-SelectedItems="@TreeListSelectedItems"
SelectionMode="@TreeListSelectionMode.Multiple"
ShowColumnMenu="true"
Sortable="true"
Height="400px">
<TreeListSettings>
<TreeListPagerSettings PageSizes="@( new List<int?>() { null, 5, 10 } )" />
</TreeListSettings>
<TreeListToolBarTemplate>
<TreeListCommandButton Command="Add">Add Item</TreeListCommandButton>
<TreeListSearchBox />
</TreeListToolBarTemplate>
<TreeListColumns>
<TreeListCheckboxColumn SelectAll="true" />
<TreeListColumn Field="@nameof(Employee.Name)" Expandable="true" />
<TreeListColumn Field="@nameof(Employee.Salary)" DisplayFormat="{0:C2}" Width="130px" />
<TreeListColumn Field="@nameof(Employee.HireDate)" DisplayFormat="{0:d}" Width="140px" />
<TreeListColumn Field="@nameof(Employee.IsDriver)" Width="120px" />
<TreeListCommandColumn Width="160px">
<TreeListCommandButton Command="Add">Add</TreeListCommandButton>
<TreeListCommandButton Command="Edit">Edit</TreeListCommandButton>
<TreeListCommandButton Command="Save" ShowInEdit="true">Save</TreeListCommandButton>
<TreeListCommandButton Command="Cancel" ShowInEdit="true">Cancel</TreeListCommandButton>
</TreeListCommandColumn>
</TreeListColumns>
</TelerikTreeList>
<div id="console">
<code class="@TreeListStateChangedPropertyClass">OnStateChanged</code> count:
@OnStateChangedCount
<TelerikButton OnClick="@( () => OnStateChangedCount = 0 )">Reset</TelerikButton>
<br /><br />
Last <code>OnStateChanged</code> event:
<br />
<strong class="@TreeListStateChangedPropertyClass">PropertyName</strong>:
<code>"@TreeListStateChangedProperty"</code>
<br />
<strong>TreeListState</strong>:
<pre>
@( new MarkupString(TreeListStateString) )
</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-treelist {
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 IEnumerable<Employee>? TreeListData { get; set; }
private int TreeListPageSize { get; set; } = 5;
private IEnumerable<Employee> TreeListSelectedItems { get; set; } = new List<Employee>();
private EmployeeService TreeListEmployeeService { get; set; } = new();
private int OnStateChangedCount { get; set; }
private string TreeListStateChangedProperty { get; set; } = string.Empty;
private string TreeListStateChangedPropertyClass { get; set; } = string.Empty;
private string TreeListStateString { get; set; } = string.Empty;
private bool _doubleStateChanged { get; set; }
private List<string> _operationsWithMultipleStateChanged = new List<string>() {
"FilterDescriptors",
"SearchFilter"
};
private async Task OnTreeListStateChanged(TreeListStateEventArgs<Employee> args)
{
if (_doubleStateChanged)
{
_doubleStateChanged = false;
await Task.Delay(1500);
TreeListStateChangedPropertyClass = string.Empty;
}
++OnStateChangedCount;
TreeListStateChangedProperty = args.PropertyName;
// serialize the TreeListState and highlight the changed property
TreeListStateString = JsonSerializer.Serialize(args.TreeListState, new JsonSerializerOptions() { WriteIndented = true })
.Replace($"\"{TreeListStateChangedProperty}\"", $"\"<strong class='latest-changed-property'>{TreeListStateChangedProperty}</strong>\"");
// highlight first TreeListStateChangedProperty during filtering, grouping and search
if (_operationsWithMultipleStateChanged.Contains(TreeListStateChangedProperty))
{
_doubleStateChanged = true;
TreeListStateChangedPropertyClass = "first-of-two";
}
}
private async Task OnTreeListCreate(TreeListCommandEventArgs args)
{
var createdItem = (Employee)args.Item;
var parentItem = (Employee?)args.ParentItem;
await TreeListEmployeeService.Create(createdItem, parentItem);
TreeListData = await TreeListEmployeeService.Read();
}
private async Task OnTreeListUpdate(TreeListCommandEventArgs args)
{
var updatedItem = (Employee)args.Item;
await TreeListEmployeeService.Update(updatedItem);
TreeListData = await TreeListEmployeeService.Read();
}
protected override async Task OnInitializedAsync()
{
TreeListData = await TreeListEmployeeService.Read();
}
public class Employee
{
public int Id { get; set; }
public int? ParentId { get; set; }
public bool HasChildren { get; set; }
[Required]
public string Name { get; set; } = string.Empty;
public string Notes { get; set; } = string.Empty;
[Required]
public decimal? Salary { get; set; }
[Required]
public DateTime? HireDate { get; set; }
public bool IsDriver { get; set; }
public override bool Equals(object? obj)
{
return obj is Employee && ((Employee)obj).Id == Id;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
#region Data Service
public class EmployeeService
{
private List<Employee> Items { get; set; } = new();
private readonly int TreeLevelCount;
private readonly int RootItemCount;
private readonly int ChildItemCount;
private int LastId { get; set; }
private Random Rnd { get; set; } = Random.Shared;
public async Task<int> Create(Employee createdEmployee, Employee? parentEmployee)
{
await SimulateAsyncOperation();
createdEmployee.Id = ++LastId;
createdEmployee.ParentId = parentEmployee?.Id;
Items.Insert(0, createdEmployee);
if (parentEmployee != null)
{
parentEmployee.HasChildren = true;
}
return LastId;
}
public async Task<bool> Delete(Employee deletedEmployee)
{
await SimulateAsyncOperation();
if (Items.Contains(deletedEmployee))
{
DeleteChildren(deletedEmployee.Id);
Items.Remove(deletedEmployee);
if (deletedEmployee.ParentId.HasValue && !Items.Any(x => x.ParentId == deletedEmployee.ParentId.Value))
{
Items.First(x => x.Id == deletedEmployee.ParentId.Value).HasChildren = false;
}
return true;
}
return false;
}
public async Task<List<Employee>> Read()
{
await SimulateAsyncOperation();
return Items;
}
public async Task<DataSourceResult> Read(DataSourceRequest request)
{
return await Items.ToDataSourceResultAsync(request);
}
public async Task<bool> Update(Employee updatedEmployee)
{
await SimulateAsyncOperation();
int originalItemIndex = Items.FindIndex(x => x.Id == updatedEmployee.Id);
if (originalItemIndex != -1)
{
Items[originalItemIndex] = updatedEmployee;
return true;
}
return false;
}
private async Task SimulateAsyncOperation()
{
await Task.Delay(100);
}
private void DeleteChildren(int parentId)
{
List<Employee> children = Items.Where(x => x.ParentId == parentId).ToList();
foreach (Employee child in children)
{
DeleteChildren(child.Id);
}
Items.RemoveAll(x => x.ParentId == parentId);
}
private void PopulateChildren(List<Employee> items, int? parentId, int level)
{
int itemCount = level == 1 ? RootItemCount : ChildItemCount;
for (int i = 1; i <= itemCount; i++)
{
int itemId = ++LastId;
items.Add(new Employee()
{
Id = itemId,
ParentId = parentId,
HasChildren = level < TreeLevelCount,
Name = $"Employee Name {itemId}", // {level}-{i}
Notes = $"Multi-line\nnotes {itemId}",
Salary = Rnd.Next(1_000, 10_000) * 1.23m,
HireDate = DateTime.Today.AddDays(-Rnd.Next(365, 3650)),
IsDriver = itemId % 2 == 0
});
if (level < TreeLevelCount)
{
PopulateChildren(items, itemId, level + 1);
}
}
}
public EmployeeService(int treeLevelCount = 3, int rootItemCount = 3, int childItemCount = 2)
{
TreeLevelCount = treeLevelCount;
RootItemCount = rootItemCount;
ChildItemCount = childItemCount;
List<Employee> items = new();
PopulateChildren(items, null, 1);
Items = items;
}
}
#endregion Data Service
}
Methods
The GetState
and SetStateAsync
methods of the TreeList instance let you get and set the current TreeList state on demand at any time after OnStateInit
.
-
GetState
returns the current TreeList 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 TreeList column properties like order index, width, and others). -
SetStateAsync
receives an instance of aTreeListState<TItem>
object and applies it to the TreeList. For example, you can have a button that puts the TreeList in a certain configuration programmatically, for example sort or filter the data, enter or exit edit mode, expand or collapse rows, etc.
If you want to make changes to the current TreeList state:
- First, get the current state with the
GetState
method. - Apply the desired modifications to the obtained
TreeListState
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 theTreeListState
property of the event argument.Avoid calling
SetStateAsync
in the TreeList CRUD methods (such asOnUpdate
,OnEdit
,OnCreate
,OnCancel
). Doing so may lead to unexpected results because the TreeList has more logic to execute after these events.
To reset the TreeList state to its initial markup configuration, call
SetStateAsync(null)
.To reset the TreeList state to a completely new configuration, create a
new TreeListState<T>()
and apply the settings there. Then pass the state object toSetStateAsync()
.
SetStateAsync Examples
The tabs below show how to set the TreeList state and control filtering, sorting and other TreeList features.
If you want to set an initial state to the TreeList, use a similar snippet, but in the
OnStateInit event
@* This snippet shows how to set sorting state to the TreeList from your code *@
@using Telerik.DataSource;
<TelerikButton OnClick="@SetTreeListSort">Set sorted state</TelerikButton>
<TelerikTreeList Data="@Data"
ItemsField="@(nameof(Employee.DirectReports))"
Reorderable="true"
Resizable="true"
Sortable="true"
Pageable="true"
Width="850px"
@ref="TreeListRef">
<TreeListColumns>
<TreeListColumn Field="Name" Expandable="true" Width="320px" />
<TreeListColumn Field="Id" Editable="false" Width="120px" />
<TreeListColumn Field="EmailAddress" Width="220px" />
<TreeListColumn Field="HireDate" Width="220px" />
</TreeListColumns>
</TelerikTreeList>
@code {
public TelerikTreeList<Employee> TreeListRef { get; set; } = new TelerikTreeList<Employee>();
async Task SetTreeListSort()
{
var sortedState = new TreeListState<Employee>()
{
SortDescriptors = new List<SortDescriptor>()
{
new SortDescriptor(){ Member = nameof(Employee.Id), SortDirection = ListSortDirection.Descending }
}
};
await TreeListRef.SetStateAsync(sortedState);
}
public List<Employee> Data { get; set; }
// sample model
public class Employee
{
// hierarchical data collections
public List<Employee> DirectReports { get; set; }
// data fields for display
public int Id { get; set; }
public string Name { get; set; }
public string EmailAddress { get; set; }
public DateTime HireDate { get; set; }
}
// data generation
// used in this example for data generation and retrieval for CUD operations on the current view-model data
public int LastId { get; set; } = 1;
protected override async Task OnInitializedAsync()
{
Data = await GetTreeListData();
}
async Task<List<Employee>> GetTreeListData()
{
List<Employee> data = new List<Employee>();
for (int i = 1; i < 15; i++)
{
Employee root = new Employee
{
Id = LastId,
Name = $"root: {i}",
EmailAddress = $"{i}@example.com",
HireDate = DateTime.Now.AddYears(-i),
DirectReports = new List<Employee>(), // prepare a collection for the child items, will be populated later in the code
};
data.Add(root);
LastId++;
for (int j = 1; j < 4; j++)
{
int currId = LastId;
Employee firstLevelChild = new Employee
{
Id = currId,
Name = $"first level child {j} of {i}",
EmailAddress = $"{currId}@example.com",
HireDate = DateTime.Now.AddDays(-currId),
DirectReports = new List<Employee>(), // collection for child nodes
};
root.DirectReports.Add(firstLevelChild); // populate the parent's collection
LastId++;
for (int k = 1; k < 3; k++)
{
int nestedId = LastId;
// populate the parent's collection
firstLevelChild.DirectReports.Add(new Employee
{
Id = LastId,
Name = $"second level child {k} of {j} and {i}",
EmailAddress = $"{nestedId}@example.com",
HireDate = DateTime.Now.AddMinutes(-nestedId)
}); ;
LastId++;
}
}
}
return await Task.FromResult(data);
}
}
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 TreeList 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 TreeList data. Thus, you may want to override the .Equals()
method of the TreeList 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 TreeList data.
Examples
You can find multiple examples for using the TreeList state in the following Knowledge Base articles:
- Save and load the TreeList state from
localStorage
- Save the TreeList state in a WebAssembly app
- Override a user action that changes the TreeList state, for example, sort descending first
- Initiate programmatic editing or inserting of a TreeList row
- Get current TreeList column state (order index, width, and others)