861 lines
26 KiB
JavaScript
861 lines
26 KiB
JavaScript
|
/*!
|
||
|
* v0.1.5
|
||
|
* 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 CommonJS, AMD or browser globals to create a jQuery plugin.
|
||
|
//
|
||
|
// Similar to jqueryPlugin.js but also tries to
|
||
|
// work in a CommonJS environment.
|
||
|
// It is unlikely jQuery will run in a CommonJS
|
||
|
// environment. See jqueryPlugin.js if you do
|
||
|
// not want to add the extra CommonJS detection.
|
||
|
//
|
||
|
(function (root, factory) {
|
||
|
if (typeof define === 'function' && define.amd) {
|
||
|
// AMD. Register as an anonymous module.
|
||
|
define(['jQuery'], factory);
|
||
|
} else if (typeof exports === 'object') {
|
||
|
factory(require('jQuery'));
|
||
|
} else {
|
||
|
// Browser globals
|
||
|
factory(root.jQuery);
|
||
|
}
|
||
|
}(this, function (jQuery) {
|
||
|
|
||
|
|
||
|
/*
|
||
|
* 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);
|
||
|
|
||
|
|
||
|
|
||
|
// A really lightweight plugin wrapper around the constructor,
|
||
|
// preventing against multiple instantiations
|
||
|
var pluginName = 'formatter';
|
||
|
|
||
|
$.fn[pluginName] = function (options) {
|
||
|
|
||
|
// Initiate plugin if options passed
|
||
|
if (typeof options == 'object') {
|
||
|
this.each(function () {
|
||
|
if (!$.data(this, 'plugin_' + pluginName)) {
|
||
|
$.data(this, 'plugin_' + pluginName,
|
||
|
new formatter(this, options));
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Add resetPattern method to plugin
|
||
|
this.resetPattern = function (str) {
|
||
|
this.each(function () {
|
||
|
var formatted = $.data(this, 'plugin_' + pluginName);
|
||
|
// resetPattern for instance
|
||
|
if (formatted) { formatted.resetPattern(str); }
|
||
|
});
|
||
|
// Chainable please
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
// Chainable please
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
$.fn[pluginName].addInptType = function (chr, regexp) {
|
||
|
formatter.addInptType(chr, regexp);
|
||
|
};
|
||
|
|
||
|
|
||
|
}));
|