Search code examples
c#blazor.net-8.0inputtext

Custom Blazor InputText component


I'm trying to create a "composite" Blazor text input component.

I'd like to combine

  • a title/label for the input text field
  • the actual input text field
  • a counter to show how many characters of the defined max length have already been used

So far, I've created something like this (slightly simplified):

MyInputText.razor:

<div class="row">
    <div class="col-md-3">
        <label>@Title</label>
    </div>
    <div class="col-md-8">
        <InputText class="form-control" name="textbox" @bind-Value="Value" @oninput="TextInputEvent" />
        <ValidationMessage For="() => Value" />
    </div>
    <div class="col-md-1">
        <label>@CurrentChars / @MaxChars</label>
    </div>
</div>

MyInputText.razor.cs:

public partial class MyTextInput
{
    [Parameter]
    [EditorRequired]
    public string Title { get; set; } = string.Empty;

    [Parameter]
    [EditorRequired]
    public string Value { get; set; } = string.Empty;

    [Parameter]
    [EditorRequired]
    public int MaxChars { get; set; } = 255;

    public int CurrentChars { get; set; }

    private void TextInputEvent()
    {
        CurrentChars = Value.Length;
    }
}

It seems to almost work - but:

  • It seems to be always "one step behind" - so if I have four characters, and type in a fifth one - the "CurrentChars" is set to 4. Seems it's looking at the "old" content of Value - before the char I've just typed is registered

  • It seems to work only for the first char I type - any subsequent chars don't seem to trigger the TextInputEvent handler anymore...

Any ideas what I'm missing here?


Solution

  • Other answers cover the need for manual binding in your solution.

    The alternative is to inherit directly from InputText.

    The key advantage in doing so is you don't need to reinvent the wheel. You leverage all the built in InputBase functionality with the EditContext and validation.

    In the example I've added the UpdateOnInput option as you will probably use it at some point.

    @inherits InputText
    
    <div class="row">
        <div class="col-md-3">
            <label>@Title</label>
        </div>
        <div class="col-md-8">
                <input class="@this.CssClass"
                       type="text"
                       value="@this.CurrentValueAsString"
                       @oninput="this.OnInput"
                       @onchange="this.OnChange"
                       @attributes=this.AdditionalAttributes
                       @ref=this.Element />
            <ValidationMessage For="() => Value" />
        </div>
        <div class="col-md-1">
            <label>@CurrentChars / @MaxChars</label>
        </div>
    </div>
    @code {
        [Parameter, EditorRequired] public string Title { get; set; } = string.Empty;
        [Parameter, EditorRequired] public int MaxChars { get; set; } = 255;
        [Parameter] public bool UpdateOnInput { get; set; }
    
        public int CurrentChars { get; set; }
    
        private void OnInput(ChangeEventArgs e)
        {
            CurrentChars = e?.Value?.ToString()?.Length ?? 0;
            if(UpdateOnInput)
                CurrentValueAsString = e?.Value?.ToString() ?? null;
    
        }
    
        private void OnChange(ChangeEventArgs e)
        {
            if (!UpdateOnInput)
                CurrentValueAsString = e?.Value?.ToString() ?? null;
        }
    }
    

    Demo:

    @page "/"
    
    <PageTitle>Home</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <EditForm Model="_model">
        <MyInputText class="form-control" @bind-Value="_model.Value" MaxChars="10" Title="My Value" />
        <MyInputText class="form-control" @bind-Value="_model.Value2" UpdateOnInput MaxChars="10" Title="My Value" />
    </EditForm>
    
    <div class="bg-dark text-white m-2 p-2">
        <pre>Value: @_model.Value</pre>
        <pre>Value2: @_model.Value2</pre>
    </div>
    
    @code{
        private Model _model = new();
    
    
        public class Model
        {
            public string? Value { get; set; }
            public string? Value2 { get; set; }
        }
    }