imaskjs

vanilla javascript input mask

Getting Started

Install from npm npm install imaskjs or download.

Include imask.js or imask.min.js from dist folder:

<script src="dist/imask.js"></script>

Simple use case:

unmasked:
var element = document.getElementById('selector');
var maskOptions = {
  mask: '+{7}(000)000-00-00'
};
var mask = new IMask(element, maskOptions);

Since v1.0.0 IMask consists of two independent layers: model and view.
Model layer contains all masking facilities which can be used independently without UI.

View layer is a glue between UI component and model, it connects listeners and controls changes in both directions.

Input processing is based on a simple idea of comparing states before and after change. State before change is obtained on keydown and on input actual processing takes place. In order to support older browsers manually call _saveSelection to save state and _onInput to handle changes. Pull requests for the beauty are welcomed.

Currently, view layer contains only one component InputMask to provide HTML input-like API. Instance of InputMask is returned when IMask constructor is called.

Basic use case:

var mask = new IMask(element, maskOptions);

Get/set value and unmasked value

mask.value = "+7(999)999-99-99";
console.log(mask.value);  // logs "+7(999)999-99-99"
console.log(mask.unmaskedValue);  // logs "79999999999"

mask.unmaskedValue = "70000000000";
console.log(mask.value);  // logs "+7(000)000-00-00"
console.log(mask.unmaskedValue);  // logs "70000000000"

Get/set mask

mask.mask = "000-000"; // auto-updates UI

Update options

mask.updateOptions({
  mask: Number,
  min: 0,
  max: 100
});  // also updates UI

Clean and destroy

mask.destroy();

Listen to events

// 'accept' event fired on input when mask value has changed
function log () {console.log(mask.value)};
mask.on("accept", log);

// 'complete' event fired when the value is completely filled
// This makes sense only for Pattern-based masks
mask.on("complete", function () {console.log(mask.value)});

Stop listening to events

mask.off("accept", log);

// omit handler argument to unlisten all
mask.off("complete");

Get masked model

var masked = mask.masked;
masked.reset(); // UI will NOT be updated

In the above example all changes are proxied to the model layer first and then UI is updated. The core of masking on model layer is IMask.Masked base class. There are also several other model classes for the different mask property types that provide additional functionality:

mask prop Model class
IMask.Masked descendant or instance IMask.Masked
RegExp instance IMask.MaskedRegExp
Function instance IMask.MaskedFunction
String instance IMask.MaskedPattern
Number IMask.MaskedNumber
Date IMask.MaskedDate

Common

IMask.Masked is a base class of all other *Masked. When you call IMask with mask options they are just directly passed to Masked model.

Example usage:

var digitsMask = new IMask(element, {
  mask: /^\d+$/
});

Get/set mask (only same type allowed!)

masked.mask = /^\w+$/;  // ok
masked.mask = "0000";  // ERROR! changing mask type on existing mask is not allowed!

Get/set value and unmasked value

masked.value = 'hello world!';
console.log(masked.unmaskedValue);

Use prepare (value, masked) option for preprocessing input and commit (value, masked) option for postprocessing after UI was deactivated:

var caseMask = new IMask(element, {
  mask: /^\w+$/,
  prepare: function (str) {
    return str.toUpperCase();
  },
  commit: function (value, masked) {
    // Don't change value manually! All changes should be done in mask!
    // But it works and could help to understand what is really changes
    masked._value = value.toLowerCase();  // Don't do it
  }
});

Usually you don't need to manually create instances of that type, because it will be done by IMask internally. But you can subclass from it to add some specific behavior.

Additionaly to mask option you can pass custom validator as validate (value, masked) option for some complex checks on any mask types excluding Function and RegExp, because they are already validators themselves. But don't change masked instance inside callbacks.

Also make sure that your mask or validator works with any of intermediate states, not just final value. For example you want to restrict input to "123" and do:

var masked = new IMask.Masked({
  mask: /^123$/
});

It will not allow to input any symbol at all, because it matches only whole string "123" and not "1" nor "12". Always think about intermediate values at first, otherwise it might not work as expected. In complex cases it is better to use Pattern or Function masks.

Example of using Function mask to accept any growing sequence from 0 to 9:

var sequenceMask = new IMask(element, {
  mask: function (value) {
    return /^\d*$/.test(value) &&
      value.split('').every(function(ch, i) {
        var prevCh = value[i-1];
        return !prevCh || prevCh < ch;
      });
  }
});

Pattern

Use pattern when:

Pattern mask is just a string:

unmasked:
Options
Values
 or 
var patternMask = new IMask(element, {
  mask: '{#}000[aaa]/NIC-`*[**]'
});

// or without UI
var masked = new IMask.PatternMasked({
  mask: '{#}000[aaa]/NIC-`*[**]'
});
where:

If definition character should be treated as fixed it should be escaped by \\ (E.g. \\0).

Additionally you could provide custom definitions:

unmasked:
var zipMask = new IMask(element, {
  mask: '#00000',
  definitions: {
    // <any single char>: <same type as mask (RegExp, Function, etc.)>
    // defaults are '0', 'a', '*'
    '#': /[1-6]/
  }
});

To configure placeholder use placeholder option:

unmasked:
var phoneMask = new IMask(element, {
  mask: '+{7}(000)000-00-00',
  placeholder: {
    lazy: false,  // make placeholder always visible
    char: '#'  // defaults to '_'
  }
});

One more custom option for pattern is groups:

unmasked:
var groupsMask = new IMask(element, {
  mask: 'Ple\\ase fill ye\\ar 19YY, month MM \\and v\\alue VL',
  placeholder: {lazy: false},  // make placeholder always visible

  // let define groups
  groups: {
    // custom group definition
    YY: {
      mask: '00',
      // optional validator
      // validate: function (value, group) {}
    },

    // use range to restrict input to numbers in range
    // mask size is the length in chars of max bound or could be provided as second parameter
    // to input smaller values pad zeros at beginning
    MM: new IMask.MaskedPattern.Group.Range([1, 12], /* optional size */),

    // restrict input to enum
    // all values should be same length
    VL: new IMask.MaskedPattern.Group.Enum(['TV', 'HD', 'VR'])
  }
});

Number

Number mask restricts input to integer or decimal numbers.

number:
Options
var numberMask = new IMask(element, {
  mask: Number,  // enable number mask

  // other options are optional with defaults
  scale: 2,  // digits after point, 0 for integers
  signed: false,  // disallow negative
  thousandsSeparator: '',  // could be any single char
  postFormat: {
    padFractionalZeros: false,  // if true, then pads zeros at end to the length of scale
    normalizeZeros: true  // appends or removes zeros at ends
  },
  radix: ',',  // fractional delimiter
  mapToRadix: ['.']  // symbols to process as radix

  // number interval options additionally could be set (e.g.)
  min: -10000,
  max: 10000
});

Date

Date mask extends Pattern mask with more options.

date:
var dateMask = new IMask(element, {
  mask: Date,  // enable date mask

  // other options are optional
  pattern: 'Y-`m-`d',  // Pattern mask with defined groups, default is 'd{.}`m{.}`Y'
  // you could provide your own groups definitions, default groups for date mask are:
  groups: {
    d: new IMask.MaskedPattern.Group.Range([1, 31]),
    m: new IMask.MaskedPattern.Group.Range([1, 12]),
    Y: new IMask.MaskedPattern.Group.Range([1900, 9999])
  },
  // define date -> str convertion
  format: function (date) {
    var day = date.getDate();
    var month = date.getMonth() + 1;
    var year = date.getFullYear();

    if (day < 10) day = "0" + day;
    if (month < 10) month = "0" + month;

    return [year, month, day].join('-');
  },
  // define str -> date convertion
  parse: function (str) {
    var yearMonthDay = str.split('-');
    return new Date(yearMonthDay[0], yearMonthDay[1] - 1, yearMonthDay[2]);
  },

  // optional interval options
  min: new Date(2000, 0, 1),  // defaults to 1900-01-01
  max: new Date(2020, 0, 1),  // defaults to 9999-01-01

  // also Pattern options could be set
  placeholder: {lazy: false}
});

Easy integration with Moment.js:

date:
var momentFormat = 'YYYY/MM/DD HH:mm';
var momentMask = new IMask(element, {
  mask: Date,
  pattern: momentFormat,
  placeholder: {lazy: false},
  min: new Date(1970, 0, 1),
  max: new Date(2030, 0, 1),

  format: function (date) {
    return moment(date).format(momentFormat);
  },
  parse: function (str) {
    return moment(str, momentFormat);
  },

  groups: {
    YYYY: new IMask.MaskedPattern.Group.Range([1970, 2030]),
    MM: new IMask.MaskedPattern.Group.Range([1, 12]),
    DD: new IMask.MaskedPattern.Group.Range([1, 31]),
    HH: new IMask.MaskedPattern.Group.Range([0, 23]),
    mm: new IMask.MaskedPattern.Group.Range([0, 59])
  }
});