-
Notifications
You must be signed in to change notification settings - Fork 10.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
InputRadio component with form support (#23415)
* Started on InputRadio forms component. * Added E2E test for InputRadio. * Added docstring for InputRadio. * Changed value to be serialized using BindConverter. * Added InputChoice for choice-based inputs. InputChoice contains checks for valid choice types that used to exist in InputSelect. Both InputSelect and InputRadio now derive from InputChoice and thus also contain those checks. * Added InputRadioGroup. * Small fix. * Removed InputChoice, cleaned up. * Added internal access modifier to InputExtensions. * Small improvements. * Updated an outdated exception message. * Updated test to reflect updated exception message. * Improved API to enforce InputRadioGroup. * Added support for InputSelect int and Guid bindings. * Changed validation CSS classes to influence InputRadio components.
- Loading branch information
1 parent
b7d9e8c
commit a729c42
Showing
15 changed files
with
629 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.