Blazor Forms
- <EditForm> is the Html Form
- It refers to a model (field or property, probably created in OnInitializedAsync)
- There is an OnValidSubmit event. There is also a OnSubmit (if you handle your own validation)
- <InputText> is just an Html Input type=text
@page "/useredit/{Id:int}"
@using BlazorApp.Shared
@inject HttpClient Http
@inject NavigationManager Navigation
<p>Editing user @Id</p>
<EditForm Model="_user" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label for="UserName" class="form-label">Name</label>
<InputText id="UserName" @bind-Value="_user.Name" class="form-control"/>
</p>
<button type="submit" class="btn btn-primary">Submit</button>
</EditForm>
@code {
[Parameter]
public int Id { get; set; }
private User? _user = new();
protected override async Task OnInitializedAsync()
{
_user = await Http.GetFromJsonAsync<User>($"User/{Id}");
}
private async Task HandleValidSubmit(EditContext arg)
{
await Http.PostAsJsonAsync("User", _user);
Navigation.NavigateTo("/");
}
}
@using BlazorApp.Shared
@inject HttpClient Http
@inject NavigationManager Navigation
<p>Editing user @Id</p>
<EditForm Model="_user" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label for="UserName" class="form-label">Name</label>
<InputText id="UserName" @bind-Value="_user.Name" class="form-control"/>
</p>
<button type="submit" class="btn btn-primary">Submit</button>
</EditForm>
@code {
[Parameter]
public int Id { get; set; }
private User? _user = new();
protected override async Task OnInitializedAsync()
{
_user = await Http.GetFromJsonAsync<User>($"User/{Id}");
}
private async Task HandleValidSubmit(EditContext arg)
{
await Http.PostAsJsonAsync("User", _user);
Navigation.NavigateTo("/");
}
}
Binding
<p>One way: <input value="@Name"></p>
<p>Two way (default onblur): <input @bind="Name"></p>
<p>Two way (specific property- which for input is default value): <input @bind-value="Name"></p>
<p>Custom event <input @bind="Name" @bind:event="oninput"></p>
<EditForm EditContext="editContext">
<p>With blazor InputText component: <InputText @bind-Value="Name" /></p>
</EditForm>
<p>Two way (default onblur): <input @bind="Name"></p>
<p>Two way (specific property- which for input is default value): <input @bind-value="Name"></p>
<p>Custom event <input @bind="Name" @bind:event="oninput"></p>
<EditForm EditContext="editContext">
<p>With blazor InputText component: <InputText @bind-Value="Name" /></p>
</EditForm>
- For two way binding, the component must also have an EventChanged parameter with name xChanged
- @bind:after to a async Task method
- @bind:format="dd-MM-yyyy" for formats
- It can have an EditContext:
- Add a property EditContext
- Remove the Model, and initialize (OnInitialized) the EditContext = new (Model)
- You can subscribe to OnFieldChanged and disable/hide elements according to valid status
- remember to add StateHasChanged in the event
<EditForm OnSubmit="Save" EditContext="_editContext">
<p><InputNumber @bind-Value="_model.Count"/></p>
<button type="submit" disabled="@_formInvalid">Submit</button>
</EditForm>
@code {
private EditContext _editContext = default!;
private bool _formInvalid = true;
private CounterModel _model = default!;
protected override void OnInitialized()
{
_model = new CounterModel();
_editContext = new(_model);
_editContext.OnFieldChanged += (_, __) =>
{
_formInvalid = !_editContext.Validate();
StateHasChanged();
};
}
<p><InputNumber @bind-Value="_model.Count"/></p>
<button type="submit" disabled="@_formInvalid">Submit</button>
</EditForm>
@code {
private EditContext _editContext = default!;
private bool _formInvalid = true;
private CounterModel _model = default!;
protected override void OnInitialized()
{
_model = new CounterModel();
_editContext = new(_model);
_editContext.OnFieldChanged += (_, __) =>
{
_formInvalid = !_editContext.Validate();
StateHasChanged();
};
}
Event handling
Components have html-DOM type event handlers (which fire WASM/.net delegates).
<button @onclick="BtnClick">Click Me</button>
<input type="checkbox" @onclick="HandleCheck" />
<input type="checkbox" @onclick="@(e => MyString = "Checked")" />
<input type="checkbox" @onclick="HandleCheck" />
<input type="checkbox" @onclick="@(e => MyString = "Checked")" />
You can use onclick:preventDefault and onclick:stopPropagation modifiers.
For a child component, you may want one or more events passed up to the parent. Add a parameter of type EventCallback or EventCallback<TValue>
Parent:
<UserList Users="UserList"
OnSelectedCallback="SelectedUser"
OnClickCallback="SelectedAll"></UserList>
<p>Selected @_selectedUser</p> @code {
private int? _selectedUser;
private void SelectedUser(int id)
{
_selectedUser = id;
}
private void SelectedAll(MouseEventArgs obj)
{
_selectedUser = 9999; //magic value
}
OnSelectedCallback="SelectedUser"
OnClickCallback="SelectedAll"></UserList>
<p>Selected @_selectedUser</p> @code {
private int? _selectedUser;
private void SelectedUser(int id)
{
_selectedUser = id;
}
private void SelectedAll(MouseEventArgs obj)
{
_selectedUser = 9999; //magic value
}
Child
<button @onclick="OnClickCallback">Select All</button>
<br />
<ul>
@for (var i = 0; i < Users.Count; i++)
{
//we have to use a for-loop with counter
var user = Users[i];
<li>
<button @onclick="() => Trigger(user.Id)">Select</button> @user.Name
</li>
}
</ul>
@code {
[Parameter]
public IList<User> Users { get; set; } = default!;
//event for the Select All button click- pass it to parent
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
//event from the individual rows, but fired from the Trigger event below
[Parameter]
public EventCallback<int> OnSelectedCallback { get; set; }
//the private event called from the lambda with custom data
private async Task Trigger(int userId)
{
await OnSelectedCallback.InvokeAsync(userId);
}
}
<br />
<ul>
@for (var i = 0; i < Users.Count; i++)
{
//we have to use a for-loop with counter
var user = Users[i];
<li>
<button @onclick="() => Trigger(user.Id)">Select</button> @user.Name
</li>
}
</ul>
@code {
[Parameter]
public IList<User> Users { get; set; } = default!;
//event for the Select All button click- pass it to parent
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
//event from the individual rows, but fired from the Trigger event below
[Parameter]
public EventCallback<int> OnSelectedCallback { get; set; }
//the private event called from the lambda with custom data
private async Task Trigger(int userId)
{
await OnSelectedCallback.InvokeAsync(userId);
}
}