inputmask.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. /*
  2. * Input Mask Core
  3. * http://github.com/RobinHerbots/jquery.inputmask
  4. * Copyright (c) Robin Herbots
  5. * Licensed under the MIT license
  6. */
  7. import defaults from "./defaults";
  8. import definitions from "./definitions";
  9. import $ from "./dependencyLibs/inputmask.dependencyLib";
  10. import { EventRuler } from "./eventruler";
  11. import window from "./global/window";
  12. import { checkVal, unmaskedvalue } from "./inputHandling";
  13. import { mask } from "./mask";
  14. import { generateMaskSet, analyseMask } from "./mask-lexer";
  15. import {
  16. determineLastRequiredPosition,
  17. getBuffer,
  18. getBufferTemplate,
  19. isMask
  20. } from "./positioning";
  21. import { isComplete } from "./validation";
  22. import { getMaskTemplate } from "./validation-tests";
  23. const document = window.document,
  24. dataKey = "_inputmask_opts";
  25. function Inputmask(alias, options, internal) {
  26. // allow instanciating without new
  27. if (!(this instanceof Inputmask)) {
  28. return new Inputmask(alias, options, internal);
  29. }
  30. this.dependencyLib = $;
  31. this.el = undefined;
  32. this.events = {};
  33. this.maskset = undefined;
  34. if (internal !== true) {
  35. // init options
  36. if (Object.prototype.toString.call(alias) === "[object Object]") {
  37. options = alias;
  38. } else {
  39. options = options || {};
  40. if (alias) options.alias = alias;
  41. }
  42. this.opts = $.extend(true, {}, this.defaults, options);
  43. this.noMasksCache = options && options.definitions !== undefined;
  44. this.userOptions = options || {}; // user passed options
  45. resolveAlias(this.opts.alias, options, this.opts);
  46. }
  47. // maskscope properties
  48. this.refreshValue = false; // indicate a refresh from the inputvalue is needed (form.reset)
  49. this.undoValue = undefined;
  50. this.$el = undefined;
  51. this.skipInputEvent = false; // skip when triggered from within inputmask
  52. this.validationEvent = false;
  53. this.ignorable = false;
  54. // eslint-disable-next-line no-unused-expressions
  55. this.maxLength;
  56. this.mouseEnter = false;
  57. this.clicked = 0;
  58. this.originalPlaceholder = undefined; // needed for FF
  59. this.isComposing = false; // keydowncode == 229 compositionevent fallback
  60. this.hasAlternator = false;
  61. }
  62. Inputmask.prototype = {
  63. dataAttribute: "data-inputmask", // data attribute prefix used for attribute binding
  64. // options default
  65. defaults,
  66. definitions,
  67. aliases: {}, // aliases definitions
  68. masksCache: {},
  69. i18n: {},
  70. get isRTL() {
  71. return this.opts.isRTL || this.opts.numericInput;
  72. },
  73. mask: function (elems) {
  74. const that = this;
  75. if (typeof elems === "string") {
  76. elems =
  77. document.getElementById(elems) || document.querySelectorAll(elems);
  78. }
  79. elems = elems.nodeName
  80. ? [elems]
  81. : Array.isArray(elems)
  82. ? elems
  83. : [].slice.call(elems); // [].slice as alternate for Array.from (Yandex browser)
  84. elems.forEach(function (el, ndx) {
  85. const scopedOpts = $.extend(true, {}, that.opts);
  86. if (
  87. importAttributeOptions(
  88. el,
  89. scopedOpts,
  90. $.extend(true, {}, that.userOptions),
  91. that.dataAttribute
  92. )
  93. ) {
  94. const maskset = generateMaskSet(scopedOpts, that.noMasksCache);
  95. if (maskset !== undefined) {
  96. if (el.inputmask !== undefined) {
  97. el.inputmask.opts.autoUnmask = true; // force autounmasking when remasking
  98. el.inputmask.remove();
  99. }
  100. // store inputmask instance on the input with element reference
  101. el.inputmask = new Inputmask(undefined, undefined, true);
  102. el.inputmask.opts = scopedOpts;
  103. el.inputmask.noMasksCache = that.noMasksCache;
  104. el.inputmask.userOptions = $.extend(true, {}, that.userOptions);
  105. // el.inputmask.isRTL = scopedOpts.isRTL || scopedOpts.numericInput;
  106. el.inputmask.el = el;
  107. el.inputmask.$el = $(el);
  108. el.inputmask.maskset = maskset;
  109. $.data(el, dataKey, that.userOptions);
  110. mask.call(el.inputmask);
  111. }
  112. }
  113. });
  114. return elems && elems[0] ? elems[0].inputmask || this : this;
  115. },
  116. option: function (options, noremask) {
  117. // set extra options || retrieve value of a current option
  118. if (typeof options === "string") {
  119. return this.opts[options];
  120. } else if (typeof options === "object") {
  121. $.extend(this.userOptions, options); // user passed options
  122. // remask
  123. if (this.el && noremask !== true) {
  124. this.mask(this.el);
  125. }
  126. return this;
  127. }
  128. },
  129. unmaskedvalue: function (value) {
  130. this.maskset =
  131. this.maskset || generateMaskSet(this.opts, this.noMasksCache);
  132. if (this.el === undefined || value !== undefined) {
  133. const valueBuffer = (
  134. typeof this.opts.onBeforeMask === "function"
  135. ? this.opts.onBeforeMask.call(this, value, this.opts) || value
  136. : value
  137. ).split("");
  138. checkVal.call(this, undefined, false, false, valueBuffer);
  139. if (typeof this.opts.onBeforeWrite === "function")
  140. this.opts.onBeforeWrite.call(
  141. this,
  142. undefined,
  143. getBuffer.call(this),
  144. 0,
  145. this.opts
  146. );
  147. }
  148. return unmaskedvalue.call(this, this.el);
  149. },
  150. remove: function () {
  151. if (this.el) {
  152. $.data(this.el, dataKey, null); // invalidate
  153. // writeout the value
  154. const cv = this.opts.autoUnmask
  155. ? unmaskedvalue(this.el)
  156. : this._valueGet(this.opts.autoUnmask);
  157. if (cv !== getBufferTemplate.call(this).join(""))
  158. this._valueSet(cv, this.opts.autoUnmask);
  159. else this._valueSet("");
  160. // unbind all events
  161. EventRuler.off(this.el);
  162. // restore the value property
  163. let valueProperty;
  164. if (Object.getOwnPropertyDescriptor && Object.getPrototypeOf) {
  165. valueProperty = Object.getOwnPropertyDescriptor(
  166. Object.getPrototypeOf(this.el),
  167. "value"
  168. );
  169. if (valueProperty) {
  170. if (this.__valueGet) {
  171. Object.defineProperty(this.el, "value", {
  172. get: this.__valueGet,
  173. set: this.__valueSet,
  174. configurable: true
  175. });
  176. }
  177. }
  178. } else if (
  179. document.__lookupGetter__ &&
  180. this.el.__lookupGetter__("value")
  181. ) {
  182. if (this.__valueGet) {
  183. this.el.__defineGetter__("value", this.__valueGet);
  184. this.el.__defineSetter__("value", this.__valueSet);
  185. }
  186. }
  187. // clear data
  188. this.el.inputmask = undefined;
  189. }
  190. return this.el;
  191. },
  192. getemptymask: function () {
  193. // return the default (empty) mask value, usefull for setting the default value in validation
  194. this.maskset =
  195. this.maskset || generateMaskSet(this.opts, this.noMasksCache);
  196. return (
  197. this.isRTL
  198. ? getBufferTemplate.call(this).reverse()
  199. : getBufferTemplate.call(this)
  200. ).join("");
  201. },
  202. hasMaskedValue: function () {
  203. // check wheter the returned value is masked or not; currently only works reliable when using jquery.val fn to retrieve the value
  204. return !this.opts.autoUnmask;
  205. },
  206. isComplete: function () {
  207. this.maskset =
  208. this.maskset || generateMaskSet(this.opts, this.noMasksCache);
  209. return isComplete.call(this, getBuffer.call(this));
  210. },
  211. getmetadata: function () {
  212. // return mask metadata if exists
  213. this.maskset =
  214. this.maskset || generateMaskSet(this.opts, this.noMasksCache);
  215. if (Array.isArray(this.maskset.metadata)) {
  216. let maskTarget = getMaskTemplate.call(this, true, 0, false).join("");
  217. this.maskset.metadata.forEach(function (mtdt) {
  218. if (mtdt.mask === maskTarget) {
  219. maskTarget = mtdt;
  220. return false;
  221. }
  222. return true;
  223. });
  224. return maskTarget;
  225. }
  226. return this.maskset.metadata;
  227. },
  228. isValid: function (value) {
  229. this.maskset =
  230. this.maskset || generateMaskSet(this.opts, this.noMasksCache);
  231. if (value) {
  232. const valueBuffer = (
  233. typeof this.opts.onBeforeMask === "function"
  234. ? this.opts.onBeforeMask.call(this, value, this.opts) || value
  235. : value
  236. ).split("");
  237. checkVal.call(this, undefined, true, false, valueBuffer);
  238. } else {
  239. value = this.isRTL
  240. ? getBuffer.call(this).slice().reverse().join("")
  241. : getBuffer.call(this).join("");
  242. }
  243. let buffer = getBuffer.call(this),
  244. rl = determineLastRequiredPosition.call(this),
  245. lmib = buffer.length - 1;
  246. for (; lmib > rl; lmib--) {
  247. if (isMask.call(this, lmib)) break;
  248. }
  249. buffer.splice(rl, lmib + 1 - rl);
  250. return (
  251. isComplete.call(this, buffer) &&
  252. value ===
  253. (this.isRTL
  254. ? getBuffer.call(this).slice().reverse().join("")
  255. : getBuffer.call(this).join(""))
  256. );
  257. },
  258. format: function (value, metadata) {
  259. this.maskset =
  260. this.maskset || generateMaskSet(this.opts, this.noMasksCache);
  261. const valueBuffer = (
  262. typeof this.opts.onBeforeMask === "function"
  263. ? this.opts.onBeforeMask.call(this, value, this.opts) || value
  264. : value
  265. ).split("");
  266. checkVal.call(this, undefined, true, false, valueBuffer);
  267. const formattedValue = this.isRTL
  268. ? getBuffer.call(this).slice().reverse().join("")
  269. : getBuffer.call(this).join("");
  270. return metadata
  271. ? {
  272. value: formattedValue,
  273. metadata: this.getmetadata()
  274. }
  275. : formattedValue;
  276. },
  277. setValue: function (value) {
  278. if (this.el) {
  279. $(this.el).trigger("setvalue", [value]);
  280. }
  281. },
  282. analyseMask
  283. };
  284. function resolveAlias(aliasStr, options, opts) {
  285. const aliasDefinition = Inputmask.prototype.aliases[aliasStr];
  286. if (aliasDefinition) {
  287. if (aliasDefinition.alias)
  288. resolveAlias(aliasDefinition.alias, undefined, opts); // alias is another alias
  289. $.extend(true, opts, aliasDefinition); // merge alias definition in the options
  290. $.extend(true, opts, options); // reapply extra given options
  291. return true;
  292. } // alias not found - try as mask
  293. else if (opts.mask === null) {
  294. opts.mask = aliasStr;
  295. }
  296. return false;
  297. }
  298. function importAttributeOptions(npt, opts, userOptions, dataAttribute) {
  299. function importOption(option, optionData) {
  300. const attrOption =
  301. dataAttribute === "" ? option : dataAttribute + "-" + option;
  302. optionData =
  303. optionData !== undefined ? optionData : npt.getAttribute(attrOption);
  304. if (optionData !== null) {
  305. if (typeof optionData === "string") {
  306. if (option.indexOf("on") === 0) {
  307. optionData = window[optionData];
  308. } // get function definition
  309. else if (optionData === "false") {
  310. optionData = false;
  311. } else if (optionData === "true") optionData = true;
  312. }
  313. userOptions[option] = optionData;
  314. }
  315. }
  316. if (opts.importDataAttributes === true) {
  317. let attrOptions = npt.getAttribute(dataAttribute),
  318. option,
  319. dataoptions,
  320. optionData,
  321. p;
  322. if (attrOptions && attrOptions !== "") {
  323. attrOptions = attrOptions.replace(/'/g, '"');
  324. dataoptions = JSON.parse("{" + attrOptions + "}");
  325. }
  326. // resolve aliases
  327. if (dataoptions) {
  328. // pickup alias from dataAttribute
  329. optionData = undefined;
  330. for (p in dataoptions) {
  331. if (p.toLowerCase() === "alias") {
  332. optionData = dataoptions[p];
  333. break;
  334. }
  335. }
  336. }
  337. importOption("alias", optionData); // pickup alias from dataAttribute-alias
  338. if (userOptions.alias) {
  339. resolveAlias(userOptions.alias, userOptions, opts);
  340. }
  341. for (option in opts) {
  342. if (dataoptions) {
  343. optionData = undefined;
  344. for (p in dataoptions) {
  345. if (p.toLowerCase() === option.toLowerCase()) {
  346. optionData = dataoptions[p];
  347. break;
  348. }
  349. }
  350. }
  351. importOption(option, optionData);
  352. }
  353. }
  354. $.extend(true, opts, userOptions);
  355. // handle dir=rtl
  356. if (npt.dir === "rtl" || opts.rightAlign) {
  357. npt.style.textAlign = "right";
  358. }
  359. if (npt.dir === "rtl" || opts.numericInput) {
  360. npt.dir = "ltr";
  361. npt.removeAttribute("dir");
  362. opts.isRTL = true;
  363. }
  364. return Object.keys(userOptions).length;
  365. }
  366. // apply defaults, definitions, aliases
  367. Inputmask.extendDefaults = function (options) {
  368. $.extend(true, Inputmask.prototype.defaults, options);
  369. };
  370. Inputmask.extendDefinitions = function (definition) {
  371. $.extend(true, Inputmask.prototype.definitions, definition);
  372. };
  373. Inputmask.extendAliases = function (alias) {
  374. $.extend(true, Inputmask.prototype.aliases, alias);
  375. };
  376. // static fn on inputmask
  377. Inputmask.format = function (value, options, metadata) {
  378. return Inputmask(options).format(value, metadata);
  379. };
  380. Inputmask.unmask = function (value, options) {
  381. return Inputmask(options).unmaskedvalue(value);
  382. };
  383. Inputmask.isValid = function (value, options) {
  384. return Inputmask(options).isValid(value);
  385. };
  386. Inputmask.remove = function (elems) {
  387. if (typeof elems === "string") {
  388. elems = document.getElementById(elems) || document.querySelectorAll(elems);
  389. }
  390. elems = elems.nodeName ? [elems] : elems;
  391. elems.forEach(function (el) {
  392. if (el.inputmask) el.inputmask.remove();
  393. });
  394. };
  395. Inputmask.setValue = function (elems, value) {
  396. if (typeof elems === "string") {
  397. elems = document.getElementById(elems) || document.querySelectorAll(elems);
  398. }
  399. elems = elems.nodeName ? [elems] : elems;
  400. elems.forEach(function (el) {
  401. if (el.inputmask) el.inputmask.setValue(value);
  402. else $(el).trigger("setvalue", [value]);
  403. });
  404. };
  405. Inputmask.dependencyLib = $;
  406. // make inputmask available
  407. window.Inputmask = Inputmask;
  408. export default Inputmask;