Created
January 11, 2016 20:13
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$(document).ready(function() { | |
new Masker(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment