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 1 commit
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 @@ -100,7 +100,15 @@ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Renderin
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 : Microsoft.AspNetCore.Components.ComponentBase
public partial class InputRadioContext
{
public InputRadioContext(Microsoft.AspNetCore.Components.Forms.InputRadioContext? parentContext, string groupName, object? currentValue, Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.ChangeEventArgs> changeEventCallback) { }
public Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.ChangeEventArgs> ChangeEventCallback { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
public object? CurrentValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
public string GroupName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
public Microsoft.AspNetCore.Components.Forms.InputRadioContext? FindContextInAncestors(string groupName) { throw null; }
}
public partial class InputRadioGroup<TValue> : Microsoft.AspNetCore.Components.Forms.InputBase<TValue>
{
public InputRadioGroup() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
Expand All @@ -109,18 +117,22 @@ public InputRadioGroup() { }
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.Forms.InputBase<TValue>
public partial class InputRadio<TValue> : Microsoft.AspNetCore.Components.ComponentBase
{
public InputRadio() { }
protected string? GroupName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
[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 { } }
protected Microsoft.AspNetCore.Components.Forms.InputRadioContext? Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
[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 SelectedValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
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() { }
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 InputSelect<TValue> : Microsoft.AspNetCore.Components.Forms.InputBase<TValue>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,15 @@ 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 : Microsoft.AspNetCore.Components.ComponentBase
public partial class InputRadioContext
{
public InputRadioContext(Microsoft.AspNetCore.Components.Forms.InputRadioContext? parentContext, string groupName, object? currentValue, Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.ChangeEventArgs> changeEventCallback) { }
public Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.ChangeEventArgs> ChangeEventCallback { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
public object? CurrentValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
public string GroupName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
public Microsoft.AspNetCore.Components.Forms.InputRadioContext? FindContextInAncestors(string groupName) { throw null; }
}
public partial class InputRadioGroup<TValue> : Microsoft.AspNetCore.Components.Forms.InputBase<TValue>
{
public InputRadioGroup() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
Expand All @@ -105,16 +113,20 @@ public InputRadioGroup() { }
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.Forms.InputBase<TValue>
public partial class InputRadio<TValue> : Microsoft.AspNetCore.Components.ComponentBase
{
public InputRadio() { }
protected string? GroupName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
[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 { } }
protected Microsoft.AspNetCore.Components.Forms.InputRadioContext? Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public TValue SelectedValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
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() { }
protected override bool TryParseValueFromString(string? value, out TValue result, out string? validationErrorMessage) { throw null; }
}
public partial class InputSelect<TValue> : Microsoft.AspNetCore.Components.Forms.InputBase<TValue>
{
Expand Down
47 changes: 24 additions & 23 deletions src/Components/Web/src/Forms/InputRadio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components.Rendering;
Expand All @@ -11,58 +12,58 @@ namespace Microsoft.AspNetCore.Components.Forms
/// <summary>
/// An input component used for selecting a value from a group of choices.
/// </summary>
public class InputRadio<TValue> : InputBase<TValue>
public class InputRadio<TValue> : ComponentBase
{
/// <summary>
/// Gets the name of this <see cref="InputRadio{TValue}"/> group.
/// Gets context for this <see cref="InputRadio{TValue}"/>.
/// </summary>
protected string? GroupName { get; private set; }
protected InputRadioContext? Context { get; private set; }
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets or sets the value that will be bound when this radio input is selected.
/// 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 SelectedValue { get; set; } = default;
public TValue Value { get; set; } = default;

/// <summary>
/// Gets or sets group name inherited from an ancestor <see cref="InputRadioGroup"/>.
/// Gets or sets the name of the parent input radio group.
/// </summary>
[CascadingParameter] InputRadioGroup? CascadedRadioGroup { get; set; }
[Parameter] public string? Name { get; set; }

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

/// <inheritdoc />
protected override void OnParametersSet()
{
GroupName = AdditionalAttributes != null && AdditionalAttributes.TryGetValue("name", out var nameAttribute) ?
nameAttribute as string :
CascadedRadioGroup?.GroupName;
Context = string.IsNullOrEmpty(Name) ? CascadedContext : CascadedContext?.FindContextInAncestors(Name);

if (string.IsNullOrEmpty(GroupName))
if (Context == null)
{
throw new InvalidOperationException($"{GetType()} requires either an explicit string attribute 'name' or " +
$"an ancestor {nameof(InputRadioGroup)}.");
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(GroupName != null);
Debug.Assert(Context != null);

builder.OpenElement(0, "input");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "type", "radio");
builder.AddAttribute(3, "class", CssClass);
builder.AddAttribute(4, "name", GroupName);
builder.AddAttribute(5, "value", BindConverter.FormatValue(FormatValueAsString(SelectedValue)));
builder.AddAttribute(6, "checked", SelectedValue?.Equals(CurrentValue));
builder.AddAttribute(7, "onchange", EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
builder.AddAttribute(3, "name", Context.GroupName);
builder.AddAttribute(4, "value", BindConverter.FormatValue(Value?.ToString()));
builder.AddAttribute(5, "checked", Context.CurrentValue?.Equals(Value));
builder.AddAttribute(6, "onchange", Context.ChangeEventCallback!);
builder.CloseElement();
}

/// <inheritdoc />
protected override bool TryParseValueFromString(string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
=> this.TryParseSelectableValueFromString(value, out result, out validationErrorMessage);
}
}
56 changes: 56 additions & 0 deletions src/Components/Web/src/Forms/InputRadioContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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>
public 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 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="changeEventCallback">The event callback to be invoked when the selected value is changed.</param>
public InputRadioContext(
InputRadioContext? parentContext,
string groupName,
object? currentValue,
EventCallback<ChangeEventArgs> changeEventCallback)
{
_parentContext = parentContext;

GroupName = groupName;
CurrentValue = currentValue;
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
}
}
35 changes: 24 additions & 11 deletions src/Components/Web/src/Forms/InputRadioGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@

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

namespace Microsoft.AspNetCore.Components.Forms
{
/// <summary>
/// Groups child <see cref="InputRadio{TValue}"/> components.
/// </summary>
public class InputRadioGroup : ComponentBase
public class InputRadioGroup<TValue> : InputBase<TValue>
{
private readonly string _defaultGroupName = Guid.NewGuid().ToString("N");

internal string? GroupName { get; private set; }
private InputRadioContext? _context;

/// <summary>
/// Gets or sets the child content to be rendering inside the <see cref="InputRadioGroup"/>.
/// Gets or sets the child content to be rendering inside the <see cref="InputRadioGroup{TValue}"/>.
/// </summary>
[Parameter] public RenderFragment? ChildContent { get; set; }

Expand All @@ -26,22 +26,35 @@ public class InputRadioGroup : ComponentBase
/// </summary>
[Parameter] public string? Name { get; set; }

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

/// <inheritdoc />
protected override void OnParametersSet()
{
GroupName = !string.IsNullOrEmpty(Name) ? Name : _defaultGroupName;
var changeEventCallback = EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, CurrentValueAsString);
var groupName = !string.IsNullOrEmpty(Name) ? Name : _defaultGroupName;

_context = new InputRadioContext(CascadedContext, groupName, CurrentValue, changeEventCallback);
}

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

builder.OpenComponent<CascadingValue<InputRadioGroup>>(0);
builder.AddAttribute(1, "IsFixed", true);
builder.AddAttribute(2, "Value", this);
builder.AddAttribute(3, "ChildContent", ChildContent);
Debug.Assert(_context != null);

builder.OpenElement(0, "div");
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", CssClass);
builder.OpenComponent<CascadingValue<InputRadioContext>>(3);
builder.AddAttribute(4, "IsFixed", true);
builder.AddAttribute(5, "Value", _context);
builder.AddAttribute(6, "ChildContent", ChildContent);
builder.CloseComponent();
builder.CloseElement();
}

/// <inheritdoc />
protected override bool TryParseValueFromString(string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
=> this.TryParseSelectableValueFromString(value, out result, out validationErrorMessage);
}
}
Loading