Blazor has the InputNumber
component that constrains input to digits. However that renders an <input type=number .../>
which firefox does not respect (it permits any text).
So I tried to create a custom component that filters input:
@inherits InputNumber<int?>
<input type="number" @bind=_value @onkeypress=OnKeypress />
<p>@_value</p>
@code {
private string _value;
private void OnKeypress(KeyboardEventArgs e) {
if (Regex.IsMatch(e.Key, "[0-9]"))
_value += e.Key;
}
}
The <p>
shows the correct value. But the input itself shows all keypresses.
How do I write a custom component to filter keystrokes? (In this example I'm filtering for digits, but I assume the same approach would apply for filtering any char.)
If you want to use the Blazor EditForm infrastructure you can create a custom InputNumber
.
The following code inherits from InputNumber
and makes the following changes. I've tested it with Firefox and Edge, but don't have Chrome install on my laptop.
text
. This ensures consistent behaviour from all browsers - not the case if set to `number.value
is mapped to a new internal field which will update from a new setter.oninput
is wired to SetValue
to capture each keypress.SetValue
contains the new logic to check for valid numeric input. The code has inline commentary.@inherits InputNumber<TValue>
@typeparam TValue
<input type="text"
value="@this.displayValue"
class="@this.CssClass"
@oninput=SetValue
@attributes=this.AdditionalAttributes
@ref=this.Element />
@code {
// create a unique string based on the null Ascii char
//private static string emptyString = ((char)0).ToString();
private static string emptyString = string.Empty;
private string? displayValue;
public async Task SetValue(ChangeEventArgs e)
{
// Get the current typed value
var value = e.Value?.ToString();
// Check if it's a number of the TValue or null
var isValidNumber = BindConverter.TryConvertTo<TValue>(value, System.Globalization.CultureInfo.CurrentCulture, out var num)
|| value is null;
// If it's not valid we need to reset the value
if (!isValidNumber)
{
// Set the value to an empty string
displayValue = emptyString;
// Call state has changed to render the component
StateHasChanged();
// Give thw renderer some processor time to execute the queued render by creating a Task continuation
await Task.Delay(1);
// Set the display to the previous value stored in CurrentValue
displayValue = FormatValueAsString(CurrentValue);
// done
return;
}
// We have a numbr so set the two fields to the current value
// This is the display value
displayValue = value;
// This triggers the full InputBase/EditContext logic
CurrentValueAsString = value;
}
}
We have to double render if the number in invalid to fix an inconsistency that occurs between the actual DOM and the Render's DOM.
Consider this:
value="12"
.12q
.value="12q"
, while the Renderer DOM is still value="12"
If we now set the Renderer DOM to value="12"
it hasn't changed. The Diffing engine sees no difference and doesn't update the browser UI.
To solve this we have to make sure the Renderer's DOM is set to something else before we set it to the original value. We set it to an empty string, give the renderer some processor time with Task.Delay
to actually render the component and then finally set it back to it's original setting.