diff --git a/src/BootstrapBlazor.Server/Components/Pages/Coms.razor.cs b/src/BootstrapBlazor.Server/Components/Pages/Coms.razor.cs index e154a99d0e7..662ec3b8fa4 100644 --- a/src/BootstrapBlazor.Server/Components/Pages/Coms.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Pages/Coms.razor.cs @@ -14,9 +14,9 @@ public sealed partial class Coms private string? SearchText { get; set; } - private Task> OnSearch(string searchText) + private Task> OnSearch(string searchText) { SearchText = searchText; - return Task.FromResult>(ComponentItems.Where(i => i.Contains(searchText, StringComparison.OrdinalIgnoreCase))); + return Task.FromResult>(ComponentItems.Where(i => i.Contains(searchText, StringComparison.OrdinalIgnoreCase)).ToList()); } } diff --git a/src/BootstrapBlazor.Server/Components/Samples/AutoFills.razor b/src/BootstrapBlazor.Server/Components/Samples/AutoFills.razor index e77487641d0..52221f3d69e 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/AutoFills.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/AutoFills.razor @@ -10,7 +10,7 @@ @((MarkupString)@Localizer["NormalDesc"].Value) - +
@@ -32,7 +32,7 @@
@((MarkupString)Localizer["CustomFilterDesc"].Value)
- +
@@ -54,7 +54,7 @@ @((MarkupString)@Localizer["ShowDropdownListOnFocusDesc"].Value)
- +
diff --git a/src/BootstrapBlazor.Server/Components/Samples/InputGroups.razor b/src/BootstrapBlazor.Server/Components/Samples/InputGroups.razor index 13051e8819e..4de65623fb9 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/InputGroups.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/InputGroups.razor @@ -89,7 +89,7 @@ - + diff --git a/src/BootstrapBlazor.Server/Components/Samples/Searches.razor b/src/BootstrapBlazor.Server/Components/Samples/Searches.razor index ac4aea7997b..bf26b1f551d 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Searches.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/Searches.razor @@ -35,6 +35,18 @@ + + + +
@context.Name
+
@context.Address
+
+
+
+ diff --git a/src/BootstrapBlazor.Server/Components/Samples/Searches.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Searches.razor.cs index 02002766368..6cd10797ebf 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Searches.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Searches.razor.cs @@ -54,6 +54,12 @@ private Task> OnKeyboardSearch(string searchText) private Foo Model { get; set; } = new Foo() { Name = "" }; + private static async Task> OnSearchFoo(string searchText) + { + await Task.Delay(100); + return Enumerable.Range(1, 10).Select(i => new Foo() { Name = $"{searchText}-{i}", Address = $"Address - 10{i}" }).ToList(); + } + /// /// 获得属性方法 /// diff --git a/src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs b/src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs index b11835927ba..4272cac2715 100644 --- a/src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs +++ b/src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs @@ -70,6 +70,14 @@ public partial class AutoFill [Parameter] public Func>>? OnCustomFilter { get; set; } + /// + /// 获得/设置 候选项模板 默认 null + /// + [Parameter] + [Obsolete("已弃用,请使用 ItemTemplate 代替;Deprecated please use ItemTemplate parameter")] + [ExcludeFromCodeCoverage] + public RenderFragment? Template { get => ItemTemplate; set => ItemTemplate = value; } + [Inject] [NotNull] private IStringLocalizer? Localizer { get; set; } diff --git a/src/BootstrapBlazor/Components/Search/Search.razor b/src/BootstrapBlazor/Components/Search/Search.razor index a9fb830cbae..e5bdd549224 100644 --- a/src/BootstrapBlazor/Components/Search/Search.razor +++ b/src/BootstrapBlazor/Components/Search/Search.razor @@ -1,5 +1,6 @@ @namespace BootstrapBlazor.Components -@inherits PopoverCompleteBase +@typeparam TValue +@inherits PopoverCompleteBase
@@ -27,7 +28,7 @@ } else { -
@item
+
@GetDisplayText(item)
} } diff --git a/src/BootstrapBlazor/Components/Search/Search.razor.cs b/src/BootstrapBlazor/Components/Search/Search.razor.cs index 6c070a20648..9b9854f98a0 100644 --- a/src/BootstrapBlazor/Components/Search/Search.razor.cs +++ b/src/BootstrapBlazor/Components/Search/Search.razor.cs @@ -10,7 +10,7 @@ namespace BootstrapBlazor.Components; /// /// Search 组件 /// -public partial class Search +public partial class Search { /// /// 获得/设置 是否显示清除按钮 默认为 false 不显示 @@ -77,17 +77,24 @@ public partial class Search /// 获得/设置 点击搜索按钮时回调委托 /// [Parameter] - public Func>>? OnSearch { get; set; } + public Func>>? OnSearch { get; set; } + + /// + /// 获得/设置 通过模型获得显示文本方法 默认使用 ToString 重载方法 + /// + [Parameter] + [NotNull] + public Func? OnGetDisplayText { get; set; } /// /// 获得/设置 点击清空按钮时回调委托 /// [Parameter] - public Func? OnClear { get; set; } + public Func? OnClear { get; set; } [Inject] [NotNull] - private IStringLocalizer? Localizer { get; set; } + private IStringLocalizer>? Localizer { get; set; } /// /// @@ -105,7 +112,7 @@ public partial class Search /// 获得/设置 UI 呈现数据集合 /// [NotNull] - private List? FilterItems { get; set; } + private List? FilterItems { get; set; } /// /// @@ -139,6 +146,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) } } + private string _displayText = ""; private bool _show; /// /// 点击搜索按钮时触发此方法 @@ -151,12 +159,12 @@ private async Task OnSearchClick() ButtonIcon = SearchButtonLoadingIcon; await Task.Yield(); - var items = await OnSearch(Value); + var items = await OnSearch(_displayText); FilterItems = items.ToList(); ButtonIcon = SearchButtonIcon; if (IsAutoClearAfterSearch) { - Value = ""; + _displayText = ""; } if (IsOnInputTrigger == false) { @@ -174,12 +182,22 @@ private async Task OnClearClick() { if (OnClear != null) { - await OnClear(Value); + await OnClear(_displayText); } - CurrentValue = ""; + _displayText = ""; FilterItems = []; } + private string? GetDisplayText(TValue item) + { + var displayText = item?.ToString(); + if (OnGetDisplayText != null) + { + displayText = OnGetDisplayText(item); + } + return displayText; + } + /// /// TriggerOnChange 方法 /// @@ -188,7 +206,7 @@ private async Task OnClearClick() [JSInvokable] public async Task TriggerOnChange(string val, bool search = true) { - CurrentValue = val; + _displayText = val; if (search) { diff --git a/test/UnitTest/Components/AutoFillTest.cs b/test/UnitTest/Components/AutoFillTest.cs index b33e86fabfa..b04ebdda406 100644 --- a/test/UnitTest/Components/AutoFillTest.cs +++ b/test/UnitTest/Components/AutoFillTest.cs @@ -163,7 +163,7 @@ public void OnGetDisplayText_Ok() { pb.Add(a => a.Value, Model); pb.Add(a => a.Items, Items); - pb.Add(a => a.OnGetDisplayText, foo => foo.Name ?? ""); + pb.Add(a => a.OnGetDisplayText, foo => foo.Name); }); var input = cut.Find("input"); Assert.Equal("张三 1000", input.Attributes["value"]?.Value); diff --git a/test/UnitTest/Components/SearchTest.cs b/test/UnitTest/Components/SearchTest.cs index 2c874cc3531..0857063dcbc 100644 --- a/test/UnitTest/Components/SearchTest.cs +++ b/test/UnitTest/Components/SearchTest.cs @@ -10,7 +10,7 @@ public class SearchTest : BootstrapBlazorTestBase [Fact] public void Items_Ok() { - var cut = Context.RenderComponent(); + var cut = Context.RenderComponent>(); Assert.Contains("
() { "test1", "test2" }; - var cut = Context.RenderComponent(pb => + var items = new List() { new() { Name = "test1", Address = "Address 1" }, new() { Name = "test2", Address = "Address 2" } }; + var cut = Context.RenderComponent>(pb => { pb.Add(a => a.ItemTemplate, item => builder => { - builder.AddContent(0, $"Template-{item}"); + builder.AddContent(0, $"Template-{item.Name}-{item.Address}"); }); pb.Add(a => a.OnSearch, async v => { @@ -43,14 +43,35 @@ public async Task ItemTemplate_Ok() await cut.InvokeAsync(() => cut.Instance.TriggerOnChange("t")); await Task.Delay(20); - Assert.Contains("Template-test1", cut.Markup); - Assert.Contains("Template-test2", cut.Markup); + Assert.Contains("Template-test1-Address 1", cut.Markup); + Assert.Contains("Template-test2-Address 2", cut.Markup); + } + + [Fact] + public async Task OnGetDisplayText_Ok() + { + var items = new List() { null, new() { Name = "test1", Address = "Address 1" }, new() { Name = "test2", Address = "Address 2" } }; + var cut = Context.RenderComponent>(pb => + { + pb.Add(a => a.OnSearch, async v => + { + await Task.Delay(1); + return items; + }); + pb.Add(a => a.OnGetDisplayText, foo => foo?.Name); + }); + + await cut.InvokeAsync(() => cut.Instance.TriggerOnChange("t")); + await Task.Delay(20); + + Assert.Contains("test1", cut.Markup); + Assert.Contains("test2", cut.Markup); } [Fact] public void IsOnInputTrigger_Ok() { - var cut = Context.RenderComponent(builder => + var cut = Context.RenderComponent>(builder => { builder.Add(s => s.IsOnInputTrigger, true); }); @@ -65,7 +86,7 @@ public async Task OnSearchClick_Ok() { string? val = null; var items = new List() { "test1", "test2" }; - var cut = Context.RenderComponent(builder => + var cut = Context.RenderComponent>(builder => { builder.Add(s => s.SearchButtonIcon, "fa-fw fa-solid fa-magnifying-glass"); builder.Add(s => s.SearchButtonText, "SearchText"); @@ -82,10 +103,6 @@ public async Task OnSearchClick_Ok() var menus = cut.FindAll(".dropdown-item"); Assert.Single(menus); - await cut.InvokeAsync(() => cut.Instance.TriggerOnChange("test")); - Assert.Equal("test", val); - Assert.Empty(cut.Instance.Value); - var button = cut.Find(".fa-magnifying-glass"); await cut.InvokeAsync(() => button.Click()); await Task.Delay(10); @@ -98,7 +115,7 @@ public async Task OnSearchClick_Ok() public async Task OnClearClick_Ok() { var ret = false; - var cut = Context.RenderComponent(builder => + var cut = Context.RenderComponent>(builder => { builder.Add(s => s.Value, "1"); builder.Add(s => s.ShowClearButton, true);