Reputation: 1617
I am trying to write a Blazor component that uses a <select...>
, to update the variable passed in from the parent.
I want to click on the drop-down (select/options), choose an option, and have that automatically update the variable in the parent.
I can get it to work if I don't use a component. But the only way I can get it to work from a component is if I also use a button in the component (see the button in the Component code). But eventually there will be several components called from the same parent. It would not be practical to have a separate button for each component. So the button has to go away.
The @bind uses the "onchange" parameter to the select, so that won't even compile.
I've tried CascadingValue/CascadingParam but that does not update the parent value either (unless I use a button...)
Here is the parent:
@page "/test"
@page "/test/{param}"
@namespace ComponentTest.Pages
@using System.Web
@using ComponentTest.Pages
<h3>Test Page</h3>
<div class="border col-3">
<h4>Parent</h4>
<hr />
<label for="parentValue">Parent Value</label>
<input id="parentValue" @bind="@param"/>
</div>
<div class="col-3"> </div>
<div class="border col-3">
<h4>Component Select</h4>
<hr />
<SelectComponent @bind-Item="@param"/>
</div>
@code {
[Parameter]
public string param { get; set; }
}
And here is the component:
@using System.Collections.Generic
@using System.Web
<div class="form-group">
<p>Component Value = @Item</p>
<label for="Site" class="font-weight-bold form-check-label">List</label>
@if (list == null)
{
<input id="Item" class="form-control-sm" @bind="@Item" />
}
else
{
<select id="Item" class="form-control-sm" @bind="@Item">
@foreach (var l in list)
{
@if (Item != null && String.Equals(l, Item, StringComparison.OrdinalIgnoreCase))
{
<option selected value="@l">@l</option>
}
else
{
<option value="@l">@l</option>
}
}
</select>
}
</div>
<button @onclick="UpdateParentsItem" class="btn btn-primary">Update Parent</button>
@code {
public IEnumerable<string> list = new List<string>()
{
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5"
};
[Parameter]
public string Item { get; set; }
[Parameter]
public EventCallback<string> ItemChanged { get; set; }
private async Task UpdateParentsItem()
{
await ItemChanged.InvokeAsync(Item);
}
}
I followed a few groovy examples for implementing events but none that I found actually solved the problem.
Upvotes: 5
Views: 12798
Reputation: 1617
I give credit to @enet for the answer but here's my full solution with his(her?) bit included, and with an extra validation check:
Test.razor (Parent)
@page "/test"
@page "/test/{param}"
<h3>Select Component Test</h3>
<div class="border col-2">
<h4>Parent</h4>
<hr />
<div class="form-group">
<label for="parentValue">Parent Value</label>
<input id="parentValue" type="text" class="form-control" @bind="@param" />
</div>
</div>
<div class="col-3"> </div>
<div class="border col-2">
<h4>Component</h4>
<hr />
<SelectComponent @bind-Item="@param" />
</div>
<hr />
<ul>Links
<li><a href="/test/item 1">item 1</a></li>
<li><a href="/test/ITEM 2">ITEM 2</a></li>
<li><a href="/test/iTeM 3">iTeM 3</a></li>
<li><a href="/test/ItEm 4">ItEm 4</a></li>
<li><a href="/test/item 99">item 99</a></li>
</ul>
@code
{
[Parameter]
public string param { get; set; }
}
SelectComponent.razor
<!-- Select Component -->
<div class="form-group">
@if (message != null)
{
<p class="text-warning">@message</p>
}
<label for="Item" class="form-check-label">Item</label>
@if (list == null)
{
<input id="Item" class="form-control-sm" @bind="@Item" />
}
else
{
<select id="Item" class="form-control-sm" @bind="@Item">
@foreach (var l in list)
{
<option value="@l">@l</option>
}
</select>
}
</div>
@code {
private string message;
public IEnumerable<string> list = new List<string>()
{
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5"
};
private string item { get; set; }
[Parameter]
public string Item
{
get { return item; }
set
{
if (item != value) // skip if it's the same string
{
if (list == null || list.Count() == 0)
{
item = value;
}
else
{
// validate the new item value against my list
foreach (var l in list)
{
if (value != null && String.Equals(l, value, StringComparison.OrdinalIgnoreCase))
{
item = l; // match exact case to my list so "selected" option works properly
message = null;
}
}
// if there's no match, clear the item
if (!String.Equals(item, value, StringComparison.OrdinalIgnoreCase))
{
item = null;
message = "Invalid Item";
}
}
// make sure there is a parent
if (ItemChanged.HasDelegate)
{
// this is the magic that updates the parent
ItemChanged.InvokeAsync(item);
}
}
}
}
[Parameter]
public EventCallback<string> ItemChanged { get; set; }
}
I will use this as a template for several Entity Framework based form validation dropdowns.
Upvotes: 1
Reputation: 45636
This code snippet is working. Try it... Run the code and start typing the word Item 3. As you type you'll see the type text displayed from the both the child component and the parent component. As you finish typing Item 3, you'll notice that the text Item 3 is displayed in the combo box. Select an item from the combo box, and it'll appear in the parent's input box.
<div style="border:solid 1px red">
<select id="Item" class="form-control-sm" @bind="@Item">
@foreach (var l in list)
{
@if (Item != null && String.Equals(l, Item,
StringComparison.OrdinalIgnoreCase))
{
<option selected value="@l">@l</option>
}
else
{
<option value="@l">@l</option>
}
}
</select>
</div>
<p>@Item</p>
@code {
public IEnumerable<string> list = new List<string>()
{
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5"
};
private string item { get; set; }
[Parameter]
public string Item
{
get { return item; }
set
{
if (item != value)
{
item = value;
if (ItemChanged.HasDelegate)
{
ItemChanged.InvokeAsync(value);
}
}
}
}
[Parameter]
public EventCallback<string> ItemChanged { get; set; }
}
@page "/test"
<div>
<label for="parentValue">Parent Value</label>
<input type="text" id="parentValue" @bind="@Param"
@bind:event="oninput" />
<p>@Param</p>
</div>
<div>
<SelectComponent @bind-Item="@Param"/>
</div>
@code {
[Parameter]
public string Param { get; set; } = string.Empty;
}
Good luck... If something is not clear, don't hesitate to ask...
Upvotes: 7