Customized Input Masking Using Plain Javascript

Jo IE
5 min readAug 25, 2019
Masking credit card numbers

Say you were building an app in which you wanted to hide sensitive information that is being typed by a user in the UI like a social security number or a password. Easy. HTML gives us a straightforward way to do this.

<input type=”password” />

What if, however, you wanted to customize the way the input is masked, for example, changing the characters used to mask the input or the amount of time for which the input is displayed in the UI? There are, unsurprisingly, other options such as using plain Javascript. In this article, I will go over how to mask the digits of a credit card number as a user enters them into a form input field.

Here is the HTML for the form. Since this is a simple example, I am including the styles as well as the Javascript in the same file.

HTML and CSS for the form

This will produce something like this:

Input Form

For the Javascript, we want to ensure that as the user types, we replace the real credit card numbers with a sign or symbol (eg, #, %, $) but in a real world app, we would also need access to the actual credit card number when the user submits the form so we could validate it. So how can we mask the input in the UI yet have access to it in our application? We could use a global state variable in our application where we store the real credit card number as the user types.

const state = {};

Since the credit card has four input fields, and 16 digits in total, we could store the numbers in a two-dimensional array with four slots.

state.cardDigits = [[], [],[],[]];

Each slot will house the credit card numbers for a given field. For example, the fifth digit in the credit card number will belong in the first position of the second array in the state.cardDigits array (state.cardDigits[1][0]),

Now, we are ready to make our function to mask user inputs. This function will be later passed into an event handler that listens for keydown events because we want to respond as the user types. Let’s create that function.

const maskInput = () => {}

Next, we create another function in which we set a keydown event listener for all four input fields in our form.

Enable Masking Function

First, we get an array containing our input elements using the query selector API. This is very straightforward as the four input fields we need are the only ones in our HTML. Notice we have used the spread operator (ES6) to convert what was originally a node list to an array. The node list can also be converted to array using other methods outlined here. Next, we used the Array.forEach method to set an event listener for each of the four input fields in our documents, passing the event (key down event) and the index (index of the input field in our array) to the maskInput function which we are calling for each keydown event in each of those input fields.

Our entire application should now look like this:

Application so far

There are three main key actions a user will be performing when they are trying to make an input into the credit card input fields. These are navigation (for example, when the user is tabbing through the fields), deletion (when a user has entered a wrong value and wants to clear it out) and insertion (when a user enters digits into the field).

Since we need to save the actual credit card digits to the state.cardDigits array, we need to keep track of which digits the user deletes and which ones the user enters and so this will be implemented in the maskInput function.

If a user hits a key that is not a number key, a deletion key or a navigation key, we are not interested in any information from that event, so we could prevent the entry of that field and make the implementation of the function easier:

Prevent Default action for Non-numeric and Non-navigation Keys

Our keydown event has a key property which stores a read-only identification of the key that produced the event. Read more about them here

Handling Deletion of Digits

If the user performs a delete action (hit the backspace or delete key), we want to first check which digit was deleted. The selectionStart variable returns the index of the first selected character in the keydown event. On insertion of digits, this value will correspond to the index of the digit in the input field. For example, the keydown event for the third digit in the input field will have a selectionStart value of two.

On deletion of a digit, the selectionStart value is not going to correspond with the index of the value that the user deletes. For example, if a user types up to the third digit in an input field, when the user hits the key for the fourth digit, the selectionStart value will be three. In the same way, what if the user hits delete or backspace instead of the fourth digit? The selectionStart value will still be three. Now the user wants to delete the third digit (digit at index two). Therefore, we have to subtract one from selectionStart to get the appropriate index position of the digit. We then splice that digit out of the state.cardDigits array.

Next, we set the value of the input field. Using a map function, we replace the digits we now have (after the deletion) in the corresponding state.cardDigits array with a “#’ character and join them into a string. This will produce a string of ‘#’ characters.

Next we handle insertion of digits:

Handling Insertion of Digits

Finally, if the typed key is a valid number key (insertion action), we want to render that digit to the UI as well as save it into our state.cardDigits array. For example if we just typed ‘5’ in the second position of the second input field, state.cardDigits[1][1] = event.key = 5. Remember selectionStart gives us the index of the cursor during the event so in this case, it would be at index 1.

We are using a setTimeout function because we want the digit to display for a very short period of time (half a second in this case) before it is masked. In this setTimeout function, we are using the String.prototype.replace method to replace the newly typed digit with a ‘#’ character after half a second. Note we are only implementing the setTimeout function on the first 3 input fields. Therefore, the last four digits of the credit card will be displayed in the UI.

The entire code should now look like this:

Final HTML File

I performed input masking using plain Javascript as part of a challenge that was given during the course of phase one of the Google Certification Scholarship. My full app can be found here. It looks something like this (with added styling):

Fully Functional App

Thank you for reading!

--

--