Search code examples
c#asp.net-coreblazormudblazor

Mudblazor table with multiselect and a search filter resets selection when searching


I have a mudblazor page that contains a table with tickets, it is possible to select these tickets and apply a filter based on a string entered in the searchbar. The selected tickets and the search string are saved in a session storage. The problem is that when I use the search function, the session storage for the selected tickets gets emptied.

I have figured out that it calls my updateSelection for storing the selected tickets in a session the same amount of times as the amount of rows currently loaded in. So if i have 25 rows, it will run updateSelecion() 25 times and setting an empty list.

This is the part of the table that would be calling the update functions:

<MudTable @ref="mudTableRef" Items="@TicketList" SelectedItemsChanged="@UpdateSelection" RowsPerPage="rowCount" RowsPerPageChanged="@UpdateRowCount" Filter="new Func<Ticket,bool>(FilterFunc1)" T="Ticket" OnRowClick="@RowClicked" SelectOnRowClick="false" Hover Bordered Striped MultiSelection>
    <ToolBarContent>
        <MudTextField @ref="mudSearchRef" Value="@searchString" ValueChanged="@(new EventCallback<string>(this, UpdateSearch))" Placeholder="Search" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0" Clearable></MudTextField>
        </ToolBarContent>
        //headers, rows etc...
    </MudTable>

These are the update functions:

    private string searchString = "";
    private HashSet<Ticket> selectedTickets = new HashSet<Ticket>();

    async public void UpdateSearch(string searchStringField)
    {
        searchString = searchStringField;

        // Save selected items to session storage
        await sessionStorage.SetItemAsync("SessionSearch", searchString);

        // Trigger a render to update the UI
        StateHasChanged();
    }

    async public void UpdateSelection(HashSet<Ticket> selectedRows)
    {
        selectedTickets = selectedRows;

        // Save selected items to session storage
        await sessionStorage.SetItemAsync("SelectedTickets", selectedTickets);

        // Trigger a render to update the UI
        StateHasChanged();
    }

This is the method I use for filtering the rows:

    //Search for entered string in table data
    private bool FilterFunc1(Ticket ticket) => FilterFunc(ticket, searchString);

    private bool FilterFunc(Ticket ticket, string searchString)
    {
        if (string.IsNullOrWhiteSpace(searchString))
            return true;

        var selectedFilters = options.ToList();

        // Define a dictionary to map field names to check functions
        var fieldCheckFunctions = new Dictionary<string, Func<Ticket, string, bool>>
        {
            { "Ticket Nr", (t, s) => $"{t.TicketId}".Contains(s) },
            { "Customer name", (t, s) => t.CustomerName.Contains(s, StringComparison.OrdinalIgnoreCase) },
            { "Main Category", (t, s) => t.MainCategory.Contains(s, StringComparison.OrdinalIgnoreCase) },
            { "Subcategory", (t, s) => t.Subcategory.Contains(s, StringComparison.OrdinalIgnoreCase) },
            { "Category type", (t, s) => t.CategoryType.Contains(s, StringComparison.OrdinalIgnoreCase) },
            { "Collection ticket Nr", (t, s) => t.CollectionTicketNr.Contains(s, StringComparison.OrdinalIgnoreCase) },
            { "Owner", (t, s) => t.Owner.Contains(s, StringComparison.OrdinalIgnoreCase) },
            { "Reported on", (t, s) => t.ReportedOn?.ToShortDateString().Contains(s, StringComparison.OrdinalIgnoreCase) == true },
            { "Due date", (t, s) => t.DueDate?.ToShortDateString().Contains(s, StringComparison.OrdinalIgnoreCase) == true }
        };

        // Check if the searchString matches any of the selected fields
        foreach (var field in selectedFilters)
        {
            if (fieldCheckFunctions.TryGetValue(field, out var checkFunction) && checkFunction(ticket, searchString))
            {
                return true;
            }
        }
        return false;
    }

I have tried locking the update function when a search is called but it only works half of the time for some reason:

    async public void UpdateSelection(HashSet<Ticket> selectedRows)
    {
        if (searchCalled)
        {
            searchCalledCount += 1;
            if (searchCalledCount > TicketList.Count())
            {
                searchCalled = false;
                searchCalledCount = 0;
            }
        }
        else
        {
            searchCalled = false;
            selectedTickets = selectedRows;

            // Save selected items to session storage
            await sessionStorage.SetItemAsync("SelectedTickets", selectedTickets);

            // Trigger a render to update the UI
            StateHasChanged();
        }
    }

    async public void UpdateSearch(string searchStringField)
    {
        searchCalled = true;
        searchString = searchStringField;

        // Save selected items to session storage
        await sessionStorage.SetItemAsync("SessionSearch", searchString);

        // Trigger a render to update the UI
        OnAfterRenderAsync(true);
        StateHasChanged();
    }

Also, this is how I get the values back from the session if that matters here because when searching you don't reload or leave the page:

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            //Get table rowcount from session
            rowCount = await sessionStorage.GetItemAsync<int?>("SessionRowCount") ?? 25;

            //Get selected filters from session
            options = await sessionStorage.GetItemAsync<HashSet<string>>("SessionOptions") ?? new HashSet<string>
                { "Ticket Nr", "Customer name", "Main Category", "Subcategory", "Category type", "Collection ticket Nr", "Owner", "Reported on", "Due date" };

            mudSelectRef.SelectedValues = options;

            //Get search string from session
            searchString = (await sessionStorage.GetItemAsStringAsync("SessionSearch"))?.Trim('"') ?? string.Empty;

            await mudSearchRef.SetText(searchString);

            //Get Selected ticket from session
            selectedTickets = await sessionStorage.GetItemAsync<HashSet<Ticket>>("SelectedTickets") ?? new HashSet<Ticket>();

            // Bind selected items to the table instance
            foreach (var ticket in selectedTickets)
            {
                var sessionTicket = mudTableRef.Items.SingleOrDefault(s => s.TicketId.Equals(ticket.TicketId));
                if (sessionTicket is not null) mudTableRef.SelectedItems.Add(sessionTicket);
            }

            StateHasChanged();
        }
    }

Any help would be greatly appreciated!


Solution

  • I fixed it by making a copy of the current list before executing the search, i know this probably is not the best fix but I currently can't figure out how to do it any other way:

        async public void UpdateSearch(string searchStringField)
        {
            // Create a copy of the current selectedTickets
            HashSet<Ticket> selectedTicketsCopy = new HashSet<Ticket>(selectedTickets);
    
            searchString = searchStringField;
    
            // Save selected items to session storage
            await sessionStorage.SetItemAsync("SessionSearch", searchString);
    
            // Merge the copy and the current selectedTickets, removing duplicates
            selectedTickets.UnionWith(selectedTicketsCopy);
            UpdateSelection(selectedTickets);
        }
    
        async public void UpdateSelection(HashSet<Ticket> selectedRows)
        {
            selectedTickets = selectedRows;
    
            // Save selected items to session storage
            await sessionStorage.SetItemAsync("SelectedTickets", selectedTickets);
        }