Keep TreeGrid Selected Rows on Filter/Search

Hello everyone,

I've added a TreeGrid in my Blazor server app that retrieves parent and child folders from our API and displays them in a hierarchy. 

I only load the top-level parent folders when the app initially loads because there are many folders. To prevent performance issues, child folders are loaded when a top-level folder is expanded. It works as expected, except the TreeGrid does not update itself, so I have to force a DataSource refresh in the OnRowExpanded method.

Now that I have added selection and search/filter options to the TreeGrid, I am experiencing another problem. I lose my selections when I select items and then filter the grid. In order to keep track of the selected items, I had to create a new list.

    1. Can the grid be automatically refreshed when the child folders are added to the data source?
    2. Is there a way to keep my selections when filtering/searching the TreeGrid? I checked the documentation, but I am struggling a bit.

I would like to update the SelectedItems list when a TreeGrid row is selected or un-selected. Thanks for your help!

Here is my code:

<h3>Folder Search Demo</h3>

<SfTreeGrid @ref="TreeGrid" DataSource="@Folders" IdMapping="Id" ParentIdMapping="ParentId" TreeColumnIndex="2" AllowFiltering="true" HasChildMapping="HasChildren" ExpandStateMapping="Expanded" Width="500px">
    <TreeGridEvents TValue="Folder" Expanded="@OnRowExpanded" RowSelected="@OnRowSelected"></TreeGridEvents>
    <TreeGridFilterSettings HierarchyMode="FilterHierarchyMode.Both" Type="Syncfusion.Blazor.TreeGrid.FilterType.Excel"></TreeGridFilterSettings>
    <TreeGridSelectionSettings Type="Syncfusion.Blazor.Grids.SelectionType.Multiple"></TreeGridSelectionSettings>
    <TreeGridColumns>
        <TreeGridColumn Type="Syncfusion.Blazor.Grids.ColumnType.CheckBox" Width="5"></TreeGridColumn>
        <TreeGridColumn Visible="false" Field="Id" HeaderText="Folder ID" Width="5" ShowCheckbox="true" IsPrimaryKey="true" IsIdentity="true"></TreeGridColumn>
        <TreeGridColumn Field="Name" HeaderText="Folder" Width="30"></TreeGridColumn>
    </TreeGridColumns>
</SfTreeGrid>

@code {
    SfTreeGrid<Folder> TreeGrid;
    public string ErrorMessage { get; set; }
    public class Folder
    {
        public string Id { get; set; }
        public string ParentId { get; set; }
        public string ParentFolderName { get; set; }
        public string Name { get; set; }
        public bool Expanded { get; set; }
        public bool HasChildren { get; set; }
        public bool ChildrenLoaded { get; set; } = false;
        public bool isChecked { get; set; }
    }
    List<Folder> Folders = new List<Folder>();

    // List to track selected folders
    public List<Folder> SelectedFolders { get; set; } = new List<Folder>();

    protected override async Task OnInitializedAsync()
    {
        await LoadTopLevelFolder();
    }
    // Get top level parent records (folders) for initial grid rendering
    private async Task LoadTopLevelFolder()
    {
        try
        {
            VaultApi vaultApi = new VaultApi(vvSecrets);
            // Call the API
            var topLevelFolder = vaultApi.Folders.GetFolderByPath("/Employees");
            if (topLevelFolder != null)
            {
                var letterFolders = vaultApi.Folders.GetChildFolders(topLevelFolder.Id);
                if (letterFolders != null && letterFolders.Any())
                {
                    foreach (var letterFolder in letterFolders)
                    {
                        Folders.Add(new Folder
                            {
                                Id = letterFolder.Id.ToString(),
                                Name = letterFolder.Name,
                                ParentId = null,
                                ParentFolderName = "Parent",
                                ChildrenLoaded = false,
                                Expanded = false,
                                HasChildren = true
                            });
                    }
                }
            }
        }
        catch (Exception ex)
        {
            ErrorMessage = ex.Message;
        }
    }
    // Get child records (folders) and adding them to the folder list hierarchy // Debugging statements included to track actions in blazor console for dev purposes only     private async Task LoadChildren(Folder folder)
    {
        try
        {
            VaultApi vaultApi = new VaultApi(vvSecrets);


            Console.WriteLine($"Sending API request for Folder ID: {folder.Id}");


            var childFolders = vaultApi.Folders.GetChildFolders(Guid.Parse(folder.Id));


            Console.WriteLine($"API response for Folder ID {folder.Id}: {JsonConvert.SerializeObject(childFolders)}");


            Console.WriteLine($"Number of child folders in API response: {childFolders?.Count()}");


            if (childFolders != null && childFolders.Any())
            {
                // Check if the parent folder's name consists only of alphabet letters A-Z
                var isParentAlphabetic = Regex.IsMatch(folder.Name, @"^[A-Za-z]+$");


                foreach (var childFolder in childFolders)
                {
                    // If the parent folder's name is only alphabetic, set HasChildren to false for child folders
                    var hasChildren = isParentAlphabetic ? false : true;


                    Folders.Add(new Folder
                        {
                            Id = childFolder.Id.ToString(),
                            Name = childFolder.Name,
                            ParentId = folder.Id,
                            ParentFolderName = folder.Name,
                            Expanded = false,
                            HasChildren = hasChildren
                        });
                }

                folder.HasChildren = true;
                folder.Expanded = true;
            }
            else
            {
                folder.HasChildren = false;
            }
        }
        catch (Exception ex)
        {
            ErrorMessage = ex.Message;
        }
    }

    // Loads child folders when the parent folder row is expanded
    private async Task OnRowExpanded(RowExpandedEventArgs<Folder> args)
    {
        var folder = args.Data; // The folder record of the expanded row
        if (folder.HasChildren && !folder.ChildrenLoaded)
        {
            await LoadChildren(folder);
            folder.ChildrenLoaded = true; // Mark true since children loaded after successful loading to prevent duplicate API calls if the child folders have already been loaded.
            this.Folders = new List<Folder>(Folders); // Refresh the list to trigger UI update
            StateHasChanged();
        }
    }
    // Get selected folder data from row and add it to the SelectedFolders tracking list
    public async Task OnRowSelected(RowSelectEventArgs<Folder> args)
    {
        SelectedFolders.Add(args.Data);
        StateHasChanged();
    }

3 Replies 1 reply marked as answer

SM Shek Mohammed Asiq Abdul Jabbar Syncfusion Team March 13, 2024 02:15 PM UTC

Hi Patrick,


Please refer to the response for your queries.


Query 1 : Can the grid be automatically refreshed when the child folders are added to the data source?


We can use remote data binding in treegrid to render the children on demand (i.e., on expanding, the children will be rendered). Please refer to the documentation and demo below.


Documentation :

https://blazor.syncfusion.com/documentation/treegrid/data-binding#remote-service-binding


Demo :

https://blazor.syncfusion.com/demos/tree-grid/remote-data?theme=fluent


Query 2 : Is there a way to keep my selections when filtering/searching the TreeGrid? I checked the documentation, but I am struggling a bit.


We can manually select the rows using SelectRowAsync method after sorting and filtering in treegrid by RowSelected event and ActionComplete event. We can add the id of the selected index to a list and on action such as sorting and filtering we can find the index of the id and select the records manually by GetCurrentViewRecords.


Please refer to the following code snippet.

<TreeGridEvents OnActionComplete=" ActionComplete" RowSelected="RowSelected" TValue="BusinessObject"></TreeGridEvents>


public void ActionComplete(Syncfusion.Blazor.Grids.ActionEventArgs<BusinessObject> args)

    {

        if (args.RequestType == Syncfusion.Blazor.Grids.Action.Sorting || args.RequestType == Syncfusion.Blazor.Grids.Action.Filtering || args.RequestType == Syncfusion.Blazor.Grids.Action.ClearFiltering)

        {

            var index = this.Treegrid.GetCurrentViewRecords().FindIndex(a => a.TaskId == this.selectedId[0]);

            this.Treegrid.SelectRowAsync(index);

        }

    }

 

    public void RowSelected(RowSelectEventArgs<BusinessObject> args)

    {

        if (this.selectedId != null)

        {

            this.selectedId = new List<int>();

            this.selectedId.Add(args.Data.TaskId);

        }        

    }

 


Sample : https://blazorplayground.syncfusion.com/rXVftUiUrdqcAitb

Kindly get back to us for further assistance.


Regards,

Shek


Marked as answer

PA Patrick replied to Shek Mohammed Asiq Abdul Jabbar March 19, 2024 10:21 PM UTC

Thank you so much, Shek! Using your example, I was able to resolve both issues in my code. I appreciate your thr



FS Farveen Sulthana Thameeztheen Basha Syncfusion Team March 20, 2024 05:54 AM UTC

Patrick,


Thanks for your update. Please get back to us if you need any further assistance. We are happy to assist you further.


Regards,
Farveen sulthana T


Loader.
Up arrow icon