inputmask.js 14 KB

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