0

This is probably a simple goof, but can't figure it out.

I have a Blazor form with a few input controls mapped to an object (FormFieldsModel) mapped to an edit context. I have an Add button that uses those fields to add the data to a grid that uses a collection of those objects. Then another Submit button to send the whole collection to the API.

The desired flow would be, any time the user presses Add, validation is run against the edit context form fields. If they are invalid, display any messages. If it is valid, add the item to the collection (see OnAddClicked), and reset the form, but retaining the SubmitterInitials data (see ClearInput). They can then either submit the collection of items, or add another item.

What is happening is, if there are no items yet added to the collection, my Validation works correctly if the user presses Add. If they enter the data correctly, the item is added to the list, a new form object is created, but validation messages display.

<div>
<EditForm EditContext="@editContext">
    <DataAnnotationsValidator />
    <ValidationSummary />
    ....
</EditForm>
....
@code{
    ObservableCollection<FormFieldsModel> items;
    FormFieldsModel? formFields;
    EditContext exitContext;
    protected override async Task OnInitializedAsync()
    {
        formFields = new FormFieldsModel();
        editContext = new EditContext(formFields);
        validationMessageStore = new(editContext);        
    }
    public void OnAddClicked()
    {
        validationMessageStore.Clear();
        if (!editContext.Validate()) return;
        items.Add(formFields);
        ClearInput();
    }
    public void ClearInput()
    {
        formFields = new FormFieldsModel
        {
            // I want this to persist for any additional records
            SubmitterInitials = formFields.SubmitterInitials 
        };
        editContext = new EditContext(formFields);
        validationMessageStore.Clear();
        editContext.NotifyValidationStateChanged();
    }
    public class FormFieldsModel
    {
        public string ItemNumber {get; set;}
        public int Quantity {get; set;
        public string SubmitterInitials {get; set;}
    }
}
M Kenyon II
  • 4,136
  • 4
  • 46
  • 94

1 Answers1

1

Here's a working version of your page.

I've:

  1. Fixed various code issues.
  2. Removed the ValidationMessageStore code. ValidationMessageStore is compartmentalised: you can't clear other component's entries in the store, only read them.
  3. Created code to rebuild the form on successful submit. You need to do this to reset EditForm correctly.
@page "/"
@using System.ComponentModel.DataAnnotations

<PageTitle>Index</PageTitle>

@if (!_resetForm)
{
    <EditForm EditContext="@editContext">
        <DataAnnotationsValidator />
        <div class="col mb-3">
            <label>Item No:</label>
            <InputText class="form-control" @bind-Value=formFields.ItemNumber />
            <ValidationMessage For="() => formFields.ItemNumber" />
        </div>
        <div class="col mb-3">
            <label>Quantity:</label>
            <InputNumber class="form-control" @bind-Value=formFields.Quantity />
            <ValidationMessage For="() => formFields.Quantity" />
        </div>
        <div class="col mb-3">
            <label>Submitter Initials:</label>
            <InputText class="form-control" @bind-Value=formFields.SubmitterInitials />
            <ValidationMessage For="() => formFields.SubmitterInitials" />
        </div>
        <div class="col mb-3 text-end">
            <button class="btn btn-primary" @onclick=this.OnAddClicked>Submit</button>
        </div>
    </EditForm>
}

<div class="bg-dark text-white p-3 m-3">
    @foreach (var item in items)
    {
        <pre>Item: @(item.ItemNumber) - Qty : @item.Quantity - Initials : @item.SubmitterInitials </pre>
    }
</div>

@code {
    List<FormFieldsModel> items = new();
    FormFieldsModel formFields = new();
    EditContext editContext = default!;
    bool _resetForm;

    protected override void OnInitialized()
    {
        formFields = new FormFieldsModel();
        editContext = new EditContext(formFields);
        ArgumentNullException.ThrowIfNull(editContext);
    }

    public async Task OnAddClicked()
    {
        if (!editContext.Validate())
            return;

        items.Add(formFields);
        // Set reset form and yield so Renderer renders the page with no form
        _resetForm = true;
        await Task.Delay(1);
        // Set back so at the end of the handler the Renderer will render the page with a new form
        _resetForm = false;
        ClearInput();
    }

    public void ClearInput()
    {
        formFields = new FormFieldsModel
            {
                // I want this to persist for any additional records
                SubmitterInitials = formFields.SubmitterInitials
            };
        editContext = new EditContext(formFields);
    }

    public class FormFieldsModel
    {
        [Required]
        [StringLength(5)]
        public string? ItemNumber { get; set; }

        public int Quantity { get; set; }

        [Required]
        [StringLength(3)]
        public string? SubmitterInitials { get; set; }
    }
}
MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31
  • You still have form state issues. Can the user exit the page with unsaved data? – MrC aka Shaun Curtis Mar 21 '23 at 23:21
  • At this point, yes they could. So, are you saying that wrapping the form in the if statement, get's it re-rendered, and therefore truly resets the validation? (PS. This code was a sample of what I really have, but shortened for simplicity.) – M Kenyon II Mar 22 '23 at 12:47
  • The `await Task.Delay(1)` yields control back to the UI event handler which calls `StateHasChanged` and yields. The Renderer has thread timt to service it's queue and render the component. As `_resetForm` is true it renders the component without the `EditForm` control which goes out of scope and is destroyed. After the delay the `OnAddClicked` code runs to completion and the UI event handler calls `StateHasChanged` again. `_resetForm` is now false, so a new `EditForm` is created. – MrC aka Shaun Curtis Mar 22 '23 at 16:45
  • If you want to see how to lock the form during editing see this answer - https://stackoverflow.com/questions/75157902/managing-state-and-preventing-blazor-navigation-in-an-edit-form and this repo - https://github.com/ShaunCurtis/Blazr.EditContext – MrC aka Shaun Curtis Mar 22 '23 at 16:49