Reputation: 381
I have a simple class InputLength.razor. It contains on it a Length and Length Type. From the parent I'd like to retrieve those properties from the child InputLength component. I know I can bind to the individual properties, but imagine I had 10 properties. That'd be way tedious.
I'd like to simply create an instance of the InputLength component, then pass that model in to the InputLength child as a bound parameter and when ever any of the properties updates I'd like the bound model to be updated on the parent.
See component code below:
@code {
[Parameter]
public InputLength m { get; set; }
[Parameter]
public EventCallback<InputLength> mChanged { get; set; }
public double _Length;
public double Length { get { return _Length; } set { _Length = value; m.Length = value; mChanged.InvokeAsync(m); } }
public LengthType _SelectedLengthType;
public LengthType SelectedLengthType { get { return _SelectedLengthType; } set { _SelectedLengthType = value; mChanged.InvokeAsync(m); } }
public enum LengthType
{
Inches,
Centimeters,
};
}
As you can see, in order to facilitate this what I've tried doing is simply creating an m property of type InputLength on the InputLength component and it's the only property. On the parent I'm binding to it
<InputLength bind-m=inputLengthInstance></InputLength>
For some reason when it goes to update the m.Length value (prior to me invoking mChanged), it seems like it's creating a new component and never actually makes it to the point of calling mChanged.InvokeAsync. If I manually go in, move the breakpoint and adjust the value on the object, and invokeAsync it totally works so it's kinda on the right path.
Edit: the reason it fails is because the model exists on the class and when updating it, it's triggering updates on the model's model
What am I doing wrong. It seems like I've written a lot of code to try and perform a pretty simple task. All the guides on microsofts website assume you're down to just bind to all basic the properties you want, which again, works but seems unnecessary. That's why classes exist. Just bind to the whole stinkin class.
Anyone have suggestions to make this work, hopefully in a cleaner way that makes sense?
Upvotes: 0
Views: 1523
Reputation: 30036
If I have understood this correctly you want to build an editor for class called Length, and use it in various places.
Define a data class - I've called it Length
.
public class Length
{
public double LengthValue { get; set; }
public UnitType Units { get; set; }
public string DisplayValue => $"{this.LengthValue} {this.Units}";
public enum UnitType
{
Inches,
Centimeters,
};
}
Define and edit component - I've called it InputLength.
<div class="m-2 p-2">
<InputNumber @bind-Value=LengthValue></InputNumber>
<InputSelect @bind-Value=UnitsValue>
@foreach (var lt in Enum.GetValues(typeof(Length.UnitType)))
{
<option>@lt</option>
}
</InputSelect>
</div>
<div class="m-2 p-2">
<br />Component Value: @Value.DisplayValue
</div>
@code {
[Parameter] public Length Value { get; set; }
[Parameter] public EventCallback<Length> ValueChanged { get; set; }
private Length _length;
private double LengthValue
{
get => _length.LengthValue;
set
{
if (value != _length.LengthValue)
{
_length.LengthValue = value;
this.InvokeModelChanged();
}
}
}
private Length.UnitType UnitsValue
{
get => _length.Units;
set
{
if (value != _length.Units)
{
_length.Units = value;
this.InvokeModelChanged();
}
}
}
protected override void OnInitialized()
{
_length = new Length() { LengthValue = this.Value.LengthValue, Units = this.Value.Units };
}
private void InvokeModelChanged()
{
if (ValueChanged.HasDelegate)
ValueChanged.InvokeAsync(_length);
}
}
Here's an edit form to demo it.
@page "/NewEditor1"
@page "/"
<h3>EditForm</h3>
<EditForm EditContext="this._editContext" OnValidSubmit="SubmitForm">
<div class="p-2">
Long Side: <InputLength @bind-Value="_model.LongSide" />
</div>
<div class="p-2">
Short Side: <InputLength @bind-Value="_model.ShortSide" />
</div>
<div class="m-2 p-2">
<button class="btn btn-success" type="submit">Submit</button>
</div>
</EditForm>
<div class="m-2 p-2">Form Long Side Value: @_model.LongSide.DisplayValue </div>
<div class="m-2 p-2">Form Short Side Value: @_model.ShortSide.DisplayValue </div>
@code {
public class Model
{
public Length LongSide { get; set; } = new Length() { Units = Length.UnitType.Centimeters, LengthValue = 0 };
public Length ShortSide { get; set; } = new Length() { Units = Length.UnitType.Centimeters, LengthValue = 0 };
}
private string message;
Model _model = new Model();
EditContext _editContext;
protected override void OnInitialized()
{
_editContext = new EditContext(_model);
}
void SubmitForm()
{
message = $"Form Submitted at :{DateTime.Now.ToLongTimeString()}";
var x = true;
}
}
Note that this is a very simplistic input control. It doesn't handle passing edit changes up to the edit context or interacting with validation.
Upvotes: 5
Reputation: 381
The trick to make this work in a way that makes the most sense so far is to reverse your idea of how the public and private versions of the properties should work. Your public property will be what ever you want the model to represent (this way it's settable via the model instance). The corresponding private property should be what you bind to your input field. On Set, you can update your model's public property and then trigger the changeEvent. This will subsequently update the model object on the parent and pass the updated values back down to the child for the visual update in both places.
Again, the goal of this was to not have to double represent a model as both a seperate class and as a razorComponent class. With this design I've created a razor component that can be used as input to the remainder of the code.
<InputNumber @bind-Value=_Length></InputNumber>
<InputSelect @bind-Value=_SelectedLengthType>
@foreach (var lt in Enum.GetValues(typeof(LengthType)))
{
<option>@lt</option>
}
</InputSelect>
<br />On Child: @Model.Length ; @Model.SelectedLengthType
@code {
[Parameter]
public InputLength Model { get; set; }
[Parameter]
public EventCallback<InputLength> ModelChanged { get; set; }
public double Length;
private double _Length
{
get { return Model.Length; }
set { Model.Length = value; ModelChanged.InvokeAsync(Model); }
}
public LengthType SelectedLengthType;
private LengthType _SelectedLengthType
{
get { return Model.SelectedLengthType; }
set { Model.SelectedLengthType = value; ModelChanged.InvokeAsync(Model); }
}
public enum LengthType
{
Inches,
Centimeters,
};
}
Upvotes: 0