blob: 6f2417d0d24d30220f57201daab46233d26dbe2b [file] [log] [blame]
swissChilif0cbdc32023-01-05 17:21:38 -05001/**
2 * jquery.mask.js
3 * @version: v1.14.16
4 * @author: Igor Escobar
5 *
6 * Created by Igor Escobar on 2012-03-10. Please report any bug at github.com/igorescobar/jQuery-Mask-Plugin
7 *
8 * Copyright (c) 2012 Igor Escobar http://igorescobar.com
9 *
10 * The MIT License (http://www.opensource.org/licenses/mit-license.php)
11 *
12 * Permission is hereby granted, free of charge, to any person
13 * obtaining a copy of this software and associated documentation
14 * files (the "Software"), to deal in the Software without
15 * restriction, including without limitation the rights to use,
16 * copy, modify, merge, publish, distribute, sublicense, and/or sell
17 * copies of the Software, and to permit persons to whom the
18 * Software is furnished to do so, subject to the following
19 * conditions:
20 *
21 * The above copyright notice and this permission notice shall be
22 * included in all copies or substantial portions of the Software.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
26 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
28 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
29 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
30 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
31 * OTHER DEALINGS IN THE SOFTWARE.
32 */
33
34/* jshint laxbreak: true */
35/* jshint maxcomplexity:17 */
36/* global define */
37
38// UMD (Universal Module Definition) patterns for JavaScript modules that work everywhere.
39// https://github.com/umdjs/umd/blob/master/templates/jqueryPlugin.js
40(function (factory, jQuery, Zepto) {
41
42 if (typeof define === 'function' && define.amd) {
43 define(['jquery'], factory);
44 } else if (typeof exports === 'object' && typeof Meteor === 'undefined') {
45 module.exports = factory(require('jquery'));
46 } else {
47 factory(jQuery || Zepto);
48 }
49
50}(function ($) {
51 'use strict';
52
53 var Mask = function (el, mask, options) {
54
55 var p = {
56 invalid: [],
57 getCaret: function () {
58 try {
59 var sel,
60 pos = 0,
61 ctrl = el.get(0),
62 dSel = document.selection,
63 cSelStart = ctrl.selectionStart;
64
65 // IE Support
66 if (dSel && navigator.appVersion.indexOf('MSIE 10') === -1) {
67 sel = dSel.createRange();
68 sel.moveStart('character', -p.val().length);
69 pos = sel.text.length;
70 }
71 // Firefox support
72 else if (cSelStart || cSelStart === '0') {
73 pos = cSelStart;
74 }
75
76 return pos;
77 } catch (e) {}
78 },
79 setCaret: function(pos) {
80 try {
81 if (el.is(':focus')) {
82 var range, ctrl = el.get(0);
83
84 // Firefox, WebKit, etc..
85 if (ctrl.setSelectionRange) {
86 ctrl.setSelectionRange(pos, pos);
87 } else { // IE
88 range = ctrl.createTextRange();
89 range.collapse(true);
90 range.moveEnd('character', pos);
91 range.moveStart('character', pos);
92 range.select();
93 }
94 }
95 } catch (e) {}
96 },
97 events: function() {
98 el
99 .on('keydown.mask', function(e) {
100 el.data('mask-keycode', e.keyCode || e.which);
101 el.data('mask-previus-value', el.val());
102 el.data('mask-previus-caret-pos', p.getCaret());
103 p.maskDigitPosMapOld = p.maskDigitPosMap;
104 })
105 .on($.jMaskGlobals.useInput ? 'input.mask' : 'keyup.mask', p.behaviour)
106 .on('paste.mask drop.mask', function() {
107 setTimeout(function() {
108 el.keydown().keyup();
109 }, 100);
110 })
111 .on('change.mask', function(){
112 el.data('changed', true);
113 })
114 .on('blur.mask', function(){
115 if (oldValue !== p.val() && !el.data('changed')) {
116 el.trigger('change');
117 }
118 el.data('changed', false);
119 })
120 // it's very important that this callback remains in this position
121 // otherwhise oldValue it's going to work buggy
122 .on('blur.mask', function() {
123 oldValue = p.val();
124 })
125 // select all text on focus
126 .on('focus.mask', function (e) {
127 if (options.selectOnFocus === true) {
128 $(e.target).select();
129 }
130 })
131 // clear the value if it not complete the mask
132 .on('focusout.mask', function() {
133 if (options.clearIfNotMatch && !regexMask.test(p.val())) {
134 p.val('');
135 }
136 });
137 },
138 getRegexMask: function() {
139 var maskChunks = [], translation, pattern, optional, recursive, oRecursive, r;
140
141 for (var i = 0; i < mask.length; i++) {
142 translation = jMask.translation[mask.charAt(i)];
143
144 if (translation) {
145
146 pattern = translation.pattern.toString().replace(/.{1}$|^.{1}/g, '');
147 optional = translation.optional;
148 recursive = translation.recursive;
149
150 if (recursive) {
151 maskChunks.push(mask.charAt(i));
152 oRecursive = {digit: mask.charAt(i), pattern: pattern};
153 } else {
154 maskChunks.push(!optional && !recursive ? pattern : (pattern + '?'));
155 }
156
157 } else {
158 maskChunks.push(mask.charAt(i).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'));
159 }
160 }
161
162 r = maskChunks.join('');
163
164 if (oRecursive) {
165 r = r.replace(new RegExp('(' + oRecursive.digit + '(.*' + oRecursive.digit + ')?)'), '($1)?')
166 .replace(new RegExp(oRecursive.digit, 'g'), oRecursive.pattern);
167 }
168
169 return new RegExp(r);
170 },
171 destroyEvents: function() {
172 el.off(['input', 'keydown', 'keyup', 'paste', 'drop', 'blur', 'focusout', ''].join('.mask '));
173 },
174 val: function(v) {
175 var isInput = el.is('input'),
176 method = isInput ? 'val' : 'text',
177 r;
178
179 if (arguments.length > 0) {
180 if (el[method]() !== v) {
181 el[method](v);
182 }
183 r = el;
184 } else {
185 r = el[method]();
186 }
187
188 return r;
189 },
190 calculateCaretPosition: function(oldVal) {
191 var newVal = p.getMasked(),
192 caretPosNew = p.getCaret();
193 if (oldVal !== newVal) {
194 var caretPosOld = el.data('mask-previus-caret-pos') || 0,
195 newValL = newVal.length,
196 oldValL = oldVal.length,
197 maskDigitsBeforeCaret = 0,
198 maskDigitsAfterCaret = 0,
199 maskDigitsBeforeCaretAll = 0,
200 maskDigitsBeforeCaretAllOld = 0,
201 i = 0;
202
203 for (i = caretPosNew; i < newValL; i++) {
204 if (!p.maskDigitPosMap[i]) {
205 break;
206 }
207 maskDigitsAfterCaret++;
208 }
209
210 for (i = caretPosNew - 1; i >= 0; i--) {
211 if (!p.maskDigitPosMap[i]) {
212 break;
213 }
214 maskDigitsBeforeCaret++;
215 }
216
217 for (i = caretPosNew - 1; i >= 0; i--) {
218 if (p.maskDigitPosMap[i]) {
219 maskDigitsBeforeCaretAll++;
220 }
221 }
222
223 for (i = caretPosOld - 1; i >= 0; i--) {
224 if (p.maskDigitPosMapOld[i]) {
225 maskDigitsBeforeCaretAllOld++;
226 }
227 }
228
229 // if the cursor is at the end keep it there
230 if (caretPosNew > oldValL) {
231 caretPosNew = newValL * 10;
232 } else if (caretPosOld >= caretPosNew && caretPosOld !== oldValL) {
233 if (!p.maskDigitPosMapOld[caretPosNew]) {
234 var caretPos = caretPosNew;
235 caretPosNew -= maskDigitsBeforeCaretAllOld - maskDigitsBeforeCaretAll;
236 caretPosNew -= maskDigitsBeforeCaret;
237 if (p.maskDigitPosMap[caretPosNew]) {
238 caretPosNew = caretPos;
239 }
240 }
241 }
242 else if (caretPosNew > caretPosOld) {
243 caretPosNew += maskDigitsBeforeCaretAll - maskDigitsBeforeCaretAllOld;
244 caretPosNew += maskDigitsAfterCaret;
245 }
246 }
247 return caretPosNew;
248 },
249 behaviour: function(e) {
250 e = e || window.event;
251 p.invalid = [];
252
253 var keyCode = el.data('mask-keycode');
254
255 if ($.inArray(keyCode, jMask.byPassKeys) === -1) {
256 var newVal = p.getMasked(),
257 caretPos = p.getCaret(),
258 oldVal = el.data('mask-previus-value') || '';
259
260 // this is a compensation to devices/browsers that don't compensate
261 // caret positioning the right way
262 setTimeout(function() {
263 p.setCaret(p.calculateCaretPosition(oldVal));
264 }, $.jMaskGlobals.keyStrokeCompensation);
265
266 p.val(newVal);
267 p.setCaret(caretPos);
268 return p.callbacks(e);
269 }
270 },
271 getMasked: function(skipMaskChars, val) {
272 var buf = [],
273 value = val === undefined ? p.val() : val + '',
274 m = 0, maskLen = mask.length,
275 v = 0, valLen = value.length,
276 offset = 1, addMethod = 'push',
277 resetPos = -1,
278 maskDigitCount = 0,
279 maskDigitPosArr = [],
280 lastMaskChar,
281 check;
282
283 if (options.reverse) {
284 addMethod = 'unshift';
285 offset = -1;
286 lastMaskChar = 0;
287 m = maskLen - 1;
288 v = valLen - 1;
289 check = function () {
290 return m > -1 && v > -1;
291 };
292 } else {
293 lastMaskChar = maskLen - 1;
294 check = function () {
295 return m < maskLen && v < valLen;
296 };
297 }
298
299 var lastUntranslatedMaskChar;
300 while (check()) {
301 var maskDigit = mask.charAt(m),
302 valDigit = value.charAt(v),
303 translation = jMask.translation[maskDigit];
304
305 if (translation) {
306 if (valDigit.match(translation.pattern)) {
307 buf[addMethod](valDigit);
308 if (translation.recursive) {
309 if (resetPos === -1) {
310 resetPos = m;
311 } else if (m === lastMaskChar && m !== resetPos) {
312 m = resetPos - offset;
313 }
314
315 if (lastMaskChar === resetPos) {
316 m -= offset;
317 }
318 }
319 m += offset;
320 } else if (valDigit === lastUntranslatedMaskChar) {
321 // matched the last untranslated (raw) mask character that we encountered
322 // likely an insert offset the mask character from the last entry; fall
323 // through and only increment v
324 maskDigitCount--;
325 lastUntranslatedMaskChar = undefined;
326 } else if (translation.optional) {
327 m += offset;
328 v -= offset;
329 } else if (translation.fallback) {
330 buf[addMethod](translation.fallback);
331 m += offset;
332 v -= offset;
333 } else {
334 p.invalid.push({p: v, v: valDigit, e: translation.pattern});
335 }
336 v += offset;
337 } else {
338 if (!skipMaskChars) {
339 buf[addMethod](maskDigit);
340 }
341
342 if (valDigit === maskDigit) {
343 maskDigitPosArr.push(v);
344 v += offset;
345 } else {
346 lastUntranslatedMaskChar = maskDigit;
347 maskDigitPosArr.push(v + maskDigitCount);
348 maskDigitCount++;
349 }
350
351 m += offset;
352 }
353 }
354
355 var lastMaskCharDigit = mask.charAt(lastMaskChar);
356 if (maskLen === valLen + 1 && !jMask.translation[lastMaskCharDigit]) {
357 buf.push(lastMaskCharDigit);
358 }
359
360 var newVal = buf.join('');
361 p.mapMaskdigitPositions(newVal, maskDigitPosArr, valLen);
362 return newVal;
363 },
364 mapMaskdigitPositions: function(newVal, maskDigitPosArr, valLen) {
365 var maskDiff = options.reverse ? newVal.length - valLen : 0;
366 p.maskDigitPosMap = {};
367 for (var i = 0; i < maskDigitPosArr.length; i++) {
368 p.maskDigitPosMap[maskDigitPosArr[i] + maskDiff] = 1;
369 }
370 },
371 callbacks: function (e) {
372 var val = p.val(),
373 changed = val !== oldValue,
374 defaultArgs = [val, e, el, options],
375 callback = function(name, criteria, args) {
376 if (typeof options[name] === 'function' && criteria) {
377 options[name].apply(this, args);
378 }
379 };
380
381 callback('onChange', changed === true, defaultArgs);
382 callback('onKeyPress', changed === true, defaultArgs);
383 callback('onComplete', val.length === mask.length, defaultArgs);
384 callback('onInvalid', p.invalid.length > 0, [val, e, el, p.invalid, options]);
385 }
386 };
387
388 el = $(el);
389 var jMask = this, oldValue = p.val(), regexMask;
390
391 mask = typeof mask === 'function' ? mask(p.val(), undefined, el, options) : mask;
392
393 // public methods
394 jMask.mask = mask;
395 jMask.options = options;
396 jMask.remove = function() {
397 var caret = p.getCaret();
398 if (jMask.options.placeholder) {
399 el.removeAttr('placeholder');
400 }
401 if (el.data('mask-maxlength')) {
402 el.removeAttr('maxlength');
403 }
404 p.destroyEvents();
405 p.val(jMask.getCleanVal());
406 p.setCaret(caret);
407 return el;
408 };
409
410 // get value without mask
411 jMask.getCleanVal = function() {
412 return p.getMasked(true);
413 };
414
415 // get masked value without the value being in the input or element
416 jMask.getMaskedVal = function(val) {
417 return p.getMasked(false, val);
418 };
419
420 jMask.init = function(onlyMask) {
421 onlyMask = onlyMask || false;
422 options = options || {};
423
424 jMask.clearIfNotMatch = $.jMaskGlobals.clearIfNotMatch;
425 jMask.byPassKeys = $.jMaskGlobals.byPassKeys;
426 jMask.translation = $.extend({}, $.jMaskGlobals.translation, options.translation);
427
428 jMask = $.extend(true, {}, jMask, options);
429
430 regexMask = p.getRegexMask();
431
432 if (onlyMask) {
433 p.events();
434 p.val(p.getMasked());
435 } else {
436 if (options.placeholder) {
437 el.attr('placeholder' , options.placeholder);
438 }
439
440 // this is necessary, otherwise if the user submit the form
441 // and then press the "back" button, the autocomplete will erase
442 // the data. Works fine on IE9+, FF, Opera, Safari.
443 if (el.data('mask')) {
444 el.attr('autocomplete', 'off');
445 }
446
447 // detect if is necessary let the user type freely.
448 // for is a lot faster than forEach.
449 for (var i = 0, maxlength = true; i < mask.length; i++) {
450 var translation = jMask.translation[mask.charAt(i)];
451 if (translation && translation.recursive) {
452 maxlength = false;
453 break;
454 }
455 }
456
457 if (maxlength) {
458 el.attr('maxlength', mask.length).data('mask-maxlength', true);
459 }
460
461 p.destroyEvents();
462 p.events();
463
464 var caret = p.getCaret();
465 p.val(p.getMasked());
466 p.setCaret(caret);
467 }
468 };
469
470 jMask.init(!el.is('input'));
471 };
472
473 $.maskWatchers = {};
474 var HTMLAttributes = function () {
475 var input = $(this),
476 options = {},
477 prefix = 'data-mask-',
478 mask = input.attr('data-mask');
479
480 if (input.attr(prefix + 'reverse')) {
481 options.reverse = true;
482 }
483
484 if (input.attr(prefix + 'clearifnotmatch')) {
485 options.clearIfNotMatch = true;
486 }
487
488 if (input.attr(prefix + 'selectonfocus') === 'true') {
489 options.selectOnFocus = true;
490 }
491
492 if (notSameMaskObject(input, mask, options)) {
493 return input.data('mask', new Mask(this, mask, options));
494 }
495 },
496 notSameMaskObject = function(field, mask, options) {
497 options = options || {};
498 var maskObject = $(field).data('mask'),
499 stringify = JSON.stringify,
500 value = $(field).val() || $(field).text();
501 try {
502 if (typeof mask === 'function') {
503 mask = mask(value);
504 }
505 return typeof maskObject !== 'object' || stringify(maskObject.options) !== stringify(options) || maskObject.mask !== mask;
506 } catch (e) {}
507 },
508 eventSupported = function(eventName) {
509 var el = document.createElement('div'), isSupported;
510
511 eventName = 'on' + eventName;
512 isSupported = (eventName in el);
513
514 if ( !isSupported ) {
515 el.setAttribute(eventName, 'return;');
516 isSupported = typeof el[eventName] === 'function';
517 }
518 el = null;
519
520 return isSupported;
521 };
522
523 $.fn.mask = function(mask, options) {
524 options = options || {};
525 var selector = this.selector,
526 globals = $.jMaskGlobals,
527 interval = globals.watchInterval,
528 watchInputs = options.watchInputs || globals.watchInputs,
529 maskFunction = function() {
530 if (notSameMaskObject(this, mask, options)) {
531 return $(this).data('mask', new Mask(this, mask, options));
532 }
533 };
534
535 $(this).each(maskFunction);
536
537 if (selector && selector !== '' && watchInputs) {
538 clearInterval($.maskWatchers[selector]);
539 $.maskWatchers[selector] = setInterval(function(){
540 $(document).find(selector).each(maskFunction);
541 }, interval);
542 }
543 return this;
544 };
545
546 $.fn.masked = function(val) {
547 return this.data('mask').getMaskedVal(val);
548 };
549
550 $.fn.unmask = function() {
551 clearInterval($.maskWatchers[this.selector]);
552 delete $.maskWatchers[this.selector];
553 return this.each(function() {
554 var dataMask = $(this).data('mask');
555 if (dataMask) {
556 dataMask.remove().removeData('mask');
557 }
558 });
559 };
560
561 $.fn.cleanVal = function() {
562 return this.data('mask').getCleanVal();
563 };
564
565 $.applyDataMask = function(selector) {
566 selector = selector || $.jMaskGlobals.maskElements;
567 var $selector = (selector instanceof $) ? selector : $(selector);
568 $selector.filter($.jMaskGlobals.dataMaskAttr).each(HTMLAttributes);
569 };
570
571 var globals = {
572 maskElements: 'input,td,span,div',
573 dataMaskAttr: '*[data-mask]',
574 dataMask: true,
575 watchInterval: 300,
576 watchInputs: true,
577 keyStrokeCompensation: 10,
578 // old versions of chrome dont work great with input event
579 useInput: !/Chrome\/[2-4][0-9]|SamsungBrowser/.test(window.navigator.userAgent) && eventSupported('input'),
580 watchDataMask: false,
581 byPassKeys: [9, 16, 17, 18, 36, 37, 38, 39, 40, 91],
582 translation: {
583 '0': {pattern: /\d/},
584 '9': {pattern: /\d/, optional: true},
585 '#': {pattern: /\d/, recursive: true},
586 'A': {pattern: /[a-zA-Z0-9]/},
587 'S': {pattern: /[a-zA-Z]/}
588 }
589 };
590
591 $.jMaskGlobals = $.jMaskGlobals || {};
592 globals = $.jMaskGlobals = $.extend(true, {}, globals, $.jMaskGlobals);
593
594 // looking for inputs with data-mask attribute
595 if (globals.dataMask) {
596 $.applyDataMask();
597 }
598
599 setInterval(function() {
600 if ($.jMaskGlobals.watchDataMask) {
601 $.applyDataMask();
602 }
603 }, globals.watchInterval);
604}, window.jQuery, window.Zepto));