Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,13 @@ protected override void OnInitialized()
Foos = Foo.GenerateFoo(LocalizerFoo);
}

private IEnumerable<SelectedItem> OnSearchTextChanged(string searchText)
{
return Foos.Where(i => i.Name!.Contains(searchText, StringComparison.OrdinalIgnoreCase)).Select(i => new SelectedItem(i.Name!, i.Name!));
}

private async Task<QueryData<SelectedItem>> OnQueryAsync(VirtualizeQueryOption option)
{
await Task.Delay(200);
var items = Foos;
if (!string.IsNullOrEmpty(option.SearchText))
{
items = items.Where(i => i.Name!.Contains(option.SearchText, StringComparison.OrdinalIgnoreCase)).ToList();
items = Foos.Where(i => i.Name!.Contains(option.SearchText, StringComparison.OrdinalIgnoreCase)).ToList();
}
return new QueryData<SelectedItem>
{
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/BootstrapBlazor.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>9.1.0</Version>
<Version>9.1.1</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
13 changes: 5 additions & 8 deletions src/BootstrapBlazor/Components/Select/Select.razor
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,16 @@
@Options
</CascadingValue>
<RenderTemplate>
@{
ResetSelectedItem();
}
<div class="dropdown-toggle" data-bs-toggle="@ToggleString" data-bs-placement="@PlacementString" data-bs-offset="@OffsetString" data-bs-custom-class="@CustomClassString">
@if (DisplayTemplate != null)
{
<div id="@InputId" class="@InputClassString" tabindex="0">
@DisplayTemplate(SelectedItem)
@DisplayTemplate(SelectedRow)
</div>
}
else
{
<input type="text" id="@InputId" disabled="@Disabled" placeholder="@PlaceHolder" class="@InputClassString" value="@SelectedItem?.Text" @onchange="OnChange" readonly="@ReadonlyString" />
<input type="text" id="@InputId" disabled="@Disabled" placeholder="@PlaceHolder" class="@InputClassString" value="@SelectedRow?.Text" @onchange="OnChange" readonly="@ReadonlyString" />
}
<span class="@AppendClassString"><i class="@DropdownIcon"></i></span>
</div>
Expand Down Expand Up @@ -59,11 +56,11 @@
@if (ShowSearch)
{
<div class="@SearchClassString">
<input type="text" class="search-text form-control" autocomplete="off" @bind-value="@SearchText" @bind-value:event="oninput" aria-label="Search">
<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">
<i class="@SearchIconString"></i>
</div>
}
@foreach (var itemGroup in _dataSource.GroupBy(i => i.GroupName))
@foreach (var itemGroup in Rows.GroupBy(i => i.GroupName))
{
if (!string.IsNullOrEmpty(itemGroup.Key))
{
Expand All @@ -81,7 +78,7 @@
@RenderRow(item)
}
}
@if (!_dataSource.Any())
@if (Rows.Count == 0)
{
<div class="dropdown-item">@NoSearchDataText</div>
}
Expand Down
160 changes: 80 additions & 80 deletions src/BootstrapBlazor/Components/Select/Select.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>

private readonly List<SelectedItem> _children = [];

private readonly List<SelectedItem> _dataSource = [];

/// <summary>
/// 获得/设置 右侧清除图标 默认 fa-solid fa-angle-up
/// </summary>
Expand All @@ -73,7 +71,6 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
/// 获得/设置 搜索文本发生变化时回调此方法
/// </summary>
[Parameter]
[NotNull]
public Func<string, IEnumerable<SelectedItem>>? OnSearchTextChanged { get; set; }

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

private bool _init = true;

private List<SelectedItem>? _itemsCache;

private ItemsProviderResult<SelectedItem> _result;

private List<SelectedItem> Rows
{
get
{
_itemsCache ??= string.IsNullOrEmpty(SearchText) ? GetRowsByItems() : GetRowsBySearch();
return _itemsCache;
}
}

private SelectedItem? SelectedRow
{
get
{
SelectedItem ??= GetSelectedRow();
return SelectedItem;
}
}

private SelectedItem? GetSelectedRow()
{
var item = Rows.Find(Match)
?? Rows.Find(i => i.Active)
?? Rows.Where(i => !i.IsDisabled).FirstOrDefault()
?? GetVirtualizeItem();

if (item != null)
{
if (_init && DisableItemChangedWhenFirstRender)
{

}
else
{
_ = SelectedItemChanged(item);
_init = false;
}
}
return item;
}

private List<SelectedItem> GetRowsByItems()
{
var items = new List<SelectedItem>();
items.AddRange(Items);
items.AddRange(_children);
return items;
}

private List<SelectedItem> GetRowsBySearch()
{
var items = OnSearchTextChanged?.Invoke(SearchText) ?? FilterBySearchText(GetRowsByItems());
return items.ToList();
}

private IEnumerable<SelectedItem> FilterBySearchText(IEnumerable<SelectedItem> source) => string.IsNullOrEmpty(SearchText)
? source
: source.Where(i => i.Text.Contains(SearchText, StringComparison));

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand All @@ -221,7 +280,6 @@ protected override void OnParametersSet()
base.OnParametersSet();

Items ??= [];
OnSearchTextChanged ??= text => Items.Where(i => i.Text.Contains(text, StringComparison));
PlaceHolder ??= Localizer[nameof(PlaceHolder)];
NoSearchDataText ??= Localizer[nameof(NoSearchDataText)];
DropdownIcon ??= IconTheme.GetIconByKey(ComponentIcons.SelectDropdownIcon);
Expand All @@ -233,16 +291,17 @@ protected override void OnParametersSet()
var item = NullableUnderlyingType == null ? "" : PlaceHolder;
Items = ValueType.ToSelectList(string.IsNullOrEmpty(item) ? null : new SelectedItem("", item));
}

_itemsCache = null;
SelectedItem = null;
}

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

private IEnumerable<SelectedItem>? VirtualItems { get; set; }

private List<SelectedItem> GetVirtualItems() => (VirtualItems ?? Items).ToList();
private List<SelectedItem> GetVirtualItems() => FilterBySearchText(GetRowsByItems()).ToList();

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

TotalCount = data.TotalCount;
VirtualItems = data.Items ?? [];
return new ItemsProviderResult<SelectedItem>(VirtualItems, TotalCount);
var items = data.Items ?? [];
_result = new ItemsProviderResult<SelectedItem>(items, TotalCount);
return _result;

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

private async Task SearchTextChanged(string val)
{
SearchText = val;
if (OnQueryAsync == null)
{
// 通过 Items 提供数据
VirtualItems = OnSearchTextChanged(SearchText);
}
else
_itemsCache = null;

if (OnQueryAsync != null)
{
// 通过 ItemProvider 提供数据
await VirtualizeElement.RefreshDataAsync();
}
StateHasChanged();
}

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

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

private void ResetSelectedItem()
{
_dataSource.Clear();

if (string.IsNullOrEmpty(SearchText))
{
_dataSource.AddRange(Items);
_dataSource.AddRange(_children);

if (VirtualItems != null)
{
_dataSource.AddRange(VirtualItems);
}

SelectedItem = _dataSource.Find(Match)
?? _dataSource.Find(i => i.Active)
?? _dataSource.Where(i => !i.IsDisabled).FirstOrDefault()
?? GetVirtualizeItem();

if (SelectedItem != null)
{
if (_init && DisableItemChangedWhenFirstRender)
{

}
else
{
_ = SelectedItemChanged(SelectedItem);
_init = false;
}
}
}
else if (IsVirtualize)
{
if (Items.Any())
{
VirtualItems = OnSearchTextChanged(SearchText);
}
}
else
{
_dataSource.AddRange(OnSearchTextChanged(SearchText));
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand All @@ -374,12 +384,11 @@ private void ResetSelectedItem()
[JSInvokable]
public async Task ConfirmSelectedItem(int index)
{
var ds = string.IsNullOrEmpty(SearchText)
? _dataSource
: OnSearchTextChanged(SearchText);
var item = ds.ElementAt(index);
await OnClickItem(item);
StateHasChanged();
if (index < Rows.Count)
{
await OnClickItem(Rows[index]);
StateHasChanged();
}
}

/// <summary>
Expand Down Expand Up @@ -483,24 +492,15 @@ private async Task OnClearValue()
}

SelectedItem? item;
if (IsVirtualize)
if (OnQueryAsync != null)
{
if (VirtualizeElement != null)
{
await VirtualizeElement.RefreshDataAsync();
item = VirtualItems!.FirstOrDefault();
}
else
{
VirtualItems = Items;
item = Items.FirstOrDefault();
}
await VirtualizeElement.RefreshDataAsync();
item = _result.Items.FirstOrDefault();
}
else
{
item = Items.FirstOrDefault();
}

if (item != null)
{
await SelectedItemChanged(item);
Expand Down
33 changes: 24 additions & 9 deletions test/UnitTest/Components/SelectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace UnitTest.Components;
public class SelectTest : BootstrapBlazorTestBase
{
[Fact]
public void OnSearchTextChanged_Null()
public async Task OnSearchTextChanged_Null()
{
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
{
Expand All @@ -30,7 +30,7 @@ public void OnSearchTextChanged_Null()
});

var ctx = cut.FindComponent<Select<string>>();
ctx.InvokeAsync(async () =>
await ctx.InvokeAsync(async () =>
{
await ctx.Instance.ConfirmSelectedItem(0);

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

ctx.Instance.ClearSearchText();

ctx.SetParametersAndRender(pb =>
{
pb.Add(a => a.OnBeforeSelectedItemChange, null);
pb.Add(a => a.OnSelectedItemChanged, null);
pb.Add(a => a.OnSearchTextChanged, text =>
{
return new List<SelectedItem>()
{
new("1", "Test1")
};
});
});

await ctx.InvokeAsync(() =>
{
ctx.Find(".search-text").Input("T");
});
cut.DoesNotContain("Test2");
}

[Fact]
Expand Down Expand Up @@ -719,12 +738,8 @@ public async Task IsVirtualize_OnQueryAsync_Clearable_Ok()
return Task.FromResult(new QueryData<SelectedItem>()
{
Items = string.IsNullOrEmpty(searchText)
? new SelectedItem[]
{
new("", "All"),
new("1", "Test1"),
new("2", "Test2")
} : [new("2", "Test2")],
? [new("", "All"), new("1", "Test1"), new("2", "Test2")]
: [new("2", "Test2")],
TotalCount = string.IsNullOrEmpty(searchText) ? 2 : 1
});
});
Expand Down
Loading