Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial TimeSpan support in TextBox #1777

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,24 @@ private void AddDefaultToStringTranslations()
.Invoke(args[0].WithAnnotation(ShouldBeObservableAnnotation.Instance), args[1])
.WithAnnotation(ResultIsObservableAnnotation.Instance)
));
AddMethodTranslator(() => default(TimeSpan).ToString(), new GenericMethodCompiler(
args => new JsIdentifierExpression("dotvvm").Member("globalize").Member("bindingTimeOnlyToString")
.WithAnnotation(new GlobalizeResourceBindingProperty())
.Invoke(args[0].WithAnnotation(ShouldBeObservableAnnotation.Instance))
.WithAnnotation(ResultIsObservableAnnotation.Instance)
));
AddMethodTranslator(() => default(Nullable<TimeSpan>).ToString(), new GenericMethodCompiler(
args => new JsIdentifierExpression("dotvvm").Member("globalize").Member("bindingTimeOnlyToString")
.WithAnnotation(new GlobalizeResourceBindingProperty())
.Invoke(args[0].WithAnnotation(ShouldBeObservableAnnotation.Instance))
.WithAnnotation(ResultIsObservableAnnotation.Instance)
));
AddMethodTranslator(() => default(TimeSpan).ToString("fmt"), new GenericMethodCompiler(
args => new JsIdentifierExpression("dotvvm").Member("globalize").Member("bindingTimeOnlyToString")
.WithAnnotation(new GlobalizeResourceBindingProperty())
.Invoke(args[0].WithAnnotation(ShouldBeObservableAnnotation.Instance), args[1])
.WithAnnotation(ResultIsObservableAnnotation.Instance)
));

foreach (var num in ReflectionUtils.GetNumericTypes().Except(new[] { typeof(char) }))
{
Expand Down
2 changes: 1 addition & 1 deletion src/Framework/Framework/Controls/TextBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public static FormatValueType ResolveValueType(IValueBinding? binding)
{
return FormatValueType.DateOnly;
}
else if (binding != null && (binding.ResultType == typeof(TimeOnly) || binding.ResultType == typeof(TimeOnly?)))
else if (binding != null && (binding.ResultType == typeof(TimeOnly) || binding.ResultType == typeof(TimeOnly?) || binding.ResultType == typeof(TimeSpan) || binding.ResultType == typeof(TimeSpan?)))
{
return FormatValueType.TimeOnly;
}
Expand Down
13 changes: 11 additions & 2 deletions src/Framework/Framework/Resources/Scripts/Globalize/globalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ Globalize.cultures[ "default" ] = {
g: "M/d/yyyy h:mm tt",
// G is a combination of the short date ("d") and short time ("T") patterns, separated by a space
G: "M/d/yyyy h:mm:ss tt"
}
},
additionalDefaultPatterns: ["H:mm", "H:mm:ss"]
// optional fields for each calendar:
/*
monthsGenitive:
Expand Down Expand Up @@ -1536,7 +1537,7 @@ Globalize.localize = function( key, cultureSelector ) {
Globalize.parseDate = function (value, formats, culture, previousValue) {
culture = this.findClosestCulture( culture );

var date, prop, patterns;
var date, prop, patterns, pattern;
if ( formats ) {
if ( typeof formats === "string" ) {
formats = [ formats ];
Expand All @@ -1560,6 +1561,14 @@ Globalize.parseDate = function (value, formats, culture, previousValue) {
break;
}
}
if (!date) {
for ( pattern of culture.calendar.additionalDefaultPatterns) {
date = parseExact(value, pattern, culture, previousValue);
if ( date ) {
break;
}
}
}
}

return date || null;
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ function validateTimeSpan(value: any) {
if (typeof value === "number") {
return { value: serializeTimeSpan(value), wasCoerced: true };
}

if (value instanceof Date) {
return { value: serializeTimeOnly(value, false), wasCoerced: true };
}
}

function validateDateTimeOffset(value: any) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,13 @@ export function serializeDateOnly(date: Date): string {
return padNumber(date.getFullYear(), 4) + "-" + padNumber(date.getMonth() + 1, 2) + "-" + padNumber(date.getDate(), 2)
}

export function serializeTimeOnly(date: Date): string {
return padNumber(date.getHours(), 2) + ':' + padNumber(date.getMinutes(), 2) + ':' + padNumber(date.getSeconds(), 2) + '.' + padNumber(date.getMilliseconds(), 3) + '0000';
export function serializeTimeOnly(date: Date, useMilliseconds: boolean = true): string {
const result = padNumber(date.getHours(), 2) + ':' + padNumber(date.getMinutes(), 2) + ':' + padNumber(date.getSeconds(), 2);
if (useMilliseconds) {
return result + '.' + padNumber(date.getMilliseconds(), 3) + '0000';
} else{
return result;
}
}

export function serializeTimeSpan(ticks: number): string {
Expand Down
1 change: 1 addition & 0 deletions src/Framework/Framework/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"build-development": "rollup -c && npm run tsc-types",
"build-rollup": "npm run build-production && npm run build-development",
"build-production": "rollup -c --environment BUILD:production",
"build-globalize": "esbuild --minify --outfile=./Resources/Scripts/Globalize/globalize.min.js ./Resources/Scripts/Globalize/globalize.js",
"test": "jest --silent",
"tsc-check": "tsc -p . --noEmit",
"tsc-types": "tsc -d -p . --outFile ./obj/typescript-types/dotvvm.d.ts --emitDeclarationOnly --skipLibCheck"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public TextBoxFormatViewModel()
public DateTime DateValue { get; set; } = DateTime.Parse("2015-12-27T00:00:00.0000000");
public DateTime? NullableDateValue { get; set; } = DateTime.Parse("2015-12-27T00:00:00.0000000");
public double NumberValue { get; set; } = 123.123456789;
public TimeSpan TimeSpanValue { get; set; } = new TimeSpan(11, 48, 25);
public double? NullableNumberValue { get; set; } = 123.123456789;
public double BigNumberValue { get; set; } = 12356789.987654;
public string CurrentCulture => Context.GetCurrentUICulture().Name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class TextBoxTypesViewModel : DotvvmViewModelBase
public double? NullableNumber { get; set; } = 42.42;
public int Int { get; set; }
public int? NullableInt { get; set; }
public TimeSpan TimeSpan { get; set; } = new TimeSpan(11, 48, 25);
public TimeSpan? NullableTimeSpan { get; } = new TimeSpan(11, 48, 25);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@
Text="{value: NullableNumberValue}">
</dot:Literal>
</p>
<p ID="timespanFormat">
TimeSpan Format: <br>
<dot:textbox ID="timespanTextbox"
ClientIDMode="Static"
Text="{value: TimeSpanValue }"
style="height: 20px; width: 200px;"
FormatString="HH:mm" />
<dot:Literal ID="timespanValueText"
ClientIDMode="Static"
Text="{value: TimeSpanValue }"
ValueType="Time"/>
<dot:Literal ID="timespanFormatText"
ClientIDMode="Static"
Text="(Format: HH:mm)"
ValueType="Text" />
</p>
<p ID="customNumberformat">
Custom Numeric Format: <br>
<dot:textbox ID="customFormatTextbox"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@
Text="{value: NumberValue}">
</dot:Literal>
</p>
<p ID="timespanFormat">
TimeSpan Format: <br>
<dot:textbox ID="timespanTextbox"
ClientIDMode="Static"
Text="{value: TimeSpanValue.ToString('HH:mm') }"
style="height: 20px; width: 200px;"/>
<dot:Literal ID="timespanValueText"
ClientIDMode="Static"
Text="{value: TimeSpanValue }"
ValueType="Time" />
<dot:Literal ID="timespanFormatText"
ClientIDMode="Static"
Text="(Format: HH:mm)"
ValueType="Text" />
</p>
<p ID="nullableNumberformat">
Nullable Number: <br>
<dot:TextBox ID="nullableNumberTextbox"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
<br />
<dot:TextBox Text="{value: NullableDate}" Type="Time" data-ui="nullable-time-textbox" />
<br />
<dot:TextBox Text="{value: TimeSpan}" Type="Time" data-ui="timespan-textbox" />
<br />
<dot:TextBox Text="{value: NullableTimeSpan}" Type="Time" data-ui="nullable-timespan-textbox" />
<br />
<dot:TextBox Text="{value: Date}" Type="DateTimeLocal" data-ui="datetime-textbox" />
<br />
<dot:TextBox Text="{value: NullableDate}" Type="DateTimeLocal" data-ui="nullable-datetime-textbox" />
Expand Down
18 changes: 18 additions & 0 deletions src/Samples/Tests/Tests/Control/TextBoxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ public void Control_TextBox_StringFormat(string cultureName, string url, string
var nullableNumberValueText = browser.First("#nullableNumberValueText");
AssertUI.InnerTextEquals(nullableNumberValueText, 123.123456789.ToString(culture));

var timespanTextbox = browser.First("#timespanTextbox");
AssertUI.Attribute(timespanTextbox, "value", new TimeSpan(11, 48, 25).ToString("hh\\:mm"));

var timespanValueText = browser.First("#timespanValueText");
AssertUI.InnerTextEquals(timespanValueText, new TimeSpan(11, 48, 25).ToString());

//write new valid values
dateTextBox.Clear().SendKeys(dateResult2);
numberTextbox.Clear().SendKeys(2000.ToString("n0", culture));
Expand All @@ -180,23 +186,28 @@ public void Control_TextBox_StringFormat(string cultureName, string url, string
//write invalid values
dateTextBox.Clear().SendKeys("dsasdasd");
numberTextbox.Clear().SendKeys("000//a");
timespanTextbox.Clear().SendKeys("08:23");
dateTextBox.Click();

//check displayed values (behavior change in 3.0 - previous values should stay there)
AssertUI.InnerTextEquals(dateText, new DateTime(2018, 12, 27).ToString("G", culture));
AssertUI.InnerTextEquals(numberValueText, 2000.ToString(culture));
AssertUI.InnerTextEquals(timespanTextbox, new TimeSpan(8, 23, 0).ToString("hh\\:mm"));
AssertUI.InnerTextEquals(timespanValueText, new TimeSpan(8, 23, 0).ToString());

AssertUI.Attribute(numberTextbox, "value", "000//a");
AssertUI.Attribute(dateTextBox, "value", "dsasdasd");

//write new valid values
dateTextBox.Clear().SendKeys(new DateTime(2018, 1, 1).ToString("d", culture));
numberTextbox.Clear().SendKeys(1000.550277.ToString(culture));
timespanTextbox.Clear().SendKeys(new TimeSpan(11, 48, 25).ToString("hh\\:mm"));
dateTextBox.Click();

//check new values
AssertUI.InnerTextEquals(dateText, new DateTime(2018, 1, 1).ToString("G", culture));
AssertUI.InnerTextEquals(numberValueText, 1000.550277.ToString(culture));
AssertUI.InnerTextEquals(timespanValueText, new TimeSpan(11, 48, 0).ToString());

AssertUI.Attribute(numberTextbox, "value", 1000.550277.ToString("n4", culture));
AssertUI.Attribute(dateTextBox, "value", dateResult3);
Expand All @@ -209,6 +220,10 @@ public void Control_TextBox_StringFormat(string cultureName, string url, string
nullableDateTextBox.Clear().SendKeys(new DateTime(2020, 4, 2).ToString("d", culture)).SendKeys(Keys.Tab);
AssertUI.Attribute(nullableDateTextBox, "value", new DateTime(2020, 4, 2).ToString("G", culture));
AssertUI.InnerTextEquals(nullableDateText, new DateTime(2020, 4, 2).ToString("G", culture));

timespanTextbox.Clear().SendKeys(new TimeSpan(22, 23, 45).ToString("t", culture)).SendKeys(Keys.Tab);
AssertUI.InnerTextEquals(timespanTextbox, new TimeSpan(22, 23, 45).ToString("hh\\:mm"));
AssertUI.InnerTextEquals(timespanValueText, new TimeSpan(22, 23, 45).ToString());
});
}

Expand Down Expand Up @@ -288,6 +303,9 @@ public void Control_TextBox_TextBox_Types(string localizationId)
AssertUI.Value(browser.Single("input[data-ui='datetime-textbox']"), "2017-01-01T08:08");
AssertUI.Value(browser.Single("input[data-ui='nullable-datetime-textbox']"), "2017-01-01T20:10");

AssertUI.Value(browser.Single("input[data-ui='timespan-textbox']"), "11:48:25");
AssertUI.Value(browser.Single("input[data-ui='nullable-timespan-textbox']"), "11:48:25");

var intTextBox = browser.Single("input[data-ui='int-textbox']");
AssertUI.Value(intTextBox, "0");
intTextBox.SetFocus();
Expand Down
Loading