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

InputRadio component with form support #23415

Merged
merged 15 commits into from
Jul 2, 2020
Merged
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 @@ -61,7 +61,7 @@ protected InputBase() { }
protected TValue CurrentValue { get { throw null; } set { } }
protected string? CurrentValueAsString { get { throw null; } set { } }
protected Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected internal Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
[System.Diagnostics.CodeAnalysis.MaybeNullAttribute]
[System.Diagnostics.CodeAnalysis.AllowNullAttribute]
Expand All @@ -71,7 +71,7 @@ protected InputBase() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public System.Linq.Expressions.Expression<System.Func<TValue>>? ValueExpression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected virtual void Dispose(bool disposing) { }
protected virtual string? FormatValueAsString(TValue value) { throw null; }
protected virtual string? FormatValueAsString([System.Diagnostics.CodeAnalysis.AllowNullAttribute] TValue value) { throw null; }
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved
public override System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; }
void System.IDisposable.Dispose() { }
protected abstract bool TryParseValueFromString(string? value, [System.Diagnostics.CodeAnalysis.MaybeNullAttribute] out TValue result, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(false)] out string? validationErrorMessage);
Expand All @@ -88,7 +88,7 @@ public InputDate() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { }
protected override string FormatValueAsString(TValue value) { throw null; }
protected override string FormatValueAsString([System.Diagnostics.CodeAnalysis.AllowNullAttribute] TValue value) { throw null; }
protected override bool TryParseValueFromString(string? value, [System.Diagnostics.CodeAnalysis.MaybeNullAttribute] out TValue result, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(false)] out string? validationErrorMessage) { throw null; }
}
public partial class InputNumber<TValue> : Microsoft.AspNetCore.Components.Forms.InputBase<TValue>
Expand All @@ -97,9 +97,34 @@ public InputNumber() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { }
protected override string? FormatValueAsString(TValue value) { throw null; }
protected override string? FormatValueAsString([System.Diagnostics.CodeAnalysis.AllowNullAttribute] TValue value) { throw null; }
protected override bool TryParseValueFromString(string? value, [System.Diagnostics.CodeAnalysis.MaybeNullAttribute] out TValue result, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(false)] out string? validationErrorMessage) { throw null; }
}
public partial class InputRadioGroup<TValue> : Microsoft.AspNetCore.Components.Forms.InputBase<TValue>
{
public InputRadioGroup() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment? ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public string? Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { }
protected override void OnParametersSet() { }
protected override bool TryParseValueFromString(string? value, [System.Diagnostics.CodeAnalysis.MaybeNullAttribute] out TValue result, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(false)] out string? validationErrorMessage) { throw null; }
}
public partial class InputRadio<TValue> : Microsoft.AspNetCore.Components.ComponentBase
{
public InputRadio() { }
[Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)]
public System.Collections.Generic.IReadOnlyDictionary<string, object>? AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public string? Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
[System.Diagnostics.CodeAnalysis.MaybeNullAttribute]
[System.Diagnostics.CodeAnalysis.AllowNullAttribute]
public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { }
protected override void OnParametersSet() { }
}
public partial class InputSelect<TValue> : Microsoft.AspNetCore.Components.Forms.InputBase<TValue>
{
public InputSelect() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ protected InputBase() { }
protected TValue CurrentValue { get { throw null; } set { } }
protected string? CurrentValueAsString { get { throw null; } set { } }
protected Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected internal Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
Expand Down Expand Up @@ -96,6 +96,29 @@ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Renderin
protected override string? FormatValueAsString(TValue value) { throw null; }
protected override bool TryParseValueFromString(string? value, out TValue result, out string? validationErrorMessage) { throw null; }
}
public partial class InputRadioGroup<TValue> : Microsoft.AspNetCore.Components.Forms.InputBase<TValue>
{
public InputRadioGroup() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment? ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public string? Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { }
protected override void OnParametersSet() { }
protected override bool TryParseValueFromString(string? value, out TValue result, out string? validationErrorMessage) { throw null; }
}
public partial class InputRadio<TValue> : Microsoft.AspNetCore.Components.ComponentBase
{
public InputRadio() { }
[Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)]
public System.Collections.Generic.IReadOnlyDictionary<string, object>? AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public string? Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { }
protected override void OnParametersSet() { }
}
public partial class InputSelect<TValue> : Microsoft.AspNetCore.Components.Forms.InputBase<TValue>
{
public InputSelect() { }
Expand Down
4 changes: 2 additions & 2 deletions src/Components/Web/src/Forms/InputBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public abstract class InputBase<TValue> : ComponentBase, IDisposable
/// <summary>
/// Gets the <see cref="FieldIdentifier"/> for the bound value.
/// </summary>
protected FieldIdentifier FieldIdentifier { get; set; }
protected internal FieldIdentifier FieldIdentifier { get; set; }

/// <summary>
/// Gets or sets the current value of the input.
Expand Down Expand Up @@ -142,7 +142,7 @@ protected InputBase()
/// </summary>
/// <param name="value">The value to format.</param>
/// <returns>A string representation of the value.</returns>
protected virtual string? FormatValueAsString(TValue value)
protected virtual string? FormatValueAsString([AllowNull] TValue value)
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved
=> value?.ToString();

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Web/src/Forms/InputDate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
}

/// <inheritdoc />
protected override string FormatValueAsString(TValue value)
protected override string FormatValueAsString([AllowNull] TValue value)
{
switch (value)
{
Expand Down
35 changes: 35 additions & 0 deletions src/Components/Web/src/Forms/InputExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

namespace Microsoft.AspNetCore.Components.Forms
{
internal static class InputExtensions
{
public static bool TryParseSelectableValueFromString<TValue>(this InputBase<TValue> input, string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
try
{
if (BindConverter.TryConvertTo<TValue>(value, CultureInfo.CurrentCulture, out var parsedValue))
{
result = parsedValue;
validationErrorMessage = null;
return true;
}
else
{
result = default;
validationErrorMessage = $"The {input.FieldIdentifier.FieldName} field is not valid.";
return false;
}
}
catch (InvalidOperationException ex)
{
throw new InvalidOperationException($"{input.GetType()} does not support the type '{typeof(TValue)}'.", ex);
}
}
}
}
2 changes: 1 addition & 1 deletion src/Components/Web/src/Forms/InputNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ protected override bool TryParseValueFromString(string? value, [MaybeNull] out T
/// </summary>
/// <param name="value">The value to format.</param>
/// <returns>A string representation of the value.</returns>
protected override string? FormatValueAsString(TValue value)
protected override string? FormatValueAsString([AllowNull] TValue value)
{
// Avoiding a cast to IFormattable to avoid boxing.
switch (value)
Expand Down
82 changes: 82 additions & 0 deletions src/Components/Web/src/Forms/InputRadio.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components.Rendering;

namespace Microsoft.AspNetCore.Components.Forms
{
/// <summary>
/// An input component used for selecting a value from a group of choices.
/// </summary>
public class InputRadio<TValue> : ComponentBase
{
/// <summary>
/// Gets context for this <see cref="InputRadio{TValue}"/>.
/// </summary>
internal InputRadioContext? Context { get; private set; }

/// <summary>
/// Gets or sets a collection of additional attributes that will be applied to the input element.
/// </summary>
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; }

/// <summary>
/// Gets or sets the value of this input.
/// </summary>
[AllowNull]
[MaybeNull]
[Parameter]
public TValue Value { get; set; } = default;

/// <summary>
/// Gets or sets the name of the parent input radio group.
/// </summary>
[Parameter] public string? Name { get; set; }

[CascadingParameter] private InputRadioContext? CascadedContext { get; set; }

private string GetCssClass(string fieldClass)
{
if (AdditionalAttributes != null &&
AdditionalAttributes.TryGetValue("class", out var @class) &&
!string.IsNullOrEmpty(Convert.ToString(@class)))
{
return $"{@class} {fieldClass}";
}

return fieldClass;
}

/// <inheritdoc />
protected override void OnParametersSet()
{
Context = string.IsNullOrEmpty(Name) ? CascadedContext : CascadedContext?.FindContextInAncestors(Name);

if (Context == null)
{
throw new InvalidOperationException($"{GetType()} must have an ancestor {typeof(InputRadioGroup<TValue>)} " +
$"with a matching 'Name' property, if specified.");
}
}

/// <inheritdoc />
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
Debug.Assert(Context != null);

builder.OpenElement(0, "input");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", GetCssClass(Context.FieldClass));
builder.AddAttribute(3, "type", "radio");
builder.AddAttribute(4, "name", Context.GroupName);
builder.AddAttribute(5, "value", BindConverter.FormatValue(Value?.ToString()));
builder.AddAttribute(6, "checked", Context.CurrentValue?.Equals(Value));
builder.AddAttribute(7, "onchange", Context.ChangeEventCallback);
builder.CloseElement();
}
}
}
64 changes: 64 additions & 0 deletions src/Components/Web/src/Forms/InputRadioContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.AspNetCore.Components.Forms
{
/// <summary>
/// Describes context for an <see cref="InputRadio{TValue}"/> component.
/// </summary>
internal class InputRadioContext
{
private readonly InputRadioContext? _parentContext;

/// <summary>
/// Gets the name of the input radio group.
/// </summary>
public string GroupName { get; }

/// <summary>
/// Gets the current selected value in the input radio group.
/// </summary>
public object? CurrentValue { get; }

/// <summary>
/// Gets a css class indicating the validation state of input radio elements.
/// </summary>
public string FieldClass { get; }

/// <summary>
/// Gets the event callback to be invoked when the selected value is changed.
/// </summary>
public EventCallback<ChangeEventArgs> ChangeEventCallback { get; }

/// <summary>
/// Instantiates a new <see cref="InputRadioContext" />.
/// </summary>
/// <param name="parentContext">The parent <see cref="InputRadioContext" />.</param>
/// <param name="groupName">The name of the input radio group.</param>
/// <param name="currentValue">The current selected value in the input radio group.</param>
/// <param name="fieldClass">The css class indicating the validation state of input radio elements.</param>
/// <param name="changeEventCallback">The event callback to be invoked when the selected value is changed.</param>
public InputRadioContext(
InputRadioContext? parentContext,
string groupName,
object? currentValue,
string fieldClass,
EventCallback<ChangeEventArgs> changeEventCallback)
{
_parentContext = parentContext;

GroupName = groupName;
CurrentValue = currentValue;
FieldClass = fieldClass;
ChangeEventCallback = changeEventCallback;
}

/// <summary>
/// Finds an <see cref="InputRadioContext"/> in the context's ancestors with the matching <paramref name="groupName"/>.
/// </summary>
/// <param name="groupName">The group name of the ancestor <see cref="InputRadioContext"/>.</param>
/// <returns>The <see cref="InputRadioContext"/>, or <c>null</c> if none was found.</returns>
public InputRadioContext? FindContextInAncestors(string groupName)
=> string.Equals(GroupName, groupName) ? this : _parentContext?.FindContextInAncestors(groupName);
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading