Как написать обработчик событий изменения пользовательского значения в Blazor?
Я хочу написать настраиваемый компонент раскрывающегося списка для Blazor отчасти из-за того, что существующий компонент InputSelect не привязывается ни к чему, кроме типов string и enum. Для меня этого недостаточно, поскольку мои модели имеют свойства типа int и обнуляемого типа int, которые я хочу привязать к раскрывающемуся списку. Пока у меня есть это:
@using System.Globalization
@typeparam TValue
@typeparam TData
@inherits InputBase<TValue>
<select id="@Id" @bind="CurrentValueAsString" class="f-select js-form-field">
@if (!string.IsNullOrWhiteSpace(OptionLabel) || Value == null)
{
<option value="">@(OptionLabel ?? "-- SELECT --")</option>
}
@foreach (var item in Data)
{
<option value="@GetPropertyValue(item, ValueFieldName)">@GetPropertyValue(item, TextFieldName)</option>
}
</select>
<span>Component Value is: @Value</span>
@code {
[Parameter]
public string Id { get; set; }
[Parameter]
public IEnumerable<TData> Data { get; set; } = new List<TData>();
[Parameter]
public string ValueFieldName { get; set; }
[Parameter]
public string TextFieldName { get; set; }
[Parameter]
public string OptionLabel { get; set; }
private Type ValueType => IsValueTypeNullable() ? Nullable.GetUnderlyingType(typeof(TValue)) : typeof(TValue);
protected override void OnInitialized()
{
base.OnInitialized();
ValidateInitialization();
}
private void ValidateInitialization()
{
if (string.IsNullOrWhiteSpace(ValueFieldName))
{
throw new ArgumentNullException(nameof(ValueFieldName), $"Parameter {nameof(ValueFieldName)} is required.");
}
if (string.IsNullOrWhiteSpace(TextFieldName))
{
throw new ArgumentNullException(nameof(TextFieldName), $"Parameter {nameof(TextFieldName)} is required.");
}
if (!HasProperty(ValueFieldName))
{
throw new Exception($"Data type {typeof(TData)} does not have a property called {ValueFieldName}.");
}
if (!HasProperty(TextFieldName))
{
throw new Exception($"Data type {typeof(TData)} does not have a property called {TextFieldName}.");
}
}
protected override bool TryParseValueFromString(string value, out TValue result, out string validationErrorMessage)
{
validationErrorMessage = null;
if (ValueType == typeof(string))
{
result = (TValue)(object)value;
return true;
}
if (ValueType == typeof(int))
{
if (string.IsNullOrWhiteSpace(value))
{
result = default;
}
else
{
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedValue))
{
result = (TValue)(object)parsedValue;
}
else
{
result = default;
validationErrorMessage = $"Specified value cannot be converted to type {typeof(TValue)}";
return false;
}
}
return true;
}
if (ValueType == typeof(Guid))
{
validationErrorMessage = null;
if (string.IsNullOrWhiteSpace(value))
{
result = default;
}
else
{
if (Guid.TryParse(value, out var parsedValue))
{
result = (TValue)(object)parsedValue;
}
else
{
result = default;
validationErrorMessage = $"Specified value cannot be converted to type {typeof(TValue)}";
return false;
}
}
return true;
}
throw new InvalidOperationException($"{GetType()} does not support the type '{typeof(TValue)}'. Supported types are string, int and Guid.");
}
private string GetPropertyValue(TData source, string propertyName)
{
return source.GetType().GetProperty(propertyName)?.GetValue(source, null).ToString();
}
private bool HasProperty(string propertyName)
{
return typeof(TData).GetProperty(propertyName) != null;
}
private bool IsValueTypeNullable()
{
return Nullable.GetUnderlyingType(typeof(TValue)) != null;
}
}
А в родительском компоненте я могу использовать это так:
<DropDownList Id="@nameof(Model.SelectedYear)"
@bind-Value="Model.SelectedYear"
Data="Model.Years"
ValueFieldName="@nameof(Year.Id)"
TextFieldName="@nameof(Year.YearName)">
</DropDownList>
Это работает очень хорошо, модель привязывается к раскрывающемуся списку, и значение в родительской модели изменяется при изменении значения раскрывающегося списка. Однако теперь я хочу зафиксировать это событие изменения значения для своего родителя и выполнить некоторую настраиваемую логику, в основном загрузить некоторые дополнительные данные на основе выбранного года. Я предполагаю, что мне нужен собственный EventCallback, но все, что я пробовал, вызывает какую-то ошибку сборки или выполнения. Кажется, что если мой компонент наследуется от InputBase, то я очень ограничен в том, что могу делать.
Может ли кто-нибудь сказать мне, как я могу зафиксировать изменение значения дочернего компонента в родительском компоненте?
1 ответ
Я предполагаю, что мне нужен собственный EventCallback
Вам обязательно нужен EventCallback
, но дело в том, что он у вас уже есть, просто не смотрите.
Чтобы иметь возможность использовать @bind-Value
вам нужно два параметра, T Value
а также EventCallback<T> ValueChanged
.
Когда вы пройдете @bind-Foo
, blazor устанавливает эти два параметра, Foo
а также FooChanged
и в FooChanged
он просто установит новое значение на Foo
.
Итак, когда вы это сделаете @bind-Foo="Bar"
то, что делает блейзер под капотом, передает эти два параметра
Foo="@Bar"
FooChanged="@(newValue => Bar = newValue)"
Итак, в вашем случае вам нужно передать свой собственный ValueChanged
функция, которая устанавливает новое значение в Value
но также делайте некоторые дополнительные вещи, которые хотите.
<DropDownList Id="@nameof(Model.SelectedYear)"
Value="Model.SelectedYear"
ValueChanged="@((TYPE_OF_VALUE newValue) => HandleValueChanged(newValue))"
Data="Model.Years"
ValueFieldName="@nameof(Year.Id)"
TextFieldName="@nameof(Year.YearName)">
</DropDownList>
@code
{
void HandleValueChanged(TYPE_OF_VALUE newValue)
{
// do what you want to do
// set the newValue if you want
Model.SelectedYear = newValue;
}
}
В TYPE_OF_VALUE
, вы просто замените его типом Model.SelectedYear
.
Вы можете посмотреть это объяснение в документации.
редактировать
Поскольку вы хотите использовать типы, допускающие значение NULL, вам также необходимо передать FooExpression
который в вашем случае будет Expression<Func<T>> ValueExpression
.
<DropDownList Id="@nameof(Model.SelectedYear)"
Value="Model.SelectedYear"
ValueChanged="@((TYPE_OF_VALUE newValue) => HandleValueChanged(newValue))"
ValueExpression="@(() => Model.SelectedYear)"
Data="Model.Years"
ValueFieldName="@nameof(Year.Id)"
TextFieldName="@nameof(Year.YearName)">
</DropDownList>