Skip to content

Commit

Permalink
feat(DatePicker): add IsEditable parameter (#2963)
Browse files Browse the repository at this point in the history
* feat: add IsEditable parameter

* feat: add GetReadOnlyAttribute

* feat: notify Value property changes

* feat: added GetUID extension method to JSModuleExtensions

* feat: Add support for manual input in DateTimePicker component

* refactor: refactoring code

* refactor: 移除无效引用

* doc: 添加文档

* refactor: refactoring code

* fix: 修复时间间隔符不一致问题

* feat: Add support for manual input in DateTime component

* refactor: 更新手动输入逻辑

* doc: 更新示例

* doc: 更新示例

* doc: 更新资源文件

* refactor: 重构 Readonly 字符串

* refactor: Range 组件支持手工输入

* test: 更新单元测试

* test: 更新单元测试

* test: 更新单元测试

* test: 更新单元测试

* test: 更新单元测试

---------

Co-authored-by: Argo-AscioTech <argo@live.ca>
  • Loading branch information
h2ls and ArgoZhang authored Feb 25, 2024
1 parent eea0d1e commit 8db1be5
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<DemoBlock Title="@Localizer["BindValueTitle"]" Introduction="@Localizer["BindValueIntro"]" Name="BindValue">
<div class="row g-3">
<div class="col-sm-6">
<DateTimePicker @bind-Value="@BindValue" />
<DateTimePicker @bind-Value="@BindValue" IsEditable="true" DateFormat="dd/MM/yyyy" />
</div>
<div class="col-sm-6">
<input class="form-control" @bind="@BindValueString" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ private AttributeItem[] GetAttributes() =>
Type = "bool",
ValueList = "true/false",
DefaultValue = "false"
},
new() {
Name = "IsEditable",
Description = Localizer["AttrIsEditable"],
Type = "bool",
ValueList = "true/false",
DefaultValue = "false"
}
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

<h4>@Localizer["Description"]</h4>

@* <DemoBlock Title="@Localizer["NormalTitle"]" Introduction="@Localizer["NormalIntro"]" Name="Normal">
<DateTimeRange @bind-Value="@NormalDateTimeRangeValue" OnConfirm="OnNormalConfirm" ShowSidebar="true" ViewMode="DatePickerViewMode.DateTime">
<DemoBlock Title="@Localizer["NormalTitle"]" Introduction="@Localizer["NormalIntro"]" Name="Normal">
<DateTimeRange @bind-Value="@NormalDateTimeRangeValue" OnConfirm="OnNormalConfirm" ShowSidebar="true" ViewMode="DatePickerViewMode.Date" IsEditable="true" DateFormat="dd/MM/yyyy">
<TimePickerSetting ShowClockScale="true" IsAutoSwitch="false" />
</DateTimeRange>
<ConsoleLogger @ref="NormalLogger" class="mt-3" />
</DemoBlock>
*@

<DemoBlock Title="@Localizer["BindValueTitle"]" Introduction="@Localizer["BindValueIntro"]" Name="BindValue">
<div class="row g-3">
<div class="col-12 col-sm-6">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,14 @@ private static List<AttributeItem> GetAttributes() =>
Type = "IEnumerable<DateTimeRangeSidebarItem>",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "IsEditable",
Description = "Is manual date entry allowed",
Type = "bool",
ValueList = "true/false",
DefaultValue = "false"
}
];
}
5 changes: 3 additions & 2 deletions src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -2411,13 +2411,13 @@
"NormalTitle": "Select the day",
"NormalIntro": "Select the control based on the date of the day in 「day」as the base unit",
"ShowIconTitle": "Wether show the component icon",
"ShowIconIntro": "Save space by setting<code>ShowIcon=\"false\" to not display component icons",
"ShowIconIntro": "Save space by setting <code>ShowIcon=\"false\"</code> to not display component icons",
"ValidateFormTitle": "Client validation",
"ValidateFormIntro": "Check data validity and prompt automatically based on custom validation rules",
"DateTimeOffsetTitle": "Click on the pop-up date box",
"DateTimeOffsetIntro": "Select the control based on the date of the day in 「day」 as the base unit",
"BindValueTitle": "Data is bound in both directions",
"BindValueIntro": "The values in the text box change as the date component time changes",
"BindValueIntro": "The values in the text box change as the date component time changes. Enable manual input function by setting <code>IsEditable=\"true\"</code>",
"ViewModeTitle": "Selector with time",
"ViewModeIntro": "Select the date and time in the same selector, click the confirm button and close the pop-up window",
"ViewModeTip": "Set the value of the <code>viewMode</code> property to The <code>DateTime</code> of DatePickerViewMode.DateTime",
Expand Down Expand Up @@ -2447,6 +2447,7 @@
"Att8": "The value of the component is a two-way binding with ValueChanged",
"Att9": "Get/Set Component display mode The default is the month-to-day display mode",
"AttrAutoClose": "Whether auto close the popup window",
"AttrIsEditable": "Is manual date entry allowed",
"Event1": "Confirm that the button calls back the delegate",
"Event2": "Callback delegates are used for bidirectional binding when component values change",
"BlockGroupLabel": "Prev",
Expand Down
3 changes: 2 additions & 1 deletion src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2417,7 +2417,7 @@
"DateTimeOffsetTitle": "点击弹出日期框",
"DateTimeOffsetIntro": "以「日」为基本单位,基础的日期选择控件,此示例绑定 <code>DateTimeOffset</code> 数据类型",
"BindValueTitle": "数据双向绑定",
"BindValueIntro": "日期组件时间改变时文本框内的数值也跟着改变",
"BindValueIntro": "日期组件时间改变时文本框内的数值也跟着改变,通过设置 <code>IsEditable=\"true\"</code> 开启手工录入功能",
"ViewModeTitle": "带时间的选择器",
"ViewModeIntro": "在同一个选择器里选择日期和时间,点击确认按钮后关闭弹窗",
"ViewModeTip": "设置 <code>ViewMode</code> 属性值为 <code>DatePickerViewMode.DateTime</code>",
Expand Down Expand Up @@ -2447,6 +2447,7 @@
"Att8": "组件值与 ValueChanged 作为双向绑定的值",
"Att9": "获得/设置 组件显示模式 默认为显示年月日模式",
"AttrAutoClose": "选中日期后是否自动关闭弹窗",
"AttrIsEditable": "是否允许手动录入日期",
"Event1": "确认按钮回调委托",
"Event2": "组件值改变时回调委托供双向绑定使用",
"BlockGroupLabel": "前置标签",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<BootstrapLabel required="@Required" for="@Id" ShowLabelTooltip="ShowLabelTooltip" Value="@DisplayText" />
}
<div @attributes="@AdditionalAttributes" tabindex="@TabIndexString" id="@Id" class="@ClassString" data-bb-dropdown=".picker-panel" data-bb-dismiss=".picker-panel-link-btn">
<input readonly class="@InputClassName" value="@CurrentValueAsString" placeholder="@PlaceholderString" disabled="@Disabled" data-bs-toggle="@Constants.DropdownToggleString" data-bs-placement="@PlacementString" data-bs-custom-class="@CustomClassString" />
<input readonly="@ReadonlyString" class="@InputClassName" @bind="@CurrentValueAsString" placeholder="@PlaceholderString" disabled="@Disabled" data-bs-toggle="@Constants.DropdownToggleString" data-bs-placement="@PlacementString" data-bs-custom-class="@CustomClassString" />
@if (ShowIcon)
{
<i class="@DateTimePickerIconClassString"></i>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Website: https://www.blazor.zone or https://argozhang.github.io/

using Microsoft.Extensions.Localization;
using System.Globalization;

namespace BootstrapBlazor.Components;

Expand Down Expand Up @@ -134,6 +135,12 @@ public string? Format
[Parameter]
public bool AutoClose { get; set; } = true;

/// <summary>
/// 获得/设置 是否可以编辑内容 默认 false
/// </summary>
[Parameter]
public bool IsEditable { get; set; }

/// <summary>
/// 获得/设置 是否自动设置值为当前时间 默认 true
/// </summary>
Expand Down Expand Up @@ -312,4 +319,26 @@ private async Task OnClear()
}
return ret;
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="value"></param>
/// <param name="result"></param>
/// <param name="validationErrorMessage"></param>
/// <returns></returns>
protected override bool TryParseValueFromString(string value, [MaybeNullWhen(false)] out TValue result, out string? validationErrorMessage)
{
var format = ViewMode == DatePickerViewMode.DateTime ? DateTimeFormat : DateFormat;
result = default;
validationErrorMessage = null;
var ret = DateTime.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var val);
if (ret)
{
result = (TValue)(object)val;
}
return ret;
}

private string? ReadonlyString => IsEditable ? null : "readonly";
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
<div class="dropdown-toggle datetime-range-control" data-bs-toggle="@Constants.DropdownToggleString" data-bs-placement="@PlacementString" data-bs-custom-class="@CustomClassString">
<div class="position-relative">
<i class="@DateTimePickerIconClassString"></i>
<input readonly="readonly" class="@ValueClassString" value="@StartValueString" placeholder="@StartPlaceHolderText" disabled="@Disabled" />
<input readonly="@ReadonlyString" class="@ValueClassString" @bind="@StartValueString" placeholder="@StartPlaceHolderText" disabled="@Disabled" />
</div>
<span class="range-separator">@SeparateText</span>
<input readonly="readonly" class="@ValueClassString" value="@EndValueString" placeholder="@EndPlaceHolderText" disabled="@Disabled" />
<input readonly="@ReadonlyString" class="@ValueClassString" @bind="@EndValueString" placeholder="@EndPlaceHolderText" disabled="@Disabled" />
</div>
@if (ShowClearButton)
{
Expand Down
69 changes: 49 additions & 20 deletions src/BootstrapBlazor/Components/DateTimeRange/DateTimeRange.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Website: https://www.blazor.zone or https://argozhang.github.io/

using Microsoft.Extensions.Localization;
using System.Globalization;
using System.Reflection;

namespace BootstrapBlazor.Components;
Expand Down Expand Up @@ -37,15 +38,47 @@ public partial class DateTimeRange

private DateTime StartValue { get; set; }

private string? StartValueString => Value.Start != DateTime.MinValue
? GetValueString(Value.Start)
: null;
private string? StartValueString
{
set
{
var format = ViewMode == DatePickerViewMode.DateTime ? DateTimeFormat : DateFormat;
var ret = DateTime.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var startDateValue);
if (ret)
{
StartValue = startDateValue;
Value.Start = startDateValue;
SelectedValue.Start = startDateValue;
}
}
get
{
var format = ViewMode == DatePickerViewMode.DateTime ? DateTimeFormat : DateFormat;
return Value.Start != DateTime.MinValue ? Value.Start.ToString(format) : null;
}
}

private DateTime EndValue { get; set; }

private string? EndValueString => Value.End != DateTime.MinValue
? GetValueString(Value.End)
: null;
private string? EndValueString
{
set
{
var format = ViewMode == DatePickerViewMode.DateTime ? DateTimeFormat : DateFormat;
var ret = DateTime.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var endDateValue);
if (ret)
{
EndValue = endDateValue;
Value.End = endDateValue;
SelectedValue.End = endDateValue;
}
}
get
{
var format = ViewMode == DatePickerViewMode.DateTime ? DateTimeFormat : DateFormat;
return Value.End != DateTime.MinValue ? Value.End.ToString(format) : null;
}
}

[NotNull]
private string? StartPlaceHolderText { get; set; }
Expand All @@ -56,6 +89,12 @@ public partial class DateTimeRange
[NotNull]
private string? SeparateText { get; set; }

/// <summary>
/// 获得/设置 是否可以编辑内容 默认 false
/// </summary>
[Parameter]
public bool IsEditable { get; set; }

/// <summary>
/// 获得/设置 是否点击快捷侧边栏自动关闭弹窗 默认 false
/// </summary>
Expand Down Expand Up @@ -200,6 +239,8 @@ public bool AllowNull
[NotNull]
private IIconTheme? IconTheme { get; set; }

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

private string? ValueClassString => CssBuilder.Default("datetime-range-input")
.AddClass("datetime", ViewMode == DatePickerViewMode.DateTime)
.AddClass("disabled", IsDisabled)
Expand Down Expand Up @@ -295,20 +336,6 @@ private async Task OnClickSidebarItem(DateTimeRangeSidebarItem item)
}
}

private string GetValueString(DateTime value)
{
string? ret;
//if (ViewMode == DatePickerViewMode.DateTime)
//{
// ret = value.ToString(DateTimeFormat);
//}
//else
//{
ret = value.ToString(DateFormat);
//}
return ret;
}

/// <summary>
/// 点击 清除按钮调用此方法
/// </summary>
Expand Down Expand Up @@ -446,4 +473,6 @@ private void UpdateValue(DateTime d)
public override bool IsComplexValue(object? propertyValue) => false;

private static DateTime GetEndDateTime(DateTime dt) => dt.Date.AddHours(23).AddMinutes(59).AddSeconds(59);

private DateTime GetSafeStartValue() => SelectedValue.Start.Date == SelectedValue.End.Date ? SelectedValue.Start.GetSafeMonthDateTime(-1) : SelectedValue.Start.Date;
}

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions test/UnitTest/Components/DateTimePickerTest.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 AngleSharp.Dom;

namespace UnitTest.Components;

public class DateTimePickerTest : BootstrapBlazorTestBase
Expand Down Expand Up @@ -923,6 +925,44 @@ public void FormatValueAsString_Ok()
Assert.Equal($"{DateTime.Today:yyyy-MM-dd}", v);
}

[Fact]
public async Task IsEditable_Ok()
{
var cut = Context.RenderComponent<DateTimePicker<DateTime>>(pb =>
{
pb.Add(a => a.IsEditable, true);
pb.Add(a => a.ViewMode, DatePickerViewMode.Date);
pb.Add(a => a.DateFormat, "MM/dd/yyyy");
});
var input = cut.Find(".datetime-picker-input");
Assert.False(input.HasAttribute("readonly"));

// input value
await cut.InvokeAsync(() =>
{
input.Change("02/15/2024");
});
Assert.Equal("02/15/2024", cut.Instance.Value.ToString("MM/dd/yyyy"));

// error input value
await cut.InvokeAsync(() =>
{
input.Change("test");
});
Assert.Equal("02/15/2024", cut.Instance.Value.ToString("MM/dd/yyyy"));

cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.ViewMode, DatePickerViewMode.DateTime);
pb.Add(a => a.DateTimeFormat, "MM/dd/yyyy HH:mm:ss");
});
await cut.InvokeAsync(() =>
{
input.Change("02/15/2024 01:00:00");
});
Assert.Equal("02/15/2024 01:00:00", cut.Instance.Value.ToString("MM/dd/yyyy HH:mm:ss"));
}

class MockDateTimePicker : DatePickerBody
{
public static bool GetSafeYearDateTime_Ok()
Expand Down
30 changes: 30 additions & 0 deletions test/UnitTest/Components/DateTimeRangeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -541,4 +541,34 @@ await cut.InvokeAsync(() =>
Assert.Equal(DateTime.Today, cut.Instance.Value.Start);
Assert.Equal(DateTime.Today.AddDays(1).AddSeconds(-1), cut.Instance.Value.End);
}

[Fact]
public async Task IsEditable_Ok()
{
var cut = Context.RenderComponent<DateTimeRange>(pb =>
{
pb.Add(a => a.Value, new DateTimeRangeValue());
pb.Add(a => a.IsEditable, true);
pb.Add(a => a.ViewMode, DatePickerViewMode.Date);
pb.Add(a => a.DateFormat, "MM/dd/yyyy");
});
var inputs = cut.FindAll(".datetime-range-input");
Assert.False(inputs[0].HasAttribute("readonly"));
Assert.False(inputs[1].HasAttribute("readonly"));

// input value
var input = cut.Find(".datetime-range-input");
await cut.InvokeAsync(() =>
{
input.Change("02/15/2024");
});

inputs = cut.FindAll(".datetime-range-input");
await cut.InvokeAsync(() =>
{
inputs[1].Change("02/16/2024");
});
Assert.Equal("02/15/2024", cut.Instance.Value.Start.ToString("MM/dd/yyyy"));
Assert.Equal("02/16/2024", cut.Instance.Value.End.ToString("MM/dd/yyyy"));
}
}

0 comments on commit 8db1be5

Please sign in to comment.