New to Telerik UI for Blazor? Download free 30-day trial

Debounce grid data source operations

Environment

Product Grid for Blazor

Description

I want to specify a debounce time for filtering. This way I can (for example) set the debounce time to 500(ms), and then only have the grid filter when the user stops typing.

This can be useful for filtering with remote data when using the FilterRow mode - it invokes a filter on every keystroke.

Solution

There are three ideas on the basic approach how to do this:

  • Use the FilterMenu filtering mode because it fires filtering requests only when the user presses a button.

  • Implement the desired throttling/debouncing in the OnRead event. Below is an example of this.

  • Implement your own filtering (a second example is available below).

Debounce grid data source requests

@* This example debounces all actions. You may want to add logic that checks how the data source request changed
    for example, whether the filters changed or something else, so you can debounce only filtering, for example *@

@implements IDisposable
@using System.Threading

@using Telerik.DataSource
@using Telerik.DataSource.Extensions

<TelerikGrid TItem="@Employee"
             OnRead="@ReadItems"
             Pageable="true" PageSize="15"
             FilterMode="@GridFilterMode.FilterRow">
    <GridColumns>
        <GridColumn Field=@nameof(Employee.Id) Title="ID" />
        <GridColumn Field=@nameof(Employee.Name) Title="Name" />
    </GridColumns>
</TelerikGrid>

@code {
    CancellationTokenSource tokenSource = new CancellationTokenSource(); // for debouncing

    protected async Task ReadItems(GridReadEventArgs args)
    {
        // debouncing
        tokenSource.Cancel();
        tokenSource.Dispose();

        tokenSource = new CancellationTokenSource();
        var token = tokenSource.Token;

        await Task.Delay(1500, token); // 500ms timeout for the debouncing

        //new data collection comes down from the service after debouncing
        await RequestData(args);
    }

    async Task RequestData(GridReadEventArgs args)
    {
        DataEnvelope DataResult = await FetchPagedData(args.Request);

        args.Data = DataResult.CurrentPageData;
        args.Total = DataResult.TotalItemCount;
    }

    public void Dispose()
    {
        try
        {
            tokenSource.Dispose();
        }
        catch { }
    }

    //sample paging and data request logic - this is just data generation from here on

    public async Task<DataEnvelope> FetchPagedData(DataSourceRequest request)
    {
        Console.WriteLine("I am called more rarely when the user types a filter");

        List<Employee> fullList = new List<Employee>();

        int totalCount = 100;
        for (int i = 1; i <= totalCount; i++)
        {
            fullList.Add(new Employee()
            {
                Id = i,
                Name = "Name " + i,
            });
        }

        DataEnvelope result = new DataEnvelope();

        var dataSourceResult = fullList.ToDataSourceResult(request);
        result.CurrentPageData = (dataSourceResult.Data as IEnumerable<Employee>).ToList();
        result.TotalItemCount = dataSourceResult.Total;

        return result;
    }

    public class DataEnvelope
    {
        public List<Employee> CurrentPageData { get; set; }
        public int TotalItemCount { get; set; }
    }

    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

Own filtering in the grid (header template is used until a filter template becomes available)

<style>
    .block-headers-with-sorting th.k-header .k-icon.k-i-sort-asc-sm,
    .block-headers-with-sorting th.k-header .k-icon.k-i-sort-desc-sm {
        position: absolute;
        right: 0;
        top: 8px;
    }
</style>

<TelerikGrid Data="@MyData" Height="300px" Pageable="true" Sortable="true" FilterMode="@GridFilterMode.None" Class="block-headers-with-sorting">
    <GridColumns>
        <GridColumn Field="@(nameof(SampleData.ID))">
            <HeaderTemplate>
                <div>
                    <div style="margin-bottom: .5rem">Id</div>
                    <input style="width: 100%;" class="k-textbox"
                           @onclick:stopPropagation 
                           @oninput="@( (ChangeEventArgs e) => UserFilters((string)e.Value, "ID") )" />
                </div>
            </HeaderTemplate>
        </GridColumn>
        <GridColumn Field="@(nameof(SampleData.Name))">
            <HeaderTemplate>
                <div>
                    <div style="margin-bottom: .5rem">Name</div>
                    <input style="width: 100%;" class="k-textbox" 
                           @onclick:stopPropagation 
                           @oninput="@( (ChangeEventArgs e) => UserFilters((string)e.Value, "Name") )" />
                </div>
            </HeaderTemplate>
        </GridColumn>
        <GridColumn Field="HireDate" Width="350px">
            <HeaderTemplate>
                <div>
                    <div style="margin-bottom: .5rem">Hire Date</div>
                    <input style="width: 100%;" class="k-textbox"
                           @onclick:stopPropagation 
                           @oninput="@( (ChangeEventArgs e) => UserFilters((string)e.Value, "HireDate") )" />
                </div>
            </HeaderTemplate>
        </GridColumn>
    </GridColumns>
</TelerikGrid>

@operationsList

@code {
    MarkupString operationsList { get; set; }

    void UserFilters(string input, string field)
    {
        // do debouncing and filtering of the grid data (MyData in this sample) here
        // see the previous snippet on a way to implement debouncing
        // see also how you can tell the grid to call filtering https://docs.telerik.com/blazor-ui/components/grid/filtering#filter-from-code
        operationsList = new MarkupString($"{operationsList}<br />filter string: {input}, field: {field}");
        StateHasChanged();
    }

    public class SampleData
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public DateTime HireDate { get; set; }
    }

    public IEnumerable<SampleData> MyData = Enumerable.Range(1, 50).Select(x => new SampleData
    {
        ID = x,
        Name = "name " + x,
        HireDate = DateTime.Now.AddDays(-x)
    });
}
In this article