New to Telerik UI for BlazorStart a free 30-day trial

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:

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.

The TreeListPopupEditSettings nested tag exposes the following parameters to allow popup customization:

ParameterTypeDescription
ClassstringThe CSS class of the edit popup
TitlestringThe title of the edit popup
ThemeColorstringThe color scheme of the window. Use the available members of the static class ThemeConstants.Window.ThemeColor.
WidthstringThe Width of the edit popup
MaxWidthstringThe maximum width of the window
MinWidthstringThe minimum width of the window
HeightstringThe height of edit popup
MaxHeightstringThe maximum height of the window
MinHeightstringThe 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:

ParameterType and Default ValueDescription
ButtonsLayoutFormButtonsLayout
(End)
The horizontal alignment of the buttons. Takes a member of the FormButtonsLayout enum:
- Start
- End
- Center
- Stretch
ColumnsintThe number of the form columns
ColumnSpacingintThe column spacing
OrientationFormOrientation
(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 and OnUpdate 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 an EditorTemplate.
  • 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
}

See Also