inputmask.js 13 KB

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