Reputation: 3127
I want to build some reusable input components using Blazor.
Here is my code in the child component:
<div style="width: 100%">
<div class="create-approval-flow-drop-down">
<Label Display="Display.Flex">@ChildLabel</Label>
<Autocomplete TItem="GraphUser"
TValue="string"
Data="@users"
TextField="@(( item ) => item.DisplayName)"
ValueField="@(( item ) => item.DisplayName)"
Placeholder="Search..."
@bind-SelectedValue="@selectedSearchValue"
@bind-SelectedText="@selectedAutoCompleteText">
</Autocomplete>
</div>
</div>
@code {
[Parameter] public string ChildLabel { get; set; }
[Parameter] public string ChildValue { get; set; }
[Parameter] public string selectedSearchValue { get; set; }
[Parameter] public EventCallback<string> ChildLabelChanged { get; set; }
[Parameter] public EventCallback<string> ChildValueChanged { get; set; }
[Parameter] public EventCallback<string> selectedSearchValueChanged { get; set; }
[Inject] public IGraphService GraphService { get; set; }
public IEnumerable<GraphUser> users = new List<GraphUser>();
protected override async Task OnInitializedAsync()
{
users = await GraphService.GetUsers();
await base.OnInitializedAsync();
}
private Task OnChildValueChanged(ChangeEventArgs e)
{
return ChildValueChanged.InvokeAsync(selectedAutoCompleteText);
}
string selectedAutoCompleteText { get; set; }
}
And here is my parent component:
@page "/Test"
@using WireDesk.Web.SubComponents;
@using Blazored.FluentValidation;
@using WireDesk.Models
<WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete
ChildLabel="Location Manager"
ChildValue="">
</WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete>
@ChildLabel;
@ChildValue;
@code
{
private string ChildLabel { get; set; }
private string ChildValue { get; set; }
public ApprovalFlowForm approvalFlowForm = new ApprovalFlowForm();
}
The child will display the label and create a single select autocomplete text box. The parent will contain a form with a variety of fields, many of which will be a single select autocomplete text box.
I don't want to have to duplicate the code in the child over and over in the parent, but I cannot determine how to pass the string that the user has selected in the child component.
========================================================= This is almost working but not completely, I think I didn't explain two things clearly. The component will be used for input on a create/update page, and I need to use it more than once. The parent component should call the child just to do the work of displaying/updating the value and returning that to the parent, and then the parent will bind that to the Form.Field. Seems to me that the power of components would be that they could be reused, not just in different parents, but multiple times in the same parent (I am sure this is possible, but sure I don't know how to do it).
My code for the child is as so:
<div style="width: 100%">
<div class="create-approval-flow-drop-down">
<Label Display="Display.Flex">@ChildLabel</Label>
<Autocomplete TItem="GraphUser"
TValue="string"
Data="@users"
TextField="@(( item ) => item.DisplayName)"
ValueField="@(( item ) => item.DisplayName)"
Placeholder="Search..."
SelectedValue="@ChildValue"
SelectedValueChanged="OnChildValueChanged">
</Autocomplete>
</div>
</div>
@code {
[Parameter] public string ChildLabel { get; set; }
[Parameter] public string ChildValue { get; set; }
[Parameter] public string FieldValue { get; set; }
[Parameter] public EventCallback<string> ChildValueChanged { get; set; }
[Inject] public IGraphService GraphService { get; set; }
public IEnumerable<GraphUser> users = new List<GraphUser>();
protected override async Task OnInitializedAsync()
{
users = await GraphService.GetUsers();
await base.OnInitializedAsync();
}
private async Task OnChildValueChanged(string selectedValue)
{
ChildValue = selectedValue;
await ChildValueChanged.InvokeAsync(ChildValue);
}
}
And the parent is
@page "/Test"
@using WireDesk.Web.SubComponents;
@using Blazored.FluentValidation;
@using WireDesk.Models
<WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete
ChildLabel="First Manager"
@bind-ChildValue="@childValue">
@*@bind-SelectedValue="@ApprovalFlowForm.FirstManager"*@
</WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete>
<WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete
ChildLabel="Second Manager"
@bind-ChildValue="@childValue">
@*bind-SelectedValue="@ApprovalFlowForm.SecondManager"*@
</WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete>
@code {
private string childValue;
}
The commented out line...
@bind-SelectedValue="@ApprovalFlowForm.SecondManager"@
..I know will probably not work, but indicates the general idea I think of what I want to accomplish.
Thank you both Joe and Dimitris very very much. I have been working on this for the last 8 hours, and I feel I am pretty close now. I appreciate your assistance.
Still struggling with this.
My parent component is below. The "old" way of displaying an input select field is listed, as is my call to the "new" way, which is a child component.
@using Microsoft.IdentityModel.Tokens
@using System.Collections.Immutable
@using System.Linq
//Old
<div class="create-approval-flow-drop-down">
<Label Display="Display.Flex"> Controller </Label>
<Select TValue="string" @bind-SelectedValue="@ApprovalFlowForm.Controller">
@{foreach (var item in GraphUserOptions)
{<SelectItem Value="@item.Value">@item.Text</SelectItem>}
</Select>
</div>
//New
<div class="create-approval-flow-drop-down">
<SingleSelectAutoComplete
ChildLabel="Controller"
DataToSearch=users
@bind-SelectedValue="@ApprovalFlowForm.Controller">
</SingleSelectAutoComplete>
</div>
The child component below works perfectly except for one thing: when the parent edits a form with a value already in the data, it fails to display! So if "Ben Kew" is stored as the Controller in the Approval Form, opening the form in edit mode will not show that. Just a blank field. The user can pick from the list and changes will be saved, but the next time they open up the form they will see blank again.
If you can help me solve this, I would be so very happy and would be a big step in my project.
Thank you in advance for assistance!
Bryan
@using Blazorise.Components
@using System.Linq
<div style="width: 100%">
<div class="create-approval-flow-drop-down">
<Label Display="Display.Flex">@ChildLabel</Label>
<Autocomplete
TItem="GraphUser"
TValue="string"
Data=DataToSearch
TextField="@(( item ) => item.DisplayName)"
ValueField="@(( item ) => item.DisplayName)"
@bind-SelectedText="selectedAutoCompleteText"
SelectedValueChanged="@OnSelectedValueChanged">
Placeholder="Search...">
</Autocomplete>
</div>
</div>
@code {
[Parameter] public string ChildLabel { get; set; }
[Parameter] public IEnumerable<GraphUser> DataToSearch { get; set; }
[Parameter] public string SelectedValue { get; set; }
[Parameter] public EventCallback<string> SelectedValueChanged { get; set; }
[Parameter] public string? Param { get; set; }
public string selectedValue { get; set; }
public string selectedAutoCompleteText { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
}
private async Task OnSelectedValueChanged(string selectedValue)
{
//SelectedValue = selectedValue;
//await SelectedValueChanged.InvokeAsync(SelectedValue);
}
}
Upvotes: 0
Views: 1911
Reputation: 11302
You can do it using EventCallback
(like @Joe suggested).
<div style="width: 100%">
<div class="create-approval-flow-drop-down">
<Label Display="Display.Flex">@ChildLabel</Label>
<Autocomplete TItem="GraphUser"
TValue="string"
Data="@DataToSearch"
TextField="@(( item ) => item.DisplayName)"
ValueField="@(( item ) => item.DisplayName)"
Placeholder="Search..."
SelectedValue="@SelectedValue"
SelectedValueChanged="@OnSelectedValueChanged">
</Autocomplete>
</div>
</div>
@code {
[Parameter] public string ChildLabel { get; set; }
[Parameter] public IEnumerable<GraphUser> DataToSearch { get; set; }
[Parameter] public string SelectedValue { get; set; }
[Parameter] public EventCallback<string> SelectedValueChanged { get; set; }
private async Task OnSelectedValueChanged(string selectedValue)
{
SelectedValue = selectedValue;
await SelectedValueChanged.InvokeAsync(SelectedValue);
}
}
Usage:
<SingleSelectAutoComplete
ChildLabel="Controller"
DataToSearch="@users"
@bind-SelectedValue="@ApprovalFlowForm.Controller">
</SingleSelectAutoComplete>
Upvotes: 1
Reputation: 301
Use an EventCallBack in the child and assign it to a function in the parent. Do something like this. In the parent component:
<ChildComponent TextFieldUpdated="HandleChange" />
And the following code:
private void HandleChange(string value){
Do stuff...
}
And in the child component:
[Parameter] public EventCallBack<string> TextFieldUpdated {get;set;}
Then all you have to do is this when you want to send the value to the parent component:
TextFieldUpdated.InvokeAsync("String Value");
HandleChange() in the parent component will fire with the value from the child component.
Upvotes: 2