Support more cases for date parsing

We didn't have support for DateTimeOffset, nor support for parsing
formats with nullable DateTime.

Added E2E tests for bind + DateTime/DateTimeOffset.

Added E2E tests for 'manual bind' with number and date field. These
can't use `@bind` without a compiler update. This PR will add the
runtime support we need, so we can update the compiler.
This commit is contained in:
Ryan Nowak 2019-07-07 11:56:44 -07:00 committed by Ryan Nowak
parent 1e6739f108
commit 80f71bcd1f
8 changed files with 835 additions and 98 deletions

View File

@ -46,6 +46,9 @@ namespace Microsoft.AspNetCore.Components
public static partial class BindMethods
{
public static string GetValue(System.DateTime value, string format) { throw null; }
public static string GetValue(System.DateTimeOffset value, string format) { throw null; }
public static string GetValue(System.DateTimeOffset? value, string format) { throw null; }
public static string GetValue(System.DateTime? value, string format) { throw null; }
public static T GetValue<T>(T value) { throw null; }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
@ -124,6 +127,8 @@ namespace Microsoft.AspNetCore.Components
public static partial class EventCallbackFactoryBinderExtensions
{
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<bool> setter, bool existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTimeOffset> setter, System.DateTimeOffset existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTimeOffset> setter, System.DateTimeOffset existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTime> setter, System.DateTime existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTime> setter, System.DateTime existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<decimal> setter, decimal existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
@ -131,7 +136,10 @@ namespace Microsoft.AspNetCore.Components
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<int> setter, int existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<long> setter, long existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<bool?> setter, bool? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTimeOffset?> setter, System.DateTimeOffset? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTimeOffset?> setter, System.DateTimeOffset? existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTime?> setter, System.DateTime? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTime?> setter, System.DateTime? existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<decimal?> setter, decimal? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<double?> setter, double? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<int?> setter, int? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }

View File

@ -23,6 +23,25 @@ namespace Microsoft.AspNetCore.Components
value == default ? null
: (format == null ? value.ToString() : value.ToString(format));
/// <summary>
/// Not intended to be used directly.
/// </summary>
public static string GetValue(DateTime? value, string format) =>
value == default ? null
: (format == null ? value.ToString() : value.Value.ToString(format));
/// <summary>
/// Not intended to be used directly.
/// </summary>
public static string GetValue(DateTimeOffset value, string format) =>
value == default ? null
: (format == null ? value.ToString() : value.ToString(format));
/// <summary>
/// Not intended to be used directly.
/// </summary>
public static string GetValue(DateTimeOffset? value, string format) =>
value == default ? null
: (format == null ? value.ToString() : value.Value.ToString(format));
}
}

View File

@ -4,7 +4,6 @@
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
@ -26,6 +25,7 @@ namespace Microsoft.AspNetCore.Components
public static class EventCallbackFactoryBinderExtensions
{
private delegate bool BindConverter<T>(object obj, CultureInfo culture, out T value);
private delegate bool BindConverterWithFormat<T>(object obj, CultureInfo culture, string format, out T value);
// Perf: conversion delegates are written as static funcs so we can prevent
// allocations for these simple cases.
@ -261,9 +261,16 @@ namespace Microsoft.AspNetCore.Components
}
private static BindConverter<DateTime> ConvertToDateTime = ConvertToDateTimeCore;
private static BindConverterWithFormat<DateTime> ConvertToDateTimeWithFormat = ConvertToDateTimeCore;
private static BindConverter<DateTime?> ConvertToNullableDateTime = ConvertToNullableDateTimeCore;
private static BindConverterWithFormat<DateTime?> ConvertToNullableDateTimeWithFormat = ConvertToNullableDateTimeCore;
private static bool ConvertToDateTimeCore(object obj, CultureInfo culture, out DateTime value)
{
return ConvertToDateTimeCore(obj, culture, format: null, out value);
}
private static bool ConvertToDateTimeCore(object obj, CultureInfo culture, string format, out DateTime value)
{
var text = (string)obj;
if (string.IsNullOrEmpty(text))
@ -272,17 +279,27 @@ namespace Microsoft.AspNetCore.Components
return false;
}
if (!DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
{
value = default;
return false;
value = converted;
return true;
}
else if (format == null && DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
{
value = converted;
return true;
}
value = converted;
return true;
value = default;
return false;
}
private static bool ConvertToNullableDateTimeCore(object obj, CultureInfo culture, out DateTime? value)
{
return ConvertToNullableDateTimeCore(obj, culture, format: null, out value);
}
private static bool ConvertToNullableDateTimeCore(object obj, CultureInfo culture, string format, out DateTime? value)
{
var text = (string)obj;
if (string.IsNullOrEmpty(text))
@ -291,14 +308,82 @@ namespace Microsoft.AspNetCore.Components
return true;
}
if (!DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
{
value = converted;
return true;
}
else if (format == null && DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
{
value = converted;
return true;
}
value = default;
return false;
}
private static BindConverter<DateTimeOffset> ConvertToDateTimeOffset = ConvertToDateTimeOffsetCore;
private static BindConverterWithFormat<DateTimeOffset> ConvertToDateTimeOffsetWithFormat = ConvertToDateTimeOffsetCore;
private static BindConverter<DateTimeOffset?> ConvertToNullableDateTimeOffset = ConvertToNullableDateTimeOffsetCore;
private static BindConverterWithFormat<DateTimeOffset?> ConvertToNullableDateTimeOffsetWithFormat = ConvertToNullableDateTimeOffsetCore;
private static bool ConvertToDateTimeOffsetCore(object obj, CultureInfo culture, out DateTimeOffset value)
{
return ConvertToDateTimeOffsetCore(obj, culture, format: null, out value);
}
private static bool ConvertToDateTimeOffsetCore(object obj, CultureInfo culture, string format, out DateTimeOffset value)
{
var text = (string)obj;
if (string.IsNullOrEmpty(text))
{
value = default;
return false;
}
value = converted;
return true;
if (format != null && DateTimeOffset.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
{
value = converted;
return true;
}
else if (format == null && DateTimeOffset.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
{
value = converted;
return true;
}
value = default;
return false;
}
private static bool ConvertToNullableDateTimeOffsetCore(object obj, CultureInfo culture, out DateTimeOffset? value)
{
return ConvertToNullableDateTimeOffsetCore(obj, culture, format: null, out value);
}
private static bool ConvertToNullableDateTimeOffsetCore(object obj, CultureInfo culture, string format, out DateTimeOffset? value)
{
var text = (string)obj;
if (string.IsNullOrEmpty(text))
{
value = default;
return true;
}
if (format != null && DateTimeOffset.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
{
value = converted;
return true;
}
else if (format == null && DateTimeOffset.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
{
value = converted;
return true;
}
value = default;
return false;
}
private static bool ConvertToEnum<T>(object obj, CultureInfo culture, out T value) where T : struct, Enum
@ -602,26 +687,7 @@ namespace Microsoft.AspNetCore.Components
DateTime existingValue,
CultureInfo culture = null)
{
return CreateBinderCore<DateTime>(factory, receiver, setter, culture, ConvertToDateTime);
}
/// <summary>
/// For internal use only.
/// </summary>
/// <param name="factory"></param>
/// <param name="receiver"></param>
/// <param name="setter"></param>
/// <param name="existingValue"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static EventCallback<UIChangeEventArgs> CreateBinder(
this EventCallbackFactory factory,
object receiver,
Action<DateTime?> setter,
DateTime? existingValue,
CultureInfo culture = null)
{
return CreateBinderCore<DateTime?>(factory, receiver, setter, culture, ConvertToNullableDateTime);
return CreateBinderCore<DateTime>(factory, receiver, setter, culture, format: null, ConvertToDateTimeWithFormat);
}
/// <summary>
@ -642,45 +708,127 @@ namespace Microsoft.AspNetCore.Components
string format,
CultureInfo culture = null)
{
// Avoiding CreateBinderCore so we can avoid an extra allocating lambda
// when a format is used.
Action<UIChangeEventArgs> callback = (e) =>
{
DateTime value = default;
var converted = false;
try
{
value = ConvertDateTime(e.Value, culture, format);
converted = true;
}
catch
{
}
return CreateBinderCore<DateTime>(factory, receiver, setter, culture, format, ConvertToDateTimeWithFormat);
}
// See comments in CreateBinderCore
if (converted)
{
setter(value);
}
};
return factory.Create<UIChangeEventArgs>(receiver, callback);
/// <summary>
/// For internal use only.
/// </summary>
/// <param name="factory"></param>
/// <param name="receiver"></param>
/// <param name="setter"></param>
/// <param name="existingValue"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static EventCallback<UIChangeEventArgs> CreateBinder(
this EventCallbackFactory factory,
object receiver,
Action<DateTime?> setter,
DateTime? existingValue,
CultureInfo culture = null)
{
return CreateBinderCore<DateTime?>(factory, receiver, setter, culture, format: null, ConvertToNullableDateTimeWithFormat);
}
static DateTime ConvertDateTime(object obj, CultureInfo culture, string format)
{
var text = (string)obj;
if (string.IsNullOrEmpty(text))
{
return default;
}
else if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.RoundtripKind, out var value))
{
return value;
}
else
{
return DateTime.Parse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.RoundtripKind);
}
}
/// <summary>
/// For internal use only.
/// </summary>
/// <param name="factory"></param>
/// <param name="receiver"></param>
/// <param name="setter"></param>
/// <param name="existingValue"></param>
/// <param name="format"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static EventCallback<UIChangeEventArgs> CreateBinder(
this EventCallbackFactory factory,
object receiver,
Action<DateTime?> setter,
DateTime? existingValue,
string format,
CultureInfo culture = null)
{
return CreateBinderCore<DateTime?>(factory, receiver, setter, culture, format, ConvertToNullableDateTimeWithFormat);
}
/// <summary>
/// For internal use only.
/// </summary>
/// <param name="factory"></param>
/// <param name="receiver"></param>
/// <param name="setter"></param>
/// <param name="existingValue"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static EventCallback<UIChangeEventArgs> CreateBinder(
this EventCallbackFactory factory,
object receiver,
Action<DateTimeOffset> setter,
DateTimeOffset existingValue,
CultureInfo culture = null)
{
return CreateBinderCore<DateTimeOffset>(factory, receiver, setter, culture, format: null, ConvertToDateTimeOffsetWithFormat);
}
/// <summary>
/// For internal use only.
/// </summary>
/// <param name="factory"></param>
/// <param name="receiver"></param>
/// <param name="setter"></param>
/// <param name="existingValue"></param>
/// <param name="format"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static EventCallback<UIChangeEventArgs> CreateBinder(
this EventCallbackFactory factory,
object receiver,
Action<DateTimeOffset> setter,
DateTimeOffset existingValue,
string format,
CultureInfo culture = null)
{
return CreateBinderCore<DateTimeOffset>(factory, receiver, setter, culture, format, ConvertToDateTimeOffsetWithFormat);
}
/// <summary>
/// For internal use only.
/// </summary>
/// <param name="factory"></param>
/// <param name="receiver"></param>
/// <param name="setter"></param>
/// <param name="existingValue"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static EventCallback<UIChangeEventArgs> CreateBinder(
this EventCallbackFactory factory,
object receiver,
Action<DateTimeOffset?> setter,
DateTimeOffset? existingValue,
CultureInfo culture = null)
{
return CreateBinderCore<DateTimeOffset?>(factory, receiver, setter, culture, format: null, ConvertToNullableDateTimeOffsetWithFormat);
}
/// <summary>
/// For internal use only.
/// </summary>
/// <param name="factory"></param>
/// <param name="receiver"></param>
/// <param name="setter"></param>
/// <param name="existingValue"></param>
/// <param name="format"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static EventCallback<UIChangeEventArgs> CreateBinder(
this EventCallbackFactory factory,
object receiver,
Action<DateTimeOffset?> setter,
DateTimeOffset? existingValue,
string format,
CultureInfo culture = null)
{
return CreateBinderCore<DateTimeOffset?>(factory, receiver, setter, culture, format, ConvertToNullableDateTimeOffsetWithFormat);
}
/// <summary>
@ -746,6 +894,50 @@ namespace Microsoft.AspNetCore.Components
return factory.Create<UIChangeEventArgs>(receiver, callback);
}
private static EventCallback<UIChangeEventArgs> CreateBinderCore<T>(
this EventCallbackFactory factory,
object receiver,
Action<T> setter,
CultureInfo culture,
string format,
BindConverterWithFormat<T> converter)
{
Action<UIChangeEventArgs> callback = e =>
{
T value = default;
var converted = false;
try
{
converted = converter(e.Value, culture, format, out value);
}
catch
{
}
// We only invoke the setter if the conversion didn't throw, or if the newly-entered value is empty.
// If the user entered some non-empty value we couldn't parse, we leave the state of the .NET field
// unchanged, which for a two-way binding results in the UI reverting to its previous valid state
// because the diff will see the current .NET output no longer matches the render tree since we
// patched it to reflect the state of the UI.
//
// This reversion behavior is valuable because alternatives are problematic:
// - If we assigned default(T) on failure, the user would lose whatever data they were editing,
// for example if they accidentally pressed an alphabetical key while editing a number with
// @bind:event="oninput"
// - If the diff mechanism didn't revert to the previous good value, the user wouldn't necessarily
// know that the data they are submitting is different from what they think they've typed
if (converted)
{
setter(value);
}
else if (string.Empty.Equals(e.Value))
{
setter(default);
}
};
return factory.Create<UIChangeEventArgs>(receiver, callback);
}
// We can't rely on generics + static to cache here unfortunately. That would require us to overload
// CreateBinder on T : struct AND T : class, which is not allowed.
private static class BinderConverterCache
@ -821,6 +1013,14 @@ namespace Microsoft.AspNetCore.Components
{
converter = ConvertToNullableDateTime;
}
else if (typeof(T) == typeof(DateTimeOffset))
{
converter = ConvertToDateTimeOffset;
}
else if (typeof(T) == typeof(DateTimeOffset?))
{
converter = ConvertToNullableDateTimeOffset;
}
else if (typeof(T).IsEnum)
{
// We have to deal invoke this dynamically to work around the type constraint on Enum.TryParse.

View File

@ -421,7 +421,6 @@ namespace Microsoft.AspNetCore.Components
Assert.Equal(1, component.Count);
}
// For now format is only supported by this specific method.
[Fact]
public async Task CreateBinder_DateTime_Format()
{
@ -442,6 +441,104 @@ namespace Microsoft.AspNetCore.Components
Assert.Equal(1, component.Count);
}
[Fact]
public async Task CreateBinder_NullableDateTime_Format()
{
// Arrange
var value = (DateTime?)DateTime.Now;
var component = new EventCountingComponent();
Action<DateTime?> setter = (_) => value = _;
var format = "ddd yyyy-MM-dd";
var binder = EventCallback.Factory.CreateBinder(component, setter, value, format);
var expectedValue = new DateTime(2018, 3, 4);
// Act
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(format), });
Assert.Equal(expectedValue, value);
Assert.Equal(1, component.Count);
}
[Fact]
public async Task CreateBinder_DateTimeOffset()
{
// Arrange
var value = DateTimeOffset.Now;
var component = new EventCountingComponent();
Action<DateTimeOffset> setter = (_) => value = _;
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
var expectedValue = new DateTime(2018, 3, 4, 1, 2, 3);
// Act
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(), });
Assert.Equal(expectedValue, value);
Assert.Equal(1, component.Count);
}
[Fact]
public async Task CreateBinder_NullableDateTimeOffset()
{
// Arrange
var value = (DateTimeOffset?)DateTimeOffset.Now;
var component = new EventCountingComponent();
Action<DateTimeOffset?> setter = (_) => value = _;
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
var expectedValue = new DateTime(2018, 3, 4, 1, 2, 3);
// Act
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(), });
Assert.Equal(expectedValue, value);
Assert.Equal(1, component.Count);
}
[Fact]
public async Task CreateBinder_DateTimeOffset_Format()
{
// Arrange
var value = DateTimeOffset.Now;
var component = new EventCountingComponent();
Action<DateTimeOffset> setter = (_) => value = _;
var format = "ddd yyyy-MM-dd";
var binder = EventCallback.Factory.CreateBinder(component, setter, value, format);
var expectedValue = new DateTime(2018, 3, 4);
// Act
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(format), });
Assert.Equal(expectedValue, value);
Assert.Equal(1, component.Count);
}
[Fact]
public async Task CreateBinder_NullableDateTimeOffset_Format()
{
// Arrange
var value = (DateTimeOffset?)DateTimeOffset.Now;
var component = new EventCountingComponent();
Action<DateTimeOffset?> setter = (_) => value = _;
var format = "ddd yyyy-MM-dd";
var binder = EventCallback.Factory.CreateBinder(component, setter, value, format);
var expectedValue = new DateTime(2018, 3, 4);
// Act
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(format), });
Assert.Equal(expectedValue, value);
Assert.Equal(1, component.Count);
}
// This uses a type converter
[Fact]
public async Task CreateBinder_Guid()

View File

@ -91,6 +91,88 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo), () => display.Text);
}
// The logic is different for verifying culture-invariant fields. The problem is that the logic for what
// kinds of text a field accepts is determined by the browser and language - it's not general. So while
// type="number" and type="date" produce fixed-format and culture-invariant input/output via the "value"
// attribute - the actual input processing is harder to nail down. In practice this is only a problem
// with dates.
//
// For this reason we avoid sending keys directly to the field, and let two-way binding do its thing instead.
//
// A brief summary:
// 1. Input a value (invariant culture if using number field, or current culture to extra input if using date field)
// 2. trigger onchange
// 3. Verify "value" field (current culture)
// 4. Verify the input field's value attribute (invariant culture)
//
// We need to do step 4 to make sure that the value we're entering can "stick" in the form field.
// We can't use ".Text" because DOM reasons :(
[Theory]
[InlineData("en-US")]
[InlineData("fr-FR")]
public void CanSetCultureAndParseCultureInvariantNumbersAndDatesWithInputFields(string culture)
{
var cultureInfo = CultureInfo.GetCultureInfo(culture);
var selector = new SelectElement(Browser.FindElement(By.Id("culture-selector")));
selector.SelectByValue(culture);
// That should have triggered a redirect, wait for the main test selector to come up.
MountTestComponent<GlobalizationBindCases>();
WaitUntilExists(By.Id("globalization-cases"));
var cultureDisplay = WaitUntilExists(By.Id("culture-name-display"));
Assert.Equal($"Culture is: {culture}", cultureDisplay.Text);
// int
var input = Browser.FindElement(By.Id("input_type_number_int"));
var display = Browser.FindElement(By.Id("input_type_number_int_value"));
Browser.Equal(42.ToString(cultureInfo), () => display.Text);
Browser.Equal(42.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
input.Clear();
input.SendKeys(9000.ToString(CultureInfo.InvariantCulture));
input.SendKeys("\t");
Browser.Equal(9000.ToString(cultureInfo), () => display.Text);
Browser.Equal(9000.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
// decimal
input = Browser.FindElement(By.Id("input_type_number_decimal"));
display = Browser.FindElement(By.Id("input_type_number_decimal_value"));
Browser.Equal(4.2m.ToString(cultureInfo), () => display.Text);
Browser.Equal(4.2m.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
input.Clear();
input.SendKeys(9000.42m.ToString(CultureInfo.InvariantCulture));
input.SendKeys("\t");
Browser.Equal(9000.42m.ToString(cultureInfo), () => display.Text);
Browser.Equal(9000.42m.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
// datetime
input = Browser.FindElement(By.Id("input_type_date_datetime"));
display = Browser.FindElement(By.Id("input_type_date_datetime_value"));
var extraInput = Browser.FindElement(By.Id("input_type_date_datetime_extrainput"));
Browser.Equal(new DateTime(1985, 3, 4).ToString(cultureInfo), () => display.Text);
Browser.Equal(new DateTime(1985, 3, 4).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
ReplaceText(extraInput, new DateTime(2000, 1, 2).ToString(cultureInfo));
extraInput.SendKeys("\t");
Browser.Equal(new DateTime(2000, 1, 2).ToString(cultureInfo), () => display.Text);
Browser.Equal(new DateTime(2000, 1, 2).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
// datetimeoffset
input = Browser.FindElement(By.Id("input_type_date_datetimeoffset"));
display = Browser.FindElement(By.Id("input_type_date_datetimeoffset_value"));
extraInput = Browser.FindElement(By.Id("input_type_date_datetimeoffset_extrainput"));
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4)).ToString(cultureInfo), () => display.Text);
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4)).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
ReplaceText(extraInput, new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo));
extraInput.SendKeys("\t");
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo), () => display.Text);
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
}
// see: https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/214
//
// Calling Clear() can trigger onchange, which will revert the value to its default.

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using BasicTestApp;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
@ -640,5 +641,241 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
Browser.Equal(newValue, () => boundValue.Text);
Assert.Equal(newValue, mirrorValue.GetAttribute("value"));
}
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
// Blazor have different formatting behaviour by default.
[Fact]
public void CanBindTextboxDateTime()
{
var target = Browser.FindElement(By.Id("textbox-datetime"));
var boundValue = Browser.FindElement(By.Id("textbox-datetime-value"));
var mirrorValue = Browser.FindElement(By.Id("textbox-datetime-mirror"));
var expected = new DateTime(1985, 3, 4);
Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
// Clear textbox; value updates to 01/01/0001 because that's the default
target.Clear();
expected = default;
Browser.Equal(expected, () => DateTime.Parse(target.GetAttribute("value")));
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.SendKeys(Keys.Control + "a"); // select all
target.SendKeys("01/02/2000 00:00:00\t");
expected = new DateTime(2000, 1, 2);
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
}
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
// Blazor have different formatting behaviour by default.
[Fact]
public void CanBindTextboxNullableDateTime()
{
var target = Browser.FindElement(By.Id("textbox-nullable-datetime"));
var boundValue = Browser.FindElement(By.Id("textbox-nullable-datetime-value"));
var mirrorValue = Browser.FindElement(By.Id("textbox-nullable-datetime-mirror"));
Assert.Equal(string.Empty, target.GetAttribute("value"));
Assert.Equal(string.Empty, boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
Browser.Equal("", () => boundValue.Text);
Assert.Equal("", mirrorValue.GetAttribute("value"));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
var expected = new DateTime(2000, 1, 2);
target.SendKeys("01/02/2000 00:00:00\t");
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
target.SendKeys("\t");
Browser.Equal(string.Empty, () => boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
}
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
// Blazor have different formatting behaviour by default.
[Fact]
public void CanBindTextboxDateTimeOffset()
{
var target = Browser.FindElement(By.Id("textbox-datetimeoffset"));
var boundValue = Browser.FindElement(By.Id("textbox-datetimeoffset-value"));
var mirrorValue = Browser.FindElement(By.Id("textbox-datetimeoffset-mirror"));
var expected = new DateTimeOffset(new DateTime(1985, 3, 4), TimeSpan.FromHours(8));
Assert.Equal(expected, DateTimeOffset.Parse(target.GetAttribute("value")));
Assert.Equal(expected, DateTimeOffset.Parse(boundValue.Text));
Assert.Equal(expected, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")));
// Clear textbox; value updates to 01/01/0001 because that's the default
target.Clear();
expected = default;
Browser.Equal(expected, () => DateTimeOffset.Parse(target.GetAttribute("value")));
Assert.Equal(expected, DateTimeOffset.Parse(boundValue.Text));
Assert.Equal(expected, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.SendKeys(Keys.Control + "a"); // select all
target.SendKeys("01/02/2000 00:00:00 +08:00\t");
expected = new DateTimeOffset(new DateTime(2000, 1, 2), TimeSpan.FromHours(8));
Browser.Equal(expected, () => DateTimeOffset.Parse(boundValue.Text));
Assert.Equal(expected, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")));
}
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
// Blazor have different formatting behaviour by default.
[Fact]
public void CanBindTextboxNullableDateTimeOffset()
{
var target = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset"));
var boundValue = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset-value"));
var mirrorValue = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset-mirror"));
Assert.Equal(string.Empty, target.GetAttribute("value"));
Assert.Equal(string.Empty, boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
Browser.Equal("", () => boundValue.Text);
Assert.Equal("", mirrorValue.GetAttribute("value"));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.SendKeys("01/02/2000 00:00:00 +08:00" + "\t");
var expected = new DateTimeOffset(new DateTime(2000, 1, 2), TimeSpan.FromHours(8));
Browser.Equal(expected, () => DateTimeOffset.Parse(boundValue.Text));
Assert.Equal(expected, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
target.SendKeys("\t");
Browser.Equal(string.Empty, () => boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
}
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
// Blazor have different formatting behaviour by default.
[Fact]
public void CanBindTextboxDateTimeWithFormat()
{
var target = Browser.FindElement(By.Id("textbox-datetime-format"));
var boundValue = Browser.FindElement(By.Id("textbox-datetime-format-value"));
var mirrorValue = Browser.FindElement(By.Id("textbox-datetime-format-mirror"));
var expected = new DateTime(1985, 3, 4);
Assert.Equal("03-04", target.GetAttribute("value"));
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
// Clear textbox; value updates to emtpy because that's what we do for `default` when there's a format
target.Clear();
target.SendKeys("\t");
expected = default;
Browser.Equal(string.Empty, () => target.GetAttribute("value"));
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.SendKeys(Keys.Control + "a"); // select all
target.SendKeys("01-02\t");
expected = new DateTime(DateTime.Now.Year, 1, 2);
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
}
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
// Blazor have different formatting behaviour by default.
[Fact]
public void CanBindTextboxNullableDateTimeWithFormat()
{
var target = Browser.FindElement(By.Id("textbox-nullable-datetime-format"));
var boundValue = Browser.FindElement(By.Id("textbox-nullable-datetime-format-value"));
var mirrorValue = Browser.FindElement(By.Id("textbox-nullable-datetime-format-mirror"));
Assert.Equal(string.Empty, target.GetAttribute("value"));
Assert.Equal(string.Empty, boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
Browser.Equal("", () => boundValue.Text);
Assert.Equal("", mirrorValue.GetAttribute("value"));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.SendKeys("01-02\t");
var expected = new DateTime(DateTime.Now.Year, 1, 2);
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
target.SendKeys("\t");
Browser.Equal(string.Empty, () => boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
}
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
// Blazor have different formatting behaviour by default.
[Fact]
public void CanBindTextboxDateTimeOffsetWithFormat()
{
var target = Browser.FindElement(By.Id("textbox-datetimeoffset-format"));
var boundValue = Browser.FindElement(By.Id("textbox-datetimeoffset-format-value"));
var mirrorValue = Browser.FindElement(By.Id("textbox-datetimeoffset-format-mirror"));
var expected = new DateTimeOffset(new DateTime(1985, 3, 4), TimeSpan.FromHours(8));
Assert.Equal("03-04", target.GetAttribute("value"));
Assert.Equal(expected, DateTimeOffset.Parse(boundValue.Text));
Assert.Equal(expected, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")));
// Clear textbox; value updates to emtpy because that's what we do for `default` when there's a format
target.Clear();
expected = default;
Browser.Equal(string.Empty, () => target.GetAttribute("value"));
Assert.Equal(expected, DateTimeOffset.Parse(boundValue.Text));
Assert.Equal(expected, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.SendKeys(Keys.Control + "a"); // select all
target.SendKeys("01-02\t");
expected = new DateTimeOffset(new DateTime(DateTime.Now.Year, 1, 2), TimeSpan.FromHours(0));
Browser.Equal(expected.DateTime, () => DateTimeOffset.Parse(boundValue.Text).DateTime);
Assert.Equal(expected.DateTime, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")).DateTime);
}
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
// Blazor have different formatting behaviour by default.
//
// Guess what! Client-side and server-side also understand timezones differently. So for now we're comparing
// the parsed output without consideration for the timezone
[Fact]
public void CanBindTextboxNullableDateTimeOffsetWithFormat()
{
var target = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset"));
var boundValue = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset-value"));
var mirrorValue = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset-mirror"));
Assert.Equal(string.Empty, target.GetAttribute("value"));
Assert.Equal(string.Empty, boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
Browser.Equal("", () => boundValue.Text);
Assert.Equal("", mirrorValue.GetAttribute("value"));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.SendKeys("01-02" + "\t");
var expected = new DateTimeOffset(new DateTime(DateTime.Now.Year, 1, 2), TimeSpan.FromHours(0));
Browser.Equal(expected.DateTime, () => DateTimeOffset.Parse(boundValue.Text).DateTime);
Assert.Equal(expected.DateTime, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")).DateTime);
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
target.SendKeys("\t");
Browser.Equal(string.Empty, () => boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
}
}
}

View File

@ -110,6 +110,56 @@
<input id="textbox-generic-guid-mirror" @bind="textboxGenericGuidValue" readonly />
</p>
<h2>Date Textboxes (type=text)</h2>
<p>
DateTime:
<input id="textbox-datetime" @bind="textboxDateTimeValue" type="text" />
<span id="textbox-datetime-value">@textboxDateTimeValue</span>
<input id="textbox-datetime-mirror" @bind="textboxDateTimeValue" readonly />
</p>
<p>
Nullable DateTime:
<input id="textbox-nullable-datetime" @bind="textboxNullableDateTimeValue" type="text" />
<span id="textbox-nullable-datetime-value">@textboxNullableDateTimeValue</span>
<input id="textbox-nullable-datetime-mirror" @bind="textboxNullableDateTimeValue" readonly />
</p>
<p>
DateTimeOffset:
<input id="textbox-datetimeoffset" @bind="textboxDateTimeOffsetValue" type="text" />
<span id="textbox-datetimeoffset-value">@textboxDateTimeOffsetValue</span>
<input id="textbox-datetimeoffset-mirror" @bind="textboxDateTimeOffsetValue" readonly />
</p>
<p>
Nullable DateTimeOffset:
<input id="textbox-nullable-datetimeoffset" @bind="textboxNullableDateTimeOffsetValue" type="text" />
<span id="textbox-nullable-datetimeoffset-value">@textboxNullableDateTimeOffsetValue</span>
<input id="textbox-nullable-datetimeoffset-mirror" @bind="textboxNullableDateTimeOffsetValue" readonly />
</p>
<p>
DateTime (format):
<input id="textbox-datetime-format" @bind="textboxDateTimeFormatValue" type="text" @bind:format="MM-dd" />
<span id="textbox-datetime-format-value">@textboxDateTimeFormatValue</span>
<input id="textbox-datetime-format-mirror" @bind="textboxDateTimeFormatValue" readonly />
</p>
<p>
Nullable DateTime (format):
<input id="textbox-nullable-datetime-format" @bind="textboxNullableDateTimeFormatValue" type="text" @bind:format="MM-dd" />
<span id="textbox-nullable-datetime-format-value">@textboxNullableDateTimeFormatValue</span>
<input id="textbox-nullable-datetime-format-mirror" @bind="textboxNullableDateTimeFormatValue" readonly />
</p>
<p>
DateTimeOffset (format):
<input id="textbox-datetimeoffset-format" @bind="textboxDateTimeOffsetFormatValue" type="text" @bind:format="MM-dd" />
<span id="textbox-datetimeoffset-format-value">@textboxDateTimeOffsetFormatValue</span>
<input id="textbox-datetimeoffset-format-mirror" @bind="textboxDateTimeOffsetFormatValue" readonly />
</p>
<p>
Nullable DateTimeOffset (format):
<input id="textbox-nullable-datetimeoffset-format" @bind="textboxNullableDateTimeOffsetFormatValue" type="text" @bind:format="MM-dd" />
<span id="textbox-nullable-datetimeoffset-format-value">@textboxNullableDateTimeOffsetFormatValue</span>
<input id="textbox-nullable-datetimeoffset-format-mirror" @bind="textboxNullableDateTimeOffsetFormatValue" readonly />
</p>
<h2>Text Area</h2>
<p>
Initially blank:
@ -145,7 +195,8 @@
<h2>Select</h2>
<p>
<select id="select-box" @bind="selectValue">
<optgroup label="Some choices"> <!-- Show it also works with optgroup -->
<optgroup label="Some choices">
<!-- Show it also works with optgroup -->
<option value=@SelectableValue.First>First choice</option>
<option value=@SelectableValue.Second>Second choice</option>
<option value=@SelectableValue.Third>Third choice</option>
@ -172,40 +223,50 @@
</p>
@code {
string textboxInitiallyBlankValue = null;
string textboxInitiallyPopulatedValue = "Hello";
string textboxInitiallyBlankValue = null;
string textboxInitiallyPopulatedValue = "Hello";
string textAreaInitiallyBlankValue = null;
string textAreaInitiallyPopulatedValue = "Hello";
string textAreaInitiallyBlankValue = null;
string textAreaInitiallyPopulatedValue = "Hello";
bool? checkboxInitiallyNullValue = null;
bool checkboxInitiallyUncheckedValue = false;
bool checkboxInitiallyCheckedValue = true;
bool? checkboxInitiallyNullValue = null;
bool checkboxInitiallyUncheckedValue = false;
bool checkboxInitiallyCheckedValue = true;
int textboxIntValue = -42;
int? textboxNullableIntValue = null;
long textboxLongValue = 3_000_000_000;
long? textboxNullableLongValue = null;
float textboxFloatValue = 3.141f;
float? textboxNullableFloatValue = null;
double textboxDoubleValue = 3.14159265359d;
double? textboxNullableDoubleValue = null;
decimal textboxDecimalValue = 0.0000000000000000000000000001M;
decimal? textboxNullableDecimalValue = null;
decimal textboxDecimalInvalidValue = 0.0000000000000000000000000001M;
decimal? textboxNullableDecimalInvalidValue = null;
int textboxIntValue = -42;
int? textboxNullableIntValue = null;
long textboxLongValue = 3_000_000_000;
long? textboxNullableLongValue = null;
float textboxFloatValue = 3.141f;
float? textboxNullableFloatValue = null;
double textboxDoubleValue = 3.14159265359d;
double? textboxNullableDoubleValue = null;
decimal textboxDecimalValue = 0.0000000000000000000000000001M;
decimal? textboxNullableDecimalValue = null;
decimal textboxDecimalInvalidValue = 0.0000000000000000000000000001M;
decimal? textboxNullableDecimalInvalidValue = null;
int textboxGenericIntValue = -42;
Guid textboxGenericGuidValue = Guid.Empty;
int textboxGenericIntValue = -42;
Guid textboxGenericGuidValue = Guid.Empty;
bool includeFourthOption = false;
enum SelectableValue { First, Second, Third, Fourth }
SelectableValue selectValue = SelectableValue.Second;
SelectableValue selectMarkupValue = SelectableValue.Second;
DateTime textboxDateTimeValue = new DateTime(1985, 3, 4);
DateTime? textboxNullableDateTimeValue = null;
DateTimeOffset textboxDateTimeOffsetValue = new DateTimeOffset(new DateTime(1985, 3, 4), TimeSpan.FromHours(8));
DateTimeOffset? textboxNullableDateTimeOffsetValue = null;
void AddAndSelectNewSelectOption()
{
includeFourthOption = true;
selectValue = SelectableValue.Fourth;
}
DateTime textboxDateTimeFormatValue = new DateTime(1985, 3, 4);
DateTime? textboxNullableDateTimeFormatValue = null;
DateTimeOffset textboxDateTimeOffsetFormatValue = new DateTimeOffset(new DateTime(1985, 3, 4), TimeSpan.FromHours(8));
DateTimeOffset? textboxNullableDateTimeOffsetFormatValue = null;
bool includeFourthOption = false;
enum SelectableValue { First, Second, Third, Fourth }
SelectableValue selectValue = SelectableValue.Second;
SelectableValue selectMarkupValue = SelectableValue.Second;
void AddAndSelectNewSelectOption()
{
includeFourthOption = true;
selectValue = SelectableValue.Fourth;
}
}

View File

@ -1,3 +1,5 @@
@using System.Globalization
@using Microsoft.AspNetCore.Components.Forms
<h3 id="globalization-cases">Globalization Bind Cases</h3>
<h3 id="culture-name-display">Culture is: @System.Globalization.CultureInfo.CurrentCulture.Name</h3>
@ -25,6 +27,31 @@
</div>
</div>
<div>
<p>Numbers using bind in number fields</p>
<div>
int: <input type="number" id="input_type_number_int" value="@inputTypeNumberInt.ToString(CultureInfo.InvariantCulture)" @onchange="@EventCallback.Factory.CreateBinder(this, (value) => inputTypeNumberInt = value, inputTypeNumberInt, CultureInfo.InvariantCulture)" />
<span id="input_type_number_int_value">@inputTypeNumberInt</span>
</div>
<div>
decimal: <input type="number" id="input_type_number_decimal" value="@inputTypeNumberDecimal.ToString(CultureInfo.InvariantCulture)" @onchange="@EventCallback.Factory.CreateBinder(this, (value) => inputTypeNumberDecimal = value, inputTypeNumberDecimal, CultureInfo.InvariantCulture)"/>
<span id="input_type_number_decimal_value">@inputTypeNumberDecimal</span>
</div>
</div>
<div>
<p>Dates using bind in date fields</p>
<div>
DateTime: <input type="text" id="input_type_date_datetime_extrainput" @bind="inputTypeDateDateTime" />
<input type="date" id="input_type_date_datetime" value="@inputTypeDateDateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)" @onchange="@EventCallback.Factory.CreateBinder(this, (value) => inputTypeDateDateTime = value, inputTypeDateDateTime, "yyyy-MM-dd", CultureInfo.InvariantCulture)" />
<span id="input_type_date_datetime_value">@inputTypeDateDateTime</span>
</div>
<div>
DateTimeOffset: <input type="text" id="input_type_date_datetimeoffset_extrainput" @bind="inputTypeDateDateTimeOffset" />
<input type="date" id="input_type_date_datetimeoffset" value="@inputTypeDateDateTimeOffset.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)" @onchange="@EventCallback.Factory.CreateBinder(this, (value) => inputTypeDateDateTimeOffset = value, inputTypeDateDateTimeOffset, "yyyy-MM-dd", CultureInfo.InvariantCulture)"/>
<span id="input_type_date_datetimeoffset_value">@inputTypeDateDateTimeOffset</span>
</div>
</div>
@code {
int inputTypeTextInt = 42;
@ -32,4 +59,10 @@
DateTime inputTypeTextDateTime = new DateTime(1985, 3, 4);
DateTimeOffset inputTypeTextDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
int inputTypeNumberInt = 42;
decimal inputTypeNumberDecimal = 4.2m;
DateTime inputTypeDateDateTime = new DateTime(1985, 3, 4);
DateTimeOffset inputTypeDateDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
}