Spaces:
Running
Running
/** | |
* jquery.mask.js | |
* @version: v1.14.15 | |
* @author: Igor Escobar | |
* | |
* Created by Igor Escobar on 2012-03-10. Please report any bug at github.com/igorescobar/jQuery-Mask-Plugin | |
* | |
* Copyright (c) 2012 Igor Escobar http://igorescobar.com | |
* | |
* The MIT License (http://www.opensource.org/licenses/mit-license.php) | |
* | |
* Permission is hereby granted, free of charge, to any person | |
* obtaining a copy of this software and associated documentation | |
* files (the "Software"), to deal in the Software without | |
* restriction, including without limitation the rights to use, | |
* copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the | |
* Software is furnished to do so, subject to the following | |
* conditions: | |
* | |
* The above copyright notice and this permission notice shall be | |
* included in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
* OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* jshint laxbreak: true */ | |
/* jshint maxcomplexity:17 */ | |
/* global define */ | |
// UMD (Universal Module Definition) patterns for JavaScript modules that work everywhere. | |
// https://github.com/umdjs/umd/blob/master/templates/jqueryPlugin.js | |
(function (factory, jQuery, Zepto) { | |
if (typeof define === 'function' && define.amd) { | |
define(['jquery'], factory); | |
} else if (typeof exports === 'object') { | |
module.exports = factory(require('jquery')); | |
} else { | |
factory(jQuery || Zepto); | |
} | |
}(function ($) { | |
'use strict'; | |
var Mask = function (el, mask, options) { | |
var p = { | |
invalid: [], | |
getCaret: function () { | |
try { | |
var sel, | |
pos = 0, | |
ctrl = el.get(0), | |
dSel = document.selection, | |
cSelStart = ctrl.selectionStart; | |
// IE Support | |
if (dSel && navigator.appVersion.indexOf('MSIE 10') === -1) { | |
sel = dSel.createRange(); | |
sel.moveStart('character', -p.val().length); | |
pos = sel.text.length; | |
} | |
// Firefox support | |
else if (cSelStart || cSelStart === '0') { | |
pos = cSelStart; | |
} | |
return pos; | |
} catch (e) {} | |
}, | |
setCaret: function(pos) { | |
try { | |
if (el.is(':focus')) { | |
var range, ctrl = el.get(0); | |
// Firefox, WebKit, etc.. | |
if (ctrl.setSelectionRange) { | |
ctrl.setSelectionRange(pos, pos); | |
} else { // IE | |
range = ctrl.createTextRange(); | |
range.collapse(true); | |
range.moveEnd('character', pos); | |
range.moveStart('character', pos); | |
range.select(); | |
} | |
} | |
} catch (e) {} | |
}, | |
events: function() { | |
el | |
.on('keydown.mask', function(e) { | |
el.data('mask-keycode', e.keyCode || e.which); | |
el.data('mask-previus-value', el.val()); | |
el.data('mask-previus-caret-pos', p.getCaret()); | |
p.maskDigitPosMapOld = p.maskDigitPosMap; | |
}) | |
.on($.jMaskGlobals.useInput ? 'input.mask' : 'keyup.mask', p.behaviour) | |
.on('paste.mask drop.mask', function() { | |
setTimeout(function() { | |
el.keydown().keyup(); | |
}, 100); | |
}) | |
.on('change.mask', function(){ | |
el.data('changed', true); | |
}) | |
.on('blur.mask', function(){ | |
if (oldValue !== p.val() && !el.data('changed')) { | |
el.trigger('change'); | |
} | |
el.data('changed', false); | |
}) | |
// it's very important that this callback remains in this position | |
// otherwhise oldValue it's going to work buggy | |
.on('blur.mask', function() { | |
oldValue = p.val(); | |
}) | |
// select all text on focus | |
.on('focus.mask', function (e) { | |
if (options.selectOnFocus === true) { | |
$(e.target).select(); | |
} | |
}) | |
// clear the value if it not complete the mask | |
.on('focusout.mask', function() { | |
if (options.clearIfNotMatch && !regexMask.test(p.val())) { | |
p.val(''); | |
} | |
}); | |
}, | |
getRegexMask: function() { | |
var maskChunks = [], translation, pattern, optional, recursive, oRecursive, r; | |
for (var i = 0; i < mask.length; i++) { | |
translation = jMask.translation[mask.charAt(i)]; | |
if (translation) { | |
pattern = translation.pattern.toString().replace(/.{1}$|^.{1}/g, ''); | |
optional = translation.optional; | |
recursive = translation.recursive; | |
if (recursive) { | |
maskChunks.push(mask.charAt(i)); | |
oRecursive = {digit: mask.charAt(i), pattern: pattern}; | |
} else { | |
maskChunks.push(!optional && !recursive ? pattern : (pattern + '?')); | |
} | |
} else { | |
maskChunks.push(mask.charAt(i).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')); | |
} | |
} | |
r = maskChunks.join(''); | |
if (oRecursive) { | |
r = r.replace(new RegExp('(' + oRecursive.digit + '(.*' + oRecursive.digit + ')?)'), '($1)?') | |
.replace(new RegExp(oRecursive.digit, 'g'), oRecursive.pattern); | |
} | |
return new RegExp(r); | |
}, | |
destroyEvents: function() { | |
el.off(['input', 'keydown', 'keyup', 'paste', 'drop', 'blur', 'focusout', ''].join('.mask ')); | |
}, | |
val: function(v) { | |
var isInput = el.is('input'), | |
method = isInput ? 'val' : 'text', | |
r; | |
if (arguments.length > 0) { | |
if (el[method]() !== v) { | |
el[method](v); | |
} | |
r = el; | |
} else { | |
r = el[method](); | |
} | |
return r; | |
}, | |
calculateCaretPosition: function() { | |
var oldVal = el.data('mask-previus-value') || '', | |
newVal = p.getMasked(), | |
caretPosNew = p.getCaret(); | |
if (oldVal !== newVal) { | |
var caretPosOld = el.data('mask-previus-caret-pos') || 0, | |
newValL = newVal.length, | |
oldValL = oldVal.length, | |
maskDigitsBeforeCaret = 0, | |
maskDigitsAfterCaret = 0, | |
maskDigitsBeforeCaretAll = 0, | |
maskDigitsBeforeCaretAllOld = 0, | |
i = 0; | |
for (i = caretPosNew; i < newValL; i++) { | |
if (!p.maskDigitPosMap[i]) { | |
break; | |
} | |
maskDigitsAfterCaret++; | |
} | |
for (i = caretPosNew - 1; i >= 0; i--) { | |
if (!p.maskDigitPosMap[i]) { | |
break; | |
} | |
maskDigitsBeforeCaret++; | |
} | |
for (i = caretPosNew - 1; i >= 0; i--) { | |
if (p.maskDigitPosMap[i]) { | |
maskDigitsBeforeCaretAll++; | |
} | |
} | |
for (i = caretPosOld - 1; i >= 0; i--) { | |
if (p.maskDigitPosMapOld[i]) { | |
maskDigitsBeforeCaretAllOld++; | |
} | |
} | |
// if the cursor is at the end keep it there | |
if (caretPosNew > oldValL) { | |
caretPosNew = newValL * 10; | |
} else if (caretPosOld >= caretPosNew && caretPosOld !== oldValL) { | |
if (!p.maskDigitPosMapOld[caretPosNew]) { | |
var caretPos = caretPosNew; | |
caretPosNew -= maskDigitsBeforeCaretAllOld - maskDigitsBeforeCaretAll; | |
caretPosNew -= maskDigitsBeforeCaret; | |
if (p.maskDigitPosMap[caretPosNew]) { | |
caretPosNew = caretPos; | |
} | |
} | |
} | |
else if (caretPosNew > caretPosOld) { | |
caretPosNew += maskDigitsBeforeCaretAll - maskDigitsBeforeCaretAllOld; | |
caretPosNew += maskDigitsAfterCaret; | |
} | |
} | |
return caretPosNew; | |
}, | |
behaviour: function(e) { | |
e = e || window.event; | |
p.invalid = []; | |
var keyCode = el.data('mask-keycode'); | |
if ($.inArray(keyCode, jMask.byPassKeys) === -1) { | |
var newVal = p.getMasked(), | |
caretPos = p.getCaret(); | |
// this is a compensation to devices/browsers that don't compensate | |
// caret positioning the right way | |
setTimeout(function() { | |
p.setCaret(p.calculateCaretPosition()); | |
}, $.jMaskGlobals.keyStrokeCompensation); | |
p.val(newVal); | |
p.setCaret(caretPos); | |
return p.callbacks(e); | |
} | |
}, | |
getMasked: function(skipMaskChars, val) { | |
var buf = [], | |
value = val === undefined ? p.val() : val + '', | |
m = 0, maskLen = mask.length, | |
v = 0, valLen = value.length, | |
offset = 1, addMethod = 'push', | |
resetPos = -1, | |
maskDigitCount = 0, | |
maskDigitPosArr = [], | |
lastMaskChar, | |
check; | |
if (options.reverse) { | |
addMethod = 'unshift'; | |
offset = -1; | |
lastMaskChar = 0; | |
m = maskLen - 1; | |
v = valLen - 1; | |
check = function () { | |
return m > -1 && v > -1; | |
}; | |
} else { | |
lastMaskChar = maskLen - 1; | |
check = function () { | |
return m < maskLen && v < valLen; | |
}; | |
} | |
var lastUntranslatedMaskChar; | |
while (check()) { | |
var maskDigit = mask.charAt(m), | |
valDigit = value.charAt(v), | |
translation = jMask.translation[maskDigit]; | |
if (translation) { | |
if (valDigit.match(translation.pattern)) { | |
buf[addMethod](valDigit); | |
if (translation.recursive) { | |
if (resetPos === -1) { | |
resetPos = m; | |
} else if (m === lastMaskChar && m !== resetPos) { | |
m = resetPos - offset; | |
} | |
if (lastMaskChar === resetPos) { | |
m -= offset; | |
} | |
} | |
m += offset; | |
} else if (valDigit === lastUntranslatedMaskChar) { | |
// matched the last untranslated (raw) mask character that we encountered | |
// likely an insert offset the mask character from the last entry; fall | |
// through and only increment v | |
maskDigitCount--; | |
lastUntranslatedMaskChar = undefined; | |
} else if (translation.optional) { | |
m += offset; | |
v -= offset; | |
} else if (translation.fallback) { | |
buf[addMethod](translation.fallback); | |
m += offset; | |
v -= offset; | |
} else { | |
p.invalid.push({p: v, v: valDigit, e: translation.pattern}); | |
} | |
v += offset; | |
} else { | |
if (!skipMaskChars) { | |
buf[addMethod](maskDigit); | |
} | |
if (valDigit === maskDigit) { | |
maskDigitPosArr.push(v); | |
v += offset; | |
} else { | |
lastUntranslatedMaskChar = maskDigit; | |
maskDigitPosArr.push(v + maskDigitCount); | |
maskDigitCount++; | |
} | |
m += offset; | |
} | |
} | |
var lastMaskCharDigit = mask.charAt(lastMaskChar); | |
if (maskLen === valLen + 1 && !jMask.translation[lastMaskCharDigit]) { | |
buf.push(lastMaskCharDigit); | |
} | |
var newVal = buf.join(''); | |
p.mapMaskdigitPositions(newVal, maskDigitPosArr, valLen); | |
return newVal; | |
}, | |
mapMaskdigitPositions: function(newVal, maskDigitPosArr, valLen) { | |
var maskDiff = options.reverse ? newVal.length - valLen : 0; | |
p.maskDigitPosMap = {}; | |
for (var i = 0; i < maskDigitPosArr.length; i++) { | |
p.maskDigitPosMap[maskDigitPosArr[i] + maskDiff] = 1; | |
} | |
}, | |
callbacks: function (e) { | |
var val = p.val(), | |
changed = val !== oldValue, | |
defaultArgs = [val, e, el, options], | |
callback = function(name, criteria, args) { | |
if (typeof options[name] === 'function' && criteria) { | |
options[name].apply(this, args); | |
} | |
}; | |
callback('onChange', changed === true, defaultArgs); | |
callback('onKeyPress', changed === true, defaultArgs); | |
callback('onComplete', val.length === mask.length, defaultArgs); | |
callback('onInvalid', p.invalid.length > 0, [val, e, el, p.invalid, options]); | |
} | |
}; | |
el = $(el); | |
var jMask = this, oldValue = p.val(), regexMask; | |
mask = typeof mask === 'function' ? mask(p.val(), undefined, el, options) : mask; | |
// public methods | |
jMask.mask = mask; | |
jMask.options = options; | |
jMask.remove = function() { | |
var caret = p.getCaret(); | |
if (jMask.options.placeholder) { | |
el.removeAttr('placeholder'); | |
} | |
if (el.data('mask-maxlength')) { | |
el.removeAttr('maxlength'); | |
} | |
p.destroyEvents(); | |
p.val(jMask.getCleanVal()); | |
p.setCaret(caret); | |
return el; | |
}; | |
// get value without mask | |
jMask.getCleanVal = function() { | |
return p.getMasked(true); | |
}; | |
// get masked value without the value being in the input or element | |
jMask.getMaskedVal = function(val) { | |
return p.getMasked(false, val); | |
}; | |
jMask.init = function(onlyMask) { | |
onlyMask = onlyMask || false; | |
options = options || {}; | |
jMask.clearIfNotMatch = $.jMaskGlobals.clearIfNotMatch; | |
jMask.byPassKeys = $.jMaskGlobals.byPassKeys; | |
jMask.translation = $.extend({}, $.jMaskGlobals.translation, options.translation); | |
jMask = $.extend(true, {}, jMask, options); | |
regexMask = p.getRegexMask(); | |
if (onlyMask) { | |
p.events(); | |
p.val(p.getMasked()); | |
} else { | |
if (options.placeholder) { | |
el.attr('placeholder' , options.placeholder); | |
} | |
// this is necessary, otherwise if the user submit the form | |
// and then press the "back" button, the autocomplete will erase | |
// the data. Works fine on IE9+, FF, Opera, Safari. | |
if (el.data('mask')) { | |
el.attr('autocomplete', 'off'); | |
} | |
// detect if is necessary let the user type freely. | |
// for is a lot faster than forEach. | |
for (var i = 0, maxlength = true; i < mask.length; i++) { | |
var translation = jMask.translation[mask.charAt(i)]; | |
if (translation && translation.recursive) { | |
maxlength = false; | |
break; | |
} | |
} | |
if (maxlength) { | |
el.attr('maxlength', mask.length).data('mask-maxlength', true); | |
} | |
p.destroyEvents(); | |
p.events(); | |
var caret = p.getCaret(); | |
p.val(p.getMasked()); | |
p.setCaret(caret); | |
} | |
}; | |
jMask.init(!el.is('input')); | |
}; | |
$.maskWatchers = {}; | |
var HTMLAttributes = function () { | |
var input = $(this), | |
options = {}, | |
prefix = 'data-mask-', | |
mask = input.attr('data-mask'); | |
if (input.attr(prefix + 'reverse')) { | |
options.reverse = true; | |
} | |
if (input.attr(prefix + 'clearifnotmatch')) { | |
options.clearIfNotMatch = true; | |
} | |
if (input.attr(prefix + 'selectonfocus') === 'true') { | |
options.selectOnFocus = true; | |
} | |
if (notSameMaskObject(input, mask, options)) { | |
return input.data('mask', new Mask(this, mask, options)); | |
} | |
}, | |
notSameMaskObject = function(field, mask, options) { | |
options = options || {}; | |
var maskObject = $(field).data('mask'), | |
stringify = JSON.stringify, | |
value = $(field).val() || $(field).text(); | |
try { | |
if (typeof mask === 'function') { | |
mask = mask(value); | |
} | |
return typeof maskObject !== 'object' || stringify(maskObject.options) !== stringify(options) || maskObject.mask !== mask; | |
} catch (e) {} | |
}, | |
eventSupported = function(eventName) { | |
var el = document.createElement('div'), isSupported; | |
eventName = 'on' + eventName; | |
isSupported = (eventName in el); | |
if ( !isSupported ) { | |
el.setAttribute(eventName, 'return;'); | |
isSupported = typeof el[eventName] === 'function'; | |
} | |
el = null; | |
return isSupported; | |
}; | |
$.fn.mask = function(mask, options) { | |
options = options || {}; | |
var selector = this.selector, | |
globals = $.jMaskGlobals, | |
interval = globals.watchInterval, | |
watchInputs = options.watchInputs || globals.watchInputs, | |
maskFunction = function() { | |
if (notSameMaskObject(this, mask, options)) { | |
return $(this).data('mask', new Mask(this, mask, options)); | |
} | |
}; | |
$(this).each(maskFunction); | |
if (selector && selector !== '' && watchInputs) { | |
clearInterval($.maskWatchers[selector]); | |
$.maskWatchers[selector] = setInterval(function(){ | |
$(document).find(selector).each(maskFunction); | |
}, interval); | |
} | |
return this; | |
}; | |
$.fn.masked = function(val) { | |
return this.data('mask').getMaskedVal(val); | |
}; | |
$.fn.unmask = function() { | |
clearInterval($.maskWatchers[this.selector]); | |
delete $.maskWatchers[this.selector]; | |
return this.each(function() { | |
var dataMask = $(this).data('mask'); | |
if (dataMask) { | |
dataMask.remove().removeData('mask'); | |
} | |
}); | |
}; | |
$.fn.cleanVal = function() { | |
return this.data('mask').getCleanVal(); | |
}; | |
$.applyDataMask = function(selector) { | |
selector = selector || $.jMaskGlobals.maskElements; | |
var $selector = (selector instanceof $) ? selector : $(selector); | |
$selector.filter($.jMaskGlobals.dataMaskAttr).each(HTMLAttributes); | |
}; | |
var globals = { | |
maskElements: 'input,td,span,div', | |
dataMaskAttr: '*[data-mask]', | |
dataMask: true, | |
watchInterval: 300, | |
watchInputs: true, | |
keyStrokeCompensation: 10, | |
// old versions of chrome dont work great with input event | |
useInput: !/Chrome\/[2-4][0-9]|SamsungBrowser/.test(window.navigator.userAgent) && eventSupported('input'), | |
watchDataMask: false, | |
byPassKeys: [9, 16, 17, 18, 36, 37, 38, 39, 40, 91], | |
translation: { | |
'0': {pattern: /\d/}, | |
'9': {pattern: /\d/, optional: true}, | |
'#': {pattern: /\d/, recursive: true}, | |
'A': {pattern: /[a-zA-Z0-9]/}, | |
'S': {pattern: /[a-zA-Z]/} | |
} | |
}; | |
$.jMaskGlobals = $.jMaskGlobals || {}; | |
globals = $.jMaskGlobals = $.extend(true, {}, globals, $.jMaskGlobals); | |
// looking for inputs with data-mask attribute | |
if (globals.dataMask) { | |
$.applyDataMask(); | |
} | |
setInterval(function() { | |
if ($.jMaskGlobals.watchDataMask) { | |
$.applyDataMask(); | |
} | |
}, globals.watchInterval); | |
}, window.jQuery, window.Zepto)); | |