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
66 changes: 38 additions & 28 deletions src/BootstrapBlazor.Server/Components/Samples/Selects.razor
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,6 @@
</div>
</DemoBlock>

<DemoBlock Title="@Localizer["SelectsClearableTitle"]"
Introduction="@Localizer["SelectsClearableIntro"]"
Name="IsClearable">
<div class="row g-3">
<div class="col-12 col-sm-6">
<Select Color="Color.Primary" IsClearable="true" Items="ClearableItems" Value="ClearableModel.Name"></Select>
</div>
<div class="col-12 col-sm-6">
<Select Color="Color.Primary" IsClearable="true" Items="Items" Value="ClearableModel.Name"></Select>
</div>
</div>
</DemoBlock>

<DemoBlock Title="@Localizer["SelectsBindingSelectedItemTitle"]"
Introduction="@Localizer["SelectsBindingSelectedItemIntro"]"
Name="BindingSelectedItem">
Expand Down Expand Up @@ -348,6 +335,28 @@
</div>
</DemoBlock>

<DemoBlock Title="@Localizer["SelectsClearableTitle"]"
Introduction="@Localizer["SelectsClearableIntro"]"
Name="IsClearable">
<section ignore>
<p>@((MarkupString)Localizer["SelectsClearableDesc"].Value)</p>
</section>
<div class="row g-3">
<div class="col-12 col-sm-6">
<Select IsClearable="true" Items="ClearableItems" Value="ClearableModel.NullableName" ShowLabel="true" DisplayText="string?"></Select>
</div>
<div class="col-12 col-sm-6">
<Select IsClearable="true" Items="Items" Value="ClearableModel.Name" ShowLabel="true" DisplayText="string"></Select>
</div>
<div class="col-12 col-sm-6">
<Select IsClearable="true" Items="IntItems" Value="ClearableModel.NullableCount" ShowLabel="true" DisplayText="int?"></Select>
</div>
<div class="col-12 col-sm-6">
<Select IsClearable="true" Items="IntItems" Value="ClearableModel.Count" ShowLabel="true" DisplayText="int"></Select>
</div>
</div>
</DemoBlock>

<DemoBlock Title="@Localizer["SelectsConfirmSelectTitle"]"
Introduction="@Localizer["SelectsConfirmSelectIntro"]"
Name="ConfirmSelect">
Expand Down Expand Up @@ -412,22 +421,23 @@
<DemoBlock Title="@Localizer["SelectsVirtualizeTitle"]"
Introduction="@Localizer["SelectsVirtualizeIntro"]"
Name="IsVirtualize">
<section ignore>@((MarkupString)Localizer["SelectsVirtualizeDescription"].Value)</section>

<div class="row mb-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="IsClearable" />
<Checkbox @bind-Value="@_isClearable" />
</BootstrapInputGroup>
<section ignore>
<p>@((MarkupString)Localizer["SelectsVirtualizeDescription"].Value)</p>
<div class="row mb-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="IsClearable" />
<Checkbox @bind-Value="@_isClearable" />
</BootstrapInputGroup>
</div>
</div>
</div>
</section>

<p class="code-label">1. 使用 OnQueryAsync 作为数据源</p>
<div class="row mb-3">
Expand Down
23 changes: 21 additions & 2 deletions src/BootstrapBlazor.Server/Components/Samples/Selects.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public sealed partial class Selects

private string? _fooName;

private List<SelectedItem> _enumValueDemoItems = [
private readonly List<SelectedItem> _enumValueDemoItems = [
new("0", "Primary"),
new("1", "Middle")
];
Expand Down Expand Up @@ -101,7 +101,18 @@ private Task OnItemChanged(SelectedItem item)

private Foo BindingModel { get; set; } = new Foo();

private Foo ClearableModel { get; set; } = new Foo();
private MockModel ClearableModel { get; set; } = new();

class MockModel
{
public string? NullableName { get; set; }

public string Name { get; set; } = "";

public int Count { get; set; } = 1;

public int? NullableCount { get; set; }
}

private SelectedItem? Item { get; set; }

Expand Down Expand Up @@ -230,6 +241,14 @@ private string GetSelectedBoolItemString()
new("abcde", "abcde")
];

private readonly SelectedItem[] IntItems =
[
new("1", "1"),
new("12", "12"),
new("123", "123"),
new("1234", "1234")
];

private static Task<bool> OnBeforeSelectedItemChange(SelectedItem item)
{
return Task.FromResult(true);
Expand Down
1 change: 1 addition & 0 deletions src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -3111,6 +3111,7 @@
"SelectsBindingIntro": "The values in the text box change as you change the drop-down option by binding the <code>Model.Name</code> property to the component with <code>Select</code>",
"SelectsClearableTitle": "Clearable",
"SelectsClearableIntro": "You can clear Select using a clear icon",
"SelectsClearableDesc": "Cannot be a null integer. Setting <code>IsClearable</code> has no effect. Its default value is <b>0</b>",
"SelectsBindingSelectedItemTitle": "Select two-way binding SelectItem type",
"SelectsBindingSelectedItemIntro": "The values in the text box change as you change the drop-down option by binding the <code>SelectItem</code> property to the component with <code>Select</code> .",
"SelectsCascadingTitle": "Select cascading binding",
Expand Down
1 change: 1 addition & 0 deletions src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -3111,6 +3111,7 @@
"SelectsBindingIntro": "通过 <code>Select</code> 组件绑定 <code>Model.Name</code> 属性,改变下拉框选项时,文本框内的数值随之改变。",
"SelectsClearableTitle": "可清空单选",
"SelectsClearableIntro": "包含清空按钮,可将选择器清空为初始状态",
"SelectsClearableDesc": "不可为空整形设置 <code>IsClearable</code> 无效,其默认值为 <b>0</b>",
"SelectsBindingSelectedItemTitle": "Select 双向绑定 SelectItem",
"SelectsBindingSelectedItemIntro": "通过 <code>Select</code> 组件绑定 <code>SelectItem</code> 属性,改变下拉框选项时,文本框内的数值随之改变。",
"SelectsCascadingTitle": "Select 级联绑定",
Expand Down
28 changes: 14 additions & 14 deletions src/BootstrapBlazor/Components/Select/Select.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public partial class Select<TValue> : ISelect, ILookup
.AddClass($"text-danger", IsValid.HasValue && !IsValid.Value)
.Build();

private bool GetClearable() => IsClearable && !IsDisabled;
private bool GetClearable() => IsClearable && !IsDisabled && IsNullable();

/// <summary>
/// 设置当前项是否 Active 方法
Expand Down Expand Up @@ -294,6 +294,11 @@ private SelectedItem? SelectedRow

private SelectedItem? GetSelectedRow()
{
if (Value is null)
{
return null;
}

var item = GetItemWithEnumValue()
?? Rows.Find(i => i.Value == CurrentValueAsString)
?? Rows.Find(i => i.Active)
Expand Down Expand Up @@ -393,7 +398,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
/// </summary>
private int TotalCount { get; set; }

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

/// <summary>
/// 虚拟滚动数据加载回调方法
Expand Down Expand Up @@ -539,7 +544,6 @@ private async Task SelectedItemChanged(SelectedItem item)
{
if (_lastSelectedValueString != item.Value)
{

item.Active = true;
SelectedItem = item;

Expand All @@ -556,7 +560,7 @@ private async Task SelectedItemChanged(SelectedItem item)
}

/// <summary>
/// 添加静态下拉项方法
/// <inheritdoc/>
/// </summary>
/// <param name="item"></param>
public void Add(SelectedItem item) => _children.Add(item);
Expand All @@ -577,22 +581,18 @@ private async Task OnClearValue()
await OnClearAsync();
}

SelectedItem? item;
if (OnQueryAsync != null)
{
await VirtualizeElement.RefreshDataAsync();
item = _result.Items.FirstOrDefault();
}
else
{
item = Items.FirstOrDefault();
}
if (item != null)
{
await SelectedItemChanged(item);
}

_lastSelectedValueString = string.Empty;
CurrentValue = default;
SelectedItem = null;
}

private bool IsNullable() => !ValueType.IsValueType || NullableUnderlyingType != null;

private string? ReadonlyString => IsEditable ? null : "readonly";

private async Task OnChange(ChangeEventArgs args)
Expand Down
109 changes: 100 additions & 9 deletions test/UnitTest/Components/SelectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.AspNetCore.Components.Web.Virtualization;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace UnitTest.Components;

Expand Down Expand Up @@ -134,7 +135,7 @@ public void Select_Lookup()
}

[Fact]
public void IsClearable_Ok()
public async Task IsClearable_Ok()
{
var val = "Test2";
var cut = Context.RenderComponent<Select<string>>(pb =>
Expand All @@ -154,8 +155,8 @@ public void IsClearable_Ok()
});
});
var clearButton = cut.Find(".clear-icon");
cut.InvokeAsync(() => clearButton.Click());
Assert.Empty(val);
await cut.InvokeAsync(() => clearButton.Click());
Assert.Null(val);

// 提高代码覆盖率
var select = cut;
Expand All @@ -174,6 +175,97 @@ public void IsClearable_Ok()
validPi.SetValue(select.Instance, false);
val = pi.GetValue(select.Instance, null)!.ToString();
Assert.Contains("text-danger", val);

// 更改数据类型为不可为空 int
// IsClearable 参数无效
var cut1 = Context.RenderComponent<Select<int>>(pb =>
{
pb.Add(a => a.IsClearable, true);
pb.Add(a => a.Items, new List<SelectedItem>()
{
new("1", "Test1"),
new("2", "Test2"),
new("3", "Test3")
});
pb.Add(a => a.Value, 1);
});
cut1.DoesNotContain("clear-icon");
}

[Fact]
public void IsNullable_Ok()
{
var cut = Context.RenderComponent<Select<string>>(pb =>
{
pb.Add(a => a.Items, new List<SelectedItem>()
{
new("", "请选择"),
new("2", "Test2"),
new("3", "Test3")
});
});
Assert.True(IsNullable(cut.Instance));

var cut1 = Context.RenderComponent<Select<string?>>(pb =>
{
pb.Add(a => a.Items, new List<SelectedItem>()
{
new("", "请选择"),
new("2", "Test2"),
new("3", "Test3")
});
});
Assert.True(IsNullable(cut1.Instance));

var cut2 = Context.RenderComponent<Select<Foo>>(pb =>
{
pb.Add(a => a.Items, new List<SelectedItem>()
{
new("", "请选择"),
new("2", "Test2"),
new("3", "Test3")
});
});
Assert.True(IsNullable(cut2.Instance));

var cut3 = Context.RenderComponent<Select<Foo?>>(pb =>
{
pb.Add(a => a.Items, new List<SelectedItem>()
{
new("", "请选择"),
new("2", "Test2"),
new("3", "Test3")
});
});
Assert.True(IsNullable(cut3.Instance));

var cut4 = Context.RenderComponent<Select<int>>(pb =>
{
pb.Add(a => a.Items, new List<SelectedItem>()
{
new("", "请选择"),
new("2", "Test2"),
new("3", "Test3")
});
});
Assert.False(IsNullable(cut4.Instance));

var cut5 = Context.RenderComponent<Select<int?>>(pb =>
{
pb.Add(a => a.Items, new List<SelectedItem>()
{
new("", "请选择"),
new("2", "Test2"),
new("3", "Test3")
});
});
Assert.True(IsNullable(cut5.Instance));
}

private static bool IsNullable(object select)
{
var mi = select.GetType().GetMethod("IsNullable", BindingFlags.Instance | BindingFlags.NonPublic)!;
return (bool)mi.Invoke(select, null)!;
}

[Fact]
Expand Down Expand Up @@ -434,8 +526,7 @@ public void NullBool_Ok()
});

// 值为 null
// 候选项中无,导致默认选择第一个 Value 被更改为 true
Assert.True(cut.Instance.Value);
Assert.Null(cut.Instance.Value);
}

[Fact]
Expand Down Expand Up @@ -735,8 +826,8 @@ public async Task IsVirtualize_Items_Clearable_Ok()
var button = cut.Find(".clear-icon");
await cut.InvokeAsync(() => button.Click());

// UI 恢复 Test1
Assert.Equal("Test1", el.Value);
// 可为空数据类型 UI 为 ""
Assert.Equal("", el.Value);

// 下拉框显示所有选项
items = cut.FindAll(".dropdown-item");
Expand Down Expand Up @@ -794,8 +885,8 @@ public async Task IsVirtualize_OnQueryAsync_Clearable_Ok()
var button = cut.Find(".clear-icon");
await cut.InvokeAsync(() => button.Click());

// UI 恢复 Test1
Assert.Equal("All", el.Value);
// 可为空数据类型 UI 为 ""
Assert.Equal("", el.Value);

// 下拉框显示所有选项
Assert.True(query);
Expand Down