admin管理员组

文章数量:1390451

I am trying to create reusable components for my MudBlazor application, to create some financial products. For example in our company, all products have a commodity (that's what you are purchasing, it's something like Oil, Gas, Electricity, ...), a currency (in which you will pay the delivery) and a delivery period (it's a period because for electricity / gas you usually purchase a given amount for a given period of time, so for example a fixed quantity per day for 1 month).

Let's badly call that CommonProperties

public class CommonProperties
{
    public string? Commodity { get; set; }
    
    public string? Currency { get; set; }

    public DateTime? StartDelivery { get; set; }
        
    public DateTime? EndDelivery { get; set; }
}
  • I would like to be able to bind a CommonProperties object to my control
  • I would like to be able to put this component in a form and have errors be reported to the form
  • I would like to have some defaulting going on, so that for example when I select a given commodity, the currency is defaulted

I did a small snippnet

I mainly have the following code for the component

<MudSelect @bind-Value:get="Value.Commodity" @bind-Value:set="OnCommodityChanged" Label="Commodity" Required=true>
    @foreach (var commo in Commodities)
    {
        <MudSelectItem Value="commo">@commo</MudSelectItem>
    }
</MudSelect>
<MudSelect @bind-Value:get="Value.Currency" @bind-Value:set="OnCurrencyChanged" Label="Currency" Required=true>
    @foreach (var currency in Currencies)
    {
        <MudSelectItem Value="currency">@currency</MudSelectItem>
    }
</MudSelect>
<MudTextField T="DateTime?" Format="yyyy-MM-dd" @bind-Value:get="Value.StartDelivery" @bind-Value:set="OnDeliveryStartChanged" Label="StartDate" InputType="InputType.Date" Required=true/>

And the following code:

private readonly string[] Commodities = [ "Oil", "Gas", "Elec" ];

private readonly string[] Currencies = [ "EUR", "USD" ];

[Parameter]
public CommonProperties Value { get; set; } = new();

[Parameter]
public EventCallback<CommonProperties> ValueChanged { get; set; }

private async Task OnCommodityChanged(string? commodity)
{
    Value.Commodity = commodity;
    var defaultCurrency = commodity is "Oil" ? "EUR" : "USD";
    Value.Currency = defaultCurrency;
    await ValueChanged.InvokeAsync(Value);
}

private async Task OnCurrencyChanged(string? currency)
{
    Value.Currency = currency;
    await ValueChanged.InvokeAsync(Value);
}

private async Task OnDeliveryStartChanged(DateTime? start)
{
    Value.StartDelivery = start;
    await ValueChanged.InvokeAsync(Value);
}
  • Do I need to manually implement all change callbacks, set the object, invoke the handler, is there a way like just binding bind-Value:set=Value.Currency and that everything works fine in a parent component if I bind the Currency as an input in another component, will the changes be tracked although it's a nested property (I don't understand how the change pipeline works)
  • On the code snippnet, if I press Validate to trigger validation with everything empty, if I then fill in the commodity, in will fill in the currency, the commodity will not be in error anymore but the currency will even if it is required and filled in. I guess I have to do something with the EditContext, but I don't understand how it works with nested properties.

Do you have some tips on how to design such components / have a good validation flow ?

Thank you

I am trying to create reusable components for my MudBlazor application, to create some financial products. For example in our company, all products have a commodity (that's what you are purchasing, it's something like Oil, Gas, Electricity, ...), a currency (in which you will pay the delivery) and a delivery period (it's a period because for electricity / gas you usually purchase a given amount for a given period of time, so for example a fixed quantity per day for 1 month).

Let's badly call that CommonProperties

public class CommonProperties
{
    public string? Commodity { get; set; }
    
    public string? Currency { get; set; }

    public DateTime? StartDelivery { get; set; }
        
    public DateTime? EndDelivery { get; set; }
}
  • I would like to be able to bind a CommonProperties object to my control
  • I would like to be able to put this component in a form and have errors be reported to the form
  • I would like to have some defaulting going on, so that for example when I select a given commodity, the currency is defaulted

I did a small snippnet https://try.mudblazor/snippet/mOGfknFQoIDmgcYy

I mainly have the following code for the component

<MudSelect @bind-Value:get="Value.Commodity" @bind-Value:set="OnCommodityChanged" Label="Commodity" Required=true>
    @foreach (var commo in Commodities)
    {
        <MudSelectItem Value="commo">@commo</MudSelectItem>
    }
</MudSelect>
<MudSelect @bind-Value:get="Value.Currency" @bind-Value:set="OnCurrencyChanged" Label="Currency" Required=true>
    @foreach (var currency in Currencies)
    {
        <MudSelectItem Value="currency">@currency</MudSelectItem>
    }
</MudSelect>
<MudTextField T="DateTime?" Format="yyyy-MM-dd" @bind-Value:get="Value.StartDelivery" @bind-Value:set="OnDeliveryStartChanged" Label="StartDate" InputType="InputType.Date" Required=true/>

And the following code:

private readonly string[] Commodities = [ "Oil", "Gas", "Elec" ];

private readonly string[] Currencies = [ "EUR", "USD" ];

[Parameter]
public CommonProperties Value { get; set; } = new();

[Parameter]
public EventCallback<CommonProperties> ValueChanged { get; set; }

private async Task OnCommodityChanged(string? commodity)
{
    Value.Commodity = commodity;
    var defaultCurrency = commodity is "Oil" ? "EUR" : "USD";
    Value.Currency = defaultCurrency;
    await ValueChanged.InvokeAsync(Value);
}

private async Task OnCurrencyChanged(string? currency)
{
    Value.Currency = currency;
    await ValueChanged.InvokeAsync(Value);
}

private async Task OnDeliveryStartChanged(DateTime? start)
{
    Value.StartDelivery = start;
    await ValueChanged.InvokeAsync(Value);
}
  • Do I need to manually implement all change callbacks, set the object, invoke the handler, is there a way like just binding bind-Value:set=Value.Currency and that everything works fine in a parent component if I bind the Currency as an input in another component, will the changes be tracked although it's a nested property (I don't understand how the change pipeline works)
  • On the code snippnet, if I press Validate to trigger validation with everything empty, if I then fill in the commodity, in will fill in the currency, the commodity will not be in error anymore but the currency will even if it is required and filled in. I guess I have to do something with the EditContext, but I don't understand how it works with nested properties.

Do you have some tips on how to design such components / have a good validation flow ?

Thank you

Share Improve this question edited Mar 13 at 1:58 Qiang Fu 9,4071 gold badge6 silver badges16 bronze badges asked Mar 12 at 12:41 Victor BurckelVictor Burckel 2131 silver badge7 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

For the second issue, you could use StateHasChanged before revalidation then the function will work normally. And

        Value.Currency = defaultCurrency;

        StateHasChanged();    // Update the value state
        form?.Validate(); // revalidate

        await ValueChanged.InvokeAsync(Value);

From my understanding you want a double way binding from the child component to parent component. Then you could try following code to use @bind-Value:after instead.
Child.razor

<MudPaper Class="pa-4">
    <MudForm @ref="form" @bind-IsValid="@success" @bind-Errors="@errors" EditContext="@editContext">
        <div class="d-flex">
            <MudSelect @bind-Value="Value.Commodity" @bind-Value:after="OnCommodityChanged" Label="Commodity" Required=true>
                @foreach (var commo in Commodities)
                {
                    <MudSelectItem Value="commo">@commo</MudSelectItem>
                }
            </MudSelect>
            <MudSelect @bind-Value="Value.Currency" @bind-Value:after="OnCurrencyChanged" Label="Currency" Required=true>
                @foreach (var currency in Currencies)
                {
                    <MudSelectItem Value="currency">@currency</MudSelectItem>
                }
            </MudSelect>
            <MudTextField T="DateTime?" Format="yyyy-MM-dd" @bind-Value:get="Value.StartDelivery" @bind-Value:set="OnDeliveryStartChanged" Label="StartDate" InputType="InputType.Date" Required=true />
        </div>
        <div class="d-flex">
            <MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!success)" Class="ml-auto">Register</MudButton>
        </div>
    </MudForm>
</MudPaper>
<MudPaper Class="pa-4 mt-4">
    <MudButton Variant="Variant.Filled" Color="Color.Primary" DropShadow="false" OnClick="@(()=>form.Validate())">Validate</MudButton>
    <MudButton Variant="Variant.Filled" Color="Color.Secondary" DropShadow="false" OnClick="@(()=>form.ResetAsync())" Class="mx-2">Reset</MudButton>
    <MudButton Variant="Variant.Filled" DropShadow="false" OnClick="@(()=>form.ResetValidation())">Reset Validation</MudButton>
</MudPaper>

@code {

    public class CommonProperties
    {
        public string? Commodity { get; set; }

        public string? Currency { get; set; }

        public DateTime? StartDelivery { get; set; }

        public DateTime? EndDelivery { get; set; }
    }

    private readonly string[] Commodities = ["Oil", "Gas", "Elec"];

    private readonly string[] Currencies = ["EUR", "USD"];

    [Parameter]
    public CommonProperties Value { get; set; } = new();

    [Parameter]
    public EventCallback<CommonProperties> ValueChanged { get; set; }

    private EditContext editContext;

    protected override void OnInitialized()
    {
        editContext = new EditContext(Value);
    }

    private async Task OnCommodityChanged()
    {
        var defaultCurrency = Value.Commodity is "Oil" ? "EUR" : "USD";
        Value.Currency = defaultCurrency;

        StateHasChanged();    // Update the value state before revalidate
        form?.Validate(); // revalidate

        await ValueChanged.InvokeAsync(Value);
    }

    private async Task OnCurrencyChanged()
    {

        await ValueChanged.InvokeAsync(Value);
    }

    private async Task OnDeliveryStartChanged(DateTime? start)
    {
        Value.StartDelivery = start;
        await ValueChanged.InvokeAsync(Value);
    }

    bool success;
    string[] errors = [];
    MudForm form;
}

Parent.razor

<Child @bind-Value="ParentValue"></Child>

Parent Commodity: @ParentValue.Commodity
<br />
Parent Currency: @ParentValue.Currency
@code{

    CommonProperties ParentValue = new();
}

本文标签: