TreeList Popup Editing
TreeList popup editing allows the app to render a larger form with customizable dimensions and layout. The popup edit mode is also more suitable for mobile devices with small screens. The popup edit form may contain editable fields from hidden columns in the TreeList table.
Make sure to read the TreeList CRUD Operations article first.
Basics
To use popup TreeList editing, set the TreeList EditMode
parameter to TreeListEditMode.Popup
. During popup editing, only one table row is in edit mode. The user can:
- Press Tab or Shift + Tab to focus the next or previous input component.
- Click the Save command button to confirm the current row changes and exit edit mode.
- Click the Cancel command button or press ESC to cancel the current row changes and exit edit mode.
The popup edit mode can display a Telerik Validation Summary component if the TreeList model is configured for validation.
Commands
Popup add, edit, and delete operations use the following command buttons:
- Add
- Delete
- Edit
Without using the above command buttons, the application can:
- Manage add or edit mode programmatically through the TreeList state.
- Modify data items directly in the TreeList data source. Rebind the TreeList afterwards.
Popup edit mode does not use Save and Cancel command buttons in the TreeList command column. The TreeList renders them automatically in the popup, unless you define a Buttons Template or a Form Template.
In popup edit mode, the TreeList commands execute row by row and the corresponding TreeList events also fire row by row.
Customization
The TreeList exposes options to customize the edit popup and its form. Define the desired configuration in the TreeListPopupEditSettings
and TreeListPopupEditFormSettings
tags under the TreeListSettings
tag.
Edit Hidden Columns
Starting with version 7.0, the TreeList allows users to edit hidden columns by default. To disable editing of a hidden column, set Editable="false"
to the respective <TreeListColumn>
tag.
Popup Dimensions and Styles
The TreeListPopupEditSettings
nested tag exposes the following parameters to allow popup customization:
Parameter | Type | Description |
---|---|---|
Class | string | The CSS class of the edit popup |
Title | string | The title of the edit popup |
ThemeColor | string | The color scheme of the window. Use the available members of the static class ThemeConstants.Window.ThemeColor . |
Width | string | The Width of the edit popup |
MaxWidth | string | The maximum width of the window |
MinWidth | string | The minimum width of the window |
Height | string | The height of edit popup |
MaxHeight | string | The maximum height of the window |
MinHeight | string | The minimum height of the window |
The min/max options for the width and height apply to the initial rendering of the window and not browser resizing.
For example, here is how to set the TreeList popup edit form's title, so that it matches a property value of the edited data item.
Form Layout
The TreeListPopupEditFormSettings
nested tag exposes the following parameters to allow edit form customization:
Parameter | Type and Default Value | Description |
---|---|---|
ButtonsLayout | FormButtonsLayout ( End ) | The horizontal alignment of the buttons. Takes a member of the FormButtonsLayout enum: - Start - End - Center - Stretch |
Columns | int | The number of the form columns |
ColumnSpacing | int | The column spacing |
Orientation | FormOrientation ( Vertical ) | The orientation of the form. Takes a member of the FormOrientation enum: - Horizontal - Vertical |
These settings are not applicable if you are using a
<FormTemplate>
with a custom Form component. See more details in Form Template - Specifics.
Form Template
In the TreeListPopupEditFormSettings
, you can declare a FormTemplate
. This template enables you to fully customize the appearance and content of the create/edit Popup window in the TreeList. For more information and examples on customizing the TreeList Popup window, refer to the Popup Form Template article.
Buttons Template
You can specify a ButtonsTemplate
in the TreeListPopupEditFormSettings
to customize how the buttons look in the create/edit Popup window of the TreeList. To learn more and see an example of customizing the TreeList Popup buttons, refer to the Popup Buttons Template article.
Example
The example below shows how to:
- Implement popup TreeList CRUD operations with the minimal required number of events.
- Bind an editable TreeList to hierarchical data. Check the inline editing example for an implementation with flat data.
- Use the
OnCreate
,OnDelete
andOnUpdate
events to make changes to the TreeList data source. - Query the data service and reload the TreeList
Data
when the create, delete, or update operation is complete. - Use
DataAnnotations
validation for some model class properties. - Define the
Id
column as non-editable. - Customize the
Notes
column editor without using anEditorTemplate
. - Confirm Delete commands with the built-in TreeList Dialog. You can also intercept item deletion with a separate Dialog or a custom popup.
- Override the
Equals()
method of the TreeList model class to prevent collapsing of updated items. - Toggle the
HasChildren
property value of parent items when they lose all their children or gain their first child item. - Delete all children of a deleted parent item.
- Edit the
Notes
column that is not visible in the TreeList. - Customize the popup edit form dimensions and layout.
TreeList popup editing
@using System.ComponentModel.DataAnnotations
@using Telerik.DataSource
@using Telerik.DataSource.Extensions
<TelerikTreeList Data="@TreeListData"
IdField="@nameof(Employee.Id)"
ItemsField="@nameof(Employee.Items)"
ConfirmDelete="true"
EditMode="@TreeListEditMode.Popup"
OnCreate="@OnTreeListCreate"
OnDelete="@OnTreeListDelete"
OnUpdate="@OnTreeListUpdate"
Height="400px">
<TreeListSettings>
<TreeListPopupEditSettings Width="600px" MaxWidth="90vw" Height="400px" MaxHeight="90vh" />
<TreeListPopupEditFormSettings Columns="2" ColumnSpacing="2em" ButtonsLayout="@FormButtonsLayout.Stretch" />
</TreeListSettings>
<TreeListToolBarTemplate>
<TreeListCommandButton Command="Add">Add Item</TreeListCommandButton>
</TreeListToolBarTemplate>
<TreeListColumns>
<TreeListColumn Field="@nameof(Employee.Id)" Editable="false" Width="60px" />
<TreeListColumn Field="@nameof(Employee.Name)" Expandable="true" />
<TreeListColumn Field="@nameof(Employee.Notes)" EditorType="@TreeListEditorType.TextArea" Visible="false" Width="120px">
<Template>
@{ var dataItem = (Employee)context; }
<div style="white-space:pre">@dataItem.Notes</div>
</Template>
</TreeListColumn>
<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="80px" />
<TreeListCommandColumn Width="200px">
<TreeListCommandButton Command="Add">Add</TreeListCommandButton>
<TreeListCommandButton Command="Edit">Edit</TreeListCommandButton>
<TreeListCommandButton Command="Delete">Delete</TreeListCommandButton>
</TreeListCommandColumn>
</TreeListColumns>
</TelerikTreeList>
@code {
private IEnumerable<Employee>? TreeListData { get; set; }
private EmployeeService TreeListEmployeeService { get; set; } = new();
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 OnTreeListDelete(TreeListCommandEventArgs args)
{
var deletedItem = (Employee)args.Item;
await TreeListEmployeeService.Delete(deletedItem);
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 bool HasChildren { get; set; }
public List<Employee>? Items { 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 readonly Random Rnd = Random.Shared;
public async Task<int> Create(Employee employee, Employee? parentEmployee)
{
await SimulateAsyncOperation();
employee.Id = ++LastId;
if (parentEmployee != null)
{
parentEmployee.HasChildren = true;
parentEmployee.Items = parentEmployee.Items ?? new List<Employee>();
parentEmployee.Items.Insert(0, employee);
}
else
{
Items.Insert(0, employee);
}
return LastId;
}
public async Task<bool> Delete(Employee employee)
{
await SimulateAsyncOperation();
FindItem(Items, employee);
if (FoundChildItem != null)
{
if (FoundParentItem != null)
{
FoundParentItem?.Items?.Remove(FoundChildItem);
if (FoundParentItem?.Items?.Count == 0)
{
FoundParentItem.Items = null;
FoundParentItem.HasChildren = false;
}
}
else
{
Items.Remove(FoundChildItem);
}
ResetFoundItems();
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 employee)
{
await SimulateAsyncOperation();
FindItem(Items, employee);
if (FoundChildItem != null)
{
if (FoundParentItem != null)
{
FoundParentItem.Items![FoundChildItemIndex] = employee;
}
else
{
Items[FoundChildItemIndex] = employee;
}
ResetFoundItems();
return true;
}
return false;
}
private async Task SimulateAsyncOperation()
{
await Task.Delay(100);
}
private Employee? FoundParentItem { get; set; }
private int FoundChildItemIndex { get; set; }
private Employee? FoundChildItem { get; set; }
private void ResetFoundItems()
{
FoundParentItem = null;
FoundChildItemIndex = default;
FoundChildItem = null;
}
private void FindItem(List<Employee> items, Employee item)
{
Employee? parentCandidate;
for (int i = 0; i < items.Count; i++)
{
if (items[i].Id == item.Id)
{
FoundChildItemIndex = i;
FoundChildItem = item;
return;
}
if (items[i].Items?.Count > 0)
{
parentCandidate = items[i];
FindItem(items[i].Items!, item);
if (FoundChildItem != null)
{
if (parentCandidate.Items!.Contains(FoundChildItem))
{
FoundParentItem = parentCandidate;
}
return;
}
}
}
}
private void PopulateItems(List<Employee> items, int level)
{
for (int i = 1; i <= (level == 1 ? RootItemCount : ChildItemCount); i++)
{
var itemId = ++LastId;
var newItem = new Employee()
{
Id = itemId,
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
};
items.Add(newItem);
}
if (level < TreeLevelCount)
{
PopulateChildren(items, level + 1);
}
}
private void PopulateChildren(List<Employee> items, int level)
{
foreach (var item in items)
{
item.Items = new List<Employee>();
PopulateItems(item.Items, level);
}
}
public EmployeeService(int treeLevelCount = 3, int rootItemCount = 3, int childItemCount = 2)
{
TreeLevelCount = treeLevelCount;
RootItemCount = rootItemCount;
ChildItemCount = childItemCount;
List<Employee> items = new();
PopulateItems(items, 1);
Items = items;
}
}
#endregion Data Service
}