Skip to content

Instantly share code, notes, and snippets.

@bowheart
Created January 11, 2016 20:13
Show Gist options
  • Save bowheart/92788f21500820045683 to your computer and use it in GitHub Desktop.
Save bowheart/92788f21500820045683 to your computer and use it in GitHub Desktop.
Some things just don't look good without a mask. This library enables easy input masking, turning ugly 5555555555 into beautiful (555) 555-5555. It also provides integrated regex-based input filtering as well as length limiting.
/**
* Another library by Joshua Claunch
* https://github.com/bowheart
* https://gist.github.com/bowheart
*
* A jQuery-dependent input masking/filtering/limiting library.
*
* class Masker
* Makes input fields more intelligent. Handles three data-* attributes on input elements:
* 1) data-filter -- A regex whitelist. Anything not in this whitelist is discarded.
* 2) data-length -- A more intelligent 'maxlength' attribute for inputs.
* 3) data-mask -- Takes a value such as "(___) ___-____" and puts user input into the underscores, dodging around the other characters.
*/
(function() {
var Masker = function(inputs) {
inputs || (inputs = $('input')); // if no inputs are specified, use all the inputs on the page.
new _Masker(inputs); // Data Hiding! Create the private version of the Masker object.
return this;
};
var acceptedInputTypes = ['text', 'search', 'tel', 'url', 'password'];
var _Masker = function(inputs) {
this.inputs = inputs.filter(function() { return acceptedInputTypes.indexOf($(this).prop('type')) > -1; });
this.maskInputs = inputs.filter(function() { return $(this).data('mask'); });
this._normalizeInputs();
this._bindHandlers();
};
_Masker.prototype = {
inputs: $(),
maskInputs: $(),
keyPressed: 0,
handleFilter: function(el) {
var pieces = this._getPieces(el),
filter = new RegExp('[^' + $(el).data('filter') + ']', 'g');
for (var i = 0; i < pieces.length; i++) {
pieces[i] = pieces[i].replace(filter, '');
}
$(el).val(pieces.join(''))[0].setSelectionRange(pieces[0].length, pieces[0].length);
},
handleLength: function(el) {
var pieces = this._getPieces(el),
length = $(el).data('length');
if ($(el).val().length > length) {
pieces[0] = pieces[0].slice(0, -($(el).val().length - length));
$(el).val(pieces.join(''))[0].setSelectionRange(pieces[0].length, pieces[0].length);
}
},
handleMask: function(el) {
var mask = $(el).data('mask'),
pieces = this._mask_limitLength(this._getPieces(el), mask);
switch (this.keyPressed) {
case 8:
this.handleMask_backspace(el, pieces, mask);
break;
case 46:
this.handleMask_delete(el, pieces, mask);
break;
default:
this.handleMask_input(el, pieces, mask);
break;
}
$(el).trigger('change');
},
handleInput: function(event) {
var el = event.currentTarget;
$(el).data('preModifiedVal', $(el).val());
if ($(el).data('filter')) {
this.handleFilter(el);
}
if ($(el).data('length')) {
this.handleLength(el);
} else if ($(el).data('mask')) {
this.handleMask(el);
}
$(el).data('prevVal', $(el).val());
$(el).trigger('change');
},
handleMask_init: function(el) {
var mask = $(el).data('mask');
if (!$(el).data('prevVal')) {
$(el).val(mask).data('prevVal', mask);
el.setSelectionRange(mask.indexOf('_'), mask.indexOf('_'));
return;
}
var pieces = this._getPieces(el);
var userInput = this._mask_getUserInput(el, mask);
if (userInput.length > this._lengthOf_(pieces[0])) return;
var pos = $(el).val().indexOf('_');
el.setSelectionRange(pos, pos);
},
handleMask_backspace: function(el, pieces, mask) {
var prevVal = $(el).data('prevVal').replace(/[^a-zA-Z0-9]/g, ''),
preModifiedVal = $(el).data('preModifiedVal').replace(/[^a-zA-Z0-9]/g, '');
if (preModifiedVal === prevVal) {
pieces[0] = pieces[0].slice(0, -1); // delete the next char for the user
}
var newPieces = this._populateMask(pieces, mask);
while (newPieces[0].length && /[a-zA-Z0-9]/.test(newPieces[0]) && /[^_a-zA-Z0-9]/.test(newPieces[0][newPieces[0].length - 1])) {
newPieces[1] = newPieces[0][newPieces[0].length - 1] + newPieces[1];
newPieces[0] = newPieces[0].slice(0, -1);
}
$(el).val(newPieces.join(''))[0].setSelectionRange(newPieces[0].length, newPieces[0].length);
},
handleMask_delete: function(el, pieces, mask) {
var prevVal = $(el).data('prevVal').replace(/[^a-zA-Z0-9]/g, ''),
preModifiedVal = $(el).data('preModifiedVal').replace(/[^a-zA-Z0-9]/g, '');
if (preModifiedVal === prevVal) {
pieces[1] = pieces[1].slice(1); // delete the next char for the user
}
this.handleMask_input(el, pieces, mask);
},
handleMask_input: function(el, pieces, mask) {
var newPieces = this._populateMask(pieces, mask);
$(el).val(newPieces.join(''))[0].setSelectionRange(newPieces[0].length, newPieces[0].length);
},
_mask_limitLength: function(pieces, mask) {
var piecesLength = pieces.join('').length,
maskLength = this._lengthOf_(mask);
if (piecesLength <= maskLength) return pieces;
return [pieces[0].slice(0, maskLength - piecesLength), pieces[1]];
},
_mask_getUserInput: function(el, pieces) {
var mask = $(el).data('mask'),
inputLength0 = this._lengthOf_(mask.slice(0, pieces[0].length)),
userInput = $(el).val().replace(/[^a-zA-Z0-9]/g, ''),
userInputPieces = [userInput.slice(0, inputLength0), userInput.slice(inputLength0)];
return this._mask_limitLength(userInputPieces, mask);
},
_populateMask: function(pieces, mask) {
var maskPieces = ['', ''],
currentPiece = 0;
pieces = [[].slice.call(pieces[0]), [].slice.call(pieces[1])];
for (var i = 0; i < mask.length; i++) {
if (!pieces[0].length) currentPiece = 1;
if (mask[i] === '_') {
maskPieces[currentPiece] += pieces[currentPiece].shift() || mask[i];
} else {
maskPieces[currentPiece] += mask[i];
}
}
while (maskPieces[1].length && /[^_a-zA-Z0-9]/.test(maskPieces[1][0])) {
maskPieces[0] += maskPieces[1][0];
maskPieces[1] = maskPieces[1].slice(1);
}
return maskPieces;
},
_lengthOf_: function(string) {
return string.replace(/[^_]/g, '').length;
},
_getPieces: function(el) {
return [
$(el).val().substr(0, el.selectionEnd),
$(el).val().substr(el.selectionEnd)
];
},
_bindHandlers: function() {
var masker = this;
masker.maskInputs.on('keydown', function(event) {
masker.keyPressed = event.which;
}).on('click focus', function() {
masker.handleMask_init(this);
});
masker.inputs.each(function(i, el) {
el.addEventListener('input', masker.handleInput.bind(masker));
});
},
_normalizeInputs: function() {
this.inputs.each(function(i, el) {
// normalize data-filter
if ($(el).attr('type') === 'tel' && !$(el).data('filter')) {
$(el).data('filter', '0-9'); // put a filter on input[type="tel"] elements
}
// normalize data-mask
if ($(el).data('mask')) {
var mask = $(el).data('mask');
mask = mask.replace(/[0-9a-zA-Z]/g, '_'); // replace alphanumeric characters in the mask with an underscore
$(el).data('mask', mask);
$(el).prop('placeholder', mask); // insert the mask as a placeholder
}
});
}
};
// other helper functions
var nthOccurrence = function(string, selector, n) {
var positions = [];
string.replace(new RegExp(selector, 'g'), function(match, pos) {
positions.push(pos);
});
return typeof positions[n - 1] !== 'undefined' ? positions[n - 1] : -1;
};
// expose the Masker object to the current scope (keep _Masker hidden)
this.Masker = Masker;
}).call(typeof module !== 'undefined' && module.exports ? module.exports : window);
$(document).ready(function() {
new Masker();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment