Skip to content

Commit ec6cb1e

Browse files
committed
Move parsing away from input base.
1 parent 6d5fa8a commit ec6cb1e

22 files changed

Lines changed: 211 additions & 187 deletions

examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -658,11 +658,6 @@
658658
If true, the ClassValue property will not include the EditContext's FieldCssClass.
659659
</summary>
660660
</member>
661-
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.ParsingErrorMessage">
662-
<summary>
663-
Gets or sets the error message to show when the field can not be parsed.
664-
</summary>
665-
</member>
666661
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.EditContext">
667662
<summary>
668663
Gets the associated <see cref="T:Microsoft.AspNetCore.Components.Forms.EditContext"/>.
@@ -685,39 +680,18 @@
685680
Gets or sets the current value of the input.
686681
</summary>
687682
</member>
688-
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.CurrentValueAsString">
689-
<summary>
690-
Gets or sets the current value of the input, represented as a string.
691-
</summary>
692-
</member>
693-
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.SetCurrentValueAsStringAsync(System.String)">
694-
<summary>
695-
Attempts to set the current value of the input, represented as a string.
696-
</summary>
697-
<param name="value"></param>
698-
</member>
699683
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.#ctor">
700684
<summary>
701685
Constructs an instance of <see cref="T:Microsoft.AspNetCore.Components.Forms.InputBase`1"/>.
702686
</summary>
703687
</member>
704688
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.FormatValueAsString(`0)">
705689
<summary>
706-
Formats the value as a string. Derived classes can override this to determine the formating used for <see cref="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.CurrentValueAsString"/>.
690+
Formats the value as a string.
707691
</summary>
708692
<param name="value">The value to format.</param>
709693
<returns>A string representation of the value.</returns>
710694
</member>
711-
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.TryParseValueFromString(System.String,`0@,System.String@)">
712-
<summary>
713-
Parses a string to create an instance of <typeparamref name="TValue"/>. Derived classes can override this to change how
714-
<see cref="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.CurrentValueAsString"/> interprets incoming values.
715-
</summary>
716-
<param name="value">The string value to be parsed.</param>
717-
<param name="result">An instance of <typeparamref name="TValue"/>.</param>
718-
<param name="validationErrorMessage">If the value could not be parsed, provides a validation error message.</param>
719-
<returns>True if the value could be parsed; otherwise false.</returns>
720-
</member>
721695
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.ClassValue">
722696
<summary>
723697
Gets a CSS class string that combines the <c>class</c> attribute and and a string indicating
@@ -778,6 +752,39 @@
778752
<param name="e"></param>
779753
<returns></returns>
780754
</member>
755+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentParsableInputBase`1.ParsingErrorMessage">
756+
<summary>
757+
Gets or sets the error message to show when the field can not be parsed.
758+
</summary>
759+
</member>
760+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentParsableInputBase`1.CurrentValueAsString">
761+
<summary>
762+
Gets or sets the current value of the input, represented as a string.
763+
</summary>
764+
</member>
765+
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentParsableInputBase`1.SetCurrentValueAsStringAsync(System.String)">
766+
<summary>
767+
Attempts to set the current value of the input, represented as a string.
768+
</summary>
769+
<param name="value"></param>
770+
</member>
771+
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentParsableInputBase`1.TryParseValueFromString(System.String,`0@,System.String@)">
772+
<summary>
773+
Parses a string to create an instance of <typeparamref name="TValue"/>. Derived classes can override this to change how
774+
<see cref="P:Microsoft.FluentUI.AspNetCore.Components.FluentParsableInputBase`1.CurrentValueAsString"/> interprets incoming values.
775+
</summary>
776+
<param name="value">The string value to be parsed.</param>
777+
<param name="result">An instance of <typeparamref name="TValue"/>.</param>
778+
<param name="validationErrorMessage">If the value could not be parsed, provides a validation error message.</param>
779+
<returns>True if the value could be parsed; otherwise false.</returns>
780+
</member>
781+
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentParsableInputBase`1.ChangeHandlerAsync(Microsoft.AspNetCore.Components.ChangeEventArgs)">
782+
<summary>
783+
Handler for the OnChange event.
784+
</summary>
785+
<param name="e"></param>
786+
<returns></returns>
787+
</member>
781788
<member name="M:Microsoft.FluentUI.AspNetCore.Components.InputHelpers`1.GetMaxValue">
782789
<summary>
783790
Because of the limitation of the web component, the maximum value is set to 9999999999 for really large numbers.
@@ -1080,9 +1087,6 @@
10801087
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentCheckbox.UpdateAndRaiseCheckStateEventAsync(System.Nullable{System.Boolean})">
10811088
<summary />
10821089
</member>
1083-
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentCheckbox.TryParseValueFromString(System.String,System.Boolean@,System.String@)">
1084-
<summary />
1085-
</member>
10861090
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentCollapsibleRegion.Expanded">
10871091
<summary>
10881092
If true, the region is expaned, otherwise it is collapsed.
@@ -6621,9 +6625,6 @@
66216625
<member name="M:Microsoft.FluentUI.AspNetCore.Components.ListComponentBase`1.#ctor">
66226626
<summary />
66236627
</member>
6624-
<member name="M:Microsoft.FluentUI.AspNetCore.Components.ListComponentBase`1.TryParseValueFromString(System.String,System.String@,System.String@)">
6625-
<inheritdoc />
6626-
</member>
66276628
<member name="M:Microsoft.FluentUI.AspNetCore.Components.ListComponentBase`1.FormatValueAsString(System.String)">
66286629
<inheritdoc />
66296630
</member>

src/Core/Components/Base/FluentInputBase.cs

Lines changed: 3 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,10 @@ public abstract partial class FluentInputBase<TValue> : FluentComponentBase, IDi
2121
internal readonly string UnknownBoundField = "(unknown)";
2222

2323
private readonly EventHandler<ValidationStateChangedEventArgs> _validationStateChangedHandler;
24-
2524
private bool _hasInitializedParameters;
26-
private bool _parsingFailed;
27-
private string? _incomingValueBeforeParsing;
28-
private bool _previousParsingAttemptFailed;
29-
private ValidationMessageStore? _parsingValidationMessages;
30-
private Type? _nullableUnderlyingType;
3125

3226
[CascadingParameter]
33-
private EditContext? CascadedEditContext { get; set; }
27+
private protected EditContext? CascadedEditContext { get; set; }
3428

3529
/// <summary>
3630
/// When true, the control will be immutable by user interaction. <see href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly">readonly</see> HTML attribute for more information.
@@ -132,12 +126,6 @@ public abstract partial class FluentInputBase<TValue> : FluentComponentBase, IDi
132126
[Parameter]
133127
public virtual bool Embedded { get; set; } = false;
134128

135-
/// <summary>
136-
/// Gets or sets the error message to show when the field can not be parsed.
137-
/// </summary>
138-
[Parameter]
139-
public virtual string ParsingErrorMessage { get; set; } = "The {0} field must have a valid format.";
140-
141129
/// <summary>
142130
/// Gets the associated <see cref="Microsoft.AspNetCore.Components.Forms.EditContext"/>.
143131
/// This property is uninitialized if the input does not have a parent <see cref="EditForm"/>.
@@ -157,16 +145,14 @@ public abstract partial class FluentInputBase<TValue> : FluentComponentBase, IDi
157145
/// </summary>
158146
internal string FieldDisplayName => DisplayName ?? (FieldBound ? FieldIdentifier.FieldName : UnknownBoundField);
159147

160-
protected async Task SetCurrentValueAsync(TValue? value)
148+
protected virtual async Task SetCurrentValueAsync(TValue? value)
161149
{
162150
var hasChanged = !EqualityComparer<TValue>.Default.Equals(value, Value);
163151
if (!hasChanged)
164152
{
165153
return;
166154
}
167155

168-
_parsingFailed = false;
169-
170156
// If we don't do this, then when the user edits from A to B, we'd:
171157
// - Do a render that changes back to A
172158
// - Then send the updated value to the parent, which sends the B back to this component
@@ -197,65 +183,6 @@ protected TValue? CurrentValue
197183
set => _ = SetCurrentValueAsync(value);
198184
}
199185

200-
/// <summary>
201-
/// Gets or sets the current value of the input, represented as a string.
202-
/// </summary>
203-
protected string? CurrentValueAsString
204-
{
205-
// InputBase-derived components can hold invalid states (e.g., an InputNumber being blank even when bound
206-
// to an int value). So, if parsing fails, we keep the rejected string in the UI even though it doesn't
207-
// match what's on the .NET model. This avoids interfering with typing, but still notifies the EditContext
208-
// about the validation error message.
209-
get => _parsingFailed ? _incomingValueBeforeParsing : FormatValueAsString(CurrentValue);
210-
set => _ = SetCurrentValueAsStringAsync(value);
211-
212-
}
213-
214-
/// <summary>
215-
/// Attempts to set the current value of the input, represented as a string.
216-
/// </summary>
217-
/// <param name="value"></param>
218-
protected async Task SetCurrentValueAsStringAsync(string? value)
219-
{
220-
_incomingValueBeforeParsing = value;
221-
_parsingValidationMessages?.Clear();
222-
223-
if (_nullableUnderlyingType != null && string.IsNullOrEmpty(value))
224-
{
225-
// Assume if it's a nullable type, null/empty inputs should correspond to default(T)
226-
// Then all subclasses get nullable support almost automatically (they just have to
227-
// not reject Nullable<T> based on the type itself).
228-
_parsingFailed = false;
229-
CurrentValue = default!;
230-
}
231-
else if (TryParseValueFromString(value, out var parsedValue, out var validationErrorMessage))
232-
{
233-
_parsingFailed = false;
234-
await SetCurrentValueAsync(parsedValue);
235-
}
236-
else
237-
{
238-
_parsingFailed = true;
239-
240-
// EditContext may be null if the input is not a child component of EditForm.
241-
if (EditContext is not null && FieldBound)
242-
{
243-
_parsingValidationMessages ??= new ValidationMessageStore(EditContext);
244-
_parsingValidationMessages.Add(FieldIdentifier, validationErrorMessage);
245-
246-
// Since we're not writing to CurrentValue, we'll need to notify about modification from here
247-
EditContext.NotifyFieldChanged(FieldIdentifier);
248-
}
249-
}
250-
251-
// We can skip the validation notification if we were previously valid and still are
252-
if (_parsingFailed || _previousParsingAttemptFailed)
253-
{
254-
EditContext?.NotifyValidationStateChanged();
255-
_previousParsingAttemptFailed = _parsingFailed;
256-
}
257-
}
258-
259186
/// <summary>
260187
/// Constructs an instance of <see cref="InputBase{TValue}"/>.
261188
/// </summary>
@@ -266,23 +193,13 @@ protected FluentInputBase()
266193
}
267194

268195
/// <summary>
269-
/// Formats the value as a string. Derived classes can override this to determine the formating used for <see cref="CurrentValueAsString"/>.
196+
/// Formats the value as a string.
270197
/// </summary>
271198
/// <param name="value">The value to format.</param>
272199
/// <returns>A string representation of the value.</returns>
273200
protected virtual string? FormatValueAsString(TValue? value)
274201
=> value?.ToString();
275202

276-
/// <summary>
277-
/// Parses a string to create an instance of <typeparamref name="TValue"/>. Derived classes can override this to change how
278-
/// <see cref="CurrentValueAsString"/> interprets incoming values.
279-
/// </summary>
280-
/// <param name="value">The string value to be parsed.</param>
281-
/// <param name="result">An instance of <typeparamref name="TValue"/>.</param>
282-
/// <param name="validationErrorMessage">If the value could not be parsed, provides a validation error message.</param>
283-
/// <returns>True if the value could be parsed; otherwise false.</returns>
284-
protected abstract bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage);
285-
286203
/// <summary>
287204
/// Gets a CSS class string that combines the <c>class</c> attribute and and a string indicating
288205
/// the status of the field being edited (a combination of "modified", "valid", and "invalid").
@@ -339,7 +256,6 @@ public override Task SetParametersAsync(ParameterView parameters)
339256
EditContext.OnValidationStateChanged += _validationStateChangedHandler;
340257
}
341258

342-
_nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(TValue));
343259
_hasInitializedParameters = true;
344260
}
345261
else if (CascadedEditContext != EditContext)

src/Core/Components/Base/FluentInputBaseHandlers.cs

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// ------------------------------------------------------------------------
44

55
using Microsoft.AspNetCore.Components;
6-
using Microsoft.AspNetCore.Components.Forms;
76
using Microsoft.FluentUI.AspNetCore.Components.Utilities;
87

98
namespace Microsoft.FluentUI.AspNetCore.Components;
@@ -32,28 +31,15 @@ public partial class FluentInputBase<TValue>
3231
protected virtual async Task ChangeHandlerAsync(ChangeEventArgs e)
3332
{
3433
var _notifyCalled = false;
35-
var isValid = TryParseValueFromString(e.Value?.ToString(), out TValue? result, out var validationErrorMessage);
3634

37-
if (isValid)
35+
if (typeof(TValue) == typeof(string))
3836
{
39-
await SetCurrentValueAsync(result ?? default);
40-
_notifyCalled = true;
41-
42-
if (FieldBound && CascadedEditContext != null)
43-
{
44-
_parsingValidationMessages?.Clear(); // Clear any previous errors
45-
}
46-
}
47-
else
48-
{
49-
if (FieldBound && CascadedEditContext != null)
50-
{
51-
_parsingValidationMessages ??= new ValidationMessageStore(CascadedEditContext);
37+
object? value = e.Value?.ToString();
38+
await SetCurrentValueAsync((TValue?)(value ?? default));
5239

53-
_parsingValidationMessages.Clear();
54-
_parsingValidationMessages.Add(FieldIdentifier, validationErrorMessage ?? "Unknown parsing error");
55-
}
40+
_notifyCalled = true;
5641
}
42+
5743
if (FieldBound && !_notifyCalled)
5844
{
5945
CascadedEditContext?.NotifyFieldChanged(FieldIdentifier);

0 commit comments

Comments
 (0)