I am building an editor for a list of objects of related types. My top level shows a list of the objects. Clicking one of them expands it to show its details. Each type of detail is shown in it's own blazor control (e.g. a "NameEditor" for the Name property, which they all have).
QUESTION: How do I update the top level (list) after making a change at the lowest level?
ATM editing is very basic, a button that changes the name to "Reset"
<p>Device.Name in Type1Component.razor = @Device.Name</p>
updates as expected
I'm trying to work out how I propogate this update to trigger Type1Component.DeviceChanged
when Device.Name changes in NameEditor
Top Level:
@if (topLevel == null){
<p><em>Loading...</em></p>
} else if (Device == null) { @*hidden if we are editing a device*@
<h1>@topLevel.JobRef</h1>
<h2>@topLevel.Customer</h2>
<h2>@topLevel.Installation</h2>
<hr />
<select class="selectpicker show-tick" multiple title="Filter..." onchange="@Selected">
@foreach (var item in @deviceTypes) {
<option>@item</option>
}
</select>
<hr />
<p> </p>
<p>@topLevel.Devices.Count() Devices</p>
foreach (var dev in Devices ) {
<button onclick="@(() => EditDevice(dev))">@dev.FriendlyDeviceType</button>
<p>@dev.Name</p>
}
} else {
<EditDevice Device="@Device"></EditDevice>
}
@code {
topLevelRec topLevel;
//all "devices" inherit DeviceBase
IEnumerable<DeviceBase> Devices; //Devices that are shown (filtered from topLevel.Devices)
IEnumerable<string> deviceTypes; //filter display by these descriptions
DeviceBase Device; //device we are editing
protected override async Task OnInitializedAsync(){
topLevel = await toplevelservice.GettopLevelAsync();
deviceTypes = topLevel.Devices.Select(d => d.FriendlyDeviceType).Distinct();
Devices = topLevel.Devices;
}
/// <summary>
/// Apply Filter
/// </summary>
/// <param name="c">c.Value is array of selected items to filter to</param>
protected void Selected(ChangeEventArgs c) {
if (c.Value == null) { //no filtering
Devices = topLevel.Devices;
} else {
Devices = topLevel.Devices.Where(d => d.FriendlyDeviceType.IsOneOf<string>((string[])c.Value));
}
}
protected void EditDevice(DeviceBase dev) {
Device = dev;
StateHasChanged();
}
private void ActionCompleted(EditResult Result) {
Device = null; //edit cancelled, go back to showing the list
}
}
EditDevice component:
<h3>EditDevice</h3>
@if (Device is DeviceBase){
switch (Device){
case device_type1 t1:
<Type1Component @bind-Device=t1></Coupler>
break;
case device_type2 t2:
<Type2Component @bind-Device=t2></Coupler>
break;
default:
<h4>No Editor allocated for @dev.ToString()</h4>
break;
}
}
<button onclick="@(() => Cancel())">Cancel</button>
@code {
[Parameter] public DeviceBase Device { get; set; }
[Parameter] public EventCallback<EditResult> OnActionCompleted { get; set; }
private async Task Cancel() {
await OnActionCompleted.InvokeAsync(EditResult.Cancel);
}
}
Type1Component:
<h4>@Device.FriendlyName</h4>
<NameEditor @bind-Name=Device.Name></NameEditor>
<p>Device.Name in Type1Component.razor = @Device.Name</p>
<p>Other parameter editors go here...</p>
@code {
[Parameter] public device_type1 Device { get; set; }
[Parameter] public EventCallback<device_type1> DeviceChanged { get; set; }
}
NameEditor:
<p><b>@Name</b></p>
<button @onclick=Reset>Reset</button>
@code {
[Parameter] public string Name { get; set; }
[Parameter] public EventCallback<string> NameChanged { get; set; }
async Task Reset() {
Name = "Reset";
await NameChanged.InvokeAsync(Name);
}
}