Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
39 changes: 35 additions & 4 deletions src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,41 @@ private enum MultiSelectEnumFoo
</DemoBlock>

<DemoBlock Title="@Localizer["MultiSelectSearchTitle"]" Introduction="@Localizer["MultiSelectSearchIntro"]" Name="Search">
<section ignore>@((MarkupString)Localizer["MultiSelectSearchDescription"].Value)</section>
<MultiSelect Items="@Items" @bind-Value="@SelectedSearchItemsValue" ShowSearch="true" OnSearchTextChanged="@OnSearch"></MultiSelect>
<section ignore>@SelectedSearchItemsValue</section>
<ConsoleLogger @ref="Logger"></ConsoleLogger>
<section ignore>
<p>@((MarkupString)Localizer["MultiSelectSearchDescription"].Value)</p>
<div class="row g-3">
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowSearch" />
<Checkbox @bind-Value="@_showSearch" />
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsFixedSearch" />
<Checkbox @bind-Value="@_isFixedSearch" />
</BootstrapInputGroup>
</div>
</div>
</section>

<div class="row g-3">
<div class="col-12 col-sm-6">
<MultiSelect Items="@Items" @bind-Value="@SelectedSearchItemsValue"
ShowSearch="_showSearch" IsFixedSearch="_isFixedSearch" OnSearchTextChanged="@OnSearch">
</MultiSelect>
</div>
<div class="col-12 col-sm-6">
<MultiSelect Items="@LongItems" @bind-Value="@SelectedMaxItemsValue"
ShowSearch="_showSearch" IsFixedSearch="_isFixedSearch">
</MultiSelect>
</div>
</div>

<section ignore>
<p>@SelectedSearchItemsValue</p>
<ConsoleLogger @ref="Logger"></ConsoleLogger>
</section>
</DemoBlock>

<DemoBlock Title="@Localizer["MultiSelectGroupTitle"]" Introduction="@Localizer["MultiSelectGroupIntro"]" Name="Group">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ private enum MultiSelectEnumFoo

private string? _editString;

private bool _isFixedSearch;

private bool _showSearch;

private async Task<SelectedItem> OnEditCallback(string value)
{
await Task.Delay(100);
Expand Down
98 changes: 53 additions & 45 deletions src/BootstrapBlazor/Components/Select/MultiSelect.razor
Original file line number Diff line number Diff line change
Expand Up @@ -48,63 +48,71 @@
<span class="@AppendClassString"><i class="@DropdownIcon"></i></span>
}
</div>
<div class="dropdown-menu">
<div class="@SearchClassString">
<input type="text" class="form-control search-text" autocomplete="off" value="@SearchText" aria-label="search" />
<i class="@SearchIconString"></i>
<i class="@SearchLoadingIconString"></i>
</div>
@if (ShowToolbar)
<div class="@DropdownMenuClassString">
@if (ShowSearch)
{
<div class="toolbar">
@if (ShowDefaultButtons)
{
<DynamicElement TagName="button" type="button" class="btn" OnClick="SelectAll">@SelectAllText</DynamicElement>
<DynamicElement TagName="button" type="button" class="btn" OnClick="InvertSelect">@ReverseSelectText</DynamicElement>
<DynamicElement TagName="button" type="button" class="btn" OnClick="Clear">@ClearText</DynamicElement>
}
@ButtonTemplate
<div class="dropdown-menu-search">
<input type="text" class="search-text form-control" autocomplete="off" value="@SearchText" aria-label="search" />
<i class="@SearchIconString"></i>
<i class="@SearchLoadingIconString"></i>
</div>
}
@foreach (var itemGroup in Rows.GroupBy(i => i.GroupName))
@if (Rows.Count == 0)
{
if (!string.IsNullOrEmpty(itemGroup.Key))
{
if (GroupItemTemplate != null)
{
@GroupItemTemplate(itemGroup.Key)
}
else
<div class="dropdown-item">@NoSearchDataText</div>
}
else
{
<div class="dropdown-menu-body">
@if (ShowToolbar)
{
<Divider Text="@itemGroup.Key" />
}
}
@foreach (var item in itemGroup)
{
<DynamicElement OnClick="() => ToggleRow(item.Value)" TriggerClick="@CheckCanTrigger(item)" class="@GetItemClassString(item)">
<div class="multi-select-item">
<div class="form-check">
<input class="form-check-input" type="checkbox" disabled="@CheckCanSelect(item)" checked="@GetCheckedString(item)" />
</div>
@if (ItemTemplate != null)
<div class="toolbar">
@if (ShowDefaultButtons)
{
@ItemTemplate(item)
<DynamicElement TagName="button" type="button" class="btn" OnClick="SelectAll">@SelectAllText</DynamicElement>
<DynamicElement TagName="button" type="button" class="btn" OnClick="InvertSelect">@ReverseSelectText</DynamicElement>
<DynamicElement TagName="button" type="button" class="btn" OnClick="Clear">@ClearText</DynamicElement>
}
else if (IsMarkupString)
@ButtonTemplate
</div>
}
@foreach (var itemGroup in Rows.GroupBy(i => i.GroupName))
{
if (!string.IsNullOrEmpty(itemGroup.Key))
{
if (GroupItemTemplate != null)
{
@((MarkupString)item.Text)
@GroupItemTemplate(itemGroup.Key)
}
else
{
<span>@item.Text</span>
<Divider Text="@itemGroup.Key" />
}
</div>
</DynamicElement>
}
}
@if (Rows.Count == 0)
{
<div class="dropdown-item">@NoSearchDataText</div>
}
@foreach (var item in itemGroup)
{
<DynamicElement OnClick="() => ToggleRow(item.Value)" TriggerClick="@CheckCanTrigger(item)" class="@GetItemClassString(item)">
<div class="multi-select-item">
<div class="form-check">
<input class="form-check-input" type="checkbox" disabled="@CheckCanSelect(item)" checked="@GetCheckedString(item)" />
</div>
@if (ItemTemplate != null)
{
@ItemTemplate(item)
}
else if (IsMarkupString)
{
@((MarkupString)item.Text)
}
else
{
<span>@item.Text</span>
}
</div>
</DynamicElement>
}
}
</div>
}
</div>
</div>
7 changes: 0 additions & 7 deletions src/BootstrapBlazor/Components/Select/MultiSelect.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,6 @@ public partial class MultiSelect<TValue>
.AddClass("d-none", SelectedItems.Count != 0)
.Build();

private string? SearchClassString => CssBuilder.Default("search")
.AddClass("show", ShowSearch)
.Build();

/// <summary>
/// 获得 SearchLoadingIcon 图标字符串
/// </summary>
private string? SearchLoadingIconString => CssBuilder.Default("icon searching-icon")
.AddClass(SearchLoadingIcon)
.Build();
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/Components/Select/Select.razor
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
@if (ShowSearch)
{
<div class="dropdown-menu-search">
<input type="text" class="search-text form-control" autocomplete="off" value="@SearchText" aria-label="search">
<input type="text" class="search-text form-control" autocomplete="off" value="@SearchText" aria-label="search" />
<i class="@SearchIconString"></i>
<i class="@SearchLoadingIconString"></i>
</div>
Expand Down
12 changes: 1 addition & 11 deletions src/BootstrapBlazor/Components/Select/Select.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ public partial class Select<TValue> : ISelect, ILookup
.AddClass(SearchLoadingIcon)
.Build();

private string? DropdownMenuClassString => CssBuilder.Default("dropdown-menu")
.AddClass("is-fixed-search", ShowSearch && IsFixedSearch)
.Build();

private readonly List<SelectedItem> _children = [];

private string? ScrollIntoViewBehaviorString => ScrollIntoViewBehavior == ScrollIntoViewBehavior.Smooth ? null : ScrollIntoViewBehavior.ToDescriptionString();
Expand All @@ -68,12 +64,6 @@ public partial class Select<TValue> : ISelect, ILookup
[Parameter]
public Func<string, IEnumerable<SelectedItem>>? OnSearchTextChanged { get; set; }

/// <summary>
/// Gets or sets whether the search bar in the dropdown is fixed. Default is false.
/// </summary>
[Parameter]
public bool IsFixedSearch { get; set; }

/// <summary>
/// Gets or sets whether the select component is editable. Default is false.
/// </summary>
Expand Down Expand Up @@ -106,7 +96,7 @@ public partial class Select<TValue> : ISelect, ILookup
public RenderFragment<SelectedItem?>? DisplayTemplate { get; set; }

/// <summary>
/// Gets or sets whether virtual scrolling is enabled. Default is false. Note: When virtual scrolling is enabled, <see cref="SelectBase{TValue}.ShowSearch"/>, <see cref="PopoverSelectBase{TValue}.IsPopover"/>, and <seealso cref="IsFixedSearch"/> are not supported. Set <see cref="DefaultVirtualizeItemText"/> when setting initial values.
/// Gets or sets whether virtual scrolling is enabled. Default is false.
/// </summary>
[Parameter]
public bool IsVirtualize { get; set; }
Expand Down
13 changes: 13 additions & 0 deletions src/BootstrapBlazor/Components/Select/SelectBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public abstract class SelectBase<TValue> : PopoverSelectBase<TValue>
[Parameter]
public bool ShowSearch { get; set; }

/// <summary>
/// Gets or sets whether the search bar in the dropdown is fixed. Default is false.
/// </summary>
[Parameter]
public bool IsFixedSearch { get; set; }

/// <summary>
/// Gets or sets the search icon.
/// </summary>
Expand Down Expand Up @@ -115,6 +121,13 @@ public abstract class SelectBase<TValue> : PopoverSelectBase<TValue>
.AddClass($"text-danger", IsValid.HasValue && !IsValid.Value)
.Build();

/// <summary>
/// Gets the dropdown menu class string.
/// </summary>
protected string? DropdownMenuClassString => CssBuilder.Default("dropdown-menu")
.AddClass("is-fixed-search", ShowSearch && IsFixedSearch)
.Build();

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down
23 changes: 13 additions & 10 deletions src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@
{
<span class="@ClearClassString" @onclick="OnClearValue"><i class="@ClearIcon"></i></span>
}
<div class="dropdown-menu">
<div class="@SearchClassString">
<input type="text" class="search-text form-control" autocomplete="off" value="@SearchText" aria-label="search">
<i class="@SearchIconString"></i>
<i class="@SearchLoadingIconString"></i>
</div>
<div class="@DropdownMenuClassString">
@if (ShowSearch)
{
<div class="dropdown-menu-search">
<input type="text" class="search-text form-control" autocomplete="off" value="@SearchText" aria-label="search" />
<i class="@SearchIconString"></i>
<i class="@SearchLoadingIconString"></i>
</div>
}
@if (IsVirtualize)
{
<div class="dropdown-virtual">
Expand All @@ -49,6 +52,10 @@
}
</div>
}
else if (Rows.Count == 0)
{
<div class="dropdown-item">@NoSearchDataText</div>
}
else
{
@foreach (var itemGroup in Rows.GroupBy(i => i.GroupName))
Expand All @@ -69,10 +76,6 @@
@RenderRow(item)
}
}
@if (Rows.Count == 0)
{
<div class="dropdown-item">@NoSearchDataText</div>
}
}
</div>
@if (!IsPopover)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.Extensions.Localization;
using System.ComponentModel.DataAnnotations;

namespace BootstrapBlazor.Components;

Expand Down Expand Up @@ -57,10 +55,6 @@ public partial class SelectGeneric<TValue> : ISelectGeneric<TValue>, IModelEqual
.AddClass("disabled", item.IsDisabled)
.Build();

private string? SearchClassString => CssBuilder.Default("search")
.AddClass("is-fixed", IsFixedSearch)
.Build();

private readonly List<SelectedItem<TValue>> _children = [];

/// <summary>
Expand All @@ -76,12 +70,6 @@ public partial class SelectGeneric<TValue> : ISelectGeneric<TValue>, IModelEqual
[Parameter]
public Func<string, IEnumerable<SelectedItem<TValue>>>? OnSearchTextChanged { get; set; }

/// <summary>
/// 获得/设置 是否固定下拉框中的搜索栏 默认 false
/// </summary>
[Parameter]
public bool IsFixedSearch { get; set; }

/// <summary>
/// 获得/设置 是否可编辑 默认 false
/// </summary>
Expand Down Expand Up @@ -121,7 +109,7 @@ public partial class SelectGeneric<TValue> : ISelectGeneric<TValue>, IModelEqual
public RenderFragment<SelectedItem<TValue>?>? DisplayTemplate { get; set; }

/// <summary>
/// 获得/设置 是否开启虚拟滚动 默认 false 未开启 注意:开启虚拟滚动后不支持 <see cref="SelectBase{TValue}.ShowSearch"/> <see cref="PopoverSelectBase{TValue}.IsPopover"/> <seealso cref="IsFixedSearch"/> 参数设置,设置初始值时请设置 <see cref="DefaultVirtualizeItemText"/>
/// 获得/设置 是否开启虚拟滚动 默认 false 未开启
/// </summary>
[Parameter]
public bool IsVirtualize { get; set; }
Expand Down Expand Up @@ -312,7 +300,7 @@ private List<SelectedItem<TValue>> GetRowsByItems()
private List<SelectedItem<TValue>> GetRowsBySearch()
{
var items = OnSearchTextChanged?.Invoke(SearchText) ?? FilterBySearchText(GetRowsByItems());
return items.ToList();
return [.. items];
}

private IEnumerable<SelectedItem<TValue>> FilterBySearchText(IEnumerable<SelectedItem<TValue>> source) => string.IsNullOrEmpty(SearchText)
Expand Down Expand Up @@ -363,7 +351,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
/// </summary>
private int TotalCount { get; set; }

private List<SelectedItem<TValue>> GetVirtualItems() => FilterBySearchText(GetRowsByItems()).ToList();
private List<SelectedItem<TValue>> GetVirtualItems() => [.. FilterBySearchText(GetRowsByItems())];

/// <summary>
/// 虚拟滚动数据加载回调方法
Expand Down
12 changes: 12 additions & 0 deletions test/UnitTest/Components/MultiSelectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,18 @@ public void ButtonTemplate_Ok()
pb.Add(a => a.ShowToolbar, true);
pb.Add(a => a.ButtonTemplate, builder => builder.AddContent(0, new MarkupString("<button class=\"btn-test\">ButtonTemplate</button>")));
});

// 没有数据不显示 Toolbar
Assert.DoesNotContain("btn-test", cut.Markup);

cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.Items, new List<SelectedItem>
{
new("1", "Test1"),
new("2", "Test2")
});
});
Assert.Contains("btn-test", cut.Markup);
}

Expand Down
2 changes: 1 addition & 1 deletion test/UnitTest/Components/SelectGenericTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ public void IsFixedSearch_Ok()
pb.Add(a => a.ShowSearch, true);
pb.Add(a => a.IsFixedSearch, true);
});
Assert.Contains("search is-fixed", cut.Markup);
Assert.Contains("dropdown-menu is-fixed-search", cut.Markup);
Assert.Contains("class=\"icon", cut.Markup);
}

Expand Down