Skip to content

Commit 91ad1fb

Browse files
authored
feat: add_default_formvalue (#18)
add default-value properties of LocalTimeFormValue
1 parent 1815857 commit 91ad1fb

File tree

11 files changed

+159
-38
lines changed

11 files changed

+159
-38
lines changed

example/BlazorLocalTimeSample/BlazorLocalTimeSample.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
<ItemGroup>
88
<PackageReference Include="AntDesign" Version="1.4.3" />
99
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.4" />
10-
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.8" PrivateAssets="all" />
10+
<PackageReference
11+
Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer"
12+
Version="9.0.8"
13+
PrivateAssets="all"
14+
/>
1115
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.12.1" />
1216
<PackageReference Include="MudBlazor" Version="8.10.0" />
1317
<PackageReference Include="PublishSPAforGitHubPages.Build" Version="3.0.1" />

src/BlazorLocalTime/BlazorLocalTime.csproj

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,31 @@
88
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
99
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.*" />
1010
<PackageReference Include="Microsoft.Bcl.TimeProvider" Version="*" />
11-
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.*" />
11+
<PackageReference
12+
Include="Microsoft.Extensions.DependencyInjection.Abstractions"
13+
Version="6.*"
14+
/>
1215
</ItemGroup>
1316
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
1417
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.*" />
1518
<PackageReference Include="Microsoft.Bcl.TimeProvider" Version="*" />
16-
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.*" />
19+
<PackageReference
20+
Include="Microsoft.Extensions.DependencyInjection.Abstractions"
21+
Version="7.*"
22+
/>
1723
</ItemGroup>
1824
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
1925
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.*" />
20-
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.*" />
26+
<PackageReference
27+
Include="Microsoft.Extensions.DependencyInjection.Abstractions"
28+
Version="8.*"
29+
/>
2130
</ItemGroup>
2231
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
2332
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.*" />
24-
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.*" />
33+
<PackageReference
34+
Include="Microsoft.Extensions.DependencyInjection.Abstractions"
35+
Version="9.*"
36+
/>
2537
</ItemGroup>
2638
</Project>

src/BlazorLocalTime/Components/LocalTimeForm.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ private async void OnLocalTimeZoneChangedDetailed(object? sender, TimeZoneChange
9191
}
9292

9393
private LocalTimeFormValue FormValue =>
94-
new()
94+
new(LocalTimeService)
9595
{
9696
Value = LocalTimeValue,
9797
ValueChanged = EventCallback.Factory.Create<DateTime?>(this, ValueChangedHandler),

src/BlazorLocalTime/Components/LocalTimeFormValue.cs

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ namespace BlazorLocalTime;
77
/// </summary>
88
public record LocalTimeFormValue
99
{
10+
// Since users likely won't create it themselves, the constructor is internal.
11+
internal LocalTimeFormValue(ILocalTimeService localTimeService)
12+
{
13+
_localTimeService = localTimeService;
14+
}
15+
16+
/// <summary>
17+
/// The local time service used to get the current local time.
18+
/// </summary>
19+
private readonly ILocalTimeService _localTimeService;
20+
21+
// Backing field for Value property.
1022
private DateTime? _innerValue;
1123

1224
/// <summary>
@@ -27,10 +39,7 @@ public required DateTime? Value
2739
/// </summary>
2840
public DateOnly? Date
2941
{
30-
get =>
31-
Value.HasValue
32-
? new DateOnly(Value.Value.Year, Value.Value.Month, Value.Value.Day)
33-
: null;
42+
get => Value.HasValue ? DateOnly.FromDateTime(Value.Value) : null;
3443
set => DateChanged.InvokeAsync(value);
3544
}
3645

@@ -39,10 +48,7 @@ public DateOnly? Date
3948
/// </summary>
4049
public TimeOnly? Time
4150
{
42-
get =>
43-
Value.HasValue
44-
? new TimeOnly(Value.Value.Hour, Value.Value.Minute, Value.Value.Second)
45-
: null;
51+
get => Value.HasValue ? TimeOnly.FromDateTime(Value.Value) : null;
4652
set => TimeChanged.InvokeAsync(value);
4753
}
4854

@@ -51,13 +57,50 @@ public TimeOnly? Time
5157
/// </summary>
5258
public TimeSpan? TimeSpan
5359
{
54-
get =>
55-
Value.HasValue
56-
? new TimeSpan(Value.Value.Hour, Value.Value.Minute, Value.Value.Second)
57-
: null;
60+
get => Value.HasValue ? Value.Value.TimeOfDay : null;
5861
set => TimeSpanChanged.InvokeAsync(value);
5962
}
6063

64+
/// <summary>
65+
/// It is essentially the same as <see cref="Value"/>. but returns the current date and time when the value is null. <br/>
66+
/// This is useful when binding values to date and time components that do not tolerate null values.
67+
/// </summary>
68+
public DateTime ValueOrNow
69+
{
70+
get => _innerValue ?? _localTimeService.Now.DateTime;
71+
set => Value = value;
72+
}
73+
74+
/// <summary>
75+
/// It is essentially the same as <see cref="Date"/>, but returns today's date when the value is null. <br/>
76+
/// This is useful when binding values to date components that do not tolerate null values.
77+
/// </summary>
78+
public DateOnly DateOrToday
79+
{
80+
get => Date ?? DateOnly.FromDateTime(_localTimeService.Now.DateTime);
81+
set => Date = value;
82+
}
83+
84+
/// <summary>
85+
/// It is essentially the same as <see cref="Time"/>, but returns current time when the value is null. <br/>
86+
/// This is useful when binding values to time components that do not tolerate null values.
87+
/// </summary>
88+
public TimeOnly TimeOrNow
89+
{
90+
get => Time ?? TimeOnly.FromDateTime(_localTimeService.Now.DateTime);
91+
set => Time = value;
92+
}
93+
94+
/// <summary>
95+
/// It is essentially the same as <see cref="TimeSpan"/>, but returns current time when the value is null. <br/>
96+
/// This is useful when binding values to time components that do not tolerate null values.
97+
/// </summary>
98+
public TimeSpan TimeSpanOrNow
99+
{
100+
get => TimeSpan ?? _localTimeService.Now.DateTime.TimeOfDay;
101+
set => TimeSpan = value;
102+
}
103+
61104
/// <summary>
62105
/// An <see cref="EventCallback"/> that is invoked when the value changes.
63106
/// </summary>

src/Directory.Build.props

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,23 @@
1111
<GenerateDocumentationFile>true</GenerateDocumentationFile>
1212
</PropertyGroup>
1313
<ItemGroup>
14-
<PackageReference Include="Nerdbank.GitVersioning" Version="3.*" Condition="!Exists('packages.config')">
14+
<PackageReference
15+
Include="Nerdbank.GitVersioning"
16+
Version="3.*"
17+
Condition="!Exists('packages.config')"
18+
>
1519
<PrivateAssets>all</PrivateAssets>
1620
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1721
</PackageReference>
1822
<PackageReference Include="PolySharp" Version="1.*" Condition="!Exists('packages.config')">
1923
<PrivateAssets>all</PrivateAssets>
2024
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2125
</PackageReference>
22-
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.15.0.120848" Condition="!Exists('packages.config')">
26+
<PackageReference
27+
Include="SonarAnalyzer.CSharp"
28+
Version="10.15.0.120848"
29+
Condition="!Exists('packages.config')"
30+
>
2331
<PrivateAssets>all</PrivateAssets>
2432
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2533
</PackageReference>

tests/BlazorLocalTimeTest/Approvals/PublicApiCheckTest.Run.net8.approved.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,18 @@ namespace BlazorLocalTime
4242
}
4343
public class LocalTimeFormValue : System.IEquatable<BlazorLocalTime.LocalTimeFormValue>
4444
{
45-
public LocalTimeFormValue() { }
4645
public System.DateOnly? Date { get; set; }
4746
public required Microsoft.AspNetCore.Components.EventCallback<System.DateOnly?> DateChanged { get; init; }
47+
public System.DateOnly DateOrToday { get; set; }
4848
public System.TimeOnly? Time { get; set; }
4949
public required Microsoft.AspNetCore.Components.EventCallback<System.TimeOnly?> TimeChanged { get; init; }
50+
public System.TimeOnly TimeOrNow { get; set; }
5051
public System.TimeSpan? TimeSpan { get; set; }
5152
public required Microsoft.AspNetCore.Components.EventCallback<System.TimeSpan?> TimeSpanChanged { get; init; }
53+
public System.TimeSpan TimeSpanOrNow { get; set; }
5254
public required System.DateTime? Value { get; set; }
5355
public required Microsoft.AspNetCore.Components.EventCallback<System.DateTime?> ValueChanged { get; init; }
56+
public System.DateTime ValueOrNow { get; set; }
5457
}
5558
public sealed class LocalTimeForm<T> : Microsoft.AspNetCore.Components.ComponentBase, System.IDisposable
5659
{

tests/BlazorLocalTimeTest/Approvals/PublicApiCheckTest.Run.net9.approved.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,18 @@ namespace BlazorLocalTime
4242
}
4343
public class LocalTimeFormValue : System.IEquatable<BlazorLocalTime.LocalTimeFormValue>
4444
{
45-
public LocalTimeFormValue() { }
4645
public System.DateOnly? Date { get; set; }
4746
public required Microsoft.AspNetCore.Components.EventCallback<System.DateOnly?> DateChanged { get; init; }
47+
public System.DateOnly DateOrToday { get; set; }
4848
public System.TimeOnly? Time { get; set; }
4949
public required Microsoft.AspNetCore.Components.EventCallback<System.TimeOnly?> TimeChanged { get; init; }
50+
public System.TimeOnly TimeOrNow { get; set; }
5051
public System.TimeSpan? TimeSpan { get; set; }
5152
public required Microsoft.AspNetCore.Components.EventCallback<System.TimeSpan?> TimeSpanChanged { get; init; }
53+
public System.TimeSpan TimeSpanOrNow { get; set; }
5254
public required System.DateTime? Value { get; set; }
5355
public required Microsoft.AspNetCore.Components.EventCallback<System.DateTime?> ValueChanged { get; init; }
56+
public System.DateTime ValueOrNow { get; set; }
5457
}
5558
public sealed class LocalTimeForm<T> : Microsoft.AspNetCore.Components.ComponentBase, System.IDisposable
5659
{

tests/BlazorLocalTimeTest/BlazorLocalTimeProviderCodeTest.cs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ public BlazorLocalTimeProviderCodeTest()
1919
public void BlazorLocalTimeProvider_SetsTimeZoneSuccessfully()
2020
{
2121
TestInitializer.JavaScriptInitializer(JSInterop);
22-
22+
2323
var component = RenderComponent<BlazorLocalTimeProvider>();
2424
var localTimeService = Services.GetRequiredService<ILocalTimeService>();
25-
25+
2626
localTimeService.IsTimeZoneInfoAvailable.ShouldBeTrue();
2727
localTimeService.IsSuccessLoadBrowserTimeZone?.ShouldBeTrue();
2828
localTimeService.GetBrowserTimeZone().Id.ShouldBe("Asia/Tokyo");
@@ -32,11 +32,13 @@ public void BlazorLocalTimeProvider_SetsTimeZoneSuccessfully()
3232
public void BlazorLocalTimeProvider_HandlesJSDisconnectedException()
3333
{
3434
var module = JSInterop.SetupModule(BlazorLocalTimeProvider.JsPath);
35-
module.Setup<string>("getBrowserTimeZone").SetException(new JSDisconnectedException("Test disconnection"));
36-
35+
module
36+
.Setup<string>("getBrowserTimeZone")
37+
.SetException(new JSDisconnectedException("Test disconnection"));
38+
3739
var component = RenderComponent<BlazorLocalTimeProvider>();
3840
var localTimeService = Services.GetRequiredService<ILocalTimeService>();
39-
41+
4042
localTimeService.IsTimeZoneInfoAvailable.ShouldBeFalse();
4143
localTimeService.IsSuccessLoadBrowserTimeZone?.ShouldBeFalse();
4244
}
@@ -45,11 +47,13 @@ public void BlazorLocalTimeProvider_HandlesJSDisconnectedException()
4547
public void BlazorLocalTimeProvider_HandlesJSException()
4648
{
4749
var module = JSInterop.SetupModule(BlazorLocalTimeProvider.JsPath);
48-
module.Setup<string>("getBrowserTimeZone").SetException(new JSException("Browser API not supported"));
49-
50+
module
51+
.Setup<string>("getBrowserTimeZone")
52+
.SetException(new JSException("Browser API not supported"));
53+
5054
var component = RenderComponent<BlazorLocalTimeProvider>();
5155
var localTimeService = Services.GetRequiredService<ILocalTimeService>();
52-
56+
5357
localTimeService.IsTimeZoneInfoAvailable.ShouldBeFalse();
5458
localTimeService.IsSuccessLoadBrowserTimeZone?.ShouldBeFalse();
5559
}
@@ -59,8 +63,8 @@ public void BlazorLocalTimeProvider_InitialStateIsNull()
5963
{
6064
// Don't setup JS interop to simulate initial state
6165
var localTimeService = Services.GetRequiredService<ILocalTimeService>();
62-
66+
6367
localTimeService.IsSuccessLoadBrowserTimeZone.ShouldBeNull();
6468
localTimeService.IsTimeZoneInfoAvailable.ShouldBeFalse();
6569
}
66-
}
70+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<LocalTimeForm Value="Dt" ValueChanged="DtChanged" Context="dtf">
2+
<InputDate Type="InputDateType.DateTimeLocal" @bind-Value="dtf.ValueOrNow" />
3+
<InputDate Type="InputDateType.Date" @bind-Value="dtf.DateOrToday" />
4+
<InputDate Type="InputDateType.Time" @bind-Value="dtf.TimeOrNow" />
5+
</LocalTimeForm>
6+
7+
@code {
8+
[Parameter]
9+
public DateTime? Dt { get; set; }
10+
11+
[Parameter]
12+
public EventCallback<DateTime?> DtChanged { get; set; }
13+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@using BlazorLocalTimeTest.Component
2+
@using Microsoft.Extensions.DependencyInjection.Extensions
3+
@using Shouldly
4+
@inherits TestContext
5+
@code
6+
{
7+
[Fact]
8+
public void TestFormUpdated1()
9+
{
10+
// add a mock time provider to control the current time
11+
var mockTimeProvider = new MockTimeProvider(new(2020, 1, 1, 12, 34, 56, TimeSpan.Zero));
12+
Services.AddScoped<ILocalTimeService, LocalTimeMockService>(); // Asia/Tokyo (UTC+9)
13+
Services.TryAddSingleton<TimeProvider>(mockTimeProvider);
14+
15+
DateTime? dt = null;
16+
var cut = RenderComponent<LocalTimeFormWithDefault>(parameters =>
17+
parameters.Bind(p => p.Dt, dt, n => dt = n));
18+
var input_dt = cut.Find("input[type='datetime-local']");
19+
var input_date = cut.Find("input[type='date']");
20+
var input_time = cut.Find("input[type='time']");
21+
// value is set to the local time in the input
22+
input_dt.GetAttribute("value").ShouldBe("2020-01-01T21:34:56");
23+
input_date.GetAttribute("value").ShouldBe("2020-01-01");
24+
input_time.GetAttribute("value").ShouldBe("21:34:56");
25+
26+
// call update
27+
input_dt.Change("2020-10-01T12:00:00");
28+
dt.ShouldBe(new DateTime(2020, 10, 1, 3, 00, 0, DateTimeKind.Utc));
29+
}
30+
31+
}

0 commit comments

Comments
 (0)