inputmask.js 12 KB

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