Lewis
Lewis

Reputation: 666

EditForm validation not applying invalid class to custom component

When using blazor its important to create reusable custom components; that's one of the main points to use Blazor.

I've created a basic Input component that uses Bootstrap for its CSS as shown.

@inherits InputBase<string>

<InputText type="@Type"
           class="form-control"
           id="@_id"
           placeholder="@Placeholder"
           @bind-Value="CurrentValue"
           disabled="@Disabled"
           @attributes="AdditionalAttributes"/>
/* Additional properties below for placeholder, disabled, type, etc. they're just string properties */

I place this inside an EditForm like so...

<EditForm Model="MyModel"
      OnValidSubmit="ViewModel.CreateAsync">
    <DataAnnotationsValidator />

<PrimaryInput @bind-Value="MyModel.Name"
              Placeholder="Name..."/>

</EditForm>

Problem is, this doesn't add the "invalid" class to the item. Both valid and modified appear, but once the input becomes invalid it does not change valid to invalid like it would with a none custom component. This is my model below to show I've added validation to the model.

public class MyModel
{
    [StringLength(5)]
    [Required]
    public string Name { get; set; }
}

Below is the image of what is displayed:

enter image description here

This is the html that is generated by Blazor...

<input type="text" id="aa106d17-46d7-442c-be39-6c947f17186b" placeholder="Name..." aria-describedby="14b1189e-c3c5-4e1f-ae1d-330756147b44" class="form-control valid" aria-invalid="">

...as you can see, the class is still valid but the aria-invalid attribute has been applied. Below is the HTML for a none custom component input being validated...

<input class="modified invalid" aria-invalid="">

...on this one, invalid is applied. Something is preventing the class from switching to invalid on the custom component.

Upvotes: 2

Views: 3005

Answers (3)

Lewis
Lewis

Reputation: 666

I fixed it with the following code in my custom component....

@inherits InputText

<InputText class="form-control"
 Value="@Value"
 ValueExpression="ValueExpression"
 ValueChanged="OnInputChanged" />

... it's a little strange inheriting InputText with an InputText in there. My code behind for it is like so...

private void OnInputChanged(string value)
{
    CurrentValueAsString = value;
    EditContext.NotifyFieldChanged(FieldIdentifier);
}

Then in my code I place that component like so....

<TestInput @bind-Value="ViewModel.Menu.Name"/>

It's a little strange work around, but does the job and applies the correct classes when it's valid/invalid.

Upvotes: 1

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30485

You are inheriting from the <InputBase> and placing an InputText (an InputBase control) inside it. This doesn't really make sense.

Here's an example that demonstrates how to customize a standard InputText. It shows how to add custom attributes, one way of handling disabled, how user entered attributes are added to the input, and this one updates the value on keyboard entry rather than on losing focus.

@namespace Blazor.Starter.Components
@inherits InputText

    <input type="text" disabled="@Disabled" value="@this.CurrentValue" @oninput="OnInput" @ref="Element" id="@Id" @attributes="this.AdditionalAttributes" />

@code{

   [Parameter] public ElementReference? Element { get; set; }
   [Parameter] public string? Id { get; set; }
   [Parameter] public bool Disabled { get; set; }

    private void OnInput(ChangeEventArgs e)
        => this.CurrentValueAsString = e.Value.ToString();
}
@page "/Test3"
<EditForm EditContext="@_editContext" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <label class="form-label">First Name</label>
    <CustomInput class="form-control" @bind-Value="this._formModel.FirstName" Id="mycontrol"></CustomInput>
    <label class="form-label">First Name</label>
    <CustomInput class="form-control" @bind-Value="this._formModel.FirstName" Id="mydisabledcontrol" Disabled="true"></CustomInput>
    <div class=" m-2 p-2">
        <button type="submit" class="btn btn-primary">New Person</button>
    </div>

    <div class="p3-m-3">Value: @_formModel.FirstName</div>

</EditForm>

@code {
    private EditContext? _editContext;
    private FormModel _formModel;

    private void HandleValidSubmit()
    {
        // handler
    }


    private class FormModel
    {
        [StringLength(5)][Required] public string FirstName { get; set; }
    }

    protected override Task OnInitializedAsync()
    {
        _formModel = new FormModel();
        _editContext = new EditContext(_formModel);
        return base.OnInitializedAsync();
    }
}
Aria Changes

See the two html snippets for when the input is valid/invalid.

<input type="text" id="mycontrol" class="form-control" _bl_759b54c2-54a4-461f-8101-9661c9034c17>
<input type="text" id="mycontrol" class="form-control" _bl_759b54c2-54a4-461f-8101-9661c9034c17 aria-invalid>

Upvotes: -1

Ben Sampica
Ben Sampica

Reputation: 3422

You need to trigger the form's EditContext. There are a few ways to do this - I'll outline two of 'em.

Inside your custom component you can override TryParseValueFromString and handle the validation there - InputBase<> has these properties within it. This may not make sense considering your type is a string already.

Otherwise, you can have your custom component have the EditContext inject as a cascading parameter and then have an event callback on change to invoke the EditContext's NotifiedFieldChanged.

Small example below of this second example:

[CascadingParameter]
private EditContext EditContext { get; set; }

[Parameter] 
private Expression<Func<string>> ValueExpression { get; set; }

private FieldIdentifier _field;

private void OnInitialized()
{
    _field = FieldIdentifier.Create(ValueExpression);
} 

private void OnInputChanged(ChangeEventArgs args)
{
    EditContext.NotifyFieldChanged(_fieldIdentifier);
}

Upvotes: 1

Related Questions