diff --git a/knowledge-base/combo-debounce-onread.md b/knowledge-base/combo-debounce-onread.md index 75fc0210b..c122f2fdc 100644 --- a/knowledge-base/combo-debounce-onread.md +++ b/knowledge-base/combo-debounce-onread.md @@ -31,90 +31,200 @@ I also want to implement a minimum filter length, if the input is below that len ## Solution -Implement logic in the [OnRead event]({%slug components/combobox/events%}#onread) that will debounce the calls to the service with the desired timeout. For example, use a `CancellationTokenSource`. +There are two ways to implement debouncing: -For min filter length, just add a check in the handler for the desired string length (in this example - 2 symbols). +* Use the built-in [ComboBox `DebounceDelay` parameter]({%slug components/combobox/overview%}#parameters). +* Implement logic in the [ComboBox `OnRead` event]({%slug components/combobox/events%}#onread) to debounce the calls to the data service with the desired timeout. For example, use a `CancellationTokenSource`. ->caption Use a `CancellationTokenSource` to debounce OnRead filter calls in the combo box. Add Min Filter Length +For minimum filter length, add a check in the `OnRead` event handler for the desired string length. + +>caption Debounce OnRead filter calls in the ComboBox and add minimum filter length. ````CSHTML -@implements IDisposable @using System.Threading -

@SelectedValue

+@using Telerik.DataSource +@using Telerik.DataSource.Extensions + +@implements IDisposable + +

ComboBoxValue: @ComboBoxValue

+ +

Debounce inside OnRead:

+ + + -Use DebounceDelay:

+ + + FilterOperator="@StringFilterOperator.Contains" + Id="debounce-delay" + Placeholder="Type 2+ letters or numbers to filter..." + ScrollMode="@DropDownScrollMode.Virtual" + ItemHeight="32" + PageSize="20" + ValueMapper="@ComboBoxValueMapper" + Width="300px"> @code { - public string SelectedValue { get; set; } - CancellationTokenSource tokenSource = new CancellationTokenSource(); // for debouncing the service calls + private int? ComboBoxValue { get; set; } - async Task RequestData(string userInput, string method, ComboBoxReadEventArgs args) - { - // this method calls the actual service (in this case - a local method) - args.Data = await GetOptions(userInput, method); - } + // Data items that show without filtering. + private List ComboBoxDefaultData { get; set; } = new(); + + // All data items. + private List ComboBoxData { get; set; } = new(); + + private const int ComboBoxDebounceDelay = 1000; - async Task ReadItems(ComboBoxReadEventArgs args) + private CancellationTokenSource TokenSource { get; set; } = new(); + + private async Task OnComboBoxRead1(ComboBoxReadEventArgs args) { - if (args.Request.Filters.Count > 0) // wait for user input + if (args.Request.Filters.Any()) { - Telerik.DataSource.FilterDescriptor filter = args.Request.Filters[0] as Telerik.DataSource.FilterDescriptor; - string userInput = filter.Value.ToString(); - string method = filter.Operator.ToString(); + // Require user input before making data requests. + FilterDescriptor filterDescriptor = (FilterDescriptor)args.Request.Filters.First(); + string filterValue = filterDescriptor.Value.ToString() ?? string.Empty; - if (userInput.Length > 1) // sample min filter length implementation + // Require at least 2 characters to filter. + if (filterValue.Length > 1) { - // debouncing - tokenSource.Cancel(); - tokenSource.Dispose(); + #region Debounce in OnRead + + TokenSource.Cancel(); + TokenSource.Dispose(); + + TokenSource = new CancellationTokenSource(); + var token = TokenSource.Token; + + await Task.Delay(ComboBoxDebounceDelay, token); - tokenSource = new CancellationTokenSource(); - var token = tokenSource.Token; + #endregion Debounce in OnRead - await Task.Delay(300, token); // 300ms timeout for the debouncing + // Request data after debouncing. + var result = await ComboBoxData.ToDataSourceResultAsync(args.Request); - //new service request after debouncing - await RequestData(userInput, method, args); + args.Data = result.Data; + args.Total = result.Total; } } else { - // when there is no user input you may still want to provide data - // in this example we just hardcode a few items, you can either fetch all the data - // or you can provide some subset of most common items, or something based on the business logic - args.Data = new List() { "one", "two", "three" }; + // Optionally, provide default items before the user has filtered. + // These can be the most commonly used ones, or all. + args.Data = ComboBoxDefaultData; + args.Total = ComboBoxDefaultData.Count; } } - public void Dispose() + private async Task OnComboBoxRead2(ComboBoxReadEventArgs args) { - try + if (args.Request.Filters.Any()) { - tokenSource.Dispose(); + // Require user input before making data requests. + FilterDescriptor filterDescriptor = (FilterDescriptor)args.Request.Filters.First(); + string filterValue = filterDescriptor.Value.ToString() ?? string.Empty; + + // Require at least 2 characters to filter. + if (filterValue.Length > 1) + { + // Request data after debouncing + var result = await ComboBoxData.ToDataSourceResultAsync(args.Request); + + args.Data = result.Data; + args.Total = result.Total; + } } - catch { } + else + { + // Optionally, provide default items before the user has filtered. + // These can be the most commonly used ones, or all. + args.Data = ComboBoxDefaultData; + args.Total = ComboBoxDefaultData.Count; + } + } + + private async Task ComboBoxValueMapper(int? itemValue) + { + // Simulate network delay. + await Task.Delay(50); + + return ComboBoxData.FirstOrDefault(x => x.Id == itemValue); + } + + protected override void OnInitialized() + { + int frequentItems = 5; + int allItems = 3000; + + for (int i = 1; i <= frequentItems; i++) + { + var item = new ListItem() + { + Id = i, + Text = $"Initial Item {i} {RandomChar()}{RandomChar()}{RandomChar()}" + }; + + ComboBoxDefaultData.Add(item); + ComboBoxData.Add(item); + } + + for (int i = frequentItems + 1; i <= allItems; i++) + { + var item = new ListItem() + { + Id = i, + Text = $"Item {i} {RandomChar()}{RandomChar()}{RandomChar()}" + }; + + ComboBoxData.Add(item); + } + + base.OnInitialized(); } - async Task> GetOptions(string userInput, string filterOperator) + private char RandomChar() { - Console.WriteLine("service called - debounced so there are fewer calls"); - await Task.Delay(500); // simulate network delay, remove it for a real app + return (char)Random.Shared.Next(65, 91); + } - //sample logic for getting suggestions - here they are generated, you can call a remote service - //for brevity, this example does not use the filter operator, but your actual service can - List optionsData = new List(); - for (int i = 0; i < 5; i++) + public void Dispose() + { + try { - optionsData.Add($"option {i} for input {userInput}"); + TokenSource.Dispose(); } + catch { } + } - return optionsData; + public class ListItem + { + public int Id { get; set; } + public string Text { get; set; } = string.Empty; } } ````