Skip to content

Commit

Permalink
feat(Slider): support Range attribute (#2553)
Browse files Browse the repository at this point in the history
* feat: 增加扩展方法 GetRange

* feat: 支持 RangeAttribute

* doc: 更新示例代码

* test: 增加单元测试

* chore: bump version 8.0.6-beta02
  • Loading branch information
ArgoZhang authored Dec 11, 2023
1 parent 2ecdeca commit 156609c
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 62 deletions.
68 changes: 11 additions & 57 deletions src/BootstrapBlazor.Server/Components/Samples/Sliders.razor
Original file line number Diff line number Diff line change
Expand Up @@ -81,69 +81,23 @@
</div>
</DemoBlock>

<DemoBlock Title="@Localizer["SlidersRangeTitle"]" Introduction="@Localizer["SlidersRangeIntro"]" Name="Range">
<div class="row">
<div class="col-12 col-sm-6">
<Slider @bind-Value="@RangeValue" Step="1" UseInputEvent="true"></Slider>
</div>
<div class="col-12 col-sm-6">
<Display Value="@RangeValue"></Display>
</div>
</div>
</DemoBlock>

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

<EventTable Items="@GetEvents()" />

@code {
private string DisplayText { get; set; } = "Range";

private double MaxValue { get; set; } = 100;
private double MinValue { get; set; } = -100;
private double CurrentValue { get; set; } = 0;
private double Step { get; set; } = 20;

private bool IsDisabled { get; set; }
private bool UseInput { get; set; }
private bool UseGroup { get; set; }
private bool ShowLabel { get; set; } = true;

[NotNull]
private ConsoleLogger? Logger { get; set; }

private Task OnRangeSliderValueChanged(double value)
{
Logger.Log($"RangeSlider: Bind Value: {value}");
return Task.CompletedTask;
}

RenderFragment RenderSlider =>
@<Slider @bind-Value="@CurrentValue" Max="MaxValue" Min="MinValue" Step="Step" UseInputEvent="UseInput"
DisplayText="@DisplayText" ShowLabel="ShowLabel" IsDisabled="IsDisabled" OnValueChanged="OnRangeSliderValueChanged" />;

/// <summary>
/// 获得属性方法
/// </summary>
/// <returns></returns>
private AttributeItem[] GetAttributes() =>
[
new() {
Name = "IsDisabled",
Description = Localizer["SlidersIsDisabled"],
Type = "bool",
ValueList = "",
DefaultValue = "false"
},
new() {
Name = "Value",
Description = Localizer["SlidersValue"],
Type = "int",
ValueList = "",
DefaultValue = ""
},
];

/// <summary>
/// 获得事件方法
/// </summary>
/// <returns></returns>
private EventItem[] GetEvents() =>
[
new()
{
Name = "ValueChanged",
Description = Localizer["SlidersValueChanged"],
Type ="EventCallback<int>"
}
];
}
82 changes: 82 additions & 0 deletions src/BootstrapBlazor.Server/Components/Samples/Sliders.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace BootstrapBlazor.Server.Components.Samples;

/// <summary>
/// Slider 组件示例
/// </summary>
public partial class Sliders
{
private string DisplayText { get; set; } = "Range";

private double MaxValue { get; set; } = 100;

private double MinValue { get; set; } = -100;

private double CurrentValue { get; set; } = 0;

private double Step { get; set; } = 20;

private bool IsDisabled { get; set; }

private bool UseInput { get; set; }

private bool UseGroup { get; set; }

private bool ShowLabel { get; set; } = true;

[NotNull]
private ConsoleLogger? Logger { get; set; }

/// <summary>
///
/// </summary>
[Range(10, 100)]
public int RangeValue { get; set; } = 20;

private Task OnRangeSliderValueChanged(double value)
{
Logger.Log($"RangeSlider: Bind Value: {value}");
return Task.CompletedTask;
}

/// <summary>
/// 获得属性方法
/// </summary>
/// <returns></returns>
private AttributeItem[] GetAttributes() =>
[
new()
{
Name = "IsDisabled",
Description = Localizer["SlidersIsDisabled"],
Type = "bool",
ValueList = " — ",
DefaultValue = "false"
},
new()
{
Name = "Value",
Description = Localizer["SlidersValue"],
Type = "int",
ValueList = " — ",
DefaultValue = " — "
},
];

/// <summary>
/// 获得事件方法
/// </summary>
/// <returns></returns>
private EventItem[] GetEvents() =>
[
new()
{
Name = "ValueChanged",
Description = Localizer["SlidersValueChanged"],
Type ="EventCallback<int>"
}
];
}
2 changes: 2 additions & 0 deletions src/BootstrapBlazor.Server/Locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3012,6 +3012,8 @@
"SlidersDescription": "Select within a fixed interval by dragging the slider",
"SlidersNormalTitle": "Basic usage",
"SlidersNormalIntro": "Change the current value as you drag the slider",
"SlidersRangeTitle": "Range",
"SlidersRangeIntro": "Auto generate <code>min</code> <code>max</code> by set <code>RangeAttribute</code>",
"SlidersIsDisabled": "Whether to disable it",
"SlidersValue": "The current value of the component",
"SlidersValueChanged": "ValueChanged callback method"
Expand Down
2 changes: 2 additions & 0 deletions src/BootstrapBlazor.Server/Locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -3012,6 +3012,8 @@
"SlidersDescription": "通过拖动滑块在一个固定区间内进行选择",
"SlidersNormalTitle": "基础用法",
"SlidersNormalIntro": "在拖动滑块时,改变当前值",
"SlidersRangeTitle": "Range 标签",
"SlidersRangeIntro": "通过设置绑定值标签 <code>RangeAttribute</code> 自动生成 <code>min</code> <code>max</code>",
"SlidersIsDisabled": "是否禁用",
"SlidersValue": "组件当前值",
"SlidersValueChanged": "ValueChanged 回调方法"
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>8.0.6-beta01</Version>
<Version>8.0.6-beta02</Version>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
Expand Down
23 changes: 21 additions & 2 deletions src/BootstrapBlazor/Components/Slider/Slider.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,28 @@ public partial class Slider<TValue>

private string eventName => UseInputEvent ? "oninput" : "onchange";

private string? MinString => Min.ToString() == "0" ? null : Min.ToString();
private string? MinString => Min.ToString() == "0" ? GetRangeMinString : Min.ToString();

private string? MaxString => Max.ToString() == "0" ? null : Max.ToString();
private string? GetRangeMinString => _range?.Minimum.ToString();

private string? MaxString => Max.ToString() == "0" ? GetRangeMaxString : Max.ToString();

private string? GetRangeMaxString => _range?.Maximum.ToString();

private string? StepString => Step.ToString() == "0" ? null : Step.ToString();

private RangeAttribute? _range = null;

/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();

if (FieldIdentifier.HasValue)
{
_range = FieldIdentifier.Value.GetRange();
}
}
}
7 changes: 7 additions & 0 deletions src/BootstrapBlazor/Extensions/FieldIdentifierExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,11 @@ public static class FieldIdentifierExtensions
/// <param name="fieldIdentifier"></param>
/// <returns></returns>
public static string? GetPlaceHolder(this FieldIdentifier fieldIdentifier) => Utility.GetPlaceHolder(fieldIdentifier.Model, fieldIdentifier.FieldName);

/// <summary>
/// 获取显示名称方法
/// </summary>
/// <param name="fieldIdentifier"></param>
/// <returns></returns>
public static RangeAttribute? GetRange(this FieldIdentifier fieldIdentifier) => Utility.GetRange(fieldIdentifier.Model, fieldIdentifier.FieldName);
}
24 changes: 24 additions & 0 deletions src/BootstrapBlazor/Services/CacheManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,30 @@ string FindDisplayTextByItemName(string itemName)
}
#endregion

#region Range
/// <summary>
/// 获得类型属性的描述信息
/// </summary>
/// <param name="modelType"></param>
/// <param name="fieldName"></param>
/// <returns></returns>
public static RangeAttribute? GetRange(Type modelType, string fieldName)
{
var cacheKey = $"{nameof(GetRange)}-{modelType.FullName}-{fieldName}";
return Instance.GetOrCreate(cacheKey, entry =>
{
RangeAttribute? dn = null;
if (TryGetProperty(modelType, fieldName, out var propertyInfo))
{
dn = propertyInfo.GetCustomAttribute<RangeAttribute>(true);
}

entry.SetDynamicAssemblyPolicy(modelType);
return dn;
});
}
#endregion

#region Placeholder
public static string? GetPlaceholder(Type modelType, string fieldName)
{
Expand Down
24 changes: 24 additions & 0 deletions src/BootstrapBlazor/Utils/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ public static class Utility
/// <returns></returns>
public static string GetDisplayName<TModel>(string fieldName) => GetDisplayName(typeof(TModel), fieldName);

/// <summary>
/// 获取 RangeAttribute 标签值
/// </summary>
/// <param name="model">模型实例</param>
/// <param name="fieldName">字段名称</param>
/// <returns></returns>
public static RangeAttribute? GetRange(object model, string fieldName) => GetRange(model.GetType(), fieldName);

/// <summary>
/// 获得 RangeAttribute 标签值
/// </summary>
/// <param name="modelType">模型类型</param>
/// <param name="fieldName">字段名称</param>
/// <returns></returns>
public static RangeAttribute? GetRange(Type modelType, string fieldName) => CacheManager.GetRange(Nullable.GetUnderlyingType(modelType) ?? modelType, fieldName);

/// <summary>
/// 获得 RangeAttribute 标签值
/// </summary>
/// <typeparam name="TModel">模型</typeparam>
/// <param name="fieldName">字段名称</param>
/// <returns></returns>
public static RangeAttribute? GetRange<TModel>(string fieldName) => GetRange(typeof(TModel), fieldName);

/// <summary>
/// 获取资源文件中 NullableBoolItemsAttribute 标签名称方法
/// </summary>
Expand Down
20 changes: 20 additions & 0 deletions test/UnitTest/Components/SliderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using System.ComponentModel.DataAnnotations;

namespace UnitTest.Components;

public class SliderTest : BootstrapBlazorTestBase
Expand Down Expand Up @@ -106,4 +108,22 @@ public async Task OnValueChanged_OK()
await cut.InvokeAsync(() => cut.Instance.SetValue(1));
Assert.Equal(1, expected);
}

[Fact]
public void Range_OK()
{
var model = new SliderModel();
var cut = Context.RenderComponent<Slider<int>>(builder =>
{
builder.Add(s => s.Value, 10);
builder.Add(s => s.ValueExpression, Utility.GenerateValueExpression(model, "Value", typeof(int)));
});
cut.Contains("min=\"10\" max=\"200\"");
}

public class SliderModel
{
[Range(10, 200)]
public int Value { get; set; }
}
}
12 changes: 10 additions & 2 deletions test/UnitTest/Utils/UtilityTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Data;
using System.Globalization;
using System.Reflection;
using UnitTest.Components;

namespace UnitTest.Utils;

Expand Down Expand Up @@ -60,13 +61,20 @@ public void GetPropertyValue_Ok()
Assert.Contains("张三", v3!.ToString());
}

[Fact]
public void GetRange_Ok()
{
var attribute = Utility.GetRange<SliderTest.SliderModel>("Value");
Assert.NotNull(attribute);
}

[Fact]
public void GetSortFunc_Ok()
{
var foos = new List<Foo>
{
new Foo { Count = 10 },
new Foo { Count = 20 }
new() { Count = 10 },
new() { Count = 20 }
};
var invoker = Utility.GetSortFunc<Foo>();
var orderFoos = invoker.Invoke(foos, nameof(Foo.Count), SortOrder.Asc).ToList();
Expand Down

0 comments on commit 156609c

Please sign in to comment.