Permalink
/*! | |
* v0.1.4 | |
* Copyright (c) 2014 First Opinion | |
* formatter.js is open sourced under the MIT license. | |
* | |
* thanks to digitalBush/jquery.maskedinput for some of the trickier | |
* keycode handling | |
*/ | |
// | |
// Uses Node, AMD or browser globals to create a module. This example creates | |
// a global even when AMD is used. This is useful if you have some scripts | |
// that are loaded by an AMD loader, but they still want access to globals. | |
// If you do not need to export a global for the AMD case, | |
// see returnExports.js. | |
// | |
// If you want something that will work in other stricter CommonJS environments, | |
// or if you need to create a circular dependency, see commonJsStrictGlobal.js | |
// | |
// Defines a module "returnExportsGlobal" that depends another module called | |
// "b". Note that the name of the module is implied by the file name. It is | |
// best if the file name and the exported global have matching names. | |
// | |
// If the 'b' module also uses this type of boilerplate, then | |
// in the browser, it will create a global .b that is used below. | |
// | |
(function (root, factory) { | |
if (typeof define === 'function' && define.amd) { | |
// AMD. Register as an anonymous module. | |
define([], function () { | |
return (root.returnExportsGlobal = factory()); | |
}); | |
} else if (typeof exports === 'object') { | |
// Node. Does not work with strict CommonJS, but | |
// only CommonJS-like enviroments that support module.exports, | |
// like Node. | |
module.exports = factory(); | |
} else { | |
root['Formatter'] = factory(); | |
} | |
}(this, function () { | |
/* | |
* pattern.js | |
* | |
* Utilities to parse str pattern and return info | |
* | |
*/ | |
var pattern = function () { | |
// Define module | |
var pattern = {}; | |
// Match information | |
var DELIM_SIZE = 4; | |
// Our regex used to parse | |
var regexp = new RegExp('{{([^}]+)}}', 'g'); | |
// | |
// Helper method to parse pattern str | |
// | |
var getMatches = function (pattern) { | |
// Populate array of matches | |
var matches = [], match; | |
while (match = regexp.exec(pattern)) { | |
matches.push(match); | |
} | |
return matches; | |
}; | |
// | |
// Create an object holding all formatted characters | |
// with corresponding positions | |
// | |
pattern.parse = function (pattern) { | |
// Our obj to populate | |
var info = { | |
inpts: {}, | |
chars: {} | |
}; | |
// Pattern information | |
var matches = getMatches(pattern), pLength = pattern.length; | |
// Counters | |
var mCount = 0, iCount = 0, i = 0; | |
// Add inpts, move to end of match, and process | |
var processMatch = function (val) { | |
var valLength = val.length; | |
for (var j = 0; j < valLength; j++) { | |
info.inpts[iCount] = val.charAt(j); | |
iCount++; | |
} | |
mCount++; | |
i += val.length + DELIM_SIZE - 1; | |
}; | |
// Process match or add chars | |
for (i; i < pLength; i++) { | |
if (mCount < matches.length && i === matches[mCount].index) { | |
processMatch(matches[mCount][1]); | |
} else { | |
info.chars[i - mCount * DELIM_SIZE] = pattern.charAt(i); | |
} | |
} | |
// Set mLength and return | |
info.mLength = i - mCount * DELIM_SIZE; | |
return info; | |
}; | |
// Expose | |
return pattern; | |
}(); | |
/* | |
* utils.js | |
* | |
* Independent helper methods (cross browser, etc..) | |
* | |
*/ | |
var utils = function () { | |
// Define module | |
var utils = {}; | |
// Useragent info for keycode handling | |
var uAgent = typeof navigator !== 'undefined' ? navigator.userAgent : null; | |
// | |
// Shallow copy properties from n objects to destObj | |
// | |
utils.extend = function (destObj) { | |
for (var i = 1; i < arguments.length; i++) { | |
for (var key in arguments[i]) { | |
destObj[key] = arguments[i][key]; | |
} | |
} | |
return destObj; | |
}; | |
// | |
// Add a given character to a string at a defined pos | |
// | |
utils.addChars = function (str, chars, pos) { | |
return str.substr(0, pos) + chars + str.substr(pos, str.length); | |
}; | |
// | |
// Remove a span of characters | |
// | |
utils.removeChars = function (str, start, end) { | |
return str.substr(0, start) + str.substr(end, str.length); | |
}; | |
// | |
// Return true/false is num false between bounds | |
// | |
utils.isBetween = function (num, bounds) { | |
bounds.sort(function (a, b) { | |
return a - b; | |
}); | |
return num > bounds[0] && num < bounds[1]; | |
}; | |
// | |
// Helper method for cross browser event listeners | |
// | |
utils.addListener = function (el, evt, handler) { | |
return typeof el.addEventListener !== 'undefined' ? el.addEventListener(evt, handler, false) : el.attachEvent('on' + evt, handler); | |
}; | |
// | |
// Helper method for cross browser implementation of preventDefault | |
// | |
utils.preventDefault = function (evt) { | |
return evt.preventDefault ? evt.preventDefault() : evt.returnValue = false; | |
}; | |
// | |
// Helper method for cross browser implementation for grabbing | |
// clipboard data | |
// | |
utils.getClip = function (evt) { | |
if (evt.clipboardData) { | |
return evt.clipboardData.getData('Text'); | |
} | |
if (window.clipboardData) { | |
return window.clipboardData.getData('Text'); | |
} | |
}; | |
// | |
// Loop over object and checking for matching properties | |
// | |
utils.getMatchingKey = function (which, keyCode, keys) { | |
// Loop over and return if matched. | |
for (var k in keys) { | |
var key = keys[k]; | |
if (which === key.which && keyCode === key.keyCode) { | |
return k; | |
} | |
} | |
}; | |
// | |
// Returns true/false if k is a del keyDown | |
// | |
utils.isDelKeyDown = function (which, keyCode) { | |
var keys = { | |
'backspace': { | |
'which': 8, | |
'keyCode': 8 | |
}, | |
'delete': { | |
'which': 46, | |
'keyCode': 46 | |
} | |
}; | |
return utils.getMatchingKey(which, keyCode, keys); | |
}; | |
// | |
// Returns true/false if k is a del keyPress | |
// | |
utils.isDelKeyPress = function (which, keyCode) { | |
var keys = { | |
'backspace': { | |
'which': 8, | |
'keyCode': 8, | |
'shiftKey': false | |
}, | |
'delete': { | |
'which': 0, | |
'keyCode': 46 | |
} | |
}; | |
return utils.getMatchingKey(which, keyCode, keys); | |
}; | |
// // | |
// // Determine if keydown relates to specialKey | |
// // | |
// utils.isSpecialKeyDown = function (which, keyCode) { | |
// var keys = { | |
// 'tab': { 'which': 9, 'keyCode': 9 }, | |
// 'enter': { 'which': 13, 'keyCode': 13 }, | |
// 'end': { 'which': 35, 'keyCode': 35 }, | |
// 'home': { 'which': 36, 'keyCode': 36 }, | |
// 'leftarrow': { 'which': 37, 'keyCode': 37 }, | |
// 'uparrow': { 'which': 38, 'keyCode': 38 }, | |
// 'rightarrow': { 'which': 39, 'keyCode': 39 }, | |
// 'downarrow': { 'which': 40, 'keyCode': 40 }, | |
// 'F5': { 'which': 116, 'keyCode': 116 } | |
// }; | |
// return utils.getMatchingKey(which, keyCode, keys); | |
// }; | |
// | |
// Determine if keypress relates to specialKey | |
// | |
utils.isSpecialKeyPress = function (which, keyCode) { | |
var keys = { | |
'tab': { | |
'which': 0, | |
'keyCode': 9 | |
}, | |
'enter': { | |
'which': 13, | |
'keyCode': 13 | |
}, | |
'end': { | |
'which': 0, | |
'keyCode': 35 | |
}, | |
'home': { | |
'which': 0, | |
'keyCode': 36 | |
}, | |
'leftarrow': { | |
'which': 0, | |
'keyCode': 37 | |
}, | |
'uparrow': { | |
'which': 0, | |
'keyCode': 38 | |
}, | |
'rightarrow': { | |
'which': 0, | |
'keyCode': 39 | |
}, | |
'downarrow': { | |
'which': 0, | |
'keyCode': 40 | |
}, | |
'F5': { | |
'which': 116, | |
'keyCode': 116 | |
} | |
}; | |
return utils.getMatchingKey(which, keyCode, keys); | |
}; | |
// | |
// Returns true/false if modifier key is held down | |
// | |
utils.isModifier = function (evt) { | |
return evt.ctrlKey || evt.altKey || evt.metaKey; | |
}; | |
// | |
// Iterates over each property of object or array. | |
// | |
utils.forEach = function (collection, callback, thisArg) { | |
if (collection.hasOwnProperty('length')) { | |
for (var index = 0, len = collection.length; index < len; index++) { | |
if (callback.call(thisArg, collection[index], index, collection) === false) { | |
break; | |
} | |
} | |
} else { | |
for (var key in collection) { | |
if (collection.hasOwnProperty(key)) { | |
if (callback.call(thisArg, collection[key], key, collection) === false) { | |
break; | |
} | |
} | |
} | |
} | |
}; | |
// Expose | |
return utils; | |
}(); | |
/* | |
* pattern-matcher.js | |
* | |
* Parses a pattern specification and determines appropriate pattern for an | |
* input string | |
* | |
*/ | |
var patternMatcher = function (pattern, utils) { | |
// | |
// Parse a matcher string into a RegExp. Accepts valid regular | |
// expressions and the catchall '*'. | |
// @private | |
// | |
var parseMatcher = function (matcher) { | |
if (matcher === '*') { | |
return /.*/; | |
} | |
return new RegExp(matcher); | |
}; | |
// | |
// Parse a pattern spec and return a function that returns a pattern | |
// based on user input. The first matching pattern will be chosen. | |
// Pattern spec format: | |
// Array [ | |
// Object: { Matcher(RegExp String) : Pattern(Pattern String) }, | |
// ... | |
// ] | |
function patternMatcher(patternSpec) { | |
var matchers = [], patterns = []; | |
// Iterate over each pattern in order. | |
utils.forEach(patternSpec, function (patternMatcher) { | |
// Process single property object to obtain pattern and matcher. | |
utils.forEach(patternMatcher, function (patternStr, matcherStr) { | |
var parsedPattern = pattern.parse(patternStr), regExpMatcher = parseMatcher(matcherStr); | |
matchers.push(regExpMatcher); | |
patterns.push(parsedPattern); | |
// Stop after one iteration. | |
return false; | |
}); | |
}); | |
var getPattern = function (input) { | |
var matchedIndex; | |
utils.forEach(matchers, function (matcher, index) { | |
if (matcher.test(input)) { | |
matchedIndex = index; | |
return false; | |
} | |
}); | |
return matchedIndex === undefined ? null : patterns[matchedIndex]; | |
}; | |
return { | |
getPattern: getPattern, | |
patterns: patterns, | |
matchers: matchers | |
}; | |
} | |
// Expose | |
return patternMatcher; | |
}(pattern, utils); | |
/* | |
* inpt-sel.js | |
* | |
* Cross browser implementation to get and set input selections | |
* | |
*/ | |
var inptSel = function () { | |
// Define module | |
var inptSel = {}; | |
// | |
// Get begin and end positions of selected input. Return 0's | |
// if there is no selectiion data | |
// | |
inptSel.get = function (el) { | |
// If normal browser return with result | |
if (typeof el.selectionStart === 'number') { | |
return { | |
begin: el.selectionStart, | |
end: el.selectionEnd | |
}; | |
} | |
// Uh-Oh. We must be IE. Fun with TextRange!! | |
var range = document.selection.createRange(); | |
// Determine if there is a selection | |
if (range && range.parentElement() === el) { | |
var inputRange = el.createTextRange(), endRange = el.createTextRange(), length = el.value.length; | |
// Create a working TextRange for the input selection | |
inputRange.moveToBookmark(range.getBookmark()); | |
// Move endRange begin pos to end pos (hence endRange) | |
endRange.collapse(false); | |
// If we are at the very end of the input, begin and end | |
// must both be the length of the el.value | |
if (inputRange.compareEndPoints('StartToEnd', endRange) > -1) { | |
return { | |
begin: length, | |
end: length | |
}; | |
} | |
// Note: moveStart usually returns the units moved, which | |
// one may think is -length, however, it will stop when it | |
// gets to the begin of the range, thus giving us the | |
// negative value of the pos. | |
return { | |
begin: -inputRange.moveStart('character', -length), | |
end: -inputRange.moveEnd('character', -length) | |
}; | |
} | |
//Return 0's on no selection data | |
return { | |
begin: 0, | |
end: 0 | |
}; | |
}; | |
// | |
// Set the caret position at a specified location | |
// | |
inptSel.set = function (el, pos) { | |
// Normalize pos | |
if (typeof pos !== 'object') { | |
pos = { | |
begin: pos, | |
end: pos | |
}; | |
} | |
// If normal browser | |
if (el.setSelectionRange) { | |
el.focus(); | |
el.setSelectionRange(pos.begin, pos.end); | |
} else if (el.createTextRange) { | |
var range = el.createTextRange(); | |
range.collapse(true); | |
range.moveEnd('character', pos.end); | |
range.moveStart('character', pos.begin); | |
range.select(); | |
} | |
}; | |
// Expose | |
return inptSel; | |
}(); | |
/* | |
* formatter.js | |
* | |
* Class used to format input based on passed pattern | |
* | |
*/ | |
var formatter = function (patternMatcher, inptSel, utils) { | |
// Defaults | |
var defaults = { | |
persistent: false, | |
repeat: false, | |
placeholder: ' ' | |
}; | |
// Regexs for input validation | |
var inptRegs = { | |
'9': /[0-9]/, | |
'a': /[A-Za-z]/, | |
'*': /[A-Za-z0-9]/ | |
}; | |
// | |
// Class Constructor - Called with new Formatter(el, opts) | |
// Responsible for setting up required instance variables, and | |
// attaching the event listener to the element. | |
// | |
function Formatter(el, opts) { | |
// Cache this | |
var self = this; | |
// Make sure we have an element. Make accesible to instance | |
self.el = el; | |
if (!self.el) { | |
throw new TypeError('Must provide an existing element'); | |
} | |
// Merge opts with defaults | |
self.opts = utils.extend({}, defaults, opts); | |
// 1 pattern is special case | |
if (typeof self.opts.pattern !== 'undefined') { | |
self.opts.patterns = self._specFromSinglePattern(self.opts.pattern); | |
delete self.opts.pattern; | |
} | |
// Make sure we have valid opts | |
if (typeof self.opts.patterns === 'undefined') { | |
throw new TypeError('Must provide a pattern or array of patterns'); | |
} | |
self.patternMatcher = patternMatcher(self.opts.patterns); | |
// Upate pattern with initial value | |
self._updatePattern(); | |
// Init values | |
self.hldrs = {}; | |
self.focus = 0; | |
// Add Listeners | |
utils.addListener(self.el, 'keydown', function (evt) { | |
self._keyDown(evt); | |
}); | |
utils.addListener(self.el, 'keypress', function (evt) { | |
self._keyPress(evt); | |
}); | |
utils.addListener(self.el, 'paste', function (evt) { | |
self._paste(evt); | |
}); | |
// Persistence | |
if (self.opts.persistent) { | |
// Format on start | |
self._processKey('', false); | |
self.el.blur(); | |
// Add Listeners | |
utils.addListener(self.el, 'focus', function (evt) { | |
self._focus(evt); | |
}); | |
utils.addListener(self.el, 'click', function (evt) { | |
self._focus(evt); | |
}); | |
utils.addListener(self.el, 'touchstart', function (evt) { | |
self._focus(evt); | |
}); | |
} | |
} | |
// | |
// @public | |
// Add new char | |
// | |
Formatter.addInptType = function (chr, reg) { | |
inptRegs[chr] = reg; | |
}; | |
// | |
// @public | |
// Apply the given pattern to the current input without moving caret. | |
// | |
Formatter.prototype.resetPattern = function (str) { | |
// Update opts to hold new pattern | |
this.opts.patterns = str ? this._specFromSinglePattern(str) : this.opts.patterns; | |
// Get current state | |
this.sel = inptSel.get(this.el); | |
this.val = this.el.value; | |
// Init values | |
this.delta = 0; | |
// Remove all formatted chars from val | |
this._removeChars(); | |
this.patternMatcher = patternMatcher(this.opts.patterns); | |
// Update pattern | |
var newPattern = this.patternMatcher.getPattern(this.val); | |
this.mLength = newPattern.mLength; | |
this.chars = newPattern.chars; | |
this.inpts = newPattern.inpts; | |
// Format on start | |
this._processKey('', false, true); | |
}; | |
// | |
// @private | |
// Determine correct format pattern based on input val | |
// | |
Formatter.prototype._updatePattern = function () { | |
// Determine appropriate pattern | |
var newPattern = this.patternMatcher.getPattern(this.val); | |
// Only update the pattern if there is an appropriate pattern for the value. | |
// Otherwise, leave the current pattern (and likely delete the latest character.) | |
if (newPattern) { | |
// Get info about the given pattern | |
this.mLength = newPattern.mLength; | |
this.chars = newPattern.chars; | |
this.inpts = newPattern.inpts; | |
} | |
}; | |
// | |
// @private | |
// Handler called on all keyDown strokes. All keys trigger | |
// this handler. Only process delete keys. | |
// | |
Formatter.prototype._keyDown = function (evt) { | |
// The first thing we need is the character code | |
var k = evt.which || evt.keyCode; | |
// If delete key | |
if (k && utils.isDelKeyDown(evt.which, evt.keyCode)) { | |
// Process the keyCode and prevent default | |
this._processKey(null, k); | |
return utils.preventDefault(evt); | |
} | |
}; | |
// | |
// @private | |
// Handler called on all keyPress strokes. Only processes | |
// character keys (as long as no modifier key is in use). | |
// | |
Formatter.prototype._keyPress = function (evt) { | |
// The first thing we need is the character code | |
var k, isSpecial; | |
// Mozilla will trigger on special keys and assign the the value 0 | |
// We want to use that 0 rather than the keyCode it assigns. | |
k = evt.which || evt.keyCode; | |
isSpecial = utils.isSpecialKeyPress(evt.which, evt.keyCode); | |
// Process the keyCode and prevent default | |
if (!utils.isDelKeyPress(evt.which, evt.keyCode) && !isSpecial && !utils.isModifier(evt)) { | |
this._processKey(String.fromCharCode(k), false); | |
return utils.preventDefault(evt); | |
} | |
}; | |
// | |
// @private | |
// Handler called on paste event. | |
// | |
Formatter.prototype._paste = function (evt) { | |
// Process the clipboard paste and prevent default | |
this._processKey(utils.getClip(evt), false); | |
return utils.preventDefault(evt); | |
}; | |
// | |
// @private | |
// Handle called on focus event. | |
// | |
Formatter.prototype._focus = function () { | |
// Wrapped in timeout so that we can grab input selection | |
var self = this; | |
setTimeout(function () { | |
// Grab selection | |
var selection = inptSel.get(self.el); | |
// Char check | |
var isAfterStart = selection.end > self.focus, isFirstChar = selection.end === 0; | |
// If clicked in front of start, refocus to start | |
if (isAfterStart || isFirstChar) { | |
inptSel.set(self.el, self.focus); | |
} | |
}, 0); | |
}; | |
// | |
// @private | |
// Using the provided key information, alter el value. | |
// | |
Formatter.prototype._processKey = function (chars, delKey, ignoreCaret) { | |
// Get current state | |
this.sel = inptSel.get(this.el); | |
this.val = this.el.value; | |
// Init values | |
this.delta = 0; | |
// If chars were highlighted, we need to remove them | |
if (this.sel.begin !== this.sel.end) { | |
this.delta = -1 * Math.abs(this.sel.begin - this.sel.end); | |
this.val = utils.removeChars(this.val, this.sel.begin, this.sel.end); | |
} else if (delKey && delKey === 46) { | |
this._delete(); | |
} else if (delKey && this.sel.begin - 1 >= 0) { | |
// Always have a delta of at least -1 for the character being deleted. | |
this.val = utils.removeChars(this.val, this.sel.end - 1, this.sel.end); | |
this.delta -= 1; | |
} else if (delKey) { | |
return true; | |
} | |
// If the key is not a del key, it should convert to a str | |
if (!delKey) { | |
// Add char at position and increment delta | |
this.val = utils.addChars(this.val, chars, this.sel.begin); | |
this.delta += chars.length; | |
} | |
// Format el.value (also handles updating caret position) | |
this._formatValue(ignoreCaret); | |
}; | |
// | |
// @private | |
// Deletes the character in front of it | |
// | |
Formatter.prototype._delete = function () { | |
// Adjust focus to make sure its not on a formatted char | |
while (this.chars[this.sel.begin]) { | |
this._nextPos(); | |
} | |
// As long as we are not at the end | |
if (this.sel.begin < this.val.length) { | |
// We will simulate a delete by moving the caret to the next char | |
// and then deleting | |
this._nextPos(); | |
this.val = utils.removeChars(this.val, this.sel.end - 1, this.sel.end); | |
this.delta = -1; | |
} | |
}; | |
// | |
// @private | |
// Quick helper method to move the caret to the next pos | |
// | |
Formatter.prototype._nextPos = function () { | |
this.sel.end++; | |
this.sel.begin++; | |
}; | |
// | |
// @private | |
// Alter element value to display characters matching the provided | |
// instance pattern. Also responsible for updating | |
// | |
Formatter.prototype._formatValue = function (ignoreCaret) { | |
// Set caret pos | |
this.newPos = this.sel.end + this.delta; | |
// Remove all formatted chars from val | |
this._removeChars(); | |
// Switch to first matching pattern based on val | |
this._updatePattern(); | |
// Validate inputs | |
this._validateInpts(); | |
// Add formatted characters | |
this._addChars(); | |
// Set value and adhere to maxLength | |
this.el.value = this.val.substr(0, this.mLength); | |
// Set new caret position | |
if (typeof ignoreCaret === 'undefined' || ignoreCaret === false) { | |
inptSel.set(this.el, this.newPos); | |
} | |
}; | |
// | |
// @private | |
// Remove all formatted before and after a specified pos | |
// | |
Formatter.prototype._removeChars = function () { | |
// Delta shouldn't include placeholders | |
if (this.sel.end > this.focus) { | |
this.delta += this.sel.end - this.focus; | |
} | |
// Account for shifts during removal | |
var shift = 0; | |
// Loop through all possible char positions | |
for (var i = 0; i <= this.mLength; i++) { | |
// Get transformed position | |
var curChar = this.chars[i], curHldr = this.hldrs[i], pos = i + shift, val; | |
// If after selection we need to account for delta | |
pos = i >= this.sel.begin ? pos + this.delta : pos; | |
val = this.val.charAt(pos); | |
// Remove char and account for shift | |
if (curChar && curChar === val || curHldr && curHldr === val) { | |
this.val = utils.removeChars(this.val, pos, pos + 1); | |
shift--; | |
} | |
} | |
// All hldrs should be removed now | |
this.hldrs = {}; | |
// Set focus to last character | |
this.focus = this.val.length; | |
}; | |
// | |
// @private | |
// Make sure all inpts are valid, else remove and update delta | |
// | |
Formatter.prototype._validateInpts = function () { | |
// Loop over each char and validate | |
for (var i = 0; i < this.val.length; i++) { | |
// Get char inpt type | |
var inptType = this.inpts[i]; | |
// Checks | |
var isBadType = !inptRegs[inptType], isInvalid = !isBadType && !inptRegs[inptType].test(this.val.charAt(i)), inBounds = this.inpts[i]; | |
// Remove if incorrect and inbounds | |
if ((isBadType || isInvalid) && inBounds) { | |
this.val = utils.removeChars(this.val, i, i + 1); | |
this.focusStart--; | |
this.newPos--; | |
this.delta--; | |
i--; | |
} | |
} | |
}; | |
// | |
// @private | |
// Loop over val and add formatted chars as necessary | |
// | |
Formatter.prototype._addChars = function () { | |
if (this.opts.persistent) { | |
// Loop over all possible characters | |
for (var i = 0; i <= this.mLength; i++) { | |
if (!this.val.charAt(i)) { | |
// Add placeholder at pos | |
this.val = utils.addChars(this.val, this.opts.placeholder, i); | |
this.hldrs[i] = this.opts.placeholder; | |
} | |
this._addChar(i); | |
} | |
// Adjust focus to make sure its not on a formatted char | |
while (this.chars[this.focus]) { | |
this.focus++; | |
} | |
} else { | |
// Avoid caching val.length, as they may change in _addChar. | |
for (var j = 0; j <= this.val.length; j++) { | |
// When moving backwards there are some race conditions where we | |
// dont want to add the character | |
if (this.delta <= 0 && j === this.focus) { | |
return true; | |
} | |
// Place character in current position of the formatted string. | |
this._addChar(j); | |
} | |
} | |
}; | |
// | |
// @private | |
// Add formattted char at position | |
// | |
Formatter.prototype._addChar = function (i) { | |
// If char exists at position | |
var chr = this.chars[i]; | |
if (!chr) { | |
return true; | |
} | |
// If chars are added in between the old pos and new pos | |
// we need to increment pos and delta | |
if (utils.isBetween(i, [ | |
this.sel.begin - 1, | |
this.newPos + 1 | |
])) { | |
this.newPos++; | |
this.delta++; | |
} | |
// If character added before focus, incr | |
if (i <= this.focus) { | |
this.focus++; | |
} | |
// Updateholder | |
if (this.hldrs[i]) { | |
delete this.hldrs[i]; | |
this.hldrs[i + 1] = this.opts.placeholder; | |
} | |
// Update value | |
this.val = utils.addChars(this.val, chr, i); | |
}; | |
// | |
// @private | |
// Create a patternSpec for passing into patternMatcher that | |
// has exactly one catch all pattern. | |
// | |
Formatter.prototype._specFromSinglePattern = function (patternStr) { | |
return [{ '*': patternStr }]; | |
}; | |
// Expose | |
return Formatter; | |
}(patternMatcher, inptSel, utils); | |
return formatter; | |
})); |