Skip to content

Commit 353846d

Browse files
committed
DatePicker with ShowTimeOkButton will revert value if change not confirmed by Ok button
1 parent 54b952f commit 353846d

File tree

4 files changed

+247
-5
lines changed

4 files changed

+247
-5
lines changed

Radzen.Blazor.Tests/DatePickerTests.cs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,189 @@ public void DatePicker_Multiple_Emits_IEnumerableDateOnlyAndTimeOnlyNullable()
871871
Assert.All(timeList, t => Assert.Equal(new TimeOnly(0, 0, 0), t.Value));
872872
}
873873

874+
[Fact]
875+
public void DatePicker_WithoutOkButton_TimeChange_ImmediatelyCommits()
876+
{
877+
using var ctx = new TestContext();
878+
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
879+
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
880+
881+
var changeCount = 0;
882+
DateTime? lastChangeValue = null;
883+
DateTime? lastValueChanged = null;
884+
885+
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
886+
{
887+
parameters.Add(p => p.ShowTime, true);
888+
parameters.Add(p => p.ShowTimeOkButton, false);
889+
parameters.Add(p => p.Value, new DateTime(2024, 6, 15, 10, 30, 0));
890+
parameters.Add(p => p.Change, args => { changeCount++; lastChangeValue = args; });
891+
parameters.Add(p => p.ValueChanged, args => { lastValueChanged = args; });
892+
});
893+
894+
component.Find(".rz-hour-picker .rz-numeric-up").Click();
895+
896+
Assert.Equal(1, changeCount);
897+
Assert.NotNull(lastChangeValue);
898+
Assert.Equal(11, lastChangeValue.Value.Hour);
899+
Assert.NotNull(lastValueChanged);
900+
Assert.Equal(11, lastValueChanged.Value.Hour);
901+
}
902+
903+
[Fact]
904+
public void DatePicker_WithoutOkButton_DayClick_ImmediatelyCommits()
905+
{
906+
using var ctx = new TestContext();
907+
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
908+
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
909+
910+
var changeRaised = false;
911+
DateTime? lastValueChanged = null;
912+
913+
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
914+
{
915+
parameters.Add(p => p.ShowTime, true);
916+
parameters.Add(p => p.ShowTimeOkButton, false);
917+
parameters.Add(p => p.Value, new DateTime(2024, 6, 15, 10, 30, 0));
918+
parameters.Add(p => p.Change, args => { changeRaised = true; });
919+
parameters.Add(p => p.ValueChanged, args => { lastValueChanged = args; });
920+
});
921+
922+
component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "20").ParentElement.Click();
923+
924+
Assert.True(changeRaised);
925+
Assert.NotNull(lastValueChanged);
926+
Assert.Equal(20, lastValueChanged.Value.Day);
927+
Assert.Equal(10, lastValueChanged.Value.Hour);
928+
}
929+
930+
[Fact]
931+
public void DatePicker_WithOkButton_TimeChange_RevertsOnClose()
932+
{
933+
using var ctx = new TestContext();
934+
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
935+
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
936+
937+
var initialDate = new DateTime(2024, 6, 15, 10, 30, 0);
938+
var valueChangedCount = 0;
939+
940+
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
941+
{
942+
parameters.Add(p => p.ShowTime, true);
943+
parameters.Add(p => p.ShowTimeOkButton, true);
944+
parameters.Add(p => p.Value, initialDate);
945+
parameters.Add(p => p.ValueChanged, args => { valueChangedCount++; });
946+
});
947+
948+
var hourInput = component.Find(".rz-hour-picker input");
949+
Assert.Equal("10", hourInput.GetAttribute("value"));
950+
951+
component.Find(".rz-hour-picker .rz-numeric-up").Click();
952+
953+
hourInput = component.Find(".rz-hour-picker input");
954+
Assert.Equal("11", hourInput.GetAttribute("value"));
955+
Assert.Equal(0, valueChangedCount);
956+
957+
component.InvokeAsync(() => component.Instance.Close());
958+
959+
hourInput = component.Find(".rz-hour-picker input");
960+
Assert.Equal("10", hourInput.GetAttribute("value"));
961+
962+
var input = component.Find(".rz-inputtext");
963+
Assert.Contains("10", input.GetAttribute("value"));
964+
}
965+
966+
[Fact]
967+
public void DatePicker_WithOkButton_TimeChange_CommitsOnOk()
968+
{
969+
using var ctx = new TestContext();
970+
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
971+
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
972+
973+
DateTime? lastValueChanged = null;
974+
DateTime? lastChangeValue = null;
975+
976+
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
977+
{
978+
parameters.Add(p => p.ShowTime, true);
979+
parameters.Add(p => p.ShowTimeOkButton, true);
980+
parameters.Add(p => p.Value, new DateTime(2024, 6, 15, 10, 30, 0));
981+
parameters.Add(p => p.ValueChanged, args => { lastValueChanged = args; });
982+
parameters.Add(p => p.Change, args => { lastChangeValue = args; });
983+
});
984+
985+
component.Find(".rz-hour-picker .rz-numeric-up").Click();
986+
component.FindAll(".rz-button-text").First(x => x.TextContent == "Ok").Click();
987+
988+
Assert.NotNull(lastValueChanged);
989+
Assert.Equal(11, lastValueChanged.Value.Hour);
990+
Assert.NotNull(lastChangeValue);
991+
Assert.Equal(11, lastChangeValue.Value.Hour);
992+
}
993+
994+
[Fact]
995+
public void DatePicker_WithOkButton_DayCommits_ThenTimeRevertsOnClose()
996+
{
997+
using var ctx = new TestContext();
998+
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
999+
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
1000+
1001+
DateTime? lastValueChanged = null;
1002+
1003+
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
1004+
{
1005+
parameters.Add(p => p.ShowTime, true);
1006+
parameters.Add(p => p.ShowTimeOkButton, true);
1007+
parameters.Add(p => p.Value, new DateTime(2024, 6, 15, 10, 30, 0));
1008+
parameters.Add(p => p.ValueChanged, args => { lastValueChanged = args; });
1009+
});
1010+
1011+
component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "20").ParentElement.Click();
1012+
1013+
Assert.NotNull(lastValueChanged);
1014+
Assert.Equal(20, lastValueChanged.Value.Day);
1015+
Assert.Equal(10, lastValueChanged.Value.Hour);
1016+
1017+
component.Find(".rz-hour-picker .rz-numeric-up").Click();
1018+
1019+
component.InvokeAsync(() => component.Instance.Close());
1020+
1021+
var input = component.Find(".rz-inputtext");
1022+
Assert.Contains("10", input.GetAttribute("value"));
1023+
Assert.Contains("20", input.GetAttribute("value"));
1024+
}
1025+
1026+
[Fact]
1027+
public void DatePicker_WithOkButton_OnPopupClose_RevertsTimeChange()
1028+
{
1029+
using var ctx = new TestContext();
1030+
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
1031+
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
1032+
1033+
var initialDate = new DateTime(2024, 6, 15, 10, 30, 0);
1034+
DateTime? lastValueChanged = null;
1035+
var valueChangedCount = 0;
1036+
1037+
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
1038+
{
1039+
parameters.Add(p => p.ShowTime, true);
1040+
parameters.Add(p => p.ShowTimeOkButton, true);
1041+
parameters.Add(p => p.Value, initialDate);
1042+
parameters.Add(p => p.ValueChanged, args => { valueChangedCount++; lastValueChanged = args; });
1043+
});
1044+
1045+
component.Find(".rz-hour-picker .rz-numeric-up").Click();
1046+
1047+
var countBeforeClose = valueChangedCount;
1048+
1049+
component.InvokeAsync(() => component.Instance.OnPopupClose());
1050+
1051+
Assert.Equal(countBeforeClose, valueChangedCount);
1052+
1053+
var input = component.Find("input");
1054+
Assert.Contains("10", input.GetAttribute("value"));
1055+
}
1056+
8741057
[Fact]
8751058
public void DatePicker_OkClick_Fires_OnOkButtonClick()
8761059
{

Radzen.Blazor/RadzenDatePicker.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
}
3030

3131
<Popup id="@PopupID" Lazy=@(PopupRenderMode == PopupRenderMode.OnDemand) @ref=@popup style=@PopupStyle
32+
Close="@OnPopupCloseFromEvent"
3233
role="dialog" aria-label="@PopupAriaLabel"
3334
class="@($"{(Inline ? "rz-datepicker-inline-container " : "rz-datepicker-popup-container ")}")">
3435
<div class="rz-calendar" @onkeydown="@OnPopupKeyDown">

Radzen.Blazor/RadzenDatePicker.razor.cs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,19 @@ void OnUpdateHourSeconds(ChangeEventArgs args)
206206
seconds = Math.Max(Math.Min(v, 59), 0);
207207
}
208208

209+
DateTime? _valueBeforeTimeEdit;
210+
bool _hasUncommittedTimeChange;
211+
209212
async Task UpdateValueFromTime(DateTime newValue)
210213
{
211214
if (ShowTimeOkButton)
212215
{
216+
if (!_hasUncommittedTimeChange)
217+
{
218+
_valueBeforeTimeEdit = _dateTimeValue;
219+
_hasUncommittedTimeChange = true;
220+
}
221+
213222
DateTimeValue = newValue;
214223
CurrentDate = newValue;
215224
}
@@ -221,6 +230,47 @@ async Task UpdateValueFromTime(DateTime newValue)
221230
}
222231
}
223232

233+
void RevertUncommittedTimeChange()
234+
{
235+
if (!ShowTimeOkButton || !_hasUncommittedTimeChange) return;
236+
237+
_hasUncommittedTimeChange = false;
238+
239+
if (_valueBeforeTimeEdit.HasValue)
240+
{
241+
_dateTimeValue = _valueBeforeTimeEdit;
242+
_value = ConvertToTValue(_dateTimeValue);
243+
}
244+
else
245+
{
246+
_dateTimeValue = null;
247+
_value = null;
248+
}
249+
250+
_currentDate = default(DateTime);
251+
hour = null;
252+
minutes = null;
253+
seconds = null;
254+
}
255+
256+
/// <summary>
257+
/// Called from JavaScript when the popup is closed (e.g. by clicking outside) in Initial render mode.
258+
/// </summary>
259+
[JSInvokable("OnPopupClose")]
260+
public void OnPopupClose()
261+
{
262+
RevertUncommittedTimeChange();
263+
contentStyle = "display:none;";
264+
StateHasChanged();
265+
}
266+
267+
void OnPopupCloseFromEvent()
268+
{
269+
RevertUncommittedTimeChange();
270+
contentStyle = "display:none;";
271+
StateHasChanged();
272+
}
273+
224274
async Task UpdateHour(int v)
225275
{
226276
var newHour = HourFormat == "12" ? GetHour24FormatFrom12Format(v) : v;
@@ -274,6 +324,8 @@ async Task OnOkClick(bool shouldClose = true, bool fromOkButton = false)
274324
await UpdateValueFromSelectedDates(date);
275325
}
276326

327+
_hasUncommittedTimeChange = false;
328+
277329
if (shouldClose)
278330
{
279331
Close();
@@ -287,6 +339,8 @@ async Task OnOkClick(bool shouldClose = true, bool fromOkButton = false)
287339
return;
288340
}
289341

342+
_hasUncommittedTimeChange = false;
343+
290344
if (shouldClose)
291345
{
292346
Close();
@@ -1234,6 +1288,8 @@ public void Close()
12341288
if (Disabled || ReadOnly || Inline)
12351289
return;
12361290

1291+
RevertUncommittedTimeChange();
1292+
12371293
if (PopupRenderMode == PopupRenderMode.OnDemand)
12381294
{
12391295
if (popup != null)
@@ -1513,7 +1569,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
15131569

15141570
if (Visible && !Disabled && !ReadOnly && !Inline && PopupRenderMode == PopupRenderMode.Initial && JSRuntime != null)
15151571
{
1516-
await JSRuntime.InvokeVoidAsync("Radzen.createDatePicker", Element, PopupID);
1572+
await JSRuntime.InvokeVoidAsync("Radzen.createDatePicker", Element, PopupID, Reference, nameof(OnPopupClose));
15171573
}
15181574
}
15191575

@@ -1684,7 +1740,7 @@ async Task OnKeyPress(KeyboardEventArgs args)
16841740

16851741
if (PopupRenderMode == PopupRenderMode.Initial && JSRuntime != null)
16861742
{
1687-
await JSRuntime.InvokeVoidAsync("Radzen.openPopup", Element, PopupID, false, null, null, null, null, null, true, true);
1743+
await JSRuntime.InvokeVoidAsync("Radzen.openPopup", Element, PopupID, false, null, null, null, Reference, nameof(OnPopupClose), true, true);
16881744
}
16891745
else if (popup != null)
16901746
{
@@ -1720,7 +1776,7 @@ internal async Task TogglePopup()
17201776

17211777
if (PopupRenderMode == PopupRenderMode.Initial && JSRuntime != null)
17221778
{
1723-
await JSRuntime.InvokeVoidAsync("Radzen.togglePopup", Element, PopupID, false, null, null, true, true);
1779+
await JSRuntime.InvokeVoidAsync("Radzen.togglePopup", Element, PopupID, false, Reference, nameof(OnPopupClose), true, true);
17241780
}
17251781
else if (popup != null)
17261782
{
@@ -1732,6 +1788,8 @@ async Task ClosePopup()
17321788
{
17331789
if (Inline) return;
17341790

1791+
RevertUncommittedTimeChange();
1792+
17351793
if (PopupRenderMode == PopupRenderMode.Initial && JSRuntime != null)
17361794
{
17371795
await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);

Radzen.Blazor/wwwroot/Radzen.Blazor.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,11 +1151,11 @@ window.Radzen = {
11511151
input.onclick = null;
11521152
}
11531153
},
1154-
createDatePicker(el, popupId) {
1154+
createDatePicker(el, popupId, instance, callback) {
11551155
if(!el) return;
11561156
var handler = function (e, condition) {
11571157
if (condition) {
1158-
Radzen.togglePopup(e.currentTarget.parentNode, popupId, false, null, null, true, false);
1158+
Radzen.togglePopup(e.currentTarget.parentNode, popupId, false, instance, callback, true, false);
11591159
}
11601160
};
11611161

0 commit comments

Comments
 (0)