自定义 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 并捕获其上的更改事件。

需要理解的重要部分是:

  1. 您无法绑定到 Option.

  2. @bind 的绑定使用 select 上的 Onchanged 事件,因此您不能同时绑定到它。而是使用 @oninput 绑定到事件处理程序。

  3. 返回的值是一个字符串,所以你需要做一些解析才能让它说出一个int。

  4. 如果您要触发一个事件,您需要检查该值是否确实发生了变化,并且只有在发生变化时才调用事件回调。

@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 处理程序,因为它不会执行任何操作,只会产生误导。