In Blazor, I have an EditForm
where the user can specify the details of a new task, such as the owner of the task. The tasks and clients are separate classes, that are stored in their own data table. Now, on the edit form where the user specifies a new task, in the client field, I want them to be able to search for an existing client, and, if the client doesn't exist yet, create a new one.
So I've added a button in the tasks edit form that opens a new edit form where the client details can be added. And on saving those client details, I want the user to be redirected back to the tasks edit form, so they can be saved as well.
My problem is, on redirecting to the task edit form, that page loses all data, and doesn't reinitialize the form correctly. If I don't do anything, I get an error that the parameters aren't initialized, and if I manually re-initialize the parameters, I'm losing all data, and the button to save the task form stops working. Isn't there any way to do this?
This is what my code looks like:
<EditForm Model="@task" OnValidSubmit="@OnValidSubmit">
<DataAnnotationsValidator />
<div class="form-group">
<label>Description :</label>
<div>
<InputText @bind-Value="@task.Description" class="form-control col-sm-3" />
<ValidationMessage For="@(() => task.Description)" />
</div>
</div>
<div>
<label>Client :</label>
<div>
<SfAutoComplete TValue="Client" TItem="Client" Placeholder="Specify client name" AllowCustom=true @bind-Value="@task.Client" DataSource="@clients">
<AutoCompleteFieldSettings Value="Name" />
</SfAutoComplete>
</div>
</div>
<div class ="form-group">
<a class ="btn btn-success" href="/createclient" ><i class = "oi oi-plus"></i> Create New</a>
</div>
<button type="submit" class="btn btn-success">
@ButtonText
</button>
</EditForm>
@code {
[Parameter]
public CreateJobRequest task { get; set; }
[Parameter]
public string ButtonText { get; set; } = "Save";
[Parameter]
public EventCallback OnValidSubmit { get; set; }
private IEnumerable<Api.Entities.Client>? clients;
SfAutoComplete<string, Client> AutocompleteObj { get; set; }
protected override async Task OnInitializedAsync()
{
clients = await ClientService.GetAll();
}
private async Task OnFilter(FilteringEventArgs args)
{
args.PreventDefaultAction = true;
var query = new Query().Where(new WhereFilter() { Field = "Name", Operator = "contains", value = args.Text, IgnoreCase = true });
query = !string.IsNullOrEmpty(args.Text) ? query : new Query();
await AutocompleteObj.FilterAsync(clients, query);
var t = AutocompleteObj.GetItemsAsync();
}
}
I've tried re-initializing the parameters with
@if(task == null)
{
task = new CreateJobRequest();
ButtonText = "Save";
OnValidSubmit = new EventCallback();
}
but besides then losing all data in 'task', the button to save the task form still doesn't work anymore.
Can anyone help me with this? Or is there a way to enter the client details directly in the task edit form?
Since you did not say if you are using Blazor Server of Blazor WASM with or without Entity Framework Core, for now I've assumed you're using Blazor Server with EF Core. The code below should do just fine:
Models:
CTask.cs
public class CTask
{
public int Id { get; set; }
public string Description { get; set; }
public Client Client { get; set; }
public int ClientId { get; set;
}
Client.cs
public class Client
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public List<CTask> Tasks { get; set; }
}
AddTask.razor
@page "/addtask"
@using DatabaseTestProject.Business_Logic
@inject TaskBL taskBL;
@if (!DataLoading)
{
<EditForm Model="@_task" OnValidSubmit="@OnValidSubmit">
<div class="form-group">
<label>Description :</label>
<InputText required @bind-Value="@_task.Description" class="form-control"></InputText>
</div>
@if (!AddingNewClient)
{
<div class="form-group">
<label>Client :</label>
<InputSelect required @bind-Value="_task.ClientId">
@foreach (var client in _clients)
{
<option value="@(client.Id)">@(client.Name)</option>
}
</InputSelect>
</div>
<button type="button" @onclick="AddNewClient">New Client</button>
}
else
{
<div class="form-group">
<label>Name :</label>
<InputText required @bind-Value="@_task.Client.Name" class="form-control"></InputText>
</div>
<div class="form-group">
<label>Address :</label>
<InputText required @bind-Value="@_task.Client.Address" class="form-control"></InputText>
</div>
<div class="form-group">
<label>City :</label>
<InputText required @bind-Value="@_task.Client.City" class="form-control"></InputText>
</div>
<button type="button" @onclick="ExistingClient">Existing Client</button>
}
<br />
<br />
<button type="submit">Submit</button>
</EditForm>
}
Note: I don't have Syncfusion. Just replace the InputSelect with your AutoComplete
AddTask.razor.cs
using DatabaseTestProject.Models;
using Microsoft.AspNetCore.Components;
namespace DatabaseTestProject.Pages
{
public partial class AddTask : ComponentBase
{
private CTask _task;
private bool DataLoading = false;
private List<Client> _clients;
private bool AddingNewClient = false;
protected override async Task OnInitializedAsync()
{
if (DataLoading)
{
return;
}
try
{
DataLoading = true;
_task = new CTask();
_clients = await taskBL.GetClientsAsync();
}
catch (Exception)
{
throw;
}
finally
{
DataLoading = false;
}
}
private async Task OnValidSubmit()
{
var result = await taskBL.CreateTaskAsync(_task);
if (result > 0)
{
//Task added - Logic here
}
else
{
//Task NOT added - Logic here
}
}
private void AddNewClient()
{
AddingNewClient = true;
_task.ClientId = 0;
_task.Client = new();
}
private void ExistingClient()
{
AddingNewClient = false;
_task.ClientId = 0;
_task.Client = null;
}
}
}
TaskBL.cs
using DatabaseTestProject.Models;
using DatabaseTestProject.Repositories;
namespace DatabaseTestProject.Business_Logic
{
public class TaskBL
{
private readonly TaskRepos _repos;
public TaskBL(TaskRepos taskRepos)
{
_repos = taskRepos;
}
public async Task<List<Client>> GetClientsAsync()
{
return await _repos.GetClientsAsync();
}
public async Task<int> CreateTaskAsync(CTask task)
{
var result = await _repos.AddTaskAsync(task);
return result;
}
}
}
TaskRepos.cs
using DatabaseTestProject.Data;
using DatabaseTestProject.Models;
using Microsoft.EntityFrameworkCore;
namespace DatabaseTestProject.Repositories
{
public class TaskRepos
{
private readonly IDbContextFactory<ApplicationDbContext> _factory;
public TaskRepos(IDbContextFactory<ApplicationDbContext> factory)
{
_factory = factory;
}
public async Task<List<Client>> GetClientsAsync()
{
using (var _db = _factory.CreateDbContext())
{
return await _db.Clients.ToListAsync();
}
}
public async Task<int> CreateNewClient(Client client)
{
using (var _db = _factory.CreateDbContext())
{
_db.Clients.Add(client);
return await _db.SaveChangesAsync();
}
}
public async Task<int> AddTaskAsync(CTask task)
{
using (var _db = _factory.CreateDbContext())
{
_db.Tasks.Add(task);
return await _db.SaveChangesAsync();
}
}
}
}
When an existing Client
is selected, nothing happens with the client table
. When the user added a new Client
EF Core will add this to the client table
and return the Id to the CTask