Skip to content

Commit e9fc549

Browse files
celadarisArgoZhang
andauthored
fix(AutoComplete): studder on long running OnValueChanged function call (#5819)
* fix studder * update formatting * Update AutoComplete.razor.js studder fix will be applied immediately rather than after a second or two after the component loads * Update AutoComplete.razor.cs Added a fix for #5821 * rollback undo fix for #5821 * Update AutoComplete.razor.cs re-apply fix, but use main branch as reference * Update AutoComplete.razor.cs accidently added duplicate GetFilterItemsByValue, oops. now its removed * refactor: 精简代码 * Revert "doc: 更新测试用例" This reverts commit c15ac74. * doc: 更新示例 * refactor: 精简 js 逻辑 * refactor: 使用 bind 防止卡顿 * refactor: 重构 show/closee 方法 * revert: 撤销 EnterCallback 参数 * doc: 更新测试用例 * refactor: 移除不使用的代码 * test: 更新单元测试 * refactor: 移除 TriggerChange 方法 --------- Signed-off-by: celadaris <[email protected]> Co-authored-by: Argo Zhang <[email protected]>
1 parent 46b08a3 commit e9fc549

File tree

11 files changed

+57
-160
lines changed

11 files changed

+57
-160
lines changed

src/BootstrapBlazor.Server/Components/Samples/AutoCompletes.razor

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,28 @@
88
<DemoBlock Title="@Localizer["Block1Title"]" Introduction="@Localizer["Block1Intro"]" Name="Normal">
99
<section ignore>@Localizer["NormalDescription"]</section>
1010
<div style="width: 200px;">
11-
<AutoComplete Items="@StaticItems" IsSelectAllTextOnFocus="true" Value="@Value"></AutoComplete>
11+
<AutoComplete Value="@_value" Items="@StaticItems" IsSelectAllTextOnFocus="true"></AutoComplete>
1212
</div>
1313
</DemoBlock>
1414

1515
<DemoBlock Title="@Localizer["Block2Title"]" Introduction="@Localizer["Block2Intro"]" Name="LikeMatch">
1616
<section ignore>@Localizer["LikeMatchDescription"]</section>
1717
<div style="width: 200px;">
18-
<AutoComplete Items="@StaticItems" IsLikeMatch="true" IgnoreCase="false" />
18+
<AutoComplete Value="@_matchValue" Items="@StaticItems" IsLikeMatch="true" IgnoreCase="false" />
1919
</div>
2020
</DemoBlock>
2121

2222
<DemoBlock Title="@Localizer["Block3Title"]" Introduction="@Localizer["Block3Intro"]" Name="NoDataTip">
2323
<section ignore>@((MarkupString)Localizer["NoDataTipDescription"].Value)</section>
2424
<div style="width: 200px;">
25-
<AutoComplete Items="@StaticItems" NoDataTip="@Localizer["NoDataTip"]" />
25+
<AutoComplete Value="@_tipValue" Items="@StaticItems" NoDataTip="@Localizer["NoDataTip"]" />
2626
</div>
2727
</DemoBlock>
2828

29-
<DemoBlock Title="@Localizer["Block4Title"]" Introduction="@Localizer["Block4Intro"]" Name="ValueChanged">
29+
<DemoBlock Title="@Localizer["Block4Title"]" Introduction="@Localizer["Block4Intro"]" Name="OnCustomFilter">
3030
<section ignore>@Localizer["ValueChangedDescription"]</section>
3131
<div style="width: 200px;">
32-
<AutoComplete Items="@Items" ValueChanged="@OnValueChanged" />
32+
<AutoComplete Value="@_filterValue" Items="@Items" OnCustomFilter="OnCustomFilter" />
3333
</div>
3434
</DemoBlock>
3535

@@ -48,12 +48,12 @@
4848
<DemoBlock Title="@Localizer["DebounceTitle"]" Introduction="@Localizer["DebounceIntro"]" Name="Debounce">
4949
<section ignore>@Localizer["DebounceDescription"]</section>
5050
<div style="width: 200px;">
51-
<AutoComplete Items="@Items" ValueChanged="@OnValueChanged" Debounce="500" />
51+
<AutoComplete @bind-Value="@_debounceValue" Items="@Items" Debounce="500" />
5252
</div>
5353
</DemoBlock>
5454

5555
<DemoBlock Title="@Localizer["OnSelectedItemChangedTitle"]" Introduction="@Localizer["OnSelectedItemChangedIntro"]" Name="OnSelectedItemChanged">
56-
<AutoComplete Items="@StaticItems" OnSelectedItemChanged="OnSelectedItemChanged" Debounce="500"></AutoComplete>
56+
<AutoComplete Items="@StaticItems" OnSelectedItemChanged="OnSelectedItemChanged"></AutoComplete>
5757
<section ignore>
5858
<ConsoleLogger @ref="Logger" />
5959
</section>

src/BootstrapBlazor.Server/Components/Samples/AutoCompletes.razor.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,17 @@ public sealed partial class AutoCompletes
1818

1919
private IEnumerable<string> Items => _items;
2020

21-
private string Value { get; set; } = "";
21+
private string _value = "";
22+
private string _matchValue = "";
23+
private string _tipValue = "";
24+
private string _filterValue = "";
25+
private string _debounceValue = "";
2226

23-
private Task OnValueChanged(string val)
27+
private static async Task<IEnumerable<string>> OnCustomFilter(string val)
2428
{
25-
_items.Clear();
26-
_items.Add($"{val}@163.com");
27-
_items.Add($"{val}@126.com");
28-
_items.Add($"{val}@sina.com");
29-
_items.Add($"{val}@hotmail.com");
30-
return Task.CompletedTask;
29+
await Task.Yield();
30+
var items = new List<string> { $"{val}@163.com", $"{val}@126.com", $"{val}@sina.com", $"{val}@hotmail.com" };
31+
return items;
3132
}
3233

3334
[NotNull]

src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
data-bb-auto-dropdown-focus="@ShowDropdownListOnFocusString" data-bb-debounce="@DurationString"
1313
data-bb-skip-esc="@SkipEscString" data-bb-skip-enter="@SkipEnterString" data-bb-blur="@TriggerBlurString"
1414
data-bb-scroll-behavior="@ScrollIntoViewBehaviorString" data-bb-trigger-delete="true"
15-
value="@CurrentValueAsString"
15+
@bind="@CurrentValueAsString"
1616
placeholder="@PlaceHolder" disabled="@Disabled" @ref="FocusElement"/>
1717
<span class="form-select-append"><i class="@Icon"></i></span>
1818
<span class="form-select-append ac-loading"><i class="@LoadingIcon"></i></span>

src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor.cs

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public partial class AutoComplete
8383
private List<string>? _filterItems;
8484

8585
[NotNull]
86-
private RenderTemplate? _dropdown = default;
86+
private RenderTemplate? _dropdown = null;
8787

8888
/// <summary>
8989
/// <inheritdoc/>
@@ -119,14 +119,6 @@ protected override void OnParametersSet()
119119
LoadingIcon ??= IconTheme.GetIconByKey(ComponentIcons.LoadingIcon);
120120
}
121121

122-
private bool _render = true;
123-
124-
/// <summary>
125-
/// <inheritdoc/>
126-
/// </summary>
127-
/// <returns></returns>
128-
protected override bool ShouldRender() => _render;
129-
130122
/// <summary>
131123
/// Callback method when a candidate item is clicked
132124
/// </summary>
@@ -168,7 +160,8 @@ public override async Task TriggerFilter(string val)
168160
_filterItems = [.. _filterItems.Take(DisplayCount.Value)];
169161
}
170162

171-
await TriggerChange(val);
163+
// only render the dropdown menu
164+
_dropdown.Render();
172165
}
173166

174167
private List<string> GetFilterItemsByValue(string val)
@@ -179,34 +172,4 @@ private List<string> GetFilterItemsByValue(string val)
179172
: Items.Where(s => s.StartsWith(val, comparison));
180173
return [.. items];
181174
}
182-
183-
/// <summary>
184-
/// TriggerChange method
185-
/// </summary>
186-
/// <param name="val"></param>
187-
[JSInvokable]
188-
public override Task TriggerChange(string val)
189-
{
190-
// client input does not need to be re-rendered to prevent jitter when the network is congested
191-
_render = false;
192-
CurrentValue = val;
193-
_render = true;
194-
_dropdown.Render();
195-
return Task.CompletedTask;
196-
}
197-
198-
/// <summary>
199-
/// TriggerChange method
200-
/// </summary>
201-
/// <param name="val"></param>
202-
[JSInvokable]
203-
public Task TriggerDeleteCallback(string val)
204-
{
205-
CurrentValue = val;
206-
if (!ValueChanged.HasDelegate)
207-
{
208-
StateHasChanged();
209-
}
210-
return Task.CompletedTask;
211-
}
212175
}

src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor.js

Lines changed: 29 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function init(id, invoke) {
99
const el = document.getElementById(id)
1010
const menu = el.querySelector('.dropdown-menu')
1111
const input = document.getElementById(`${id}_input`)
12-
const ac = { el, invoke, menu, input }
12+
const ac = { el, invoke, menu }
1313
Data.set(id, ac)
1414

1515
const isPopover = input.getAttribute('data-bs-toggle') === 'bb.dropdown';
@@ -36,67 +36,50 @@ export function init(id, invoke) {
3636
}
3737
}
3838

39-
// debounce
4039
const duration = parseInt(input.getAttribute('data-bb-debounce') || '0');
4140
if (duration > 0) {
4241
ac.debounce = true
43-
EventHandler.on(input, 'keydown', debounce(e => {
44-
handlerKeydown(ac, e);
45-
}, duration, e => {
46-
return ['ArrowUp', 'ArrowDown', 'Escape', 'Enter', 'NumpadEnter'].indexOf(e.key) > -1
47-
}))
42+
EventHandler.on(input, 'keyup', debounce(e => {
43+
handlerKeyup(ac, e);
44+
}, duration))
4845
}
4946
else {
50-
EventHandler.on(input, 'keydown', e => {
51-
handlerKeydown(ac, e);
47+
EventHandler.on(input, 'keyup', e => {
48+
handlerKeyup(ac, e);
5249
})
5350
}
5451

55-
EventHandler.on(input, 'keyup', e => handlerKeyup(ac, e));
56-
57-
ac.triggerBlur = () => {
58-
el.classList.remove('show');
59-
const triggerBlur = input.getAttribute('data-bb-blur') === 'true';
60-
if (triggerBlur) {
61-
invoke.invokeMethodAsync('TriggerBlur');
62-
}
63-
}
64-
6552
EventHandler.on(menu, 'click', '.dropdown-item', e => {
66-
ac.triggerBlur();
53+
ac.close();
6754
});
6855

6956
EventHandler.on(input, 'focus', e => {
7057
const showDropdownOnFocus = input.getAttribute('data-bb-auto-dropdown-focus') === 'true';
7158
if (showDropdownOnFocus) {
7259
if (isPopover === false) {
73-
el.classList.add('show');
60+
ac.show();
7461
}
7562
}
7663
});
7764

78-
EventHandler.on(input, 'change', e => {
79-
invoke.invokeMethodAsync('TriggerChange', e.target.value);
80-
});
81-
82-
let filterDuration = duration;
83-
if (filterDuration === 0) {
84-
filterDuration = 200;
85-
}
86-
const filterCallback = debounce(async v => {
87-
await invoke.invokeMethodAsync('TriggerFilter', v);
88-
el.classList.remove('is-loading');
89-
}, filterDuration);
90-
91-
Input.composition(input, v => {
65+
Input.composition(input, async v => {
9266
if (isPopover === false) {
93-
el.classList.add('show');
67+
ac.show();
9468
}
9569

9670
el.classList.add('is-loading');
97-
filterCallback(v);
71+
await invoke.invokeMethodAsync('TriggerFilter', v);
72+
el.classList.remove('is-loading');
9873
});
9974

75+
ac.show = () => {
76+
ac.el.classList.add('show');
77+
}
78+
79+
ac.close = () => {
80+
ac.el.classList.remove('show');
81+
}
82+
10083
ac.closePopover = e => {
10184
[...document.querySelectorAll('.auto-complete.show')].forEach(a => {
10285
const ac = e.target.closest('.auto-complete');
@@ -109,7 +92,7 @@ export function init(id, invoke) {
10992
const id = a.getAttribute('id');
11093
const d = Data.get(id);
11194
if (d) {
112-
d.triggerBlur();
95+
d.close();
11396
}
11497
}
11598
});
@@ -121,23 +104,21 @@ export function init(id, invoke) {
121104

122105
const handlerKeyup = (ac, e) => {
123106
const key = e.key;
124-
const { el, input, invoke, menu } = ac;
107+
const { el, invoke, menu } = ac;
125108
if (key === 'Enter' || key === 'NumpadEnter') {
126109
const skipEnter = el.getAttribute('data-bb-skip-enter') === 'true';
127110
if (!skipEnter) {
128111
const current = menu.querySelector('.active');
129112
if (current !== null) {
130113
current.click();
131-
ac.triggerBlur();
132114
}
133-
invoke.invokeMethodAsync('EnterCallback', input.value);
115+
invoke.invokeMethodAsync('EnterCallback');
134116
}
135117
}
136118
else if (key === 'Escape') {
137119
const skipEsc = el.getAttribute('data-bb-skip-esc') === 'true';
138120
if (skipEsc === false) {
139121
invoke.invokeMethodAsync('EscCallback');
140-
ac.triggerBlur();
141122
}
142123
}
143124
else if (key === 'ArrowUp' || key === 'ArrowDown') {
@@ -159,29 +140,6 @@ const handlerKeyup = (ac, e) => {
159140
current.classList.add('active');
160141
scrollIntoView(el, current);
161142
}
162-
else if (key === 'Backspace' || key === 'Delete') {
163-
if (input.getAttribute('data-bb-trigger-delete') === 'true') {
164-
invoke.invokeMethodAsync('TriggerDeleteCallback', input.value);
165-
}
166-
}
167-
}
168-
169-
const handlerKeydown = (ac, e) => {
170-
if (e.key === 'Tab') {
171-
ac.triggerBlur();
172-
}
173-
}
174-
175-
export function showList(id) {
176-
const ac = Data.get(id)
177-
if (ac) {
178-
if (ac.popover) {
179-
ac.popover.show();
180-
}
181-
else {
182-
ac.el.classList.add('show');
183-
}
184-
}
185143
}
186144

187145
export function dispose(id) {
@@ -196,17 +154,15 @@ export function dispose(id) {
196154
EventHandler.off(input, 'focus')
197155
}
198156
}
199-
EventHandler.off(input, 'change');
200-
EventHandler.off(input, 'keydown');
201-
EventHandler.off(input, 'keyup');
202157
EventHandler.off(menu, 'click');
158+
EventHandler.off(input, 'keyup');
203159
Input.dispose(input);
204-
205-
const { AutoComplete } = window.BootstrapBlazor;
206-
AutoComplete.dispose(id, () => {
207-
EventHandler.off(document, 'click', ac.closePopover);
208-
});
209160
}
161+
162+
const { AutoComplete } = window.BootstrapBlazor;
163+
AutoComplete.dispose(id, () => {
164+
EventHandler.off(document, 'click', ac.closePopover);
165+
});
210166
}
211167

212168
const scrollIntoView = (el, item) => {

src/BootstrapBlazor/Components/AutoComplete/PopoverCompleteBase.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,6 @@ public async Task TriggerBlur()
178178
[JSInvokable]
179179
public virtual Task TriggerFilter(string val) => Task.CompletedTask;
180180

181-
/// <summary>
182-
/// TriggerChange 方法
183-
/// </summary>
184-
/// <param name="val"></param>
185-
/// <returns></returns>
186-
[JSInvokable]
187-
public virtual Task TriggerChange(string val) => Task.CompletedTask;
188-
189181
/// <summary>
190182
/// <inheritdoc/>
191183
/// </summary>

src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ public override async Task TriggerFilter(string val)
295295
/// </summary>
296296
/// <param name="val">The value to change to.</param>
297297
[JSInvokable]
298-
public override Task TriggerChange(string val)
298+
public Task TriggerChange(string val)
299299
{
300300
_displayText = val;
301301
StateHasChanged();

src/BootstrapBlazor/Components/Input/BootstrapInputBase.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,10 @@ protected virtual async Task OnBlur()
213213
/// </summary>
214214
/// <returns></returns>
215215
[JSInvokable]
216-
public async Task EnterCallback(string val)
216+
public async Task EnterCallback()
217217
{
218218
if (OnEnterAsync != null)
219219
{
220-
CurrentValueAsString = val;
221220
await OnEnterAsync(Value);
222221
}
223222
}

src/BootstrapBlazor/Components/Search/Search.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ private async Task OnClickItem(TValue val)
289289
/// </summary>
290290
/// <param name="val"></param>
291291
[JSInvokable]
292-
public override async Task TriggerChange(string val)
292+
public async Task TriggerChange(string val)
293293
{
294294
_render = false;
295295
_displayText = val;

0 commit comments

Comments
 (0)