Reputation: 1906
I just created a custom Select. The API defines a SelectedValue, SeelctedText and an Items collection. All params are typed so I have a TItem and TValue type parameter.
Example:
<Select
TItem="TechItem"
TValue="Guid"
ItemTextSelector="(TechItem t) => t.Name"
ItemValueSelector="(TechItem t) => t.ID"
Items="TechItemDataSource"
@bind-SelectedValue="_selectedTech" />
Which works like a charm :-) And now my special case: we often have predefined sets of list-items. In that case, the parameters of select should be different:
<Select
ItemListID="TechItems"
@bind-SelectedValue="_selectedTech" />
I could just add the ItemListID to the select but that would give issues regarding the typing. I cannot make an override in Blazor. So I decided to try is by calling the same component from within itself:
@if(ItemListID != null) {
<Select
TValue="string"
TItem="IListItem"
Items="@(ListService.GetItems(ItemListID))"
ItemValueSelector="(IListItem i) => i.Value"
ItemTextSelector="(IListItem i) => i.Caption"
@bind-SelectedValue=@SelectedValue
/>
}
Thought this would work ... but looks like I cannot pass the @bind-SelectedValue internally to another component (same in this case).
Any thoughts?
Upvotes: 0
Views: 412
Reputation: 30036
I'm guessing your Select
looks something like this (very similar to one of my controls)
@typeparam TItem
@typeparam TValue
<select @attributes=this.AdditionalAttributes @bind:get="Value" @bind:set=this.OnChange>
@if (Value is null)
{
<option value="" disabled selected> -- Select an Item -- </option>
}
@foreach (var item in this.ItemsProvider)
{
<option value="@GetItemValue(item)">@GetItemText(item)</option>
}
</select>
@code {
[Parameter] public TValue? Value { get; set; }
[Parameter] public EventCallback<TValue> ValueChanged { get; set; }
[Parameter, EditorRequired] public Func<TItem, object>? TextProvider { get; set; }
[Parameter, EditorRequired] public Func<TItem, object>? ValueProvider { get; set; }
[Parameter, EditorRequired] public IEnumerable<TItem> ItemsProvider { get; set; } = Enumerable.Empty<TItem>();
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; }
private Func<TItem, object> _textProvider = default!;
private Func<TItem, object> _valueProvider = default!;
protected override void OnInitialized()
{
// check for nulls before anything gets rendered
ArgumentNullException.ThrowIfNull(this.TextProvider);
ArgumentNullException.ThrowIfNull(this.ValueProvider);
_textProvider = this.TextProvider!;
_valueProvider = this.ValueProvider!;
}
private async Task OnChange(TValue value)
=> await ValueChanged.InvokeAsync(value);
private string? GetItemValue(TItem item)
=> _valueProvider(item).ToString();
private string? GetItemText(TItem item)
=> _textProvider(item).ToString();
}
In which case you can create a new component for your IListItem
lists. I don't believe creating an instance of self within a component is a good idea.
<Select TItem=IListItem
TValue=string
TextProvider="(IListItem i) => i.Caption"
ValueProvider="(IListItem i) => i.Value"
ItemsProvider=this.ItemsProvider
@bind-Value:set=this.OnChange
@bind-Value:get=this.Value
@attributes=this.AdditionalAttributes />
@code {
[Parameter, EditorRequired] public IEnumerable<IListItem> ItemsProvider { get; set; } = Enumerable.Empty<IListItem>();
[Parameter] public string? Value { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; }
private async Task OnChange(string value)
=> await ValueChanged.InvokeAsync(value);
}
I've purposely left the List controller out as it ties the component to a specific data source implementation. You can wire the method directly into the ItemsProvider
in the parent.
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
<Select class="form-select"
ItemsProvider=data
TextProvider="(Data t) => t.Caption"
ValueProvider="(Data t) => t.Value"
TItem="Data"
TValue="string"
@bind-Value=_selectedValue1 />
<div class="alert alert-info m-3 p-2">@_selectedValue1</div>
<ListSelect class="form-select"
ItemsProvider="GetItems()"
@bind-Value=_selectedValue2 />
<div class="alert alert-info m-3 p-2">@_selectedValue2</div>
@code {
private string? _selectedValue1;
private string? _selectedValue2;
private List<Data> data = new()
{
new(Guid.NewGuid(), "France"),
new(Guid.NewGuid(), "Spain"),
new(Guid.NewGuid(), "Portugal"),
};
public IEnumerable<IListItem> GetItems()
=> data;
public record Data(Guid Value, string Caption) : IListItem;
}
Upvotes: 0
Reputation: 11322
Instead of using the @bind-
syntax do it like this:
@if (ItemListID != null)
{
<Select
...
SelectedValue="@SelectedValue"
SelectedValueChanged="@SelectedValueChanged" />
}
Upvotes: 1