自定义 Blazor Select-component option-element @onclick not firing - 如何让它触发?
Custom Blazor Select-component option-element @onclick not firing - How do I get it to fire?
我正在创建自定义 Blazor 组件,我需要 select 组件使用 selected 值更新绑定字段,这部分工作正常。我还需要它来执行从父组件传入的方法,这部分不起作用。
我可以将方法传递给自定义组件(子组件),但是 select 元素的选项元素的 onclick
事件没有触发。
我能够在多个 select 元素中实现它,但它在 select 元素中不起作用。
如何触发它并仍然更新绑定数据属性?
ESelect.razor
自定义(子)select 组件
@inherits InputBase<string>
<div class="form-floating">
<select class="form-control form-select @CssClass" id="@Id" @bind="@CurrentValue" >
<option disabled selected></option>
@foreach(SelectOption option in Options)
{
<option id=@option.Id onclick="@( () => OnClick(option) )" >@option.Value</option>
}
</select>
@if (!string.IsNullOrWhiteSpace(Label))
{
<label class="form-control-label" for="@Id">@Label</label>
}
<div class="form-control-validation">
<ValidationMessage For="@ValidationFor" />
</div>
</div>
ESelect.razor.cs
(ESelect.razor 的 C# 代码)
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 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();
}
}
}
SelectOption.cs
(数据模型)
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" class="mt-5">
<DataAnnotationsValidator />
<h3>Form Example:</h3>
<ValidationSummary />
<h3 class="mt-4">Enhanced Select</h3>
<ESelect Id="ESelect" @ref="eSelect" @bind-Value="Form.LastName" Options="@options" ValidationFor="@( () => Form.LastName )"
Label="Last Name" Trigger="EnableEnhancedSelect2"/>
<h5 class="mt-2">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" }
};
}
}
更新答案:
这是一个修改后的答案,向您展示了如何构建复合控件 - 在本例中为 select - 以及绑定中的 link。我简化了选项设置。
我添加了一个额外的 ValueHasChanged
EventCallback
,只要值发生变化就会被触发。我用它来打印时间更新以显示它正常工作。
@typeparam TValue
@using System.Linq.Expressions
<div class="row">
<div class="col-2">
@this.Label
</div>
<div class="col-6">
<InputSelect TValue=TValue Value=@this.Value ValueChanged=this.OnValueChanged ValueExpression=this.ValueExpression>
@ChildContent
</InputSelect>
</div>
<div class="col-6">
<ValidationMessage For=this.ValidationFor />
</div>
</div>
@code {
[Parameter, EditorRequired] public string Label { get; set; } = "Field Label";
[Parameter] public TValue? Value { get; set; }
[Parameter] public EventCallback<TValue> ValueChanged { get; set; }
[Parameter] public Expression<Func<TValue>>? ValueExpression { get; set; }
[Parameter, EditorRequired] public Expression<Func<TValue>> ValidationFor { get; set; } = default!;
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public EventCallback<TValue> ValueHasChanged { get; set; }
private void OnValueChanged(TValue value)
{
ValueChanged.InvokeAsync(value);
if (this.ValueHasChanged.HasDelegate)
this.ValueHasChanged.InvokeAsync(value);
}
}
还有一个测试页:
@page "/"
@using Microsoft.Extensions.Options
<EditForm Model=model>
<MySelect Label="Country" TValue=int @bind-Value=model.Value ValueHasChanged=this.OnValueChanged ValidationFor="() => model.Value" >
<option value=1>Spain</option>
<option value=2>Portugal</option>
<option value=3>France</option>
</MySelect>
</EditForm>
<div class="m-2 p-2">
Value = @model.Value
</div>
<div class="m-2 p-2">
Value = @message
</div>
@code {
private MyModel model = new();
private string message = string.Empty;
private void OnValueChanged(int value)
=> this.message = $"Updated at {DateTime.Now.ToLongTimeString()}";
public class MyModel
{
public int Value { get; set; } = 3;
}
}
原答案:
你的代码破译起来有点复杂。这是一个简单的示例,用于演示如何使用 select 并捕获其上的更改事件。
需要理解的重要部分是:
您无法绑定到 Option.
与 @bind
的绑定使用 select 上的 Onchanged
事件,因此您不能同时绑定到它。而是使用 @oninput
绑定到事件处理程序。
返回的值是一个字符串,所以你需要做一些解析才能让它说出一个int。
如果您要触发一个事件,您需要检查该值是否确实发生了变化,并且只有在发生变化时才调用事件回调。
@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);
}
}
}
}
简单的解决方案
您现有的代码只需少量添加即可工作。
您可以在此 Blazor Fiddle Demo 中看到一个工作示例 Fiddle 相当有限,但我尽量使代码接近原始代码。
您的点击处理程序永远不会触发,因为 <option>
元素仅在父级为 <select multiple>
时触发。然而,在这种情况下,它只是默认单曲 <select>
并且永远不会触发。我们可能会改用 onchange 事件,这通常是这样做的。然而,我们也可以只在 CurrentValue 属性 周围添加一个包装器,这将允许您重用已有的代码。
CurrentValue 是从 InputBase 继承的 属性。要调用父提供的 Trigger 方法,您需要包装 CurrentValue 以提供该新功能。这可以通过使用 "new" keyword 并调用 setter 中的方法来完成,如图所示。
要添加到组件的代码:
[Parameter]
public new string CurrentValue
{
get => base.CurrentValue;
set {
base.CurrentValue = value;
Trigger?.Invoke();
}
}
您可能还想从标记中删除现有的 onclick 处理程序,因为它不会执行任何操作,只会产生误导。
我正在创建自定义 Blazor 组件,我需要 select 组件使用 selected 值更新绑定字段,这部分工作正常。我还需要它来执行从父组件传入的方法,这部分不起作用。
我可以将方法传递给自定义组件(子组件),但是 select 元素的选项元素的 onclick
事件没有触发。
我能够在多个 select 元素中实现它,但它在 select 元素中不起作用。
如何触发它并仍然更新绑定数据属性?
ESelect.razor
自定义(子)select 组件
@inherits InputBase<string>
<div class="form-floating">
<select class="form-control form-select @CssClass" id="@Id" @bind="@CurrentValue" >
<option disabled selected></option>
@foreach(SelectOption option in Options)
{
<option id=@option.Id onclick="@( () => OnClick(option) )" >@option.Value</option>
}
</select>
@if (!string.IsNullOrWhiteSpace(Label))
{
<label class="form-control-label" for="@Id">@Label</label>
}
<div class="form-control-validation">
<ValidationMessage For="@ValidationFor" />
</div>
</div>
ESelect.razor.cs
(ESelect.razor 的 C# 代码)
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 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();
}
}
}
SelectOption.cs
(数据模型)
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" class="mt-5">
<DataAnnotationsValidator />
<h3>Form Example:</h3>
<ValidationSummary />
<h3 class="mt-4">Enhanced Select</h3>
<ESelect Id="ESelect" @ref="eSelect" @bind-Value="Form.LastName" Options="@options" ValidationFor="@( () => Form.LastName )"
Label="Last Name" Trigger="EnableEnhancedSelect2"/>
<h5 class="mt-2">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" }
};
}
}
更新答案:
这是一个修改后的答案,向您展示了如何构建复合控件 - 在本例中为 select - 以及绑定中的 link。我简化了选项设置。
我添加了一个额外的 ValueHasChanged
EventCallback
,只要值发生变化就会被触发。我用它来打印时间更新以显示它正常工作。
@typeparam TValue
@using System.Linq.Expressions
<div class="row">
<div class="col-2">
@this.Label
</div>
<div class="col-6">
<InputSelect TValue=TValue Value=@this.Value ValueChanged=this.OnValueChanged ValueExpression=this.ValueExpression>
@ChildContent
</InputSelect>
</div>
<div class="col-6">
<ValidationMessage For=this.ValidationFor />
</div>
</div>
@code {
[Parameter, EditorRequired] public string Label { get; set; } = "Field Label";
[Parameter] public TValue? Value { get; set; }
[Parameter] public EventCallback<TValue> ValueChanged { get; set; }
[Parameter] public Expression<Func<TValue>>? ValueExpression { get; set; }
[Parameter, EditorRequired] public Expression<Func<TValue>> ValidationFor { get; set; } = default!;
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public EventCallback<TValue> ValueHasChanged { get; set; }
private void OnValueChanged(TValue value)
{
ValueChanged.InvokeAsync(value);
if (this.ValueHasChanged.HasDelegate)
this.ValueHasChanged.InvokeAsync(value);
}
}
还有一个测试页:
@page "/"
@using Microsoft.Extensions.Options
<EditForm Model=model>
<MySelect Label="Country" TValue=int @bind-Value=model.Value ValueHasChanged=this.OnValueChanged ValidationFor="() => model.Value" >
<option value=1>Spain</option>
<option value=2>Portugal</option>
<option value=3>France</option>
</MySelect>
</EditForm>
<div class="m-2 p-2">
Value = @model.Value
</div>
<div class="m-2 p-2">
Value = @message
</div>
@code {
private MyModel model = new();
private string message = string.Empty;
private void OnValueChanged(int value)
=> this.message = $"Updated at {DateTime.Now.ToLongTimeString()}";
public class MyModel
{
public int Value { get; set; } = 3;
}
}
原答案:
你的代码破译起来有点复杂。这是一个简单的示例,用于演示如何使用 select 并捕获其上的更改事件。
需要理解的重要部分是:
您无法绑定到
Option.
与
@bind
的绑定使用 select 上的Onchanged
事件,因此您不能同时绑定到它。而是使用@oninput
绑定到事件处理程序。返回的值是一个字符串,所以你需要做一些解析才能让它说出一个int。
如果您要触发一个事件,您需要检查该值是否确实发生了变化,并且只有在发生变化时才调用事件回调。
@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);
}
}
}
}
简单的解决方案
您现有的代码只需少量添加即可工作。
您可以在此 Blazor Fiddle Demo 中看到一个工作示例 Fiddle 相当有限,但我尽量使代码接近原始代码。
您的点击处理程序永远不会触发,因为 <option>
元素仅在父级为 <select multiple>
时触发。然而,在这种情况下,它只是默认单曲 <select>
并且永远不会触发。我们可能会改用 onchange 事件,这通常是这样做的。然而,我们也可以只在 CurrentValue 属性 周围添加一个包装器,这将允许您重用已有的代码。
CurrentValue 是从 InputBase 继承的 属性。要调用父提供的 Trigger 方法,您需要包装 CurrentValue 以提供该新功能。这可以通过使用 "new" keyword 并调用 setter 中的方法来完成,如图所示。
要添加到组件的代码:
[Parameter]
public new string CurrentValue
{
get => base.CurrentValue;
set {
base.CurrentValue = value;
Trigger?.Invoke();
}
}
您可能还想从标记中删除现有的 onclick 处理程序,因为它不会执行任何操作,只会产生误导。