Skip to content

Commit a907b4c

Browse files
github-actions[bot]dimodiikoevska
authored
Merge kb-combo-debounce-2431 into production (#2441)
* kb(combobox): Revamp debounce and min filter KB * add link * Update knowledge-base/combo-debounce-onread.md Co-authored-by: Iva Stefanova Koevska-Atanasova <[email protected]> --------- Co-authored-by: Dimo Dimov <[email protected]> Co-authored-by: Iva Stefanova Koevska-Atanasova <[email protected]>
1 parent 808112d commit a907b4c

File tree

1 file changed

+157
-47
lines changed

1 file changed

+157
-47
lines changed

knowledge-base/combo-debounce-onread.md

Lines changed: 157 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -31,90 +31,200 @@ I also want to implement a minimum filter length, if the input is below that len
3131

3232
## Solution
3333

34-
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`.
34+
There are two ways to implement debouncing:
3535

36-
For min filter length, just add a check in the handler for the desired string length (in this example - 2 symbols).
36+
* Use the built-in [ComboBox `DebounceDelay` parameter]({%slug components/combobox/overview%}#parameters).
37+
* 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`.
3738

38-
>caption Use a `CancellationTokenSource` to debounce OnRead filter calls in the combo box. Add Min Filter Length
39+
For minimum filter length, add a check in the `OnRead` event handler for the desired string length.
40+
41+
>caption Debounce OnRead filter calls in the ComboBox and add minimum filter length.
3942
4043
````CSHTML
41-
@implements IDisposable
4244
@using System.Threading
4345
44-
<p>@SelectedValue</p>
46+
@using Telerik.DataSource
47+
@using Telerik.DataSource.Extensions
48+
49+
@implements IDisposable
50+
51+
<p><code>ComboBoxValue</code>: @ComboBoxValue</p>
52+
53+
<p>Debounce inside <code>OnRead</code>:</p>
54+
55+
<TelerikComboBox OnRead="@OnComboBoxRead1"
56+
TItem="@ListItem"
57+
TValue="@(int?)"
58+
@bind-Value="@ComboBoxValue"
59+
TextField="@nameof(ListItem.Text)"
60+
ValueField="@nameof(ListItem.Id)"
61+
Filterable="true"
62+
FilterOperator="@StringFilterOperator.Contains"
63+
Id="debounce-in-onread"
64+
Placeholder="Type 2+ letters or numbers to filter..."
65+
ScrollMode="@DropDownScrollMode.Virtual"
66+
ItemHeight="32"
67+
PageSize="20"
68+
ValueMapper="@ComboBoxValueMapper"
69+
Width="300px">
70+
</TelerikComboBox>
4571
46-
<TelerikComboBox TItem="@String" TValue="@String"
47-
OnRead="@ReadItems"
48-
@bind-Value="@SelectedValue"
72+
<p>Use <code>DebounceDelay</code>:</p>
73+
74+
<TelerikComboBox OnRead="@OnComboBoxRead2"
75+
TItem="@ListItem"
76+
TValue="@(int?)"
77+
@bind-Value="@ComboBoxValue"
78+
TextField="@nameof(ListItem.Text)"
79+
ValueField="@nameof(ListItem.Id)"
80+
DebounceDelay="@ComboBoxDebounceDelay"
4981
Filterable="true"
50-
Placeholder="Type anything">
82+
FilterOperator="@StringFilterOperator.Contains"
83+
Id="debounce-delay"
84+
Placeholder="Type 2+ letters or numbers to filter..."
85+
ScrollMode="@DropDownScrollMode.Virtual"
86+
ItemHeight="32"
87+
PageSize="20"
88+
ValueMapper="@ComboBoxValueMapper"
89+
Width="300px">
5190
</TelerikComboBox>
5291
5392
@code {
54-
public string SelectedValue { get; set; }
55-
CancellationTokenSource tokenSource = new CancellationTokenSource(); // for debouncing the service calls
93+
private int? ComboBoxValue { get; set; }
5694
57-
async Task RequestData(string userInput, string method, ComboBoxReadEventArgs args)
58-
{
59-
// this method calls the actual service (in this case - a local method)
60-
args.Data = await GetOptions(userInput, method);
61-
}
95+
// Data items that show without filtering.
96+
private List<ListItem> ComboBoxDefaultData { get; set; } = new();
97+
98+
// All data items.
99+
private List<ListItem> ComboBoxData { get; set; } = new();
100+
101+
private const int ComboBoxDebounceDelay = 1000;
62102
63-
async Task ReadItems(ComboBoxReadEventArgs args)
103+
private CancellationTokenSource TokenSource { get; set; } = new();
104+
105+
private async Task OnComboBoxRead1(ComboBoxReadEventArgs args)
64106
{
65-
if (args.Request.Filters.Count > 0) // wait for user input
107+
if (args.Request.Filters.Any())
66108
{
67-
Telerik.DataSource.FilterDescriptor filter = args.Request.Filters[0] as Telerik.DataSource.FilterDescriptor;
68-
string userInput = filter.Value.ToString();
69-
string method = filter.Operator.ToString();
109+
// Require user input before making data requests.
110+
FilterDescriptor filterDescriptor = (FilterDescriptor)args.Request.Filters.First();
111+
string filterValue = filterDescriptor.Value.ToString() ?? string.Empty;
70112
71-
if (userInput.Length > 1) // sample min filter length implementation
113+
// Require at least 2 characters to filter.
114+
if (filterValue.Length > 1)
72115
{
73-
// debouncing
74-
tokenSource.Cancel();
75-
tokenSource.Dispose();
116+
#region Debounce in OnRead
117+
118+
TokenSource.Cancel();
119+
TokenSource.Dispose();
120+
121+
TokenSource = new CancellationTokenSource();
122+
var token = TokenSource.Token;
123+
124+
await Task.Delay(ComboBoxDebounceDelay, token);
76125
77-
tokenSource = new CancellationTokenSource();
78-
var token = tokenSource.Token;
126+
#endregion Debounce in OnRead
79127
80-
await Task.Delay(300, token); // 300ms timeout for the debouncing
128+
// Request data after debouncing.
129+
var result = await ComboBoxData.ToDataSourceResultAsync(args.Request);
81130
82-
//new service request after debouncing
83-
await RequestData(userInput, method, args);
131+
args.Data = result.Data;
132+
args.Total = result.Total;
84133
}
85134
}
86135
else
87136
{
88-
// when there is no user input you may still want to provide data
89-
// in this example we just hardcode a few items, you can either fetch all the data
90-
// or you can provide some subset of most common items, or something based on the business logic
91-
args.Data = new List<string>() { "one", "two", "three" };
137+
// Optionally, provide default items before the user has filtered.
138+
// These can be the most commonly used ones, or all.
139+
args.Data = ComboBoxDefaultData;
140+
args.Total = ComboBoxDefaultData.Count;
92141
}
93142
}
94143
95-
public void Dispose()
144+
private async Task OnComboBoxRead2(ComboBoxReadEventArgs args)
96145
{
97-
try
146+
if (args.Request.Filters.Any())
98147
{
99-
tokenSource.Dispose();
148+
// Require user input before making data requests.
149+
FilterDescriptor filterDescriptor = (FilterDescriptor)args.Request.Filters.First();
150+
string filterValue = filterDescriptor.Value.ToString() ?? string.Empty;
151+
152+
// Require at least 2 characters to filter.
153+
if (filterValue.Length > 1)
154+
{
155+
// Request data after debouncing
156+
var result = await ComboBoxData.ToDataSourceResultAsync(args.Request);
157+
158+
args.Data = result.Data;
159+
args.Total = result.Total;
160+
}
100161
}
101-
catch { }
162+
else
163+
{
164+
// Optionally, provide default items before the user has filtered.
165+
// These can be the most commonly used ones, or all.
166+
args.Data = ComboBoxDefaultData;
167+
args.Total = ComboBoxDefaultData.Count;
168+
}
169+
}
170+
171+
private async Task<ListItem?> ComboBoxValueMapper(int? itemValue)
172+
{
173+
// Simulate network delay.
174+
await Task.Delay(50);
175+
176+
return ComboBoxData.FirstOrDefault(x => x.Id == itemValue);
177+
}
178+
179+
protected override void OnInitialized()
180+
{
181+
int frequentItems = 5;
182+
int allItems = 3000;
183+
184+
for (int i = 1; i <= frequentItems; i++)
185+
{
186+
var item = new ListItem()
187+
{
188+
Id = i,
189+
Text = $"Initial Item {i} {RandomChar()}{RandomChar()}{RandomChar()}"
190+
};
191+
192+
ComboBoxDefaultData.Add(item);
193+
ComboBoxData.Add(item);
194+
}
195+
196+
for (int i = frequentItems + 1; i <= allItems; i++)
197+
{
198+
var item = new ListItem()
199+
{
200+
Id = i,
201+
Text = $"Item {i} {RandomChar()}{RandomChar()}{RandomChar()}"
202+
};
203+
204+
ComboBoxData.Add(item);
205+
}
206+
207+
base.OnInitialized();
102208
}
103209
104-
async Task<List<string>> GetOptions(string userInput, string filterOperator)
210+
private char RandomChar()
105211
{
106-
Console.WriteLine("service called - debounced so there are fewer calls");
107-
await Task.Delay(500); // simulate network delay, remove it for a real app
212+
return (char)Random.Shared.Next(65, 91);
213+
}
108214
109-
//sample logic for getting suggestions - here they are generated, you can call a remote service
110-
//for brevity, this example does not use the filter operator, but your actual service can
111-
List<string> optionsData = new List<string>();
112-
for (int i = 0; i < 5; i++)
215+
public void Dispose()
216+
{
217+
try
113218
{
114-
optionsData.Add($"option {i} for input {userInput}");
219+
TokenSource.Dispose();
115220
}
221+
catch { }
222+
}
116223
117-
return optionsData;
224+
public class ListItem
225+
{
226+
public int Id { get; set; }
227+
public string Text { get; set; } = string.Empty;
118228
}
119229
}
120230
````

0 commit comments

Comments
 (0)