Prevent unwanted characters in <input>
I needed to create an <input>
that would accept only numbers and a slash symbol (/
) - because pattern for birth number in Czech republic is xxxxxx/xxxx
where all x
are numbers. You can probably come up with many scenarios like this - e.g. in my country the commonly used date format is dd.mm.yyyy
so an <input>
that accepts only numbers and dots would be nice.
The pattern
attribute
The pattern
attribute on <input>
element is handy, but it allows you to type in just anything and gives you warning only on form submission.
Input events
There are many <input>
events you can listen to on an <input>
element, but suitable ones for this scenario are keydown
, keypress
and beforeinput
.
1. keydown
The keydown
event is fired when a key is pressed down. The event.key
gives you information about what character was typed in, or in other words, what is going to appear in the <input>
. So for character 2 you get event.key
equal to 2
, for character á you get event.key
equal to á
.
So you can use this event.key
to check whether or not you want the character appear in the <input>
box.
That’s nice and all, but note that keydown event is fired for all keys on your keyboard, meaning if you press Shift
key, event.key
will equal to Shift
. Or Backspace
. Or Meta
. Or Control
. Or Enter
. Or …
Say you want to delete some characters from the <input>
. You press Backspace
and get event.key
equal to Backspace
. And because string "Backspace"
contains letters, event.preventDefault()
gets called. And you’re unable to delete characters.
2. keypress
Better alternative is keypress
event. The keypress
event is fired when a key that produces a character value is pressed down.
That sounds much better!
So the event is now fired only on key press that produces a character leaving Backspace
(and Enter
, and Shift
, and ..) out of the game. Now you can validate the pressed key and also you’re able to delete characters.
Note that keypress
event should be replaced with beforeinput
event (https://www.w3.org/TR/DOM-Level-3-Events/#event-type-keypress).
Gotchas
Oh yes, there’s always a gotcha.
The keypress
event should fire when a keypress produces a character value.
If supported by a user agent, this event MUST be dispatched when a key is pressed down, if and only if that key normally produces a character value.
Say you want to insert ö
letter. This letter is actually composed from 2 characters - ¨
and o
.
So what I would expect to happen is:
- press
¨
- press
o
- see
keypress
event fire
But what actually happens is:
- press
¨
- press
o
keypress
event does not fire
IMHO this sequence conforms to the specification, last pressed key was o
and that key normally produces a character value. Only it should produce the o
modified with an umlaut.
And since keypress
event does not fire for special characters like ö
, it actually inserts such character into the <input>
because it does not reach our regular expression.
I’m from Czech republic and we have special characters like ěščřžýáíé
(group 1) and some others like ďťň
(group 2). The difference between them is that when you use czech keyboard layout, group 1 characters are on the keyboard directly, whereas group 2 has to be created by pressing ˇ
and then d
or t
or n
.
So neither keypress
event can help us cover all the cases.
Playground
You can test it in this little playground, just press ¨
and then o
.
3. beforeinput
I have mentioned beforeinput
. Had this event been implemented correctly in browsers it would actually solve my problem.
keydown
and keypress
events receive an instance of KeyboardEvent
as a parameter and beforeinput
receives an instance of InputEvent
.
The difference here is that beforeinput
fires on every every character typed in the <input>
. So after pressing ˇ
and then d
you receive 2 events. The character typed is stored in event.data
.
After some testing (11/06/2018) when you call preventDefault()
on the InputEvent
it does not actually prevent putting the character into the <input>
(both Chrome and Firefox) even though it should.
W3C documentation reads the InputEvent
should be cancellable:
But in Chrome and Firefox you get cancellable set to false
.
Have a play with it here
Conclusion
At the end of the day I have used the solution with keypress
event because so far it seems to be the most usable and reliable one. Though to avoid sending form with invalid characters in <input>
I also have the pattern
attribute that is being consulted at <form>
submission and using a validation on the whole <input>
‘s value.
But still I would appreciate either working beforeinput
event in browsers or consistent behaviour for different special characters in keypress
event.