I am creating a custom Blazor component and I need the select component to update the bound field with the selected value, this part is working. I need it to execute a method passed in from the parent component, this is not working.
I am able to pass the method into the custom component (child), but the onclick event of the option element of the select element is not firing.
How do I get this to fire?
ESelect.razor - Custom (child) select component
@inherits InputBase<string>
@if(FloatingLabel == false)
{
<div >
@if (!string.IsNullOrWhiteSpace(Label))
{
<label for="@Id">@Label</label>
}
<select id="@Id" @bind="@CurrentValue" >
<option disabled></option>
@foreach(SelectOption option in Options)
{
<option [email protected] @onclick="( () => OnClick(option) )">@option.Value</option>
}
</select>
<div >
<ValidationMessage For="@ValidationFor" />
</div>
</div>
}
else
{
<div >
<select id="@Id" @bind="@CurrentValue" >
<option disabled selected></option>
@foreach(SelectOption option in Options)
{
<option [email protected] onclick="@( () => OnClick(option) )" >@option.Value</option>
}
</select>
@if (!string.IsNullOrWhiteSpace(Label))
{
<label for="@Id">@Label</label>
}
<div >
<ValidationMessage For="@ValidationFor" />
</div>
</div>
}
ESelect.razor.cs (C# code for ESelect.razor)
using BebComponents.DataModels;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System.Linq.Expressions;
namespace BebComponents
{
public partial class ESelect
{
[Parameter, EditorRequired]
public Expression<Func<string>> ValidationFor { get; set; } = default!;
[Parameter]
public string? Id { get; set; } = "ESelect";
[Parameter]
public string? Label { get; set; }
[Parameter]
public bool? FloatingLabel { get; set; } = false;
[Parameter]
public List<SelectOption> Options { get; set; }
[Parameter]
public SelectOption? SelectedOption { get; set; }
[Parameter]
public Action? Trigger { get; set; }
protected override bool TryParseValueFromString(string? value, out string result, out string validationErrorMessage)
{
result = value;
validationErrorMessage = null;
return true;
}
public void OnClick(SelectOption option)
{
SelectedOption = option;
Trigger?.Invoke();
}
}
}
SelectOotion.cs (datamodel)
namespace BebComponents.DataModels
{
public class SelectOption
{
public int Id { get; set; }
public string Value { get; set; }
}
}
Index.razor
@page "/"
@using BebComponents
@using BebComponents.DataModels
@using static BebComponents.EDualSelect
<EditForm Model="Form" OnValidSubmit="ValidFormSubmit" >
<DataAnnotationsValidator />
<h3>Form Example:</h3>
<ValidationSummary />
<h3 >Enhanced Select</h3>
<ESelect Id="ESelect" @ref="eSelect" @bind-Value="Form.LastName" Options="@options" ValidationFor="@( () => Form.LastName )"
Label="Last Name" FloatingLabel="true" Trigger="EnableEnhancedSelect2"/>
<h5 >The last name selected is:</h5>
<p>@Form.LastName</p>
</EditForm>
Index.razor.cs
using BlazorComponents.Pages.PageModels;
using BebComponents;
using static BebComponents.EDualSelect;
using static BebComponents.ESingleSelect;
using static BebComponents.ESelect;
using BebComponents.DataModels;
namespace BlazorComponents.Pages
{
public partial class Index
{
private ESelect eSelect;
private string eSelectResult;
private readonly List<SelectOption> options = new List<SelectOption>
{
new SelectOption { Id = 1, Value = "Jones" },
new SelectOption { Id = 2, Value = "Smith" },
new SelectOption { Id = 3, Value = "Bender" },
new SelectOption { Id = 4, Value = "Baggio" },
new SelectOption { Id = 5, Value = "Allen" },
new SelectOption { Id = 6, Value = "Biggs" },
new SelectOption { Id = 7, Value = "Randall" },
new SelectOption { Id = 8, Value = "Anderson" },
new SelectOption { Id = 8, Value = "Reeves" }
};
}
}
CodePudding user response:
Your code is bit complex to decipher. Here's a simple example to demonstrate how to use a select and capture the change event on it.
The important bits to understand are:
You can't bind to an
Option.
Binding with
@bind
uses theOnchanged
event on the select so you can't also bind to it. Instead use@oninput
to bind to an event handler.The value returned is a string, so you need to do some parsing to get it to say an int.
If you're triggering an event you need to check that the value has actually changed and only call the Event Callback if it has.
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<select @bind=this.Value @oninput=OnChange>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<div>
value: @this.Value
</div>
<div>
Int Value: @this.IntValue
</div>
@code {
public EventCallback<int> OnValueChanged;
private string Value = "3";
private int IntValue;
private async Task OnChange(ChangeEventArgs e)
{
var x = e.Value;
if (int.TryParse(e.Value?.ToString(), out int value))
{
if (value != IntValue)
{
this.IntValue = value;
if (this.OnValueChanged.HasDelegate)
await this.OnValueChanged.InvokeAsync(value);
}
}
}
}
CodePudding user response:
You can solve your issue in various ways, as for instance, implementing the binding by omitting the @bind directive, and using the value
attribute and the @onchange
directive to create a two-way data-binding. The code below does not omit the @bind directive...
Try this code...
Index.razor
@page "/"
@if (SelectedOption != null)
{
<div>@SelectedOption.Id</div>
<div>@SelectedOption.Value</div>
}
<SelectComponent Options="options" @bind-SelectedOption="SelectedOption"></SelectComponent>
@code
{
private SelectOption SelectedOption { get; set; } = new SelectOption { Id = 8, Value = "Anderson" };
private readonly List<SelectOption> options = new List<SelectOption>
{
new SelectOption { Id = 1, Value = "Jones" },
new SelectOption { Id = 2, Value = "Smith" },
new SelectOption { Id = 3, Value = "Bender" },
new SelectOption { Id = 4, Value = "Baggio" },
new SelectOption { Id = 5, Value = "Allen" },
new SelectOption { Id = 6, Value = "Biggs" },
new SelectOption { Id = 7, Value = "Randall" },
new SelectOption { Id = 8, Value = "Anderson" },
new SelectOption { Id = 9, Value = "Reeves" }
};
}
Select component
<select @bind="ID">
<option value="">Select...</option>
@foreach (SelectOption option in Options)
{
<option @key="@option.Id" value="@option.Id">@option.Value</option>
}
</select>
@code
{
[Parameter]
public List<SelectOption> Options { get; set; }
#nullable enable
[Parameter]
public SelectOption? SelectedOption { get; set; }
#nullable disable
[Parameter]
public EventCallback<SelectOption> SelectedOptionChanged { get; set; }
private int? id;
private int? ID
{
get => id;
set
{
if (id != value)
{
id = value;
_ = SelectOption(id);
}
}
}
protected override void OnParametersSet()
{
if(id != SelectedOption?.Id)
{
id = SelectedOption.Id;
}
base.OnParametersSet();
}
private async Task SelectOption(int? id)
{
SelectedOption = Options.Find(o => o.Id == id);
// Note: This returns a SelectedOption object to the
// parent component. I guess this comes instead of your
// Trigger method
await SelectedOptionChanged.InvokeAsync(SelectedOption);
}
}
public class SelectOption
{
public int? Id { get; set; }
public string Value { get; set; }
}