Skip to content

Commit f0302ee

Browse files
authored
fix(Select): not render UI sometime (#4798)
* refactor: 增加 Row 变量负责 UI 显示数据 * refactor: 增加缓存清空逻辑 * refactor: 增加回车键功能改造 * refactor: 移除 _dataSource 变量 * refactor: 移除不使用的方法 * refactor: 重构搜索逻辑 * test: 更新单元测试 * refactor: 增加私有变量缓存上次虚拟条目结果 * test: 更新单元测试 * chore: bump version 9.1.1
1 parent caa0a52 commit f0302ee

File tree

5 files changed

+111
-104
lines changed

5 files changed

+111
-104
lines changed

src/BootstrapBlazor.Shared/Components/Samples/Selects.razor.cs

+1-6
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,13 @@ protected override void OnInitialized()
6161
Foos = Foo.GenerateFoo(LocalizerFoo);
6262
}
6363

64-
private IEnumerable<SelectedItem> OnSearchTextChanged(string searchText)
65-
{
66-
return Foos.Where(i => i.Name!.Contains(searchText, StringComparison.OrdinalIgnoreCase)).Select(i => new SelectedItem(i.Name!, i.Name!));
67-
}
68-
6964
private async Task<QueryData<SelectedItem>> OnQueryAsync(VirtualizeQueryOption option)
7065
{
7166
await Task.Delay(200);
7267
var items = Foos;
7368
if (!string.IsNullOrEmpty(option.SearchText))
7469
{
75-
items = items.Where(i => i.Name!.Contains(option.SearchText, StringComparison.OrdinalIgnoreCase)).ToList();
70+
items = Foos.Where(i => i.Name!.Contains(option.SearchText, StringComparison.OrdinalIgnoreCase)).ToList();
7671
}
7772
return new QueryData<SelectedItem>
7873
{

src/BootstrapBlazor/BootstrapBlazor.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
4-
<Version>9.1.0</Version>
4+
<Version>9.1.1</Version>
55
</PropertyGroup>
66

77
<ItemGroup>

src/BootstrapBlazor/Components/Select/Select.razor

+5-8
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,16 @@
1313
@Options
1414
</CascadingValue>
1515
<RenderTemplate>
16-
@{
17-
ResetSelectedItem();
18-
}
1916
<div class="dropdown-toggle" data-bs-toggle="@ToggleString" data-bs-placement="@PlacementString" data-bs-offset="@OffsetString" data-bs-custom-class="@CustomClassString">
2017
@if (DisplayTemplate != null)
2118
{
2219
<div id="@InputId" class="@InputClassString" tabindex="0">
23-
@DisplayTemplate(SelectedItem)
20+
@DisplayTemplate(SelectedRow)
2421
</div>
2522
}
2623
else
2724
{
28-
<input type="text" id="@InputId" disabled="@Disabled" placeholder="@PlaceHolder" class="@InputClassString" value="@SelectedItem?.Text" @onchange="OnChange" readonly="@ReadonlyString" />
25+
<input type="text" id="@InputId" disabled="@Disabled" placeholder="@PlaceHolder" class="@InputClassString" value="@SelectedRow?.Text" @onchange="OnChange" readonly="@ReadonlyString" />
2926
}
3027
<span class="@AppendClassString"><i class="@DropdownIcon"></i></span>
3128
</div>
@@ -59,11 +56,11 @@
5956
@if (ShowSearch)
6057
{
6158
<div class="@SearchClassString">
62-
<input type="text" class="search-text form-control" autocomplete="off" @bind-value="@SearchText" @bind-value:event="oninput" aria-label="Search">
59+
<input type="text" class="search-text form-control" autocomplete="off" value="@SearchText" @oninput="EventCallback.Factory.CreateBinder<string>(this, async v => await SearchTextChanged(v), SearchText)" aria-label="Search">
6360
<i class="@SearchIconString"></i>
6461
</div>
6562
}
66-
@foreach (var itemGroup in _dataSource.GroupBy(i => i.GroupName))
63+
@foreach (var itemGroup in Rows.GroupBy(i => i.GroupName))
6764
{
6865
if (!string.IsNullOrEmpty(itemGroup.Key))
6966
{
@@ -81,7 +78,7 @@
8178
@RenderRow(item)
8279
}
8380
}
84-
@if (!_dataSource.Any())
81+
@if (Rows.Count == 0)
8582
{
8683
<div class="dropdown-item">@NoSearchDataText</div>
8784
}

src/BootstrapBlazor/Components/Select/Select.razor.cs

+80-80
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
6060

6161
private readonly List<SelectedItem> _children = [];
6262

63-
private readonly List<SelectedItem> _dataSource = [];
64-
6563
/// <summary>
6664
/// 获得/设置 右侧清除图标 默认 fa-solid fa-angle-up
6765
/// </summary>
@@ -73,7 +71,6 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
7371
/// 获得/设置 搜索文本发生变化时回调此方法
7472
/// </summary>
7573
[Parameter]
76-
[NotNull]
7774
public Func<string, IEnumerable<SelectedItem>>? OnSearchTextChanged { get; set; }
7875

7976
/// <summary>
@@ -213,6 +210,68 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
213210

214211
private bool _init = true;
215212

213+
private List<SelectedItem>? _itemsCache;
214+
215+
private ItemsProviderResult<SelectedItem> _result;
216+
217+
private List<SelectedItem> Rows
218+
{
219+
get
220+
{
221+
_itemsCache ??= string.IsNullOrEmpty(SearchText) ? GetRowsByItems() : GetRowsBySearch();
222+
return _itemsCache;
223+
}
224+
}
225+
226+
private SelectedItem? SelectedRow
227+
{
228+
get
229+
{
230+
SelectedItem ??= GetSelectedRow();
231+
return SelectedItem;
232+
}
233+
}
234+
235+
private SelectedItem? GetSelectedRow()
236+
{
237+
var item = Rows.Find(Match)
238+
?? Rows.Find(i => i.Active)
239+
?? Rows.Where(i => !i.IsDisabled).FirstOrDefault()
240+
?? GetVirtualizeItem();
241+
242+
if (item != null)
243+
{
244+
if (_init && DisableItemChangedWhenFirstRender)
245+
{
246+
247+
}
248+
else
249+
{
250+
_ = SelectedItemChanged(item);
251+
_init = false;
252+
}
253+
}
254+
return item;
255+
}
256+
257+
private List<SelectedItem> GetRowsByItems()
258+
{
259+
var items = new List<SelectedItem>();
260+
items.AddRange(Items);
261+
items.AddRange(_children);
262+
return items;
263+
}
264+
265+
private List<SelectedItem> GetRowsBySearch()
266+
{
267+
var items = OnSearchTextChanged?.Invoke(SearchText) ?? FilterBySearchText(GetRowsByItems());
268+
return items.ToList();
269+
}
270+
271+
private IEnumerable<SelectedItem> FilterBySearchText(IEnumerable<SelectedItem> source) => string.IsNullOrEmpty(SearchText)
272+
? source
273+
: source.Where(i => i.Text.Contains(SearchText, StringComparison));
274+
216275
/// <summary>
217276
/// <inheritdoc/>
218277
/// </summary>
@@ -221,7 +280,6 @@ protected override void OnParametersSet()
221280
base.OnParametersSet();
222281

223282
Items ??= [];
224-
OnSearchTextChanged ??= text => Items.Where(i => i.Text.Contains(text, StringComparison));
225283
PlaceHolder ??= Localizer[nameof(PlaceHolder)];
226284
NoSearchDataText ??= Localizer[nameof(NoSearchDataText)];
227285
DropdownIcon ??= IconTheme.GetIconByKey(ComponentIcons.SelectDropdownIcon);
@@ -233,16 +291,17 @@ protected override void OnParametersSet()
233291
var item = NullableUnderlyingType == null ? "" : PlaceHolder;
234292
Items = ValueType.ToSelectList(string.IsNullOrEmpty(item) ? null : new SelectedItem("", item));
235293
}
294+
295+
_itemsCache = null;
296+
SelectedItem = null;
236297
}
237298

238299
/// <summary>
239300
/// 获得/设置 数据总条目
240301
/// </summary>
241302
private int TotalCount { get; set; }
242303

243-
private IEnumerable<SelectedItem>? VirtualItems { get; set; }
244-
245-
private List<SelectedItem> GetVirtualItems() => (VirtualItems ?? Items).ToList();
304+
private List<SelectedItem> GetVirtualItems() => FilterBySearchText(GetRowsByItems()).ToList();
246305

247306
/// <summary>
248307
/// 虚拟滚动数据加载回调方法
@@ -259,26 +318,23 @@ private async ValueTask<ItemsProviderResult<SelectedItem>> LoadItems(ItemsProvid
259318
var data = await OnQueryAsync(new() { StartIndex = request.StartIndex, Count = count, SearchText = SearchText });
260319

261320
TotalCount = data.TotalCount;
262-
VirtualItems = data.Items ?? [];
263-
return new ItemsProviderResult<SelectedItem>(VirtualItems, TotalCount);
321+
var items = data.Items ?? [];
322+
_result = new ItemsProviderResult<SelectedItem>(items, TotalCount);
323+
return _result;
264324

265325
int GetCountByTotal() => TotalCount == 0 ? request.Count : Math.Min(request.Count, TotalCount - request.StartIndex);
266326
}
267327

268328
private async Task SearchTextChanged(string val)
269329
{
270330
SearchText = val;
271-
if (OnQueryAsync == null)
272-
{
273-
// 通过 Items 提供数据
274-
VirtualItems = OnSearchTextChanged(SearchText);
275-
}
276-
else
331+
_itemsCache = null;
332+
333+
if (OnQueryAsync != null)
277334
{
278335
// 通过 ItemProvider 提供数据
279336
await VirtualizeElement.RefreshDataAsync();
280337
}
281-
StateHasChanged();
282338
}
283339

284340
/// <summary>
@@ -295,7 +351,6 @@ protected override bool TryParseValueFromString(string value, [MaybeNullWhen(fal
295351
private bool TryParseSelectItem(string value, [MaybeNullWhen(false)] out TValue result, out string? validationErrorMessage)
296352
{
297353
SelectedItem = Items.FirstOrDefault(i => i.Value == value)
298-
?? VirtualItems?.FirstOrDefault(i => i.Value == value)
299354
?? GetVirtualizeItem();
300355

301356
// support SelectedItem? type
@@ -313,51 +368,6 @@ private bool TryParseSelectItem(string value, [MaybeNullWhen(false)] out TValue
313368
: new SelectedItem(CurrentValueAsString, DefaultVirtualizeItemText ?? CurrentValueAsString);
314369
}
315370

316-
private void ResetSelectedItem()
317-
{
318-
_dataSource.Clear();
319-
320-
if (string.IsNullOrEmpty(SearchText))
321-
{
322-
_dataSource.AddRange(Items);
323-
_dataSource.AddRange(_children);
324-
325-
if (VirtualItems != null)
326-
{
327-
_dataSource.AddRange(VirtualItems);
328-
}
329-
330-
SelectedItem = _dataSource.Find(Match)
331-
?? _dataSource.Find(i => i.Active)
332-
?? _dataSource.Where(i => !i.IsDisabled).FirstOrDefault()
333-
?? GetVirtualizeItem();
334-
335-
if (SelectedItem != null)
336-
{
337-
if (_init && DisableItemChangedWhenFirstRender)
338-
{
339-
340-
}
341-
else
342-
{
343-
_ = SelectedItemChanged(SelectedItem);
344-
_init = false;
345-
}
346-
}
347-
}
348-
else if (IsVirtualize)
349-
{
350-
if (Items.Any())
351-
{
352-
VirtualItems = OnSearchTextChanged(SearchText);
353-
}
354-
}
355-
else
356-
{
357-
_dataSource.AddRange(OnSearchTextChanged(SearchText));
358-
}
359-
}
360-
361371
/// <summary>
362372
/// <inheritdoc/>
363373
/// </summary>
@@ -374,12 +384,11 @@ private void ResetSelectedItem()
374384
[JSInvokable]
375385
public async Task ConfirmSelectedItem(int index)
376386
{
377-
var ds = string.IsNullOrEmpty(SearchText)
378-
? _dataSource
379-
: OnSearchTextChanged(SearchText);
380-
var item = ds.ElementAt(index);
381-
await OnClickItem(item);
382-
StateHasChanged();
387+
if (index < Rows.Count)
388+
{
389+
await OnClickItem(Rows[index]);
390+
StateHasChanged();
391+
}
383392
}
384393

385394
/// <summary>
@@ -483,24 +492,15 @@ private async Task OnClearValue()
483492
}
484493

485494
SelectedItem? item;
486-
if (IsVirtualize)
495+
if (OnQueryAsync != null)
487496
{
488-
if (VirtualizeElement != null)
489-
{
490-
await VirtualizeElement.RefreshDataAsync();
491-
item = VirtualItems!.FirstOrDefault();
492-
}
493-
else
494-
{
495-
VirtualItems = Items;
496-
item = Items.FirstOrDefault();
497-
}
497+
await VirtualizeElement.RefreshDataAsync();
498+
item = _result.Items.FirstOrDefault();
498499
}
499500
else
500501
{
501502
item = Items.FirstOrDefault();
502503
}
503-
504504
if (item != null)
505505
{
506506
await SelectedItemChanged(item);

test/UnitTest/Components/SelectTest.cs

+24-9
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace UnitTest.Components;
1414
public class SelectTest : BootstrapBlazorTestBase
1515
{
1616
[Fact]
17-
public void OnSearchTextChanged_Null()
17+
public async Task OnSearchTextChanged_Null()
1818
{
1919
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
2020
{
@@ -30,7 +30,7 @@ public void OnSearchTextChanged_Null()
3030
});
3131

3232
var ctx = cut.FindComponent<Select<string>>();
33-
ctx.InvokeAsync(async () =>
33+
await ctx.InvokeAsync(async () =>
3434
{
3535
await ctx.Instance.ConfirmSelectedItem(0);
3636

@@ -44,9 +44,28 @@ public void OnSearchTextChanged_Null()
4444
pb.Add(a => a.OnBeforeSelectedItemChange, item => Task.FromResult(false));
4545
pb.Add(a => a.OnSelectedItemChanged, item => Task.CompletedTask);
4646
});
47-
ctx.InvokeAsync(() => ctx.Instance.ConfirmSelectedItem(0));
47+
await ctx.InvokeAsync(() => ctx.Instance.ConfirmSelectedItem(0));
4848

4949
ctx.Instance.ClearSearchText();
50+
51+
ctx.SetParametersAndRender(pb =>
52+
{
53+
pb.Add(a => a.OnBeforeSelectedItemChange, null);
54+
pb.Add(a => a.OnSelectedItemChanged, null);
55+
pb.Add(a => a.OnSearchTextChanged, text =>
56+
{
57+
return new List<SelectedItem>()
58+
{
59+
new("1", "Test1")
60+
};
61+
});
62+
});
63+
64+
await ctx.InvokeAsync(() =>
65+
{
66+
ctx.Find(".search-text").Input("T");
67+
});
68+
cut.DoesNotContain("Test2");
5069
}
5170

5271
[Fact]
@@ -719,12 +738,8 @@ public async Task IsVirtualize_OnQueryAsync_Clearable_Ok()
719738
return Task.FromResult(new QueryData<SelectedItem>()
720739
{
721740
Items = string.IsNullOrEmpty(searchText)
722-
? new SelectedItem[]
723-
{
724-
new("", "All"),
725-
new("1", "Test1"),
726-
new("2", "Test2")
727-
} : [new("2", "Test2")],
741+
? [new("", "All"), new("1", "Test1"), new("2", "Test2")]
742+
: [new("2", "Test2")],
728743
TotalCount = string.IsNullOrEmpty(searchText) ? 2 : 1
729744
});
730745
});

0 commit comments

Comments
 (0)