1

I'd like to have something like:

<input id=filterString type="number" class="form-control" value="@FilterModel.FilterString" @oninput="@FilterChanged" />

but with InputNumber, to make use of validation.

So far the simplest and closest solution (maybe?) I've come across would probably be the method of creating a subclass of InputBase, passing the specified callback as a parameter, and calling it the same time InputBase's ValueChanged is called. Similar to this post.

I couldn't get that to work (I can't recall the errors specifically, but could retrieve them later if this method turns out to be recommended), and after repeated troubleshooting it started to feel clunky anyways. I've scoured the docs but still don't understand how the validation system and bindings come together. So I'm kind of lost and would appreciate some help getting this all sorted.

To sum it up:

  1. I'd like to know the simplest way to implement InputNumber (or equivalent) where I can also specify an OnInput(or equivalent) callback to do some work whenever the value is changed.
  2. It'd also be nice if someone could briefly clarify/describe the sequence of events that occur when an input is changed (i.e. what does the "pipeline" look like starting from the user interacting with an input). And in particular, when/where does InputBase do the validation? (I guess I'm just really confused how InputBase (and children) and validation work, so I'm having trouble extending them).

Thanks, all help much appreciated.

1 Answer 1

4

You can create your own component and receive a cascading parameter of type EditContext - you can then use that parameter to invoke validation, and to get any validation messages for your field.

Now that you have full control over the input, you can hook to its @oninput method and do your work (raise other events, do more logic, invoke the .Validate() method of the edit context).

You might need to add some more parameters to allow for value expression, maybe type param, maybe an identifier for the field name, things like that, to make it more reusable and generic, but the core is to get the edit context and control your own rendering and events.

EDIT: added example - note that this is just one implementation and it is neither generic, nor is it perfect

main component with the form

@using System.ComponentModel.DataAnnotations

<EditForm Model="@TheModel">
    <DataAnnotationsValidator></DataAnnotationsValidator>
    @TheModel.MyProperty
    <br />
    <MyNumberInput @bind-Value="@TheModel.MyProperty"
                   MySpecialEvent="@MySpecialEventHandler"
                   FieldId="@( new FieldIdentifier(TheModel, nameof(MyFormModel.MyProperty)) )" />

    <ValidationMessage For="@( () => TheModel.MyProperty )"></ValidationMessage>
</EditForm>

@code{
    MyFormModel TheModel { get; set; } = new MyFormModel();

    async Task MySpecialEventHandler(DateTime time)
    {
        Console.WriteLine($"special event fired at: {time.Millisecond}");
    }

    public class MyFormModel
    {
        [Required]
        [Range(3, 5)]
        public int? MyProperty { get; set; }
    }
}

child component - MyNumberInput

@implements IDisposable

<input type="number" @oninput="@OnInputHandler" value="@Value" />

@code {
    [CascadingParameter]
    public EditContext TheEditContext { get; set; }
    [Parameter]
    public FieldIdentifier FieldId { get; set; }
    [Parameter]
    public int? Value { get; set; }
    [Parameter]
    public EventCallback<int?> ValueChanged { get; set; }
    [Parameter]
    public EventCallback<DateTime> MySpecialEvent { get; set; }

    async Task OnInputHandler(ChangeEventArgs e)
    {
        try
        {
            int val = int.Parse(e.Value.ToString());
            Value = val;
        }
        catch
        {
            Value = null;
        }

        //twoway binding
        await ValueChanged.InvokeAsync(Value);
        //update validation - AFTER the value is saved in the form
        TheEditContext.NotifyFieldChanged(FieldId);
        //your own event as needed
        await MySpecialEvent.InvokeAsync(DateTime.Now);
    }

    //some general update that may not be needed but might help if other fields update this one and you need to rerender
    void ValidationStateChanged(object sender, ValidationStateChangedEventArgs e)
    {
        StateHasChanged();
    }

    protected override void OnInitialized()
    {
        if (TheEditContext != null)
            TheEditContext.OnValidationStateChanged += ValidationStateChanged;
    }

    public void Dispose()
    {
        if (TheEditContext != null)
            TheEditContext.OnValidationStateChanged -= ValidationStateChanged;
    }
}


The sequence of events is something like

  1. user types a character
  2. the input component reacts as it is written (that can be usually the oninput or onchange DOM event - the standard components use onchange, by the way)
  3. the edit context is notified of the field change and validation is invoked - it comes down as a cascading parameter
Sign up to request clarification or add additional context in comments.

5 Comments

Wow, you're really a skyrocketer. I'm here, too.
Hmm, could you please post me a code example? I'm having trouble imagining how to put this together. Thanks for clarifying the sequence of events.
@rdmptn AFAICT this method is intended specifically to make EditForm (or just forms in general) easier and more convenient to implement, customize, extend, etc.. and unfortunately I think it might not exactly be the thing I'm looking for (using a form) because my input isn't intended to be submitted once upon being filled out, but rather used immediately upon each change; and I think for this, just an input field (not a form) is the correct "tool" for the job. Am I correct and thinking forms are not the right tool for my purposes here? And if so, is it possible to do this without EditForm?
Your original post talks about InputNumber and the other form-related components, so I assumed you need that. If you don't, feel free to drop all that and just handle @oninput. Just keep your view-model updated - you still need the value otherwise it will be lost on every keystroke and the user won't be able to enter numbers with more than one digit.
Thanks very much for the code example, I think this will work nicely. Very much appreciated!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.