diff --git a/_contentTemplates/treelist/state.md b/_contentTemplates/treelist/state.md new file mode 100644 index 0000000000..8f5aabdd55 --- /dev/null +++ b/_contentTemplates/treelist/state.md @@ -0,0 +1,670 @@ +#initial-state +>tip If you want to set an initial state to the TreeList, use a similar snippet, but in the [`OnStateInit event`]({%slug treelist-state%}#set-default-initial-state) +#end + + +#set-sort-from-code +@* This snippet shows how to set sorting state to the TreeList from your code *@ + +@using Telerik.DataSource; + +Set sorted state + + + + + + + + + + +@code { + public TelerikTreeList TreeListRef { get; set; } = new TelerikTreeList(); + + async Task SetTreeListSort() + { + var sortedState = new TreeListState() + { + SortDescriptors = new List() + { + new SortDescriptor(){ Member = nameof(Employee.Id), SortDirection = ListSortDirection.Descending } + } + }; + + await TreeListRef.SetState(sortedState); + } + + public List Data { get; set; } + + // sample model + + public class Employee + { + // hierarchical data collections + public List 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> GetTreeListData() + { + List data = new List(); + + 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(), // 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(), // 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); + } +} +#end + + + +#filter-row-from-code +@* This snippet shows how to set filtering state to the TreeList from your code + Applies to the FilterRow mode *@ + +@using Telerik.DataSource; + +Set filtered state + + + + + + + + + + +@code { + public TelerikTreeList TreeListRef { get; set; } = new TelerikTreeList(); + + async Task SetTreeListFilter() + { + var filteredState = new TreeListState() + { + FilterDescriptors = new List() + { + new FilterDescriptor() + { + Member = nameof(Employee.Id), + MemberType = typeof(int), + Operator = FilterOperator.IsGreaterThan, + Value = 5 + }, + new FilterDescriptor() + { + Member = nameof(Employee.Name), + MemberType = typeof(string), + Operator = FilterOperator.Contains, + Value = "second level" + } + } + }; + + await TreeListRef.SetState(filteredState); + } + + public List Data { get; set; } + + // sample model + + public class Employee + { + // hierarchical data collections + public List 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> GetTreeListData() + { + List data = new List(); + + 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(), // 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(), // 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); + } +} +#end + +#filter-menu-from-code +@* This snippet shows how to set filtering state to the TreeList from your code + Applies to the FilterMenu mode *@ + +@using Telerik.DataSource; + +Set filtered state + + + + + + + + + + +@code { + public TelerikTreeList TreeListRef { get; set; } = new TelerikTreeList(); + + async Task SetTreeListFilter() + { + var filteredState = new TreeListState() + { + FilterDescriptors = new List() + { + new FilterDescriptor() + { + Member = nameof(Employee.Id), + MemberType = typeof(int), + Operator = FilterOperator.IsGreaterThan, + Value = 5 + }, + new FilterDescriptor() + { + Member = nameof(Employee.Name), + MemberType = typeof(string), + Operator = FilterOperator.Contains, + Value = "second level" + } + } + }; + + await TreeListRef.SetState(filteredState); + } + + public List Data { get; set; } + + // sample model + + public class Employee + { + // hierarchical data collections + public List 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> GetTreeListData() + { + List data = new List(); + + 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(), // 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(), // 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); + } +} +#end + + +#expand-items-from-code +@*Expand a root level item on a button click*@ + +@using Telerik.DataSource; + +
+ + + + + + Expand the selected root level item + +
+ +
+ + + + + + + + + + +@code { + public TelerikTreeList TreeListRef { get; set; } = new TelerikTreeList(); + public int ExpandedItemIndex { get; set; } + + async Task OnStateInitHandler(TreeListStateEventArgs args) + { + var collapsedItemsState = new TreeListState() + { + //collapse all items in the TreeList upon initialization of the state + ExpandedItems = new List() + }; + + args.TreeListState = collapsedItemsState; + } + + async Task SetTreeListExpandedItems() + { + var expandedState = new TreeListState() + { + ExpandedItems = new List() + { + Data[ExpandedItemIndex] + } + }; + + await TreeListRef.SetState(expandedState); + } + + public List Data { get; set; } + + // sample model + + public class Employee + { + // hierarchical data collections + public List 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> GetTreeListData() + { + List data = new List(); + + 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(), // 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(), // 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); + } +} +#end + +#get-column-state-from-code + +@* Click the button, reorder some columns, maybe lock one of them, hide another, and click the button again to see how the state changes but the order of the columns in the state collection remains the same. This example also shows a workaround for getting the Field of the column that will be availale in a future release as part of the column state. *@ + +@using Telerik.DataSource; + +Get the state of the Columns + +@( new MarkupString(Logger) ) + + + + + + + + + + +@code { + public TelerikTreeList TreeListRef { get; set; } = new TelerikTreeList(); + + //part of workaround for getting the field too + public List ColumnFields => new List + { + nameof(Employee.Name), + nameof(Employee.Id), + nameof(Employee.EmailAddress), + nameof(Employee.HireDate) + }; + public string Logger { get; set; } = String.Empty; + + public async Task GetColumnsFromState() + { + // final part of the workaround for getting the field + var columnsState = TreeListRef.GetState().ColumnStates; + + int index = 0; + + foreach (var item in columnsState) + { + string columnField = ColumnFields[index]; + + bool isVisible = item.Visible != false; + + string log = $"

Column: {columnField} | Index in TreeList: {item.Index} | Index in state: {index} | Visible: {isVisible} | Locked: {item.Locked}

"; + Logger += log; + index++; + } + } + + public List Data { get; set; } + + // sample model + + public class Employee + { + // hierarchical data collections + public List 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> GetTreeListData() + { + List data = new List(); + + 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(), // 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(), // 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); + } +} +#end + diff --git a/components/treelist/filtering.md b/components/treelist/filtering.md index d69b4a875e..fdba6404ca 100644 --- a/components/treelist/filtering.md +++ b/components/treelist/filtering.md @@ -410,6 +410,21 @@ The `TreeListSearchBox` component offers the following settings to customize its } ```` +### Filtering From Code + +You can set the TreeList filters from your code through the grid [state]({%slug treelist-state%}). + +@[template](/_contentTemplates/treelist/state.md#initial-state) + +>caption Set sorting programmatically + +````FilterRow +@[template](/_contentTemplates/treelist/state.md#filter-row-from-code) +```` +````FilterMenu +@[template](/_contentTemplates/treelist/state.md#filter-menu-from-code) +```` + ## See Also diff --git a/components/treelist/sorting.md b/components/treelist/sorting.md index ca26fa677f..c1c55bdad8 100644 --- a/components/treelist/sorting.md +++ b/components/treelist/sorting.md @@ -98,6 +98,16 @@ Click a column header to sort by its data ![](images/basic-sorting.png) +You can sort the TreeList from your own code through its [state]({%slug treelist-state%}). + +@[template](/_contentTemplates/treelist/state.md#initial-state) + +>caption Set sorting programmatically + +````CSHTML +@[template](/_contentTemplates/treelist/state.md#set-sort-from-code) +```` + ## See Also diff --git a/components/treelist/state.md b/components/treelist/state.md index ba9a7be716..e939ff3381 100644 --- a/components/treelist/state.md +++ b/components/treelist/state.md @@ -4,9 +4,884 @@ page_title: TreeList - State description: Save, load, change the treelist for Blazor state - sorting, filtering and so on. slug: treelist-state tags: telerik,blazor,treelist,state,save,load,layout,set,change,management -published: false +published: true position: 50 --- # TreeList State +The TreeList lets you save, load and change its current state through code. The state management includes all the user-configurable elements of the TreeList - such as managing the expanded state of the items, sorting, filtering, paging, edited items and selection. + +You can see this feature in the [Live Demo: TreeList State](https://demos.telerik.com/blazor-ui/treelist/persist-state). + +This article contains the following sections: + +* [Basics](#basics) + * [Events](#events) + * [Methods](#methods) +* [Information in the TreeList State](#information-in-the-treelist-state) +* [Examples](#examples) + * [Save and Load TreeList State from Browser LocalStorage](#save-and-load-treelist-state-from-browser-localstorage) + * [Set TreeList Options Through State](#set-treelist-options-through-state) + * [Set Default (Initial) State](#set-default-initial-state) + * [Get The User Action That Changes The TreeList](#get-the-user-action-that-changes-the-treelist) + * [Initiate Editing or Inserting of an Item](#initiate-editing-or-inserting-of-an-item) + + +## Basics + +The TreeList state is a generic class whose type is determined by the type of the data model you use for the TreeList. It contains fields that correspond to the TreeList behaviors which you can use to save, load and modify the component state. + +Fields that pertain to model data (such as edited item, inserted item, selected items) are also 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 (model) is a reference check and the reference from the storage is unlikely to match the reference from the `Data` parameter. Thus, you may want to override the `.Equals` method of the model you use so it compares by an ID, for example, or otherwise (in the app logic) re-populate the models in the state object with the new model references from the component data source. + +The TreeList exposes two events and two methods to allow flexible operations over its state: + +* [Events](#events) + +* [Methods](#methods) + +### Events + +The `OnStateInit` and `OnStateChanged` events are raised by the TreeList so you can have an easy to use hook for loading and saving state, respectively. + +* `OnStateInit` fires when the TreeList is initializing and you can provide the state you load from your storage to the `TreeListState` field of its event arguments. + +* `OnStateChanged` fires when the user makes a change to the TreeList state (such as expanding or collapsing an item, filtering a column, editing, selecting and so on). The `TreeListState` field of the event arguments provides the current state so you can store it. The `PropertyName` field of the event arguments indicates what is the aspect that changed. + * We recommend that you use an `async void` handler for the `OnStateChanged` event in order to reduce re-rendering and to avoid blocking the UI update while waiting for the service to store the data. Doing so will let the UI thread continue without waiting for the storage service to complete. + +By combining these two events you can save the TreeList layout for your users automatically by only calling upon your storage service in the respective method. + +### Methods + +The `GetState` and `SetState` instance methods provide flexibility for your business logic. They let you get and set the current TreeList state on demand outside of the component events. + +* `GetState` returns the TreeList state so you can store it only on a certain condition - for example, you may want to save the TreeList layout only on a button click, and not on every user interaction with the component. You can also use it to get information about the current state of the filters, sorts and so on. + +* `SetState` takes an instance of a TreeList state so you can use your own code to alter the component layout and state. For example, you can have a button that puts the TreeList in a certain configuration that helps your users review data (like certain filters, sorts, expanded items, initiate item editing or inserting, etc.). + +If you want to make changes on the current TreeList state, first get it from the component through the `GetState` method, then apply the modifications on the object you got, and pass it to `SaveState`. + +If you want to put the TreeList in a certain configuration without preserving the old one, create a `new TreeListState()` and apply the settings there, then pass it to `SetState`. + +To reset the TreeList state, call `SetState(null)`. + +## Information in the TreeList State + +The following information is present in the TreeList state: + +* **Editing** - whether the user was inserting or editing an item (opens the same item for editing with the current data from the built-in editors of the TreeList - the data is updated in the `OnChange` event, not on every keystroke for performance reasons). The `OriginalEditItem` carries the original model without the user modifications so you can compare. + +* **Filtering** - filter descriptors (fields by which the Treelist is filtered, the operator and value). + +* **Paging** - page index + +* **Sorting** - sort descriptors (fields by which the TreeList is sorted, and the direction). + +* **Selection** - list of selected items. + +* **Columns** - Visible, Width, Index (order) of the column that the user sees, Locked (pinned). + + * The TreeList matches the columns from its markup sequentially (in the same order) with the columns list in the state object. So, when you restore/set the state, the TreeList must initialize with the same collection of columns that were used to save the state. + + The `Index` field in the column state object represents its place (order) that the user sees and can choose through the `Reordable` feature, not its place in the TreeList markup. You can find an example below. + + If you want to change the visibility of columns, we recommend you use their `Visible` parameter rather than conditional markup - this parameter will be present in the state and will not change the columns collection count which makes it easier to reconcile changes. + + +## Examples + +You can find the following examples in this section: + +* [Save and Load TreeList State from Browser LocalStorage](#save-and-load-treelist-state-from-browser-localstorage) +* [Set TreeList Options Through State](#set-treelist-options-through-state) +* [Set Default (Initial) State](#set-default-initial-state) +* [Get The User Action That Changes The TreeList](#get-the-user-action-that-changes-the-treelist) +* [Initiate Editing or Inserting of an Item](#initiate-editing-or-inserting-of-an-item) + +### Save and Load TreeList State from Browser LocalStorage + +The following example shows one way you can store the TreeList state - through a custom service that calls the browser's LocalStorage. You can use your own database here, or a file, or Microsoft's ProtectedBrowserStorage package, or any other storage you prefer. This is just an example you can use as base and modify to suit your project. + +>note If you use [Hierarchical data]({%slug treelist-data-binding-hierarchical-data%}) for the TreeList you need to serialize the current item only and not the entire collection of child items in order not to exceed the size of the LocalStorage. + +>caption Save, Load, Reset TreeList state on every state change. Uses a sample LocalStorage in the browser. + +````Component +@using Telerik.DataSource; + +@inject LocalStorage LocalStorage +@inject IJSRuntime JsInterop + +Reload the page to see the current TreeList state preserved +Reset the state +Set the state + + + + + + + + + + + + +@code { string UniqueStorageKey = "SampleTreeListStateStorageKey"; + + async Task OnStateInitHandler(TreeListStateEventArgs args) + { + args.TreeListState = await LocalStorage.GetItem>(UniqueStorageKey); + } + + async Task OnStateChangedHandler(TreeListStateEventArgs args) + { + var state = args.TreeListState; + state.ExpandedItems = null; + await LocalStorage.SetItem(UniqueStorageKey, state); + } + + async Task ResetState() + { + // clean up the storage + await LocalStorage.RemoveItem(UniqueStorageKey); + + await TreeListRef.SetState(null); // pass null to reset the state + } + + void ReloadPage() + { + JsInterop.InvokeVoidAsync("window.location.reload"); + } + + private void SetState() + { + TreeListState state = new TreeListState() + { + FilterDescriptors = new List() + { + new FilterDescriptor() { Member="StringProp", MemberType=typeof(string), Value = "2", Operator = FilterOperator.Contains } + }, + SortDescriptors = new List() + { + new SortDescriptor() { Member = "StringProp", SortDirection = ListSortDirection.Descending } + }, + Page = 2, + ColumnStates = new List() + { + new TreeListColumnState() + { + Index = 3, + Width = "150px" + }, + new TreeListColumnState() + { + Index = 1, + Width = "120px" + }, + new TreeListColumnState() + { + Index = 2, + Width = "60px" + }, + new TreeListColumnState() + { + Index = 4, + Width = "150px" + }, + new TreeListColumnState() + { + Index = 0, + Width = "120px" + } + } + }; + + TreeListRef?.SetState(state); + } + + TelerikTreeList TreeListRef { get; set; } + + public List Data { get; set; } + + protected override async Task OnInitializedAsync() + { + Data = await GetTreeListData(); + } + + // sample model + + public class Employee + { + // denote the parent-child relationship between items + public int Id { get; set; } + public int? ParentId { get; set; } + + // custom data fields for display + public string Name { get; set; } + public string EmailAddress { get; set; } + public DateTime HireDate { get; set; } + } + + // data generation + + async Task> GetTreeListData() + { + List data = new List(); + + for (int i = 1; i < 15; i++) + { + data.Add(new Employee + { + Id = i, + ParentId = null, // indicates a root-level item + Name = $"root: {i}", + EmailAddress = $"{i}@example.com", + HireDate = DateTime.Now.AddYears(-i) + }); ; + + for (int j = 1; j < 4; j++) + { + int currId = i * 100 + j; + data.Add(new Employee + { + Id = currId, + ParentId = i, + Name = $"first level child {j} of {i}", + EmailAddress = $"{currId}@example.com", + HireDate = DateTime.Now.AddDays(-currId) + }); + + for (int k = 1; k < 3; k++) + { + int nestedId = currId * 1000 + k; + data.Add(new Employee + { + Id = nestedId, + ParentId = currId, + Name = $"second level child {k} of {i} and {currId}", + EmailAddress = $"{nestedId}@example.com", + HireDate = DateTime.Now.AddMinutes(-nestedId) + }); ; + } + } + } + + return await Task.FromResult(data); + } +} +```` +````Service +using System.Threading.Tasks; +using Microsoft.JSInterop; +using System.Text.Json; + +public class LocalStorage +{ + protected IJSRuntime JSRuntimeInstance { get; set; } + + public LocalStorage(IJSRuntime jsRuntime) + { + JSRuntimeInstance = jsRuntime; + } + + public ValueTask SetItem(string key, object data) + { + return JSRuntimeInstance.InvokeVoidAsync( + "localStorage.setItem", + new object[] { + key, + JsonSerializer.Serialize(data) + }); + } + + public async Task GetItem(string key) + { + var data = await JSRuntimeInstance.InvokeAsync("localStorage.getItem", key); + if (!string.IsNullOrEmpty(data)) + { + return JsonSerializer.Deserialize(data); + } + + return default; + } + + public ValueTask RemoveItem(string key) + { + return JSRuntimeInstance.InvokeVoidAsync("localStorage.removeItem", key); + } +} +```` + +### Set TreeList Options Through State + +The TreeList state allows you to control the behavior of the TreeList programmatically - you can, for example, set sorts, filters and expand items. + +>tip The individual tabs below show how you can use the state to programmatically set the TreeList filtering, sorting and other features. + +@[template](/_contentTemplates/treelist/state.md#initial-state) + +````ExpandedItems +@[template](/_contentTemplates/treelist/state.md#expand-items-from-code) +```` +````Sorting +@[template](/_contentTemplates/treelist/state.md#set-sort-from-code) +```` +````FilterRow +@[template](/_contentTemplates/treelist/state.md#filter-row-from-code) +```` +````FilterMenu +@[template](/_contentTemplates/treelist/state.md#filter-menu-from-code) +```` + + +### Set Default (Initial) State + +If you want the TreeList to start with certain settings for your end users, you can pre-define them in the `OnStateInit event`. + +>tip The `ExpandedItems` sample in the [Set TreeList Options Through State](#set-treelist-options-through-state) section shows how to collapse all items in the OnStateInit event handler. + +>caption Choose a default state of the TreeList for your users + +````CSHTML +@using Telerik.DataSource; + + + + + + + + + + +@code { + async Task OnStateInitHandler(TreeListStateEventArgs args) + { + var initialState = new TreeListState() + { + FilterDescriptors = new List() + { + new FilterDescriptor() + { + Member = nameof(Employee.Name), + MemberType = typeof(string), + Operator = FilterOperator.Contains, + Value = "second level" + } + }, + SortDescriptors = new List() + { + new SortDescriptor() + { + Member = nameof(Employee.Id), + SortDirection = ListSortDirection.Descending + } + }, + Page = 2 + }; + + args.TreeListState = initialState; + } + + public List Data { get; set; } + + // sample model + + public class Employee + { + // hierarchical data collections + public List 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> GetTreeListData() + { + List data = new List(); + + 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(), // 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(), // 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); + } +} +```` + +### Get The User Action That Changes The TreeList + +Sometimes you may want to know what the user changed in the TreeList (e.g., when they filter, sort and so on). + +The example below shows how to achieve it by using the`OnStateChanged` event. + +>caption Know when the TreeList state changes and which parameter changed + +````CSHTML +@using Telerik.DataSource; + + + + + + + + + + +@Result + +@code { + TelerikTreeList TreeListRef { get; set; } = new TelerikTreeList(); + + public string Result { get; set; } + + async Task OnStateChangedHandler(TreeListStateEventArgs args) + { + string changedSetting = args.PropertyName; + + if(changedSetting == "SortDescriptors") + { + foreach (var item in args.TreeListState.SortDescriptors) + { + Result = $"The {item.Member} field was sorted"; + } + } + else if(changedSetting == "FilterDescriptors") + { + foreach (FilterDescriptor item in args.TreeListState.FilterDescriptors) + { + Result = $"The {item.Member} field was filtered"; + } + } + } + + public List Data { get; set; } + + // sample model + + public class Employee + { + // hierarchical data collections + public List 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> GetTreeListData() + { + List data = new List(); + + 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(), // 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(), // 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); + } +} +```` + +### Initiate Editing or Inserting of an Item + +The TreeList state lets you store the item that the user is currently working on - both an existing model that is being edited, and a new item the user is inserting. This happens automatically when you save the TreeList state. If you want to save on every keystroke instead of on `OnChange` - use a custom editor template and update the `EditItem` or `InsertedItem` of the state object as required, then save the state into your service. + +In addition to that, you can also use the `EditItem`, `OriginalEditItem` and `InsertItem` fields of the state object to put the TreeList in edit/insert mode through your own application code, instead of needing the user to initiate this through a [command button]({%slug treelist-columns-command%}). + +>caption Put and item in Edit mode or start Inserting a new item + +````CSHTML +@using Telerik.DataSource; + +Edit item 3 +Insert Item + + + + + Add + + + + Add Child + Edit + Delete + Update + Cancel + + + + + + + + + +@code { + TelerikTreeList TreeListRef { get; set; } = new TelerikTreeList(); + + async Task EnterEditMode() + { + var state = TreeListRef.GetState(); + + Employee employeeToEdit = Employee.GetClonedInstance(FindItemRecursive(Data, 3)); + state.OriginalEditItem = employeeToEdit; + await TreeListRef.SetState(state); + } + + async Task InsertItem() + { + var state = TreeListRef.GetState(); + state.InsertedItem = new Employee() { Name = "added from code" }; + await TreeListRef.SetState(state); + } + + public List Data { get; set; } + + // used in this example for data generation and retrieval for CUD operations on the current view-model data + public int LastId { get; set; } = 1; + + // Sample CUD operations for the local data + async Task UpdateItem(TreeListCommandEventArgs args) + { + var item = args.Item as Employee; + + var foundItem = FindItemRecursive(Data, item.Id); + if (foundItem != null) + { + foundItem.Name = item.Name; + foundItem.HireDate = item.HireDate; + foundItem.EmailAddress = item.EmailAddress; + } + } + + async Task CreateItem(TreeListCommandEventArgs args) + { + var argsItem = args.Item as Employee; + + argsItem.Id = LastId++; + + if (args.ParentItem != null) + { + var parent = (Employee)args.ParentItem; + + parent.HasChildren = true; + if (parent.DirectReports == null) + { + parent.DirectReports = new List(); + } + + parent.DirectReports.Insert(0, argsItem); + } + else + { + Data.Insert(0, argsItem); + } + } + + async Task DeleteItem(TreeListCommandEventArgs args) + { + var item = args.Item as Employee; + + RemoveChildRecursive(Data, item); + } + + // sample helper methods for handling the view-model data hierarchy + + private Employee FindItemRecursive(List items, int id) + { + foreach (var item in items) + { + if (item.Id.Equals(id)) + { + return item; + } + + if (item.DirectReports?.Count > 0) + { + var childItem = FindItemRecursive(item.DirectReports, id); + + if (childItem != null) + { + return childItem; + } + } + } + + return null; + } + + private void RemoveChildRecursive(List items, Employee item) + { + for (int i = 0; i < items.Count(); i++) + { + if (item.Equals(items[i])) + { + items.Remove(item); + + return; + } + else if (items[i].DirectReports?.Count > 0) + { + RemoveChildRecursive(items[i].DirectReports, item); + + if (items[i].DirectReports.Count == 0) + { + items[i].HasChildren = false; + } + } + } + } + + + // sample model + + public class Employee + { + public int Id { get; set; } + + public string Name { get; set; } + public string EmailAddress { get; set; } + public DateTime HireDate { get; set; } + + public List DirectReports { get; set; } + public bool HasChildren { get; set; } + + public override bool Equals(object obj) + { + if (obj is Employee) + { + return this.Id == (obj as Employee).Id; + } + return false; + } + + public Employee() + { + + } + + public Employee(Employee itmToClone) + { + this.Id = itmToClone.Id; + this.Name = itmToClone.Name; + } + + public static Employee GetClonedInstance(Employee itmToClone) + { + return new Employee(itmToClone); + } + } + + // data generation + + protected override async Task OnInitializedAsync() + { + Data = await GetTreeListData(); + } + + async Task> GetTreeListData() + { + List data = new List(); + + 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(), + HasChildren = true + }; + 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(), + HasChildren = true + }; + root.DirectReports.Add(firstLevelChild); + LastId++; + + for (int k = 1; k < 3; k++) + { + int nestedId = LastId; + 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++; + } + } + } + + data[0].Name += " (non-editable, see OnEdit)"; + + return await Task.FromResult(data); + } +} +```` + +### Get Current Columns Visibility, Order, Field + +The `ColumnStates` field of the state object provides you with information about the current columns in the TreeList. The `Index` field describes the position the user chose, and the `Visible` parameter indicates whether the column is hidden or not. By looping over that collection you can know what the user sees. You could, for example, sort by the index and filter by the visibility of the columns to approximate the view of the user. + +````CSHTML +@[template](/_contentTemplates/treelist/state.md#get-column-state-from-code) +```` + + +## See Also + + * [Live Demo: TreeList State](https://demos.telerik.com/blazor-ui/treelist/persist-state) +