formatter.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
  1. /*!
  2. * v0.1.5
  3. * Copyright (c) 2014 First Opinion
  4. * formatter.js is open sourced under the MIT license.
  5. *
  6. * thanks to digitalBush/jquery.maskedinput for some of the trickier
  7. * keycode handling
  8. */
  9. //
  10. // Uses Node, AMD or browser globals to create a module. This example creates
  11. // a global even when AMD is used. This is useful if you have some scripts
  12. // that are loaded by an AMD loader, but they still want access to globals.
  13. // If you do not need to export a global for the AMD case,
  14. // see returnExports.js.
  15. //
  16. // If you want something that will work in other stricter CommonJS environments,
  17. // or if you need to create a circular dependency, see commonJsStrictGlobal.js
  18. //
  19. // Defines a module "returnExportsGlobal" that depends another module called
  20. // "b". Note that the name of the module is implied by the file name. It is
  21. // best if the file name and the exported global have matching names.
  22. //
  23. // If the 'b' module also uses this type of boilerplate, then
  24. // in the browser, it will create a global .b that is used below.
  25. //
  26. (function (root, factory) {
  27. if (typeof define === 'function' && define.amd) {
  28. // AMD. Register as an anonymous module.
  29. define([], function () {
  30. return (root.returnExportsGlobal = factory());
  31. });
  32. } else if (typeof exports === 'object') {
  33. // Node. Does not work with strict CommonJS, but
  34. // only CommonJS-like enviroments that support module.exports,
  35. // like Node.
  36. module.exports = factory();
  37. } else {
  38. root['Formatter'] = factory();
  39. }
  40. }(this, function () {
  41. /*
  42. * pattern.js
  43. *
  44. * Utilities to parse str pattern and return info
  45. *
  46. */
  47. var pattern = function () {
  48. // Define module
  49. var pattern = {};
  50. // Match information
  51. var DELIM_SIZE = 4;
  52. // Our regex used to parse
  53. var regexp = new RegExp('{{([^}]+)}}', 'g');
  54. //
  55. // Helper method to parse pattern str
  56. //
  57. var getMatches = function (pattern) {
  58. // Populate array of matches
  59. var matches = [], match;
  60. while (match = regexp.exec(pattern)) {
  61. matches.push(match);
  62. }
  63. return matches;
  64. };
  65. //
  66. // Create an object holding all formatted characters
  67. // with corresponding positions
  68. //
  69. pattern.parse = function (pattern) {
  70. // Our obj to populate
  71. var info = {
  72. inpts: {},
  73. chars: {}
  74. };
  75. // Pattern information
  76. var matches = getMatches(pattern), pLength = pattern.length;
  77. // Counters
  78. var mCount = 0, iCount = 0, i = 0;
  79. // Add inpts, move to end of match, and process
  80. var processMatch = function (val) {
  81. var valLength = val.length;
  82. for (var j = 0; j < valLength; j++) {
  83. info.inpts[iCount] = val.charAt(j);
  84. iCount++;
  85. }
  86. mCount++;
  87. i += val.length + DELIM_SIZE - 1;
  88. };
  89. // Process match or add chars
  90. for (i; i < pLength; i++) {
  91. if (mCount < matches.length && i === matches[mCount].index) {
  92. processMatch(matches[mCount][1]);
  93. } else {
  94. info.chars[i - mCount * DELIM_SIZE] = pattern.charAt(i);
  95. }
  96. }
  97. // Set mLength and return
  98. info.mLength = i - mCount * DELIM_SIZE;
  99. return info;
  100. };
  101. // Expose
  102. return pattern;
  103. }();
  104. /*
  105. * utils.js
  106. *
  107. * Independent helper methods (cross browser, etc..)
  108. *
  109. */
  110. var utils = function () {
  111. // Define module
  112. var utils = {};
  113. // Useragent info for keycode handling
  114. var uAgent = typeof navigator !== 'undefined' ? navigator.userAgent : null;
  115. //
  116. // Shallow copy properties from n objects to destObj
  117. //
  118. utils.extend = function (destObj) {
  119. for (var i = 1; i < arguments.length; i++) {
  120. for (var key in arguments[i]) {
  121. destObj[key] = arguments[i][key];
  122. }
  123. }
  124. return destObj;
  125. };
  126. //
  127. // Add a given character to a string at a defined pos
  128. //
  129. utils.addChars = function (str, chars, pos) {
  130. return str.substr(0, pos) + chars + str.substr(pos, str.length);
  131. };
  132. //
  133. // Remove a span of characters
  134. //
  135. utils.removeChars = function (str, start, end) {
  136. return str.substr(0, start) + str.substr(end, str.length);
  137. };
  138. //
  139. // Return true/false is num false between bounds
  140. //
  141. utils.isBetween = function (num, bounds) {
  142. bounds.sort(function (a, b) {
  143. return a - b;
  144. });
  145. return num > bounds[0] && num < bounds[1];
  146. };
  147. //
  148. // Helper method for cross browser event listeners
  149. //
  150. utils.addListener = function (el, evt, handler) {
  151. return typeof el.addEventListener !== 'undefined' ? el.addEventListener(evt, handler, false) : el.attachEvent('on' + evt, handler);
  152. };
  153. //
  154. // Helper method for cross browser implementation of preventDefault
  155. //
  156. utils.preventDefault = function (evt) {
  157. return evt.preventDefault ? evt.preventDefault() : evt.returnValue = false;
  158. };
  159. //
  160. // Helper method for cross browser implementation for grabbing
  161. // clipboard data
  162. //
  163. utils.getClip = function (evt) {
  164. if (evt.clipboardData) {
  165. return evt.clipboardData.getData('Text');
  166. }
  167. if (window.clipboardData) {
  168. return window.clipboardData.getData('Text');
  169. }
  170. };
  171. //
  172. // Loop over object and checking for matching properties
  173. //
  174. utils.getMatchingKey = function (which, keyCode, keys) {
  175. // Loop over and return if matched.
  176. for (var k in keys) {
  177. var key = keys[k];
  178. if (which === key.which && keyCode === key.keyCode) {
  179. return k;
  180. }
  181. }
  182. };
  183. //
  184. // Returns true/false if k is a del keyDown
  185. //
  186. utils.isDelKeyDown = function (which, keyCode) {
  187. var keys = {
  188. 'backspace': {
  189. 'which': 8,
  190. 'keyCode': 8
  191. },
  192. 'delete': {
  193. 'which': 46,
  194. 'keyCode': 46
  195. }
  196. };
  197. return utils.getMatchingKey(which, keyCode, keys);
  198. };
  199. //
  200. // Returns true/false if k is a del keyPress
  201. //
  202. utils.isDelKeyPress = function (which, keyCode) {
  203. var keys = {
  204. 'backspace': {
  205. 'which': 8,
  206. 'keyCode': 8,
  207. 'shiftKey': false
  208. },
  209. 'delete': {
  210. 'which': 0,
  211. 'keyCode': 46
  212. }
  213. };
  214. return utils.getMatchingKey(which, keyCode, keys);
  215. };
  216. // //
  217. // // Determine if keydown relates to specialKey
  218. // //
  219. // utils.isSpecialKeyDown = function (which, keyCode) {
  220. // var keys = {
  221. // 'tab': { 'which': 9, 'keyCode': 9 },
  222. // 'enter': { 'which': 13, 'keyCode': 13 },
  223. // 'end': { 'which': 35, 'keyCode': 35 },
  224. // 'home': { 'which': 36, 'keyCode': 36 },
  225. // 'leftarrow': { 'which': 37, 'keyCode': 37 },
  226. // 'uparrow': { 'which': 38, 'keyCode': 38 },
  227. // 'rightarrow': { 'which': 39, 'keyCode': 39 },
  228. // 'downarrow': { 'which': 40, 'keyCode': 40 },
  229. // 'F5': { 'which': 116, 'keyCode': 116 }
  230. // };
  231. // return utils.getMatchingKey(which, keyCode, keys);
  232. // };
  233. //
  234. // Determine if keypress relates to specialKey
  235. //
  236. utils.isSpecialKeyPress = function (which, keyCode) {
  237. var keys = {
  238. 'tab': {
  239. 'which': 0,
  240. 'keyCode': 9
  241. },
  242. 'enter': {
  243. 'which': 13,
  244. 'keyCode': 13
  245. },
  246. 'end': {
  247. 'which': 0,
  248. 'keyCode': 35
  249. },
  250. 'home': {
  251. 'which': 0,
  252. 'keyCode': 36
  253. },
  254. 'leftarrow': {
  255. 'which': 0,
  256. 'keyCode': 37
  257. },
  258. 'uparrow': {
  259. 'which': 0,
  260. 'keyCode': 38
  261. },
  262. 'rightarrow': {
  263. 'which': 0,
  264. 'keyCode': 39
  265. },
  266. 'downarrow': {
  267. 'which': 0,
  268. 'keyCode': 40
  269. },
  270. 'F5': {
  271. 'which': 116,
  272. 'keyCode': 116
  273. }
  274. };
  275. return utils.getMatchingKey(which, keyCode, keys);
  276. };
  277. //
  278. // Returns true/false if modifier key is held down
  279. //
  280. utils.isModifier = function (evt) {
  281. return evt.ctrlKey || evt.altKey || evt.metaKey;
  282. };
  283. //
  284. // Iterates over each property of object or array.
  285. //
  286. utils.forEach = function (collection, callback, thisArg) {
  287. if (collection.hasOwnProperty('length')) {
  288. for (var index = 0, len = collection.length; index < len; index++) {
  289. if (callback.call(thisArg, collection[index], index, collection) === false) {
  290. break;
  291. }
  292. }
  293. } else {
  294. for (var key in collection) {
  295. if (collection.hasOwnProperty(key)) {
  296. if (callback.call(thisArg, collection[key], key, collection) === false) {
  297. break;
  298. }
  299. }
  300. }
  301. }
  302. };
  303. // Expose
  304. return utils;
  305. }();
  306. /*
  307. * pattern-matcher.js
  308. *
  309. * Parses a pattern specification and determines appropriate pattern for an
  310. * input string
  311. *
  312. */
  313. var patternMatcher = function (pattern, utils) {
  314. //
  315. // Parse a matcher string into a RegExp. Accepts valid regular
  316. // expressions and the catchall '*'.
  317. // @private
  318. //
  319. var parseMatcher = function (matcher) {
  320. if (matcher === '*') {
  321. return /.*/;
  322. }
  323. return new RegExp(matcher);
  324. };
  325. //
  326. // Parse a pattern spec and return a function that returns a pattern
  327. // based on user input. The first matching pattern will be chosen.
  328. // Pattern spec format:
  329. // Array [
  330. // Object: { Matcher(RegExp String) : Pattern(Pattern String) },
  331. // ...
  332. // ]
  333. function patternMatcher(patternSpec) {
  334. var matchers = [], patterns = [];
  335. // Iterate over each pattern in order.
  336. utils.forEach(patternSpec, function (patternMatcher) {
  337. // Process single property object to obtain pattern and matcher.
  338. utils.forEach(patternMatcher, function (patternStr, matcherStr) {
  339. var parsedPattern = pattern.parse(patternStr), regExpMatcher = parseMatcher(matcherStr);
  340. matchers.push(regExpMatcher);
  341. patterns.push(parsedPattern);
  342. // Stop after one iteration.
  343. return false;
  344. });
  345. });
  346. var getPattern = function (input) {
  347. var matchedIndex;
  348. utils.forEach(matchers, function (matcher, index) {
  349. if (matcher.test(input)) {
  350. matchedIndex = index;
  351. return false;
  352. }
  353. });
  354. return matchedIndex === undefined ? null : patterns[matchedIndex];
  355. };
  356. return {
  357. getPattern: getPattern,
  358. patterns: patterns,
  359. matchers: matchers
  360. };
  361. }
  362. // Expose
  363. return patternMatcher;
  364. }(pattern, utils);
  365. /*
  366. * inpt-sel.js
  367. *
  368. * Cross browser implementation to get and set input selections
  369. *
  370. */
  371. var inptSel = function () {
  372. // Define module
  373. var inptSel = {};
  374. //
  375. // Get begin and end positions of selected input. Return 0's
  376. // if there is no selectiion data
  377. //
  378. inptSel.get = function (el) {
  379. // If normal browser return with result
  380. if (typeof el.selectionStart === 'number') {
  381. return {
  382. begin: el.selectionStart,
  383. end: el.selectionEnd
  384. };
  385. }
  386. // Uh-Oh. We must be IE. Fun with TextRange!!
  387. var range = document.selection.createRange();
  388. // Determine if there is a selection
  389. if (range && range.parentElement() === el) {
  390. var inputRange = el.createTextRange(), endRange = el.createTextRange(), length = el.value.length;
  391. // Create a working TextRange for the input selection
  392. inputRange.moveToBookmark(range.getBookmark());
  393. // Move endRange begin pos to end pos (hence endRange)
  394. endRange.collapse(false);
  395. // If we are at the very end of the input, begin and end
  396. // must both be the length of the el.value
  397. if (inputRange.compareEndPoints('StartToEnd', endRange) > -1) {
  398. return {
  399. begin: length,
  400. end: length
  401. };
  402. }
  403. // Note: moveStart usually returns the units moved, which
  404. // one may think is -length, however, it will stop when it
  405. // gets to the begin of the range, thus giving us the
  406. // negative value of the pos.
  407. return {
  408. begin: -inputRange.moveStart('character', -length),
  409. end: -inputRange.moveEnd('character', -length)
  410. };
  411. }
  412. //Return 0's on no selection data
  413. return {
  414. begin: 0,
  415. end: 0
  416. };
  417. };
  418. //
  419. // Set the caret position at a specified location
  420. //
  421. inptSel.set = function (el, pos) {
  422. // Normalize pos
  423. if (typeof pos !== 'object') {
  424. pos = {
  425. begin: pos,
  426. end: pos
  427. };
  428. }
  429. // If normal browser
  430. if (el.setSelectionRange) {
  431. el.focus();
  432. el.setSelectionRange(pos.begin, pos.end);
  433. } else if (el.createTextRange) {
  434. var range = el.createTextRange();
  435. range.collapse(true);
  436. range.moveEnd('character', pos.end);
  437. range.moveStart('character', pos.begin);
  438. range.select();
  439. }
  440. };
  441. // Expose
  442. return inptSel;
  443. }();
  444. /*
  445. * formatter.js
  446. *
  447. * Class used to format input based on passed pattern
  448. *
  449. */
  450. var formatter = function (patternMatcher, inptSel, utils) {
  451. // Defaults
  452. var defaults = {
  453. persistent: false,
  454. repeat: false,
  455. placeholder: ' '
  456. };
  457. // Regexs for input validation
  458. var inptRegs = {
  459. '9': /[0-9]/,
  460. 'a': /[A-Za-z]/,
  461. '*': /[A-Za-z0-9]/
  462. };
  463. //
  464. // Class Constructor - Called with new Formatter(el, opts)
  465. // Responsible for setting up required instance variables, and
  466. // attaching the event listener to the element.
  467. //
  468. function Formatter(el, opts) {
  469. // Cache this
  470. var self = this;
  471. // Make sure we have an element. Make accesible to instance
  472. self.el = el;
  473. if (!self.el) {
  474. throw new TypeError('Must provide an existing element');
  475. }
  476. // Merge opts with defaults
  477. self.opts = utils.extend({}, defaults, opts);
  478. // 1 pattern is special case
  479. if (typeof self.opts.pattern !== 'undefined') {
  480. self.opts.patterns = self._specFromSinglePattern(self.opts.pattern);
  481. delete self.opts.pattern;
  482. }
  483. // Make sure we have valid opts
  484. if (typeof self.opts.patterns === 'undefined') {
  485. throw new TypeError('Must provide a pattern or array of patterns');
  486. }
  487. self.patternMatcher = patternMatcher(self.opts.patterns);
  488. // Upate pattern with initial value
  489. self._updatePattern();
  490. // Init values
  491. self.hldrs = {};
  492. self.focus = 0;
  493. // Add Listeners
  494. utils.addListener(self.el, 'keydown', function (evt) {
  495. self._keyDown(evt);
  496. });
  497. utils.addListener(self.el, 'keypress', function (evt) {
  498. self._keyPress(evt);
  499. });
  500. utils.addListener(self.el, 'paste', function (evt) {
  501. self._paste(evt);
  502. });
  503. // Persistence
  504. if (self.opts.persistent) {
  505. // Format on start
  506. self._processKey('', false);
  507. self.el.blur();
  508. // Add Listeners
  509. utils.addListener(self.el, 'focus', function (evt) {
  510. self._focus(evt);
  511. });
  512. utils.addListener(self.el, 'click', function (evt) {
  513. self._focus(evt);
  514. });
  515. utils.addListener(self.el, 'touchstart', function (evt) {
  516. self._focus(evt);
  517. });
  518. }
  519. }
  520. //
  521. // @public
  522. // Add new char
  523. //
  524. Formatter.addInptType = function (chr, reg) {
  525. inptRegs[chr] = reg;
  526. };
  527. //
  528. // @public
  529. // Apply the given pattern to the current input without moving caret.
  530. //
  531. Formatter.prototype.resetPattern = function (str) {
  532. // Update opts to hold new pattern
  533. this.opts.patterns = str ? this._specFromSinglePattern(str) : this.opts.patterns;
  534. // Get current state
  535. this.sel = inptSel.get(this.el);
  536. this.val = this.el.value;
  537. // Init values
  538. this.delta = 0;
  539. // Remove all formatted chars from val
  540. this._removeChars();
  541. this.patternMatcher = patternMatcher(this.opts.patterns);
  542. // Update pattern
  543. var newPattern = this.patternMatcher.getPattern(this.val);
  544. this.mLength = newPattern.mLength;
  545. this.chars = newPattern.chars;
  546. this.inpts = newPattern.inpts;
  547. // Format on start
  548. this._processKey('', false, true);
  549. };
  550. //
  551. // @private
  552. // Determine correct format pattern based on input val
  553. //
  554. Formatter.prototype._updatePattern = function () {
  555. // Determine appropriate pattern
  556. var newPattern = this.patternMatcher.getPattern(this.val);
  557. // Only update the pattern if there is an appropriate pattern for the value.
  558. // Otherwise, leave the current pattern (and likely delete the latest character.)
  559. if (newPattern) {
  560. // Get info about the given pattern
  561. this.mLength = newPattern.mLength;
  562. this.chars = newPattern.chars;
  563. this.inpts = newPattern.inpts;
  564. }
  565. };
  566. //
  567. // @private
  568. // Handler called on all keyDown strokes. All keys trigger
  569. // this handler. Only process delete keys.
  570. //
  571. Formatter.prototype._keyDown = function (evt) {
  572. // The first thing we need is the character code
  573. var k = evt.which || evt.keyCode;
  574. // If delete key
  575. if (k && utils.isDelKeyDown(evt.which, evt.keyCode)) {
  576. // Process the keyCode and prevent default
  577. this._processKey(null, k);
  578. return utils.preventDefault(evt);
  579. }
  580. };
  581. //
  582. // @private
  583. // Handler called on all keyPress strokes. Only processes
  584. // character keys (as long as no modifier key is in use).
  585. //
  586. Formatter.prototype._keyPress = function (evt) {
  587. // The first thing we need is the character code
  588. var k, isSpecial;
  589. // Mozilla will trigger on special keys and assign the the value 0
  590. // We want to use that 0 rather than the keyCode it assigns.
  591. k = evt.which || evt.keyCode;
  592. isSpecial = utils.isSpecialKeyPress(evt.which, evt.keyCode);
  593. // Process the keyCode and prevent default
  594. if (!utils.isDelKeyPress(evt.which, evt.keyCode) && !isSpecial && !utils.isModifier(evt)) {
  595. this._processKey(String.fromCharCode(k), false);
  596. return utils.preventDefault(evt);
  597. }
  598. };
  599. //
  600. // @private
  601. // Handler called on paste event.
  602. //
  603. Formatter.prototype._paste = function (evt) {
  604. // Process the clipboard paste and prevent default
  605. this._processKey(utils.getClip(evt), false);
  606. return utils.preventDefault(evt);
  607. };
  608. //
  609. // @private
  610. // Handle called on focus event.
  611. //
  612. Formatter.prototype._focus = function () {
  613. // Wrapped in timeout so that we can grab input selection
  614. var self = this;
  615. setTimeout(function () {
  616. // Grab selection
  617. var selection = inptSel.get(self.el);
  618. // Char check
  619. var isAfterStart = selection.end > self.focus, isFirstChar = selection.end === 0;
  620. // If clicked in front of start, refocus to start
  621. if (isAfterStart || isFirstChar) {
  622. inptSel.set(self.el, self.focus);
  623. }
  624. }, 0);
  625. };
  626. //
  627. // @private
  628. // Using the provided key information, alter el value.
  629. //
  630. Formatter.prototype._processKey = function (chars, delKey, ignoreCaret) {
  631. // Get current state
  632. this.sel = inptSel.get(this.el);
  633. this.val = this.el.value;
  634. // Init values
  635. this.delta = 0;
  636. // If chars were highlighted, we need to remove them
  637. if (this.sel.begin !== this.sel.end) {
  638. this.delta = -1 * Math.abs(this.sel.begin - this.sel.end);
  639. this.val = utils.removeChars(this.val, this.sel.begin, this.sel.end);
  640. } else if (delKey && delKey === 46) {
  641. this._delete();
  642. } else if (delKey && this.sel.begin - 1 >= 0) {
  643. // Always have a delta of at least -1 for the character being deleted.
  644. this.val = utils.removeChars(this.val, this.sel.end - 1, this.sel.end);
  645. this.delta -= 1;
  646. } else if (delKey) {
  647. return true;
  648. }
  649. // If the key is not a del key, it should convert to a str
  650. if (!delKey) {
  651. // Add char at position and increment delta
  652. this.val = utils.addChars(this.val, chars, this.sel.begin);
  653. this.delta += chars.length;
  654. }
  655. // Format el.value (also handles updating caret position)
  656. this._formatValue(ignoreCaret);
  657. };
  658. //
  659. // @private
  660. // Deletes the character in front of it
  661. //
  662. Formatter.prototype._delete = function () {
  663. // Adjust focus to make sure its not on a formatted char
  664. while (this.chars[this.sel.begin]) {
  665. this._nextPos();
  666. }
  667. // As long as we are not at the end
  668. if (this.sel.begin < this.val.length) {
  669. // We will simulate a delete by moving the caret to the next char
  670. // and then deleting
  671. this._nextPos();
  672. this.val = utils.removeChars(this.val, this.sel.end - 1, this.sel.end);
  673. this.delta = -1;
  674. }
  675. };
  676. //
  677. // @private
  678. // Quick helper method to move the caret to the next pos
  679. //
  680. Formatter.prototype._nextPos = function () {
  681. this.sel.end++;
  682. this.sel.begin++;
  683. };
  684. //
  685. // @private
  686. // Alter element value to display characters matching the provided
  687. // instance pattern. Also responsible for updating
  688. //
  689. Formatter.prototype._formatValue = function (ignoreCaret) {
  690. // Set caret pos
  691. this.newPos = this.sel.end + this.delta;
  692. // Remove all formatted chars from val
  693. this._removeChars();
  694. // Switch to first matching pattern based on val
  695. this._updatePattern();
  696. // Validate inputs
  697. this._validateInpts();
  698. // Add formatted characters
  699. this._addChars();
  700. // Set value and adhere to maxLength
  701. this.el.value = this.val.substr(0, this.mLength);
  702. // Set new caret position
  703. if (typeof ignoreCaret === 'undefined' || ignoreCaret === false) {
  704. inptSel.set(this.el, this.newPos);
  705. }
  706. };
  707. //
  708. // @private
  709. // Remove all formatted before and after a specified pos
  710. //
  711. Formatter.prototype._removeChars = function () {
  712. // Delta shouldn't include placeholders
  713. if (this.sel.end > this.focus) {
  714. this.delta += this.sel.end - this.focus;
  715. }
  716. // Account for shifts during removal
  717. var shift = 0;
  718. // Loop through all possible char positions
  719. for (var i = 0; i <= this.mLength; i++) {
  720. // Get transformed position
  721. var curChar = this.chars[i], curHldr = this.hldrs[i], pos = i + shift, val;
  722. // If after selection we need to account for delta
  723. pos = i >= this.sel.begin ? pos + this.delta : pos;
  724. val = this.val.charAt(pos);
  725. // Remove char and account for shift
  726. if (curChar && curChar === val || curHldr && curHldr === val) {
  727. this.val = utils.removeChars(this.val, pos, pos + 1);
  728. shift--;
  729. }
  730. }
  731. // All hldrs should be removed now
  732. this.hldrs = {};
  733. // Set focus to last character
  734. this.focus = this.val.length;
  735. };
  736. //
  737. // @private
  738. // Make sure all inpts are valid, else remove and update delta
  739. //
  740. Formatter.prototype._validateInpts = function () {
  741. // Loop over each char and validate
  742. for (var i = 0; i < this.val.length; i++) {
  743. // Get char inpt type
  744. var inptType = this.inpts[i];
  745. // Checks
  746. var isBadType = !inptRegs[inptType], isInvalid = !isBadType && !inptRegs[inptType].test(this.val.charAt(i)), inBounds = this.inpts[i];
  747. // Remove if incorrect and inbounds
  748. if ((isBadType || isInvalid) && inBounds) {
  749. this.val = utils.removeChars(this.val, i, i + 1);
  750. this.focusStart--;
  751. this.newPos--;
  752. this.delta--;
  753. i--;
  754. }
  755. }
  756. };
  757. //
  758. // @private
  759. // Loop over val and add formatted chars as necessary
  760. //
  761. Formatter.prototype._addChars = function () {
  762. if (this.opts.persistent) {
  763. // Loop over all possible characters
  764. for (var i = 0; i <= this.mLength; i++) {
  765. if (!this.val.charAt(i)) {
  766. // Add placeholder at pos
  767. this.val = utils.addChars(this.val, this.opts.placeholder, i);
  768. this.hldrs[i] = this.opts.placeholder;
  769. }
  770. this._addChar(i);
  771. }
  772. // Adjust focus to make sure its not on a formatted char
  773. while (this.chars[this.focus]) {
  774. this.focus++;
  775. }
  776. } else {
  777. // Avoid caching val.length, as they may change in _addChar.
  778. for (var j = 0; j <= this.val.length; j++) {
  779. // When moving backwards there are some race conditions where we
  780. // dont want to add the character
  781. if (this.delta <= 0 && j === this.focus) {
  782. return true;
  783. }
  784. // Place character in current position of the formatted string.
  785. this._addChar(j);
  786. }
  787. }
  788. };
  789. //
  790. // @private
  791. // Add formattted char at position
  792. //
  793. Formatter.prototype._addChar = function (i) {
  794. // If char exists at position
  795. var chr = this.chars[i];
  796. if (!chr) {
  797. return true;
  798. }
  799. // If chars are added in between the old pos and new pos
  800. // we need to increment pos and delta
  801. if (utils.isBetween(i, [
  802. this.sel.begin - 1,
  803. this.newPos + 1
  804. ])) {
  805. this.newPos++;
  806. this.delta++;
  807. }
  808. // If character added before focus, incr
  809. if (i <= this.focus) {
  810. this.focus++;
  811. }
  812. // Updateholder
  813. if (this.hldrs[i]) {
  814. delete this.hldrs[i];
  815. this.hldrs[i + 1] = this.opts.placeholder;
  816. }
  817. // Update value
  818. this.val = utils.addChars(this.val, chr, i);
  819. };
  820. //
  821. // @private
  822. // Create a patternSpec for passing into patternMatcher that
  823. // has exactly one catch all pattern.
  824. //
  825. Formatter.prototype._specFromSinglePattern = function (patternStr) {
  826. return [{ '*': patternStr }];
  827. };
  828. // Expose
  829. return Formatter;
  830. }(patternMatcher, inptSel, utils);
  831. return formatter;
  832. }));