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
4 changes: 2 additions & 2 deletions src/BootstrapBlazor.Server/Components/Pages/Coms.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public sealed partial class Coms

private string? SearchText { get; set; }

private Task<IEnumerable<string>> OnSearch(string searchText)
private Task<IEnumerable<string?>> OnSearch(string searchText)
{
SearchText = searchText;
return Task.FromResult<IEnumerable<string>>(ComponentItems.Where(i => i.Contains(searchText, StringComparison.OrdinalIgnoreCase)));
return Task.FromResult<IEnumerable<string?>>(ComponentItems.Where(i => i.Contains(searchText, StringComparison.OrdinalIgnoreCase)).ToList());
}
}
12 changes: 6 additions & 6 deletions src/BootstrapBlazor.Server/Components/Samples/AutoFills.razor
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
@((MarkupString)@Localizer["NormalDesc"].Value)
</section>
<AutoFill @bind-Value="Model1" Items="Items1" IsLikeMatch="true" OnGetDisplayText="OnGetDisplayText" class="mb-3" IsSelectAllTextOnFocus="true">
<Template>
<ItemTemplate>
<div class="d-flex">
<div>
<img src="@WebsiteOption.CurrentValue.GetAvatarUrl(context.Id)" class="bb-avatar" />
Expand All @@ -20,7 +20,7 @@
<div class="bb-sub">@Foo.GetTitle(context.Id)</div>
</div>
</div>
</Template>
</ItemTemplate>
</AutoFill>
<section ignore>
<img src="@WebsiteOption.CurrentValue.GetAvatarUrl(Model1.Id)" class="shadow" style="width: 140px; margin-bottom: 1rem; border-radius: 6px;" />
Expand All @@ -32,7 +32,7 @@
<section ignore>@((MarkupString)Localizer["CustomFilterDesc"].Value)</section>
<AutoFill @bind-Value="Model2" Items="Items2" OnCustomFilter="OnCustomFilter"
OnGetDisplayText="OnGetDisplayText" class="mb-3">
<Template>
<ItemTemplate>
<div class="d-flex">
<div>
<img src="@WebsiteOption.CurrentValue.GetAvatarUrl(context.Id)" class="bb-avatar" />
Expand All @@ -42,7 +42,7 @@
<div class="bb-sub">@Foo.GetTitle(context.Id)</div>
</div>
</div>
</Template>
</ItemTemplate>
</AutoFill>
<section ignore>
<EditorForm Model="@Model2" RowType="RowType.Inline" ItemsPerRow="2" />
Expand All @@ -54,7 +54,7 @@
@((MarkupString)@Localizer["ShowDropdownListOnFocusDesc"].Value)
</section>
<AutoFill @bind-Value="Model3" Items="Items3" ShowDropdownListOnFocus="false" OnGetDisplayText="OnGetDisplayText" class="mb-3">
<Template>
<ItemTemplate>
<div class="d-flex">
<div>
<img src="@WebsiteOption.CurrentValue.GetAvatarUrl(context.Id)" class="bb-avatar" />
Expand All @@ -64,7 +64,7 @@
<div class="bb-sub">@Foo.GetTitle(context.Id)</div>
</div>
</div>
</Template>
</ItemTemplate>
</AutoFill>
<section ignore>
<EditorForm Model="@Model3" RowType="RowType.Inline" ItemsPerRow="2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="AutoFill" />
<AutoFill TValue="Foo" Items="AufoFillItems" IsLikeMatch="true" OnGetDisplayText="@(foo => foo.Name ?? "")">
<Template>
<ItemTemplate>
<div class="d-flex">
<div>
<img src="@WebsiteOption.CurrentValue.GetAvatarUrl(context.Id)" class="bb-avatar" />
Expand All @@ -99,7 +99,7 @@
<div class="bb-sub">@Foo.GetTitle(context.Id)</div>
</div>
</div>
</Template>
</ItemTemplate>
</AutoFill>
</BootstrapInputGroup>
</div>
Expand Down
12 changes: 12 additions & 0 deletions src/BootstrapBlazor.Server/Components/Samples/Searches.razor
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@
<ConsoleLogger @ref="DisplayLogger" />
</DemoBlock>

<DemoBlock Title="@Localizer["SearchesItemTemplateTitle"]"
Introduction="@Localizer["SearchesItemTemplateIntro"]"
Name="ItemTemplate">
<Search PlaceHolder="@Localizer["SearchesPlaceHolder"]"
OnSearch="@OnSearchFoo">
<ItemTemplate>
<div>@context.Name</div>
<div>@context.Address</div>
</ItemTemplate>
</Search>
</DemoBlock>

<DemoBlock Title="@Localizer["SearchesKeyboardsTitle"]"
Introduction="@Localizer["SearchesKeyboardsIntro"]"
Name="keyboards">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ private Task<IEnumerable<string>> OnKeyboardSearch(string searchText)

private Foo Model { get; set; } = new Foo() { Name = "" };

private static async Task<IEnumerable<Foo>> OnSearchFoo(string searchText)
{
await Task.Delay(100);
return Enumerable.Range(1, 10).Select(i => new Foo() { Name = $"{searchText}-{i}", Address = $"Address - 10{i}" }).ToList();
}

/// <summary>
/// 获得属性方法
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ public partial class AutoFill<TValue>
[Parameter]
public Func<string, Task<IEnumerable<TValue>>>? OnCustomFilter { get; set; }

/// <summary>
/// 获得/设置 候选项模板 默认 null
/// </summary>
[Parameter]
[Obsolete("已弃用,请使用 ItemTemplate 代替;Deprecated please use ItemTemplate parameter")]
[ExcludeFromCodeCoverage]
public RenderFragment<TValue>? Template { get => ItemTemplate; set => ItemTemplate = value; }

[Inject]
[NotNull]
private IStringLocalizer<AutoComplete>? Localizer { get; set; }
Expand Down
5 changes: 3 additions & 2 deletions src/BootstrapBlazor/Components/Search/Search.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@namespace BootstrapBlazor.Components
@inherits PopoverCompleteBase<string>
@typeparam TValue
@inherits PopoverCompleteBase<TValue>

<div @attributes="@AdditionalAttributes" class="@ClassString" id="@Id">
<div class="input-group">
Expand Down Expand Up @@ -27,7 +28,7 @@
}
else
{
<div>@item</div>
<div>@GetDisplayText(item)</div>
}
</li>
}
Expand Down
38 changes: 28 additions & 10 deletions src/BootstrapBlazor/Components/Search/Search.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace BootstrapBlazor.Components;
/// <summary>
/// Search 组件
/// </summary>
public partial class Search
public partial class Search<TValue>
{
/// <summary>
/// 获得/设置 是否显示清除按钮 默认为 false 不显示
Expand Down Expand Up @@ -77,17 +77,24 @@ public partial class Search
/// 获得/设置 点击搜索按钮时回调委托
/// </summary>
[Parameter]
public Func<string, Task<IEnumerable<string>>>? OnSearch { get; set; }
public Func<string, Task<IEnumerable<TValue>>>? OnSearch { get; set; }

/// <summary>
/// 获得/设置 通过模型获得显示文本方法 默认使用 ToString 重载方法
/// </summary>
[Parameter]
[NotNull]
public Func<TValue, string?>? OnGetDisplayText { get; set; }

/// <summary>
/// 获得/设置 点击清空按钮时回调委托
/// </summary>
[Parameter]
public Func<string, Task>? OnClear { get; set; }
public Func<string?, Task>? OnClear { get; set; }

[Inject]
[NotNull]
private IStringLocalizer<Search>? Localizer { get; set; }
private IStringLocalizer<Search<TValue>>? Localizer { get; set; }

/// <summary>
/// <inheritdoc/>
Expand All @@ -105,7 +112,7 @@ public partial class Search
/// 获得/设置 UI 呈现数据集合
/// </summary>
[NotNull]
private List<string>? FilterItems { get; set; }
private List<TValue>? FilterItems { get; set; }

/// <summary>
/// <inheritdoc/>
Expand Down Expand Up @@ -139,6 +146,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
}
}

private string _displayText = "";
private bool _show;
/// <summary>
/// 点击搜索按钮时触发此方法
Expand All @@ -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)
{
Expand All @@ -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;
}

/// <summary>
/// TriggerOnChange 方法
/// </summary>
Expand All @@ -188,7 +206,7 @@ private async Task OnClearClick()
[JSInvokable]
public async Task TriggerOnChange(string val, bool search = true)
{
CurrentValue = val;
_displayText = val;

if (search)
{
Expand Down
2 changes: 1 addition & 1 deletion test/UnitTest/Components/AutoFillTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
43 changes: 30 additions & 13 deletions test/UnitTest/Components/SearchTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class SearchTest : BootstrapBlazorTestBase
[Fact]
public void Items_Ok()
{
var cut = Context.RenderComponent<Search>();
var cut = Context.RenderComponent<Search<string>>();
Assert.Contains("<div class=\"search auto-complete\"", cut.Markup);
var menus = cut.FindAll(".dropdown-item");
Assert.Single(menus);
Expand All @@ -26,12 +26,12 @@ public void Items_Ok()
[Fact]
public async Task ItemTemplate_Ok()
{
var items = new List<string>() { "test1", "test2" };
var cut = Context.RenderComponent<Search>(pb =>
var items = new List<Foo>() { new() { Name = "test1", Address = "Address 1" }, new() { Name = "test2", Address = "Address 2" } };
var cut = Context.RenderComponent<Search<Foo>>(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 =>
{
Expand All @@ -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<Foo?>() { null, new() { Name = "test1", Address = "Address 1" }, new() { Name = "test2", Address = "Address 2" } };
var cut = Context.RenderComponent<Search<Foo?>>(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<Search>(builder =>
var cut = Context.RenderComponent<Search<string>>(builder =>
{
builder.Add(s => s.IsOnInputTrigger, true);
});
Expand All @@ -65,7 +86,7 @@ public async Task OnSearchClick_Ok()
{
string? val = null;
var items = new List<string>() { "test1", "test2" };
var cut = Context.RenderComponent<Search>(builder =>
var cut = Context.RenderComponent<Search<string>>(builder =>
{
builder.Add(s => s.SearchButtonIcon, "fa-fw fa-solid fa-magnifying-glass");
builder.Add(s => s.SearchButtonText, "SearchText");
Expand All @@ -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);
Expand All @@ -98,7 +115,7 @@ public async Task OnSearchClick_Ok()
public async Task OnClearClick_Ok()
{
var ret = false;
var cut = Context.RenderComponent<Search>(builder =>
var cut = Context.RenderComponent<Search<string>>(builder =>
{
builder.Add(s => s.Value, "1");
builder.Add(s => s.ShowClearButton, true);
Expand Down
Loading