Input Mask Algorithm
Tags: javascript, computerscience, webdev, beginners
Published: 2022-07-10 20:32:22


---
title: Input Mask Algorithm
published: true
description: Solving how to implement an input mask algorithm in javascript
tags: javascript,computerscience,webdev,beginners
cover_image: https://images.unsplash.com/photo-1586942593822-268ff3828458?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=4140&q=80

---
## What's an Input Mask? 
An [input mask](https://en.wikipedia.org/wiki/Input_mask) is a template used to constrain a user's input. It can be used to create
nicer form experiences by formatting their input as the user types:

![demo](https://raw.githubusercontent.com/calebdre/playland/master/mask%20demo.gif)

## Algorithm
Here are the pieces of information we need:
- `input`: the input from the user
- `template`: the format we want the user's input to be in. (Almost, see `targetChar`) any character can 
be used to represent phone numbers, dates, etc.  
- `targetChar`: the character in `template` that we'll replace with the user's input as they type

To solve, we'll use a straightforward approach: iterate through the `input` and `template` strings, replacing 
`targetChar` until one of them finishes first.  
  
Let's create our masking function:
```typescript
function mask(input, template, targetChar) {
    const output = [] // final masked result
    let templateIndex = 0 // template pointer
    let inputIndex = 0 // input pointer
}
```

We'll need different index pointers for `input` and `template` because their lengths and
the `targetChar` locations are arbitrary, so their indexes will need to be aligned manually
as we iterate through the strings.

We want to keep track of our offsets so that we can easily manipulate both offsets independently.
On a given iteration, we don't want to increment the offset if we didn't replace a character from
the mask (when `mask[maskIndex] !== maskChar`).

According to our approach, our stopping condition is when we've reached the end of either
`input` or `template` strings: 
```typescript
while(
    inputIndex <= input.length &&
    templateIndex <= template.length
) {
    // ...
}   
``` 

At each iteration, we'll check whether the character at `templateIndex` is
`targetChar` to decide whether we choose to add to the input from `template`
or from `input`:
 
```typescript
if (template[templateIndex] === targetChar) {
    output.push(input[inputIndex])
} else {
    output.push(template[templateIndex])
}
```

Next, we need to align the indexes.   
  
Normally, we'd always want to go to
the next character at the end of the iteration, however we don't want to 
move on if a character isn't *used*. A character from the template is always added to the output (`input` is technically part of the `template`) 
, but the input character only gets *used* when a `targetChar` is reached.

Therefore, we'll only increment `inputIndex` if we find `targetChar`
and increment `templateIndex` at every iteration:

```typescript
if (template[templateIndex] === targetChar) {
    output.push(input[inputIndex])
    inputIndex++ // increment input
} else {
    output.push(template[templateIndex])
}

templateIndex++ // increment template
```

Finally, we return the output as a string:
```typescript
return masked.join('')
```

When using a mask, it's often beneficial to use **two** inputs: 
- a visible one using the masked value to show to the user
- an invisible one that holds the raw value that the user never sees

The raw value can be retrieved by removing any character that isn't `targetChar`
from the template: 
```typescript
// targetChar: '#', template: '###-###-####'
const rawInput = maskedInput.replaceAll('-', '')
```
  
Here's the whole function:
```typescript
function mask(input, template, targetChar) { 
    input = input.replaceAll('-', '')

    const output = []
    let templateIndex = 0
    let inputIndex = 0

    while(inputIndex <= input.length && templateIndex <= template.length) {
        if (template[templateIndex] === targetChar) {
            output.push(input[inputIndex])
            inputIndex += 1
        } else {
            output.push(template[templateIndex])
        }

        templateIndex += 1
    }
    
    return masked.join('')
}
```

## Closing thoughts
It's fun to think about how extensible this can be; there can be a [polymorphic overload](https://en.wikipedia.org/wiki/Polymorphism_(computer_science)#Parametric_polymorphism) 
that accepts a function as a template to provide dynamic masks, or this would even be
great as a sort of input middleware such that it's just one transformation in a set of
other operations since it's [atomic](https://javascript.plainenglish.io/monads-for-javascript-developers-af29819823c).

I ran into this problem while working on a bigger article on implementing phone login with Firebase and 
thought I'd share this fun little exercise. Stay tuned for the other article!   
[Follow me on Twitter](https://twitter.com/caleb_dre)

bonus: first person to prove they shipped this copy+pasted code gets $5 👀