Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
652cadb
refactor: multi-select 支持虚拟滚动
ArgoZhang Mar 18, 2025
1b18d30
doc: 更新示例
ArgoZhang Mar 18, 2025
cb728b8
refactor: 增加固定搜索功能
ArgoZhang Mar 18, 2025
b0413ca
doc: 更新示例
ArgoZhang Mar 18, 2025
6a9c30b
refactor: 清除缓存
ArgoZhang Mar 18, 2025
52690f0
refactor: 更新数据初始化逻辑
ArgoZhang Mar 18, 2025
5dd08b2
doc: 更新示例
ArgoZhang Mar 18, 2025
5ac38d9
refactor: 增加 cls 样式
ArgoZhang Mar 18, 2025
b77e14d
style: 调整样式
ArgoZhang Mar 18, 2025
7eb864b
refactor: 移除重复参数
ArgoZhang Mar 18, 2025
680f8b4
test: 更新单元测试
ArgoZhang Mar 18, 2025
eebd3c4
refactor: 更新 Clear 图标
ArgoZhang Mar 18, 2025
a3012a3
test: 更新单元测试
ArgoZhang Mar 18, 2025
b64dfc9
test: 增加单元测试
ArgoZhang Mar 18, 2025
9db4965
test: 增加单元测试
ArgoZhang Mar 18, 2025
4b19a56
test: 增加单元测试
ArgoZhang Mar 18, 2025
9e4001d
doc: 更新示例
ArgoZhang Mar 18, 2025
9cd93dd
Revert "refactor: 更新 Clear 图标"
ArgoZhang Mar 18, 2025
362c985
feat: 增加 ClearableIcon 参数
ArgoZhang Mar 18, 2025
5314607
test: 增加单元测试
ArgoZhang Mar 18, 2025
b97582a
doc: 更新文档
ArgoZhang Mar 18, 2025
7b5a6ed
doc: 更新文档注释
ArgoZhang Mar 18, 2025
6942b30
style: 更新样式
ArgoZhang Mar 18, 2025
4334e4d
refactor: 移动方法到基类
ArgoZhang Mar 18, 2025
775db79
refactor: 重构 OnClearValue 方法
ArgoZhang Mar 18, 2025
f0f8866
test: 更新单元测试
ArgoZhang Mar 18, 2025
dc8186b
refactor: 移动到基类
ArgoZhang Mar 18, 2025
7c9cee0
refactor: 移动 _virtualizeElement 到基类
ArgoZhang Mar 18, 2025
ff5b664
refactor: 移动方法到基类
ArgoZhang Mar 18, 2025
df4fde0
refactor: 更新虚拟滚动逻辑
ArgoZhang Mar 18, 2025
d5d507f
refactor: 移动 IsEditable 到基类
ArgoZhang Mar 18, 2025
cead831
doc: 增加注释
ArgoZhang Mar 18, 2025
1aaf364
refactor: 移动到基类
ArgoZhang Mar 18, 2025
fada63b
refactor: 移动到基类
ArgoZhang Mar 18, 2025
97798f2
refactor: 移动到基类
ArgoZhang Mar 18, 2025
9fa331b
refactor: 复用代码
ArgoZhang Mar 18, 2025
d7a7dd2
refactor: 更改为虚类
ArgoZhang Mar 18, 2025
24f41b1
refactor: 移除 DefaultVirtualizeItemText 参数
ArgoZhang Mar 18, 2025
9023cb8
test: 更新单元测试
ArgoZhang Mar 18, 2025
2f0d6e2
chore: bump version 9.5.0-beta02
ArgoZhang Mar 18, 2025
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
62 changes: 60 additions & 2 deletions src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor
Original file line number Diff line number Diff line change
Expand Up @@ -123,18 +123,24 @@ private enum MultiSelectEnumFoo
<Checkbox @bind-Value="@_isFixedSearch" />
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsClearable" />
<Checkbox @bind-Value="@_isClearable" />
</BootstrapInputGroup>
</div>
</div>
</section>

<div class="row g-3">
<div class="col-12 col-sm-6">
<MultiSelect Items="@Items" @bind-Value="@SelectedSearchItemsValue"
ShowSearch="true" IsFixedSearch="_isFixedSearch" OnSearchTextChanged="@OnSearch">
ShowSearch="true" IsFixedSearch="_isFixedSearch" IsClearable="_isClearable" OnSearchTextChanged="@OnSearch">
</MultiSelect>
</div>
<div class="col-12 col-sm-6">
<MultiSelect Items="@LongItems" @bind-Value="@SelectedMaxItemsValue"
ShowSearch="true" IsFixedSearch="_isFixedSearch">
ShowSearch="true" IsFixedSearch="_isFixedSearch" IsClearable="_isClearable">
</MultiSelect>
</div>
</div>
Expand Down Expand Up @@ -295,6 +301,58 @@ private enum MultiSelectEnumFoo
</div>
</DemoBlock>

<DemoBlock Title="@Localizer["MultiSelectVirtualizeTitle"]"
Introduction="@Localizer["MultiSelectVirtualizeIntro"]"
Name="IsVirtualize">
<section ignore>
<p>@((MarkupString)Localizer["MultiSelectVirtualizeDescription"].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 class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsClearable" />
<Checkbox @bind-Value="@_isClearable" />
</BootstrapInputGroup>
</div>
</div>
</section>

<p class="code-label">1. 使用 OnQueryAsync 作为数据源</p>
<div class="row mb-3">
<div class="col-6">
<MultiSelect IsVirtualize="true" @bind-Value="_virtualItemString1" OnQueryAsync="OnQueryAsync"
ShowSearch="_showSearch" IsFixedSearch="_isFixedSearch" IsClearable="_isClearable">
</MultiSelect>
</div>
<div class="col-6">
<Display TValue="string" Value="@_virtualItemString1"></Display>
</div>
</div>

<p class="code-label">2. 使用 Items 作为数据源</p>
<div class="row">
<div class="col-6">
<MultiSelect IsVirtualize="true" @bind-Value="_virtualItemString2" Items="VirtualItems"
ShowSearch="_showSearch" IsFixedSearch="_isFixedSearch" IsClearable="_isClearable">
</MultiSelect>
</div>
<div class="col-6">
<Display TValue="string" Value="@_virtualItemString2"></Display>
</div>
</div>
</DemoBlock>

<AttributeTable Items="@GetAttributes()"></AttributeTable>

<EventTable Items="@GetEvents()"></EventTable>
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ namespace BootstrapBlazor.Server.Components.Samples;
/// </summary>
public partial class MultiSelects
{
[Inject]
[NotNull]
private IStringLocalizer<Foo>? LocalizerFoo { get; set; }

/// <summary>
/// Foo 类为Demo测试用,如有需要请自行下载源码查阅
/// Foo class is used for Demo tet, please download the source code if necessary
Expand Down Expand Up @@ -106,9 +110,20 @@ private enum MultiSelectEnumFoo

private List<SelectedItem> CascadingItems1 { get; set; } = [];

[NotNull]
private List<Foo>? Foos { get; set; }

private string? _virtualItemString1;

private string? _virtualItemString2;

private IEnumerable<SelectedItem> VirtualItems => Foos.Select(i => new SelectedItem(i.Name!, i.Name!)).ToList();

private string? _editString;

private bool _isFixedSearch;
private bool _isClearable;
private bool _showSearch;

private async Task<SelectedItem> OnEditCallback(string value)
{
Expand All @@ -123,6 +138,21 @@ private async Task<SelectedItem> OnEditCallback(string value)
return item;
}

private async Task<QueryData<SelectedItem>> OnQueryAsync(VirtualizeQueryOption option)
{
await Task.Delay(200);
var items = Foos;
if (!string.IsNullOrEmpty(option.SearchText))
{
items = [.. Foos.Where(i => i.Name!.Contains(option.SearchText, StringComparison.OrdinalIgnoreCase))];
}
return new QueryData<SelectedItem>
{
Items = items.Skip(option.StartIndex).Take(option.Count).Select(i => new SelectedItem(i.Name!, i.Name!)),
TotalCount = items.Count
};
}

private SelectedItem[] GroupItems { get; } =
[
new("Jilin", "吉林") { GroupName = "东北"},
Expand All @@ -142,7 +172,7 @@ private async Task<SelectedItem> OnEditCallback(string value)
];

/// <summary>
/// OnInitialized
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
Expand Down Expand Up @@ -183,6 +213,9 @@ protected override void OnInitialized()
LongItems = GenerateDataSource(LongDataSource);

Items = GenerateDataSource(DataSource);
Foos = Foo.GenerateFoo(LocalizerFoo);
_virtualItemString1 = Foos[79].Name;
_virtualItemString2 = Foos[45].Name;
}

private static List<SelectedItem> GenerateItems() =>
Expand All @@ -198,7 +231,7 @@ private static List<SelectedItem> GenerateItems() =>
new ("Lianyungang", "连云港")
];

private static List<SelectedItem> GenerateDataSource(List<SelectedItem> source) => source.Select(i => new SelectedItem(i.Value, i.Text)).ToList();
private static List<SelectedItem> GenerateDataSource(List<SelectedItem> source) => [.. source.Select(i => new SelectedItem(i.Value, i.Text))];

private void AddItems()
{
Expand Down
14 changes: 11 additions & 3 deletions src/BootstrapBlazor.Server/Components/Samples/Selects.razor
Original file line number Diff line number Diff line change
Expand Up @@ -433,13 +433,19 @@
Name="IsVirtualize">
<section ignore>
<p>@((MarkupString)Localizer["SelectsVirtualizeDescription"].Value)</p>
<div class="row mb-3">
<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 class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsClearable" />
Expand All @@ -452,7 +458,8 @@
<p class="code-label">1. 使用 OnQueryAsync 作为数据源</p>
<div class="row mb-3">
<div class="col-6">
<Select IsVirtualize="true" OnQueryAsync="OnQueryAsync" @bind-Value="VirtualItem1" ShowSearch="_showSearch" IsClearable="_isClearable"></Select>
<Select IsVirtualize="true" OnQueryAsync="OnQueryAsync" @bind-Value="VirtualItem1"
ShowSearch="_showSearch" IsFixedSearch="_isFixedSearch" IsClearable="_isClearable"></Select>
</div>
<div class="col-6">
<Display TValue="string" Value="@VirtualItem1?.Text"></Display>
Expand All @@ -462,7 +469,8 @@
<p class="code-label">2. 使用 Items 作为数据源</p>
<div class="row">
<div class="col-6">
<Select IsVirtualize="true" Items="VirtualItems" @bind-Value="VirtualItem2" ShowSearch="_showSearch" IsClearable="_isClearable"></Select>
<Select IsVirtualize="true" Items="VirtualItems" @bind-Value="VirtualItem2"
ShowSearch="_showSearch" IsFixedSearch="_isFixedSearch" IsClearable="_isClearable"></Select>
</div>
<div class="col-6">
<Display TValue="string" Value="@VirtualItem2?.Text"></Display>
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.5.0-beta01</Version>
<Version>9.5.0-beta02</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
85 changes: 64 additions & 21 deletions src/BootstrapBlazor/Components/Select/MultiSelect.razor
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@namespace BootstrapBlazor.Components
@using Microsoft.AspNetCore.Components.Web.Virtualization
@typeparam TValue
@inherits SelectBase<TValue>
@inherits SimpleSelectBase<TValue>
@attribute [BootstrapModuleAutoLoader("Select/MultiSelect.razor.js", JSObjectReference = true)]

@if (IsShowLabel)
Expand Down Expand Up @@ -48,6 +49,10 @@
<span class="@AppendClassString"><i class="@DropdownIcon"></i></span>
}
</div>
@if (GetClearable())
{
<span class="@ClearClassString" @onclick="OnClearValue"><i class="@ClearableIcon"></i></span>
}
<div class="@DropdownMenuClassString">
@if (ShowSearch)
{
Expand All @@ -57,7 +62,23 @@
<i class="@SearchLoadingIconString"></i>
</div>
}
@if (Rows.Count == 0)
@if (IsVirtualize)
{
<div class="dropdown-menu-body dropdown-virtual">
@if (OnQueryAsync == null)
{
<Virtualize ItemSize="RowHeight" OverscanCount="OverscanCount" Items="@GetVirtualItems()" ChildContent="RenderRow">
</Virtualize>
}
else
{
<Virtualize ItemSize="RowHeight" OverscanCount="OverscanCount" ItemsProvider="LoadItems"
Placeholder="RenderPlaceHolderRow" ItemContent="RenderRow" @ref="_virtualizeElement">
</Virtualize>
}
</div>
}
else if (Rows.Count == 0)
{
<div class="dropdown-item">@NoSearchDataText</div>
}
Expand Down Expand Up @@ -91,28 +112,50 @@
}
@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>
@RenderRow(item)
}

if (!string.IsNullOrEmpty(itemGroup.Key))
{
if (GroupItemTemplate != null)
{
@GroupItemTemplate(itemGroup.Key)
}
else
{
<Divider Text="@itemGroup.Key" />
}
}
}
</div>
}
</div>
</div>

@code {
RenderFragment<SelectedItem> RenderRow => item =>
@<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>;

RenderFragment<PlaceholderContext> RenderPlaceHolderRow => context =>
@<div class="dropdown-item">
<div class="is-ph"></div>
</div>;
}
Loading