Nested EditForm In Blazor - Is This Possible?

695 Views Asked by At

I have two models.

  1. Customer
  2. Address

A customer can have multiple addresses. The Customer model has a List of AddressModel

public class CustomerModel
{

[Required]
public string FirstName { get; set; }

[Required]
public string LastName { get; set; }

[Required]
public List<AddressModel> Addresses { get; set; }

}


public class AddressModel
{
public int Id { get; set; }
[Required]
public string FirstLine { get; set; }
public string SecondLine { get; set; }
[Required]
public string Town { get; set; }
[Required]
public string Region { get; set; }
[Required]
public string PostalCode { get; set; }
[Required]
public string Country { get; set; }

}

I currently have an EditForm that adds a customer - that was easy.

However, using the SAME form / input screen I would like to have a "sub-EditForm" inside the main editform where I can add the addresses, then click a final submit button to submit a new customer.

The input form would look like this:

enter image description here

I have tried simply creating a new EditForm inside the main EditForm but this did not work.

I have also messed about with Context but I feel like I am perhaps looking in the wrong direction completely.

I would really appreciate it if someone would briefly explain how to have "a form within a form" in Blazor OR if they would recommend and completely different (but effective) way to capture this information.

Thank you kindly!

2

There are 2 best solutions below

0
On BEST ANSWER

Yes, this is supported! This opens up all sorts of possibilities for sub forms that validate entry of inline dialog style interfaces for searching or adding child objects, just as you are describing here.

There are 2 tricks to this:

  1. You cannot bind to the same Model or EditorContext.
  2. You need to name the context, by setting the Context="ArbitraryConceptualName" on the outer, or each inner EditForm

    The child content element 'ChildContent' of component 'EditForm' uses the same parameter name ('context') as enclosing child content element 'EditForm' of component 'EditForm'. Specify the parameter name like: ' to resolve the ambiguity

The following structure for the form should work:
NOTE: This is not a complete or tested fragment as the view was not provided in the original post

  • I have chosen to name the outer form "CustomerForm" because that seems to make sense to me. The actual value is an arbitrary string label to the compiler, but it helps for it to have meaning to you when errors occur, or you have many contexts to manage.
<EditForm Model="@customer" Context="CustomerForm" OnValidSubmit="@SaveCustomer">
    <DataAnnotationsValidator />
...
    <div class="form-group">
        <label for="FirstName">First Name:</label>
        <InputText id="FirstName" @bind-Value="customer.FirstName" class="form-control" />
        <ValidationMessage For="@(() => customer.FirstName)" />
    </div>

    <div class="form-group">
        <label for="LastName">Last Name:</label>
        <InputText id="LastName" @bind-Value="customer.LastName" class="form-control" />
        <ValidationMessage For="@(() => customer.LastName)" />
    </div>
    <h4>Addresses</h4>
    <ul>
    @foreach (var a in customer.Addresses)
    {
        <li>@a.FirstLine, @a.SecondLine, @a.Town</li>
    }
    </ul>
...
    
    <h3>Add new address</h3>
    <EdidForm Model="@newAddress" OnValidSubmit="@AddNewAddress">

        <div class="form-group">
            <label for="FirstLine">First Line:</label>
            <InputText id="FirstLine" @bind-Value="newAddress.FirstLine" class="form-control" />
            <ValidationMessage For="@(() => newAddress.FirstLine)" />
        </div>
...
        <button type="submit" class="btn btn-outline-primary" @onclick="AddAddress">Add Address</button>
    </EditForm>

...
  <button type="submit" class="btn btn-success">Save</button>
</EditForm>

This assumes that in your code there is a placeholder for the new Address called newAddress and that your logic will add that address to the customer and reset the form:

@code {
    private CustomerModel customer = new CustomerModel { Addresses = new List<AddressModel>() };

    private AddressModel newAddress = new ();

    private void SaveCustomer()
    {
        // Save to where ever
    }

    private void AddNewAddress()
    {
        customer.Addresses.Add(newAddress);
        // reset the add address form
        newAddress = new ();
    }

} 
0
On

You do not need a second form or a sub form. You only have to bind the model property of the form to your CustomerModel since the Model holds also the Addresses you can submit all in one go.

@page "/"
@using System.ComponentModel.DataAnnotations
<h3>Add Customer</h3>

<EditForm Model="@customer" OnValidSubmit="@SaveCustomer">
    <DataAnnotationsValidator />

    <div class="form-group">
        <label for="FirstName">First Name:</label>
        <InputText id="FirstName" @bind-Value="customer.FirstName" class="form-control" />
        <ValidationMessage For="@(() => customer.FirstName)" />
    </div>

    <div class="form-group">
        <label for="LastName">Last Name:</label>
        <InputText id="LastName" @bind-Value="customer.LastName" class="form-control" />
        <ValidationMessage For="@(() => customer.LastName)" />
    </div>

    <h4>Addresses</h4>
    @foreach (var address in customer.Addresses)
    {
        <button type="button" class="btn btn-danger" @onclick="() => RemoveAddress(address)">Remove Address</button>
        <div class="form-group">
            <label for="FirstLine">First Line:</label>
            <InputText id="FirstLine" @bind-Value="address.FirstLine" class="form-control" />
            <ValidationMessage For="@(() => address.FirstLine)" />
        </div>
        <div class="form-group">
            <label for="FirstLine">Country:</label>
            <InputText id="FirstLine" @bind-Value="address.Country" class="form-control" />
            <ValidationMessage For="@(() => address.Country)" />
        </div>
    }
    <button type="button" class="btn btn-primary" @onclick="AddAddress">Add Address</button>
    <button type="submit" class="btn btn-success">Save</button>
</EditForm>

@code {
    private CustomerModel customer = new CustomerModel { Addresses = new List<AddressModel>() };

    private void SaveCustomer()
    {
        // Save to where ever
    }

    private void AddAddress()
    {
        customer.Addresses.Add(new AddressModel());
    }

    private void RemoveAddress(AddressModel address)
    {
        customer.Addresses.Remove(address);
        StateHasChanged();
    }
}