JavaScript Events Unmasked: How to Create an Input Mask for Mobile

Glauber Corrêa
Aug 8, 2017 · 10 min read

Imagine this: there you are, minding your own business, coding your time away. And then someone walks in and asks you to build an input mask for mobile applications using pure JavaScript (no external libraries!).

And you’re thinking that building an input mask is not that hard. All you need to do is create a bunch of functions to validate the key using keyboard events and cancel the event when the key is not allowed. How hard can it be?

Famous last words.

It took me about 10 seconds to remember that keyboard events work differently for different devices. Turns out, finding a solution to make an input mask work in Android, iOS and Google Chrome would not be that easy. So yeah, my confidence was short-lived.

Introducing Keyboard Events

  • Pressing down a key triggers the keydown event.
  • Pressing down a printable key triggers the keypress event.
  • Releasing a key triggers the keyup event.

Whenever a keyboard interaction happens, the event will provide an object with the information about the interaction itself. For example, what key was pressed and the physical location on the keyboard. Here are some of the properties that can be used to identify the pressed key:

  • keyCode returns a numeric value associated with a particular pressed key no matter if this key is in lowercase or uppercase.
  • charCode indicates the ASCII value of the character associated with the pressed key. This property distinguishes between lowercase and uppercase.
  • key returns the value of the pressed key. If you press the lowercase a key this property will return the letter a, instead of a code representation of the key.
  • which returns the numeric keyCode of the key pressed or the charCode for an alphanumeric key.
  • keyIdentifier returns a string representation of the pressed key.
  • code represents a physical key on the keyboard. This property returns a value that isn’t altered by the keyboard layout or the state of the modifier keys.

Introducing (Paranoid) Android Problems

The thing is Android keyboard events are not that simple. When I added the keypress event and printed the keyCode property, I had a little surprise: there was no keypress in Android.

With keypress, the uppercase A resulted in the respective keyCode in the following browsers:

I quickly found out why Chrome Mobile wasn’t triggering the event: this event is marked as Legacy in the DOM-Level-3 Standard. Okay, so this is not the end of the world. I can still use one of the two events left: keydown or keyup.

I tested both events. Pressing the uppercase A resulted in the following keyCodes:

OK, the event was fired, but what was that 229 code? Where did it come from? Well, after some searching, I learned that the auto-suggest feature or other event might follow the keydown event and invalidate it. Some devices can return the code 0, Unidentified or even empty, but the reason is the same.

And, to make things more interesting, disabling the auto-suggest feature does not solve the problem.

To help me decide which property I should use, I went into testing mode and created the following table. I know some of the following properties are deprecated but what can I tell you? I was bitten by the testing bug!

I tested those events by myself in my devices, but, if you need more information about the properties, versions, and compatibility you can check out the MDN website.

As you can see, no property works on Chrome Mobile, but hey, don’t panic yet! I have a workaround. Yes, after a great deal of searching and testing, I found a good solution to obtain the key that was pressed in both iOS and Android. But first, let’s talk about one more event and the order all these events are triggered. Bear with me.

Introducing the Input Event

Event Order

keydown > keypress > input > keyup

Note that triggering the keydown event changes the value, raising the input event. Only after the key is released is the keyup event fired. Remember that the keypress event may not trigger in some devices.

This is How You Handle Android keyCode 229

Here’s how to find the pressed key. When the keydown event is triggered, the input value doesn’t change and so you should store it then. However, if the event isn’t canceled, the input value will change. This triggers the input event and creates a new value. So if you had stored the input value before the change, you could then compare it with the new one after the change.

For example, if you have an input with an 11 value, and you press the A key, the new value will be 11A. Compare both values and you will get the A.

If you really really really need to use the charCode, you can still get the character that is the difference between oldValue and newValue and use the charCodeAt function.

These Are the Requirements for the Input Mask

The credit card mask was one required for my project. This mask is the simplest one to explain, so it’s the one I’ll be talking about from now on.

So let’s analyze its requirements:

  • Allow only 16 digits
  • Add the separator “ “ (space) for every group of 4 digits
  • Should work with copy-paste values
  • If the pasted value has unallowed keys, the mask should remove those keys and mask only the digit part that remains

Looking through the requirements, note that the input mask should treat the complete value whenever it changes instead of the last inserted key. Wait a minute! So, did we work on getting the typed value for nothing? No! We are not going to use the difference between the old value and the new one to validate keys, but we are going to use the old value anyway. Why? Keep reading!

Let’s do what we already know: Add both onkeydown and oninput event listeners to the input and store the old and the new value.

Credit Card Input Mask: Let’s Get Coding!

But why do we have to unmask the value and then mask it again?

Well, imagine the following scenario: an input has 1111 2222 3333 as a value, and then the user selects the entire value and replaces it with a new one, for example: 444455 55. This new value is masked regardless of its structure. It will always adapt to the mask, clearing all characters that are not digits.

The Mask Function

Here’s what the function looks like:

Example: If the new value is 11112, the calculation will be:

According to the table above, the separator will be added to the first position. But, this is not what I want for my mask, so I had to exclude the index 0 from the modulus calculation.

The Unmask Function

This is what the unmask function looks like:

Character Limit

Your code should look like this:

If you test the mask you will see that it works.

Cursor Position

Let’s talk about the two input properties:

  • selectionStart: Get the start of the selected portion of the field’s text.
  • selectionEnd: Get the end of the selected portion of the field’s text.

You may be asking yourself why we are using selection to get the cursor position. If you don’t have any text selected, the starting point of a selection will be the cursor position, and the ending point will be the cursor position as well.

So here’s what you do. Create a new variable and call it oldCursor.

Inside the keydown event, store the cursor position using selectionEnd before the insertion of the new value. Now you have both oldValue and oldCursor containing information about the value and the cursor position, respectively, before they change.

Create a new function that will check how many separators the string has until it reaches the cursor position. In this example I called it checkSeparator.

This function will return the rounded value of the position/interval (4) + 1, where the +1 is the compensation of the separator. In other words, return +1 for every separator before the position.

I built a formula to set a new cursor position based on the current cursor position, the oldValue and the newValue.

Now that you already have the new cursor position, you can apply it to the input using the
setSelectionRange(start, end) method. It sets a selection between the start and end parameters. If the start and end values are the same, there will be no interval between the selection.

Keyboard Types in Mobile Applications

Was I right? Nope, I was wrong! The number type input does not allow our separators and it automatically runs validations that we don’t want. So, how did I solve this? Well I went for a solution that I was trying to avoid because it’s not so elegant. I tried “tel” type.

By using the “tel” type, the keyboard is the numerical one, and the input allows special characters such as our separator. And, there won’t be any validations on the input value.

Yes, it worked! And best of all, it works on Android and iOS devices.

See it working beautifully here:

The Output of the Input Mask

My input mask adventure was a long, stressful and painful process of trying, failing, researching and doing it all over again. Having learned there is no simple way to reach some of the properties of keyboard events, I can tell you I now have such respect for them. They require your fullest attention, regardless of why you need them. So I have to admit, now whenever someone asks me something related to keyboard events, I take it very seriously.

Here’s the full code of this little input mask project by the way, so you can see how serious I am:

References and Further Reading

OutSystems Experts

Digital transformation with a low-code platform…