|
|
@@ -1,13 +1,10 @@
|
|
|
// Licensed to the .NET Foundation under one or more agreements.
|
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
|
|
|
-using System;
|
|
|
-using System.Collections.Generic;
|
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
using System.Globalization;
|
|
|
using System.Linq;
|
|
|
using System.Linq.Expressions;
|
|
|
-using System.Threading.Tasks;
|
|
|
|
|
|
namespace Microsoft.AspNetCore.Components.Forms
|
|
|
{
|
|
|
@@ -19,11 +16,12 @@ namespace Microsoft.AspNetCore.Components.Forms
|
|
|
public abstract class InputBase<TValue> : ComponentBase, IDisposable
|
|
|
{
|
|
|
private readonly EventHandler<ValidationStateChangedEventArgs> _validationStateChangedHandler;
|
|
|
+ private bool _hasInitializedParameters;
|
|
|
private bool _previousParsingAttemptFailed;
|
|
|
private ValidationMessageStore? _parsingValidationMessages;
|
|
|
private Type? _nullableUnderlyingType;
|
|
|
|
|
|
- [CascadingParameter] EditContext CascadedEditContext { get; set; } = default!;
|
|
|
+ [CascadingParameter] private EditContext? CascadedEditContext { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets a collection of additional attributes that will be applied to the created element.
|
|
|
@@ -57,6 +55,7 @@ namespace Microsoft.AspNetCore.Components.Forms
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets the associated <see cref="Forms.EditContext"/>.
|
|
|
+ /// This property is uninitialized if the input does not have a parent <see cref="EditForm"/>.
|
|
|
/// </summary>
|
|
|
protected EditContext EditContext { get; set; } = default!;
|
|
|
|
|
|
@@ -78,7 +77,7 @@ namespace Microsoft.AspNetCore.Components.Forms
|
|
|
{
|
|
|
Value = value;
|
|
|
_ = ValueChanged.InvokeAsync(Value);
|
|
|
- EditContext.NotifyFieldChanged(FieldIdentifier);
|
|
|
+ EditContext?.NotifyFieldChanged(FieldIdentifier);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -112,21 +111,21 @@ namespace Microsoft.AspNetCore.Components.Forms
|
|
|
{
|
|
|
parsingFailed = true;
|
|
|
|
|
|
- if (_parsingValidationMessages == null)
|
|
|
+ // EditContext may be null if the input is not a child component of EditForm.
|
|
|
+ if (EditContext is not null)
|
|
|
{
|
|
|
- _parsingValidationMessages = new ValidationMessageStore(EditContext);
|
|
|
- }
|
|
|
-
|
|
|
- _parsingValidationMessages.Add(FieldIdentifier, validationErrorMessage);
|
|
|
+ _parsingValidationMessages ??= new ValidationMessageStore(EditContext);
|
|
|
+ _parsingValidationMessages.Add(FieldIdentifier, validationErrorMessage);
|
|
|
|
|
|
- // Since we're not writing to CurrentValue, we'll need to notify about modification from here
|
|
|
- EditContext.NotifyFieldChanged(FieldIdentifier);
|
|
|
+ // Since we're not writing to CurrentValue, we'll need to notify about modification from here
|
|
|
+ EditContext.NotifyFieldChanged(FieldIdentifier);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// We can skip the validation notification if we were previously valid and still are
|
|
|
if (parsingFailed || _previousParsingAttemptFailed)
|
|
|
{
|
|
|
- EditContext.NotifyValidationStateChanged();
|
|
|
+ EditContext?.NotifyValidationStateChanged();
|
|
|
_previousParsingAttemptFailed = parsingFailed;
|
|
|
}
|
|
|
}
|
|
|
@@ -159,62 +158,45 @@ namespace Microsoft.AspNetCore.Components.Forms
|
|
|
protected abstract bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage);
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Gets a string that indicates the status of the field being edited. This will include
|
|
|
- /// some combination of "modified", "valid", or "invalid", depending on the status of the field.
|
|
|
- /// </summary>
|
|
|
- private string FieldClass
|
|
|
- => EditContext.FieldCssClass(FieldIdentifier);
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets a CSS class string that combines the <c>class</c> attribute and <see cref="FieldClass"/>
|
|
|
- /// properties. Derived components should typically use this value for the primary HTML element's
|
|
|
- /// 'class' attribute.
|
|
|
+ /// Gets a CSS class string that combines the <c>class</c> attribute and and a string indicating
|
|
|
+ /// the status of the field being edited (a combination of "modified", "valid", and "invalid").
|
|
|
+ /// Derived components should typically use this value for the primary HTML element's 'class' attribute.
|
|
|
/// </summary>
|
|
|
protected string CssClass
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
- if (AdditionalAttributes != null &&
|
|
|
- AdditionalAttributes.TryGetValue("class", out var @class) &&
|
|
|
- !string.IsNullOrEmpty(Convert.ToString(@class, CultureInfo.InvariantCulture)))
|
|
|
- {
|
|
|
- return $"{@class} {FieldClass}";
|
|
|
- }
|
|
|
-
|
|
|
- return FieldClass; // Never null or empty
|
|
|
+ var fieldClass = EditContext?.FieldCssClass(FieldIdentifier) ?? string.Empty;
|
|
|
+ return AttributeUtilities.CombineClassNames(AdditionalAttributes, fieldClass);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/// <inheritdoc />
|
|
|
- [MemberNotNull(nameof(EditContext), nameof(CascadedEditContext))]
|
|
|
public override Task SetParametersAsync(ParameterView parameters)
|
|
|
{
|
|
|
parameters.SetParameterProperties(this);
|
|
|
|
|
|
- if (EditContext == null)
|
|
|
+ if (!_hasInitializedParameters)
|
|
|
{
|
|
|
// This is the first run
|
|
|
// Could put this logic in OnInit, but its nice to avoid forcing people who override OnInit to call base.OnInit()
|
|
|
|
|
|
- if (CascadedEditContext == null)
|
|
|
- {
|
|
|
- throw new InvalidOperationException($"{GetType()} requires a cascading parameter " +
|
|
|
- $"of type {nameof(Forms.EditContext)}. For example, you can use {GetType().FullName} inside " +
|
|
|
- $"an {nameof(EditForm)}.");
|
|
|
- }
|
|
|
-
|
|
|
if (ValueExpression == null)
|
|
|
{
|
|
|
throw new InvalidOperationException($"{GetType()} requires a value for the 'ValueExpression' " +
|
|
|
$"parameter. Normally this is provided automatically when using 'bind-Value'.");
|
|
|
}
|
|
|
|
|
|
- EditContext = CascadedEditContext;
|
|
|
FieldIdentifier = FieldIdentifier.Create(ValueExpression);
|
|
|
- _nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(TValue));
|
|
|
|
|
|
- EditContext.OnValidationStateChanged += _validationStateChangedHandler;
|
|
|
+ if (CascadedEditContext != null)
|
|
|
+ {
|
|
|
+ EditContext = CascadedEditContext;
|
|
|
+ EditContext.OnValidationStateChanged += _validationStateChangedHandler;
|
|
|
+ }
|
|
|
+
|
|
|
+ _nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(TValue));
|
|
|
+ _hasInitializedParameters = true;
|
|
|
}
|
|
|
else if (CascadedEditContext != EditContext)
|
|
|
{
|
|
|
@@ -242,6 +224,11 @@ namespace Microsoft.AspNetCore.Components.Forms
|
|
|
|
|
|
private void UpdateAdditionalValidationAttributes()
|
|
|
{
|
|
|
+ if (EditContext is null)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
var hasAriaInvalidAttribute = AdditionalAttributes != null && AdditionalAttributes.ContainsKey("aria-invalid");
|
|
|
if (EditContext.GetValidationMessages(FieldIdentifier).Any())
|
|
|
{
|