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 @@ -351,6 +351,14 @@ private enum MultiSelectEnumFoo
</div>
</DemoBlock>

<DemoBlock Title="@Localizer["MultiSelectGenericTitle"]" Introduction="@Localizer["MultiSelectGenericIntro"]" Name="Generic">
<div class="row">
<div class="col-12">
<MultiSelectGeneric Items="@FooItems" @bind-Value="_genericValue" ShowSearch="true" IsPopover="true"></MultiSelectGeneric>
</div>
</div>
</DemoBlock>

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

<EventTable Items="@GetEvents()"></EventTable>
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ private enum MultiSelectEnumFoo
private bool _showToolbar = true;
private bool _showSearch = true;

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

private List<Foo>? _genericValue = null;

private async Task<SelectedItem> OnEditCallback(string value)
{
await Task.Delay(100);
Expand Down Expand Up @@ -188,6 +193,7 @@ protected override void OnInitialized()
Items8 = GenerateItems();
TemplateItems = GenerateItems();
EditableItems = GenerateItems();
FooItems = [.. Foo.GenerateFoo(LocalizerFoo).Select(i => new SelectedItem<Foo>(i, i.Name!))];

// 初始化数据
DataSource =
Expand Down
4 changes: 3 additions & 1 deletion src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -3088,7 +3088,9 @@
"MultiSelectVirtualizeDescription": "Component virtual scrolling supports two ways of providing data through <code>Items</code> or <code>OnQueryAsync</code> callback methods",
"MultiSelectsAttribute_ShowSearch": "Whether to display the search box",
"MultiSelectsAttribute_IsVirtualize": "Wether to enable virtualize",
"MultiSelectsAttribute_DefaultVirtualizeItemText": "The text string corresponding to the first load value when virtual scrolling is turned on is separated by commas"
"MultiSelectsAttribute_DefaultVirtualizeItemText": "The text string corresponding to the first load value when virtual scrolling is turned on is separated by commas",
"MultiSelectGenericTitle": "Generic",
"MultiSelectGenericIntro": "Data source <code>Items</code> supports generics when using <code>SelectedItem&lt;TValue&gt;</code>"
},
"BootstrapBlazor.Server.Components.Samples.Radios": {
"RadiosTitle": "Radio",
Expand Down
4 changes: 3 additions & 1 deletion src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -3088,7 +3088,9 @@
"MultiSelectVirtualizeDescription": "组件虚拟滚动支持两种形式通过 <code>Items</code> 或者 <code>OnQueryAsync</code> 回调方法提供数据",
"MultiSelectsAttribute_ShowSearch": "是否显示搜索框",
"MultiSelectsAttribute_IsVirtualize": "是否开启虚拟滚动",
"MultiSelectsAttribute_DefaultVirtualizeItemText": "开启虚拟滚动时首次加载 Value 对应的文本字符串用逗号分割"
"MultiSelectsAttribute_DefaultVirtualizeItemText": "开启虚拟滚动时首次加载 Value 对应的文本字符串用逗号分割",
"MultiSelectGenericTitle": "泛型支持",
"MultiSelectGenericIntro": "数据源 <code>Items</code> 使用 <code>SelectedItem&lt;TValue&gt;</code> 时即可支持泛型"
},
"BootstrapBlazor.Server.Components.Samples.Radios": {
"RadiosTitle": "Radio 单选框",
Expand Down
154 changes: 154 additions & 0 deletions src/BootstrapBlazor/Components/SelectGeneric/MultiSelectGeneric.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
@namespace BootstrapBlazor.Components
@using Microsoft.AspNetCore.Components.Web.Virtualization
@typeparam TValue
@inherits SelectBase<List<TValue>>
@attribute [BootstrapModuleAutoLoader("Select/MultiSelect.razor.js", JSObjectReference = true)]

@if (IsShowLabel)
{
<BootstrapLabel required="@Required" for="@Id" ShowLabelTooltip="ShowLabelTooltip" Value="@DisplayText"></BootstrapLabel>
}
<div @attributes="@AdditionalAttributes" class="@ClassString" id="@Id" data-bb-scroll-behavior="@ScrollIntoViewBehaviorString">
<div class="@ToggleClassString" data-bs-toggle="@ToggleString" data-bs-placement="@PlacementString" data-bs-offset="@OffsetString" data-bs-auto-close="outside" data-bs-custom-class="@CustomClassString" tabindex="0">
<div class="@PlaceHolderClassString">@PlaceHolder</div>
<div class="multi-select-items">
@if (DisplayTemplate != null)
{
@DisplayTemplate(SelectedItems)
}
else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Group dividers are rendered both before and after each group.

Rendering dividers both before and after each group can create duplicates. Consider rendering a divider only before each group except the first, or after each group except the last, to avoid unnecessary dividers.

{
foreach (var item in SelectedItems)
{
if (ShowCloseButton)
{
<div class="multi-select-item-group">
<DynamicElement TagName="span" class="multi-select-close"
TriggerClick="@(!IsPopover)" OnClick="() => ToggleRow(item)">
<i class="@CloseButtonIcon"></i>
</DynamicElement>
<span class="multi-select-item">@item.Text</span>
</div>
}
else
{
<span class="multi-select-item">@item.Text</span>
}
}
}
</div>
@if (!IsSingleLine)
{
<span class="@AppendClassString"><i class="@DropdownIcon"></i></span>
}
</div>
@if (GetClearable())
{
<span class="@ClearClassString" @onclick="OnClearValue"><i class="@ClearIcon"></i></span>
}
<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 (ShowToolbar)
{
<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>
}
@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>
}
else
{
<div class="dropdown-menu-body">
@foreach (var itemGroup in Rows.GroupBy(i => i.GroupName))
{
if (!string.IsNullOrEmpty(itemGroup.Key))
{
if (GroupItemTemplate != null)
{
@GroupItemTemplate(itemGroup.Key)
}
else
{
<Divider Text="@itemGroup.Key" />
}
}
@foreach (var item in itemGroup)
{
@RenderRow(item)
}

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

@code {
RenderFragment<SelectedItem<TValue>> RenderRow => item =>
@<DynamicElement OnClick="() => ToggleRow(item)" 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)" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The checked attribute is set as a string, which may cause issues.

Blazor checkboxes require a boolean for the 'checked' attribute. Update GetCheckedState to return a boolean and bind it directly to 'checked'.

</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
Loading