Add the ability to specify invariant and format
Adds the ability to specify that an `<input type="..." />` for some
specific value of `type` maps to the invariant culture and/or provides
a default format.
The primary examples of usage would be the number field which wants to
use invariant culture, or datetime which wants to use invariant culture
and a format string.
\n\nCommit migrated from 60f2d563a6
This commit is contained in:
parent
2c6e1dacac
commit
3c84c57c61
|
|
@ -59,6 +59,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
public readonly static string ChangeAttribute = "Components.Bind.ChangeAttribute";
|
public readonly static string ChangeAttribute = "Components.Bind.ChangeAttribute";
|
||||||
|
|
||||||
public readonly static string ExpressionAttribute = "Components.Bind.ExpressionAttribute";
|
public readonly static string ExpressionAttribute = "Components.Bind.ExpressionAttribute";
|
||||||
|
|
||||||
|
public readonly static string IsInvariantCulture = "Components.Bind.IsInvariantCulture";
|
||||||
|
|
||||||
|
public readonly static string Format = "Components.Bind.Format";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ChildContent
|
public static class ChildContent
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Components
|
namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
{
|
{
|
||||||
|
|
@ -115,6 +116,43 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value that indicates where the tag helper is a bind tag helper with a default
|
||||||
|
/// culture value of <see cref="CultureInfo.InvariantCulture"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagHelper">The <see cref="TagHelperDescriptor"/>.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if this tag helper is a bind tag helper and defaults in <see cref="CultureInfo.InvariantCulture"/>
|
||||||
|
/// </returns>
|
||||||
|
public static bool IsInvariantCultureBindTagHelper(this TagHelperDescriptor tagHelper)
|
||||||
|
{
|
||||||
|
if (tagHelper == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tagHelper));
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
tagHelper.Metadata.TryGetValue(ComponentMetadata.Bind.IsInvariantCulture, out var text) &&
|
||||||
|
bool.TryParse(text, out var result) &&
|
||||||
|
result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the default format value for a bind tag helper.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagHelper">The <see cref="TagHelperDescriptor"/>.</param>
|
||||||
|
/// <returns>The format, or <c>null</c>.</returns>
|
||||||
|
public static string GetFormat(this TagHelperDescriptor tagHelper)
|
||||||
|
{
|
||||||
|
if (tagHelper == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tagHelper));
|
||||||
|
}
|
||||||
|
|
||||||
|
tagHelper.Metadata.TryGetValue(ComponentMetadata.Bind.Format, out var result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsChildContentTagHelper(this TagHelperDescriptor tagHelper)
|
public static bool IsChildContentTagHelper(this TagHelperDescriptor tagHelper)
|
||||||
{
|
{
|
||||||
if (tagHelper == null)
|
if (tagHelper == null)
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,12 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
// These mappings are also provided by attributes. Primarily these are used by <input />
|
// These mappings are also provided by attributes. Primarily these are used by <input />
|
||||||
// and so we have a special case for input elements and their type attributes.
|
// and so we have a special case for input elements and their type attributes.
|
||||||
//
|
//
|
||||||
|
// Additionally, our mappings tell us about cases like <input type="number" ... /> where
|
||||||
|
// we need to treat the value as an invariant culture value. In general the HTML5 field
|
||||||
|
// types use invariant culture values when interacting with the DOM, in contrast to
|
||||||
|
// <input type="text" ... /> which is free-form text and is most likely to be
|
||||||
|
// culture-sensitive.
|
||||||
|
//
|
||||||
// 4. For components, we have a bit of a special case. We can infer a syntax that matches
|
// 4. For components, we have a bit of a special case. We can infer a syntax that matches
|
||||||
// case #2 based on property names. So if a component provides both 'Value' and 'ValueChanged'
|
// case #2 based on property names. So if a component provides both 'Value' and 'ValueChanged'
|
||||||
// we will turn that into an instance of bind.
|
// we will turn that into an instance of bind.
|
||||||
|
|
@ -254,6 +260,19 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
(string)attribute.ConstructorArguments[2].Value,
|
(string)attribute.ConstructorArguments[2].Value,
|
||||||
(string)attribute.ConstructorArguments[3].Value));
|
(string)attribute.ConstructorArguments[3].Value));
|
||||||
}
|
}
|
||||||
|
else if (attribute.AttributeClass == bindInputElement && attribute.ConstructorArguments.Length == 6)
|
||||||
|
{
|
||||||
|
results.Add(new ElementBindData(
|
||||||
|
type.ContainingAssembly.Name,
|
||||||
|
type.ToDisplayString(),
|
||||||
|
"input",
|
||||||
|
(string)attribute.ConstructorArguments[0].Value,
|
||||||
|
(string)attribute.ConstructorArguments[1].Value,
|
||||||
|
(string)attribute.ConstructorArguments[2].Value,
|
||||||
|
(string)attribute.ConstructorArguments[3].Value,
|
||||||
|
(bool)attribute.ConstructorArguments[4].Value,
|
||||||
|
(string)attribute.ConstructorArguments[5].Value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,6 +306,8 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
builder.Metadata[TagHelperMetadata.Runtime.Name] = ComponentMetadata.Bind.RuntimeName;
|
builder.Metadata[TagHelperMetadata.Runtime.Name] = ComponentMetadata.Bind.RuntimeName;
|
||||||
builder.Metadata[ComponentMetadata.Bind.ValueAttribute] = entry.ValueAttribute;
|
builder.Metadata[ComponentMetadata.Bind.ValueAttribute] = entry.ValueAttribute;
|
||||||
builder.Metadata[ComponentMetadata.Bind.ChangeAttribute] = entry.ChangeAttribute;
|
builder.Metadata[ComponentMetadata.Bind.ChangeAttribute] = entry.ChangeAttribute;
|
||||||
|
builder.Metadata[ComponentMetadata.Bind.IsInvariantCulture] = entry.IsInvariantCulture ? bool.TrueString : bool.FalseString;
|
||||||
|
builder.Metadata[ComponentMetadata.Bind.Format] = entry.Format;
|
||||||
|
|
||||||
if (entry.TypeAttribute != null)
|
if (entry.TypeAttribute != null)
|
||||||
{
|
{
|
||||||
|
|
@ -512,7 +533,9 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
string typeAttribute,
|
string typeAttribute,
|
||||||
string suffix,
|
string suffix,
|
||||||
string valueAttribute,
|
string valueAttribute,
|
||||||
string changeAttribute)
|
string changeAttribute,
|
||||||
|
bool isInvariantCulture = false,
|
||||||
|
string format = null)
|
||||||
{
|
{
|
||||||
Assembly = assembly;
|
Assembly = assembly;
|
||||||
TypeName = typeName;
|
TypeName = typeName;
|
||||||
|
|
@ -521,6 +544,8 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
Suffix = suffix;
|
Suffix = suffix;
|
||||||
ValueAttribute = valueAttribute;
|
ValueAttribute = valueAttribute;
|
||||||
ChangeAttribute = changeAttribute;
|
ChangeAttribute = changeAttribute;
|
||||||
|
IsInvariantCulture = isInvariantCulture;
|
||||||
|
Format = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Assembly { get; }
|
public string Assembly { get; }
|
||||||
|
|
@ -530,6 +555,8 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
public string Suffix { get; }
|
public string Suffix { get; }
|
||||||
public string ValueAttribute { get; }
|
public string ValueAttribute { get; }
|
||||||
public string ChangeAttribute { get; }
|
public string ChangeAttribute { get; }
|
||||||
|
public bool IsInvariantCulture { get; }
|
||||||
|
public string Format { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BindElementDataVisitor : SymbolVisitor
|
private class BindElementDataVisitor : SymbolVisitor
|
||||||
|
|
|
||||||
|
|
@ -649,6 +649,8 @@ namespace Test
|
||||||
Assert.Equal("checkbox", bind.Metadata[ComponentMetadata.Bind.TypeAttribute]);
|
Assert.Equal("checkbox", bind.Metadata[ComponentMetadata.Bind.TypeAttribute]);
|
||||||
Assert.True(bind.IsInputElementBindTagHelper());
|
Assert.True(bind.IsInputElementBindTagHelper());
|
||||||
Assert.False(bind.IsInputElementFallbackBindTagHelper());
|
Assert.False(bind.IsInputElementFallbackBindTagHelper());
|
||||||
|
Assert.False(bind.IsInvariantCultureBindTagHelper());
|
||||||
|
Assert.Null(bind.GetFormat());
|
||||||
|
|
||||||
var rule = Assert.Single(bind.TagMatchingRules);
|
var rule = Assert.Single(bind.TagMatchingRules);
|
||||||
Assert.Equal("input", rule.TagName);
|
Assert.Equal("input", rule.TagName);
|
||||||
|
|
@ -681,6 +683,46 @@ namespace Test
|
||||||
Assert.Equal(":format", parameter.DisplayName);
|
Assert.Equal(":format", parameter.DisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Execute_BindOnInputElementWithTypeAttributeAndSuffixAndInvariantCultureAndFormat_CreatesDescriptor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
[BindInputElement(""number"", null, ""value"", ""onchange"", isInvariantCulture: true, format: ""0.00"")]
|
||||||
|
public class BindAttributes
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
Assert.Empty(compilation.GetDiagnostics());
|
||||||
|
|
||||||
|
var context = TagHelperDescriptorProviderContext.Create();
|
||||||
|
context.SetCompilation(compilation);
|
||||||
|
|
||||||
|
var provider = new BindTagHelperDescriptorProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.Execute(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var matches = GetBindTagHelpers(context);
|
||||||
|
matches = AssertAndExcludeFullyQualifiedNameMatchComponents(matches, expectedCount: 0);
|
||||||
|
var bind = Assert.Single(matches);
|
||||||
|
|
||||||
|
Assert.Equal("value", bind.Metadata[ComponentMetadata.Bind.ValueAttribute]);
|
||||||
|
Assert.Equal("onchange", bind.Metadata[ComponentMetadata.Bind.ChangeAttribute]);
|
||||||
|
Assert.Equal("number", bind.Metadata[ComponentMetadata.Bind.TypeAttribute]);
|
||||||
|
Assert.True(bind.IsInputElementBindTagHelper());
|
||||||
|
Assert.False(bind.IsInputElementFallbackBindTagHelper());
|
||||||
|
Assert.True(bind.IsInvariantCultureBindTagHelper());
|
||||||
|
Assert.Equal("0.00", bind.GetFormat());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Execute_BindFallback_CreatesDescriptor()
|
public void Execute_BindFallback_CreatesDescriptor()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,13 @@ namespace Microsoft.AspNetCore.Components
|
||||||
public sealed partial class BindInputElementAttribute : System.Attribute
|
public sealed partial class BindInputElementAttribute : System.Attribute
|
||||||
{
|
{
|
||||||
public BindInputElementAttribute(string type, string suffix, string valueAttribute, string changeAttribute) { }
|
public BindInputElementAttribute(string type, string suffix, string valueAttribute, string changeAttribute) { }
|
||||||
|
public BindInputElementAttribute(string type, string suffix, string valueAttribute, string changeAttribute, bool isInvariantCulture, string format) { }
|
||||||
public string ChangeAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
public string ChangeAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||||
public string Suffix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
public string Suffix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||||
public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||||
public string ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
public string ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||||
|
public bool IsInvariantCulture { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||||
|
public string Format { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||||
}
|
}
|
||||||
public static partial class BindMethods
|
public static partial class BindMethods
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue