jquery.mask.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. /**
  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. /* jshint laxbreak: true */
  34. /* jshint maxcomplexity:17 */
  35. /* global define */
  36. // UMD (Universal Module Definition) patterns for JavaScript modules that work everywhere.
  37. // https://github.com/umdjs/umd/blob/master/templates/jqueryPlugin.js
  38. (function (factory, jQuery, Zepto) {
  39. if (typeof define === 'function' && define.amd) {
  40. define(['jquery'], factory);
  41. } else if (typeof exports === 'object' && typeof Meteor === 'undefined') {
  42. module.exports = factory(require('jquery'));
  43. } else {
  44. factory(jQuery || Zepto);
  45. }
  46. }(function ($) {
  47. 'use strict';
  48. var Mask = function (el, mask, options) {
  49. var p = {
  50. invalid: [],
  51. getCaret: function () {
  52. try {
  53. var sel,
  54. pos = 0,
  55. ctrl = el.get(0),
  56. dSel = document.selection,
  57. cSelStart = ctrl.selectionStart;
  58. // IE Support
  59. if (dSel && navigator.appVersion.indexOf('MSIE 10') === -1) {
  60. sel = dSel.createRange();
  61. sel.moveStart('character', -p.val().length);
  62. pos = sel.text.length;
  63. }
  64. // Firefox support
  65. else if (cSelStart || cSelStart === '0') {
  66. pos = cSelStart;
  67. }
  68. return pos;
  69. } catch (e) {}
  70. },
  71. setCaret: function(pos) {
  72. try {
  73. if (el.is(':focus')) {
  74. var range, ctrl = el.get(0);
  75. // Firefox, WebKit, etc..
  76. if (ctrl.setSelectionRange) {
  77. ctrl.setSelectionRange(pos, pos);
  78. } else { // IE
  79. range = ctrl.createTextRange();
  80. range.collapse(true);
  81. range.moveEnd('character', pos);
  82. range.moveStart('character', pos);
  83. range.select();
  84. }
  85. }
  86. } catch (e) {}
  87. },
  88. events: function() {
  89. el
  90. .on('keydown.mask', function(e) {
  91. el.data('mask-keycode', e.keyCode || e.which);
  92. el.data('mask-previus-value', el.val());
  93. el.data('mask-previus-caret-pos', p.getCaret());
  94. p.maskDigitPosMapOld = p.maskDigitPosMap;
  95. })
  96. .on($.jMaskGlobals.useInput ? 'input.mask' : 'keyup.mask', p.behaviour)
  97. .on('paste.mask drop.mask', function() {
  98. setTimeout(function() {
  99. el.keydown().keyup();
  100. }, 100);
  101. })
  102. .on('change.mask', function(){
  103. el.data('changed', true);
  104. })
  105. .on('blur.mask', function(){
  106. if (oldValue !== p.val() && !el.data('changed')) {
  107. el.trigger('change');
  108. }
  109. el.data('changed', false);
  110. })
  111. // it's very important that this callback remains in this position
  112. // otherwhise oldValue it's going to work buggy
  113. .on('blur.mask', function() {
  114. oldValue = p.val();
  115. })
  116. // select all text on focus
  117. .on('focus.mask', function (e) {
  118. if (options.selectOnFocus === true) {
  119. $(e.target).select();
  120. }
  121. })
  122. // clear the value if it not complete the mask
  123. .on('focusout.mask', function() {
  124. if (options.clearIfNotMatch && !regexMask.test(p.val())) {
  125. p.val('');
  126. }
  127. });
  128. },
  129. getRegexMask: function() {
  130. var maskChunks = [], translation, pattern, optional, recursive, oRecursive, r;
  131. for (var i = 0; i < mask.length; i++) {
  132. translation = jMask.translation[mask.charAt(i)];
  133. if (translation) {
  134. pattern = translation.pattern.toString().replace(/.{1}$|^.{1}/g, '');
  135. optional = translation.optional;
  136. recursive = translation.recursive;
  137. if (recursive) {
  138. maskChunks.push(mask.charAt(i));
  139. oRecursive = {digit: mask.charAt(i), pattern: pattern};
  140. } else {
  141. maskChunks.push(!optional && !recursive ? pattern : (pattern + '?'));
  142. }
  143. } else {
  144. maskChunks.push(mask.charAt(i).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'));
  145. }
  146. }
  147. r = maskChunks.join('');
  148. if (oRecursive) {
  149. r = r.replace(new RegExp('(' + oRecursive.digit + '(.*' + oRecursive.digit + ')?)'), '($1)?')
  150. .replace(new RegExp(oRecursive.digit, 'g'), oRecursive.pattern);
  151. }
  152. return new RegExp(r);
  153. },
  154. destroyEvents: function() {
  155. el.off(['input', 'keydown', 'keyup', 'paste', 'drop', 'blur', 'focusout', ''].join('.mask '));
  156. },
  157. val: function(v) {
  158. var isInput = el.is('input'),
  159. method = isInput ? 'val' : 'text',
  160. r;
  161. if (arguments.length > 0) {
  162. if (el[method]() !== v) {
  163. el[method](v);
  164. }
  165. r = el;
  166. } else {
  167. r = el[method]();
  168. }
  169. return r;
  170. },
  171. calculateCaretPosition: function(oldVal) {
  172. var newVal = p.getMasked(),
  173. caretPosNew = p.getCaret();
  174. if (oldVal !== newVal) {
  175. var caretPosOld = el.data('mask-previus-caret-pos') || 0,
  176. newValL = newVal.length,
  177. oldValL = oldVal.length,
  178. maskDigitsBeforeCaret = 0,
  179. maskDigitsAfterCaret = 0,
  180. maskDigitsBeforeCaretAll = 0,
  181. maskDigitsBeforeCaretAllOld = 0,
  182. i = 0;
  183. for (i = caretPosNew; i < newValL; i++) {
  184. if (!p.maskDigitPosMap[i]) {
  185. break;
  186. }
  187. maskDigitsAfterCaret++;
  188. }
  189. for (i = caretPosNew - 1; i >= 0; i--) {
  190. if (!p.maskDigitPosMap[i]) {
  191. break;
  192. }
  193. maskDigitsBeforeCaret++;
  194. }
  195. for (i = caretPosNew - 1; i >= 0; i--) {
  196. if (p.maskDigitPosMap[i]) {
  197. maskDigitsBeforeCaretAll++;
  198. }
  199. }
  200. for (i = caretPosOld - 1; i >= 0; i--) {
  201. if (p.maskDigitPosMapOld[i]) {
  202. maskDigitsBeforeCaretAllOld++;
  203. }
  204. }
  205. // if the cursor is at the end keep it there
  206. if (caretPosNew > oldValL) {
  207. caretPosNew = newValL * 10;
  208. } else if (caretPosOld >= caretPosNew && caretPosOld !== oldValL) {
  209. if (!p.maskDigitPosMapOld[caretPosNew]) {
  210. var caretPos = caretPosNew;
  211. caretPosNew -= maskDigitsBeforeCaretAllOld - maskDigitsBeforeCaretAll;
  212. caretPosNew -= maskDigitsBeforeCaret;
  213. if (p.maskDigitPosMap[caretPosNew]) {
  214. caretPosNew = caretPos;
  215. }
  216. }
  217. }
  218. else if (caretPosNew > caretPosOld) {
  219. caretPosNew += maskDigitsBeforeCaretAll - maskDigitsBeforeCaretAllOld;
  220. caretPosNew += maskDigitsAfterCaret;
  221. }
  222. }
  223. return caretPosNew;
  224. },
  225. behaviour: function(e) {
  226. e = e || window.event;
  227. p.invalid = [];
  228. var keyCode = el.data('mask-keycode');
  229. if ($.inArray(keyCode, jMask.byPassKeys) === -1) {
  230. var newVal = p.getMasked(),
  231. caretPos = p.getCaret(),
  232. oldVal = el.data('mask-previus-value') || '';
  233. // this is a compensation to devices/browsers that don't compensate
  234. // caret positioning the right way
  235. setTimeout(function() {
  236. p.setCaret(p.calculateCaretPosition(oldVal));
  237. }, $.jMaskGlobals.keyStrokeCompensation);
  238. p.val(newVal);
  239. p.setCaret(caretPos);
  240. return p.callbacks(e);
  241. }
  242. },
  243. getMasked: function(skipMaskChars, val) {
  244. var buf = [],
  245. value = val === undefined ? p.val() : val + '',
  246. m = 0, maskLen = mask.length,
  247. v = 0, valLen = value.length,
  248. offset = 1, addMethod = 'push',
  249. resetPos = -1,
  250. maskDigitCount = 0,
  251. maskDigitPosArr = [],
  252. lastMaskChar,
  253. check;
  254. if (options.reverse) {
  255. addMethod = 'unshift';
  256. offset = -1;
  257. lastMaskChar = 0;
  258. m = maskLen - 1;
  259. v = valLen - 1;
  260. check = function () {
  261. return m > -1 && v > -1;
  262. };
  263. } else {
  264. lastMaskChar = maskLen - 1;
  265. check = function () {
  266. return m < maskLen && v < valLen;
  267. };
  268. }
  269. var lastUntranslatedMaskChar;
  270. while (check()) {
  271. var maskDigit = mask.charAt(m),
  272. valDigit = value.charAt(v),
  273. translation = jMask.translation[maskDigit];
  274. if (translation) {
  275. if (valDigit.match(translation.pattern)) {
  276. buf[addMethod](valDigit);
  277. if (translation.recursive) {
  278. if (resetPos === -1) {
  279. resetPos = m;
  280. } else if (m === lastMaskChar && m !== resetPos) {
  281. m = resetPos - offset;
  282. }
  283. if (lastMaskChar === resetPos) {
  284. m -= offset;
  285. }
  286. }
  287. m += offset;
  288. } else if (valDigit === lastUntranslatedMaskChar) {
  289. // matched the last untranslated (raw) mask character that we encountered
  290. // likely an insert offset the mask character from the last entry; fall
  291. // through and only increment v
  292. maskDigitCount--;
  293. lastUntranslatedMaskChar = undefined;
  294. } else if (translation.optional) {
  295. m += offset;
  296. v -= offset;
  297. } else if (translation.fallback) {
  298. buf[addMethod](translation.fallback);
  299. m += offset;
  300. v -= offset;
  301. } else {
  302. p.invalid.push({p: v, v: valDigit, e: translation.pattern});
  303. }
  304. v += offset;
  305. } else {
  306. if (!skipMaskChars) {
  307. buf[addMethod](maskDigit);
  308. }
  309. if (valDigit === maskDigit) {
  310. maskDigitPosArr.push(v);
  311. v += offset;
  312. } else {
  313. lastUntranslatedMaskChar = maskDigit;
  314. maskDigitPosArr.push(v + maskDigitCount);
  315. maskDigitCount++;
  316. }
  317. m += offset;
  318. }
  319. }
  320. var lastMaskCharDigit = mask.charAt(lastMaskChar);
  321. if (maskLen === valLen + 1 && !jMask.translation[lastMaskCharDigit]) {
  322. buf.push(lastMaskCharDigit);
  323. }
  324. var newVal = buf.join('');
  325. p.mapMaskdigitPositions(newVal, maskDigitPosArr, valLen);
  326. return newVal;
  327. },
  328. mapMaskdigitPositions: function(newVal, maskDigitPosArr, valLen) {
  329. var maskDiff = options.reverse ? newVal.length - valLen : 0;
  330. p.maskDigitPosMap = {};
  331. for (var i = 0; i < maskDigitPosArr.length; i++) {
  332. p.maskDigitPosMap[maskDigitPosArr[i] + maskDiff] = 1;
  333. }
  334. },
  335. callbacks: function (e) {
  336. var val = p.val(),
  337. changed = val !== oldValue,
  338. defaultArgs = [val, e, el, options],
  339. callback = function(name, criteria, args) {
  340. if (typeof options[name] === 'function' && criteria) {
  341. options[name].apply(this, args);
  342. }
  343. };
  344. callback('onChange', changed === true, defaultArgs);
  345. callback('onKeyPress', changed === true, defaultArgs);
  346. callback('onComplete', val.length === mask.length, defaultArgs);
  347. callback('onInvalid', p.invalid.length > 0, [val, e, el, p.invalid, options]);
  348. }
  349. };
  350. el = $(el);
  351. var jMask = this, oldValue = p.val(), regexMask;
  352. mask = typeof mask === 'function' ? mask(p.val(), undefined, el, options) : mask;
  353. // public methods
  354. jMask.mask = mask;
  355. jMask.options = options;
  356. jMask.remove = function() {
  357. var caret = p.getCaret();
  358. if (jMask.options.placeholder) {
  359. el.removeAttr('placeholder');
  360. }
  361. if (el.data('mask-maxlength')) {
  362. el.removeAttr('maxlength');
  363. }
  364. p.destroyEvents();
  365. p.val(jMask.getCleanVal());
  366. p.setCaret(caret);
  367. return el;
  368. };
  369. // get value without mask
  370. jMask.getCleanVal = function() {
  371. return p.getMasked(true);
  372. };
  373. // get masked value without the value being in the input or element
  374. jMask.getMaskedVal = function(val) {
  375. return p.getMasked(false, val);
  376. };
  377. jMask.init = function(onlyMask) {
  378. onlyMask = onlyMask || false;
  379. options = options || {};
  380. jMask.clearIfNotMatch = $.jMaskGlobals.clearIfNotMatch;
  381. jMask.byPassKeys = $.jMaskGlobals.byPassKeys;
  382. jMask.translation = $.extend({}, $.jMaskGlobals.translation, options.translation);
  383. jMask = $.extend(true, {}, jMask, options);
  384. regexMask = p.getRegexMask();
  385. if (onlyMask) {
  386. p.events();
  387. p.val(p.getMasked());
  388. } else {
  389. if (options.placeholder) {
  390. el.attr('placeholder' , options.placeholder);
  391. }
  392. // this is necessary, otherwise if the user submit the form
  393. // and then press the "back" button, the autocomplete will erase
  394. // the data. Works fine on IE9+, FF, Opera, Safari.
  395. if (el.data('mask')) {
  396. el.attr('autocomplete', 'off');
  397. }
  398. // detect if is necessary let the user type freely.
  399. // for is a lot faster than forEach.
  400. for (var i = 0, maxlength = true; i < mask.length; i++) {
  401. var translation = jMask.translation[mask.charAt(i)];
  402. if (translation && translation.recursive) {
  403. maxlength = false;
  404. break;
  405. }
  406. }
  407. if (maxlength) {
  408. el.attr('maxlength', mask.length).data('mask-maxlength', true);
  409. }
  410. p.destroyEvents();
  411. p.events();
  412. var caret = p.getCaret();
  413. p.val(p.getMasked());
  414. p.setCaret(caret);
  415. }
  416. };
  417. jMask.init(!el.is('input'));
  418. };
  419. $.maskWatchers = {};
  420. var HTMLAttributes = function () {
  421. var input = $(this),
  422. options = {},
  423. prefix = 'data-mask-',
  424. mask = input.attr('data-mask');
  425. if (input.attr(prefix + 'reverse')) {
  426. options.reverse = true;
  427. }
  428. if (input.attr(prefix + 'clearifnotmatch')) {
  429. options.clearIfNotMatch = true;
  430. }
  431. if (input.attr(prefix + 'selectonfocus') === 'true') {
  432. options.selectOnFocus = true;
  433. }
  434. if (notSameMaskObject(input, mask, options)) {
  435. return input.data('mask', new Mask(this, mask, options));
  436. }
  437. },
  438. notSameMaskObject = function(field, mask, options) {
  439. options = options || {};
  440. var maskObject = $(field).data('mask'),
  441. stringify = JSON.stringify,
  442. value = $(field).val() || $(field).text();
  443. try {
  444. if (typeof mask === 'function') {
  445. mask = mask(value);
  446. }
  447. return typeof maskObject !== 'object' || stringify(maskObject.options) !== stringify(options) || maskObject.mask !== mask;
  448. } catch (e) {}
  449. },
  450. eventSupported = function(eventName) {
  451. var el = document.createElement('div'), isSupported;
  452. eventName = 'on' + eventName;
  453. isSupported = (eventName in el);
  454. if ( !isSupported ) {
  455. el.setAttribute(eventName, 'return;');
  456. isSupported = typeof el[eventName] === 'function';
  457. }
  458. el = null;
  459. return isSupported;
  460. };
  461. $.fn.mask = function(mask, options) {
  462. options = options || {};
  463. var selector = this.selector,
  464. globals = $.jMaskGlobals,
  465. interval = globals.watchInterval,
  466. watchInputs = options.watchInputs || globals.watchInputs,
  467. maskFunction = function() {
  468. if (notSameMaskObject(this, mask, options)) {
  469. return $(this).data('mask', new Mask(this, mask, options));
  470. }
  471. };
  472. $(this).each(maskFunction);
  473. if (selector && selector !== '' && watchInputs) {
  474. clearInterval($.maskWatchers[selector]);
  475. $.maskWatchers[selector] = setInterval(function(){
  476. $(document).find(selector).each(maskFunction);
  477. }, interval);
  478. }
  479. return this;
  480. };
  481. $.fn.masked = function(val) {
  482. return this.data('mask').getMaskedVal(val);
  483. };
  484. $.fn.unmask = function() {
  485. clearInterval($.maskWatchers[this.selector]);
  486. delete $.maskWatchers[this.selector];
  487. return this.each(function() {
  488. var dataMask = $(this).data('mask');
  489. if (dataMask) {
  490. dataMask.remove().removeData('mask');
  491. }
  492. });
  493. };
  494. $.fn.cleanVal = function() {
  495. return this.data('mask').getCleanVal();
  496. };
  497. $.applyDataMask = function(selector) {
  498. selector = selector || $.jMaskGlobals.maskElements;
  499. var $selector = (selector instanceof $) ? selector : $(selector);
  500. $selector.filter($.jMaskGlobals.dataMaskAttr).each(HTMLAttributes);
  501. };
  502. var globals = {
  503. maskElements: 'input,td,span,div',
  504. dataMaskAttr: '[data-mask]',
  505. dataMask: true,
  506. watchInterval: 300,
  507. watchInputs: true,
  508. keyStrokeCompensation: 10,
  509. // old versions of chrome dont work great with input event
  510. useInput: !/Chrome\/[2-4][0-9]|SamsungBrowser/.test(window.navigator.userAgent) && eventSupported('input'),
  511. watchDataMask: false,
  512. byPassKeys: [9, 16, 17, 18, 36, 37, 38, 39, 40, 91],
  513. translation: {
  514. '0': {pattern: /\d/},
  515. '9': {pattern: /\d/, optional: true},
  516. '#': {pattern: /\d/, recursive: true},
  517. 'A': {pattern: /[a-zA-Z0-9]/},
  518. 'S': {pattern: /[a-zA-Z]/}
  519. }
  520. };
  521. $.jMaskGlobals = $.jMaskGlobals || {};
  522. globals = $.jMaskGlobals = $.extend(true, {}, globals, $.jMaskGlobals);
  523. // looking for inputs with data-mask attribute
  524. if (globals.dataMask) {
  525. $.applyDataMask();
  526. }
  527. setInterval(function() {
  528. if ($.jMaskGlobals.watchDataMask) {
  529. $.applyDataMask();
  530. }
  531. }, globals.watchInterval);
  532. }, window.jQuery, window.Zepto));