Permalink
Browse files

Add multi-pattern support.

1 parent 56be36b commit 8ce0d4892511c4cf93c5b24440cd236faec6b567 @mickeyreiss mickeyreiss committed with braintreeps Mar 14, 2014
Showing with 636 additions and 70 deletions.
  1. +2 −0 Gruntfile.js
  2. +8 −0 README.md
  3. +142 −23 lib/formatter.js
  4. +1 −1 lib/formatter.min.js
  5. +142 −23 lib/jquery.formatter.js
  6. +1 −1 lib/jquery.formatter.min.js
  7. +53 −15 src/formatter.js
  8. +73 −0 src/pattern-matcher.js
  9. +22 −1 src/utils.js
  10. +49 −4 test/formatter.js
  11. +86 −0 test/pattern-matcher.js
  12. +57 −2 test/utils.js
View
@@ -54,6 +54,7 @@ module.exports = function(grunt) {
'src/tmpls/intro.js',
'src/formatter.js',
'src/pattern.js',
+ 'src/pattern-matcher.js',
'src/inpt-sel.js',
'src/utils.js',
'src/tmpls/outro.js'
@@ -65,6 +66,7 @@ module.exports = function(grunt) {
'src/tmpls/jquery.intro.js',
'src/formatter.js',
'src/pattern.js',
+ 'src/pattern-matcher.js',
'src/inpt-sel.js',
'src/utils.js',
'src/tmpls/jquery.outro.js'
View
@@ -70,6 +70,14 @@ Opts
* a: [A-Za-z]
* \*: [A-Za-z0-9]
* **persistent**: \[False\] Boolean representing if the formatted characters are always visible (persistent), or if they appear as you type.
+* **patterns** (optional, replaces *pattern*): Array representing a priority ordered set of patterns that may apply dynamically based on the current input value. Each value in the array is an object, whose key is a regular expression string and value is a *pattern* (see above). The regular expression is tested against the unformatted input value. You may use the special key `'*'` to catch all input values.
+```
+[
+ { '^\d{5}$': 'zip: {{99999}}' },
+ { '^.{6,8}$: 'postal code: {{********}}' },
+ { '*': 'unknown: {{**********}}' }
+]
+```
View
@@ -45,16 +45,21 @@ function Formatter(el, opts) {
// 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.pattern === 'undefined') {
- throw new TypeError('Must provide a pattern');
+ if (typeof self.opts.patterns === 'undefined') {
+ throw new TypeError('Must provide a pattern or array of patterns');
}
- // Get info about the given pattern
- var parsed = pattern.parse(self.opts.pattern);
- self.mLength = parsed.mLength;
- self.chars = parsed.chars;
- self.inpts = parsed.inpts;
+ self.patternMatcher = patternMatcher(self.opts.patterns);
+
+ // Upate pattern with initial value
+ self._updatePattern();
// Init values
self.hldrs = {};
@@ -100,13 +105,11 @@ Formatter.addInptType = function (chr, reg) {
//
// @public
-// Handler called on all keyDown strokes. All keys trigger
-// this handler. Only process delete keys.
+// Apply the given pattern to the current input without moving caret.
//
Formatter.prototype.resetPattern = function (str) {
// Update opts to hold new pattern
- str = str || this.opts.pattern;
- this.opts.pattern = str;
+ this.opts.patterns = str ? this._specFromSinglePattern(str) : this.opts.patterns;
// Get current state
this.sel = inptSel.get(this.el);
@@ -118,14 +121,34 @@ Formatter.prototype.resetPattern = function (str) {
// Remove all formatted chars from val
this._removeChars();
+ this.patternMatcher = patternMatcher(this.opts.patterns);
+
// Update pattern
- var parsed = pattern.parse(str);
- this.mLength = parsed.mLength;
- this.chars = parsed.chars;
- this.inpts = parsed.inpts;
+ 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);
+ 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;
+ }
};
//
@@ -202,7 +225,7 @@ Formatter.prototype._focus = function () {
// @private
// Using the provided key information, alter el value.
//
-Formatter.prototype._processKey = function (chars, delKey) {
+Formatter.prototype._processKey = function (chars, delKey,ingoreCaret) {
// Get current state
this.sel = inptSel.get(this.el);
this.val = this.el.value;
@@ -238,7 +261,7 @@ Formatter.prototype._processKey = function (chars, delKey) {
}
// Format el.value (also handles updating caret position)
- this._formatValue();
+ this._formatValue(ingoreCaret);
};
//
@@ -273,24 +296,30 @@ Formatter.prototype._nextPos = function () {
//
// @private
// Alter element value to display characters matching the provided
-// instance pattern. Also responsible for updatin
+// instance pattern. Also responsible for updating
//
-Formatter.prototype._formatValue = function () {
+Formatter.prototype._formatValue = function (ignoreCaret) {
// Set caret pos
this.newPos = this.sel.end + this.delta;
// Remove all formatted chars from val
this._removeChars();
- // Validate inpts
+
+ // Switch to first matching pattern based on val
+ this._updatePattern();
+
+ // Validate inputs
this._validateInpts();
// Add formatted characters
this._addChars();
- // Set vakye and adhere to maxLength
+ // Set value and adhere to maxLength
this.el.value = this.val.substr(0, this.mLength);
// Set new caret position
- inptSel.set(this.el, this.newPos);
+ if ((typeof ignoreCaret) === 'undefined' || ignoreCaret === false) {
+ inptSel.set(this.el, this.newPos);
+ }
};
//
@@ -419,6 +448,16 @@ Formatter.prototype._addChar = function (i) {
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 }];
+};
+
+
// Define module
var pattern = {};
@@ -483,6 +522,64 @@ pattern.parse = function (pattern) {
info.mLength = i - (mCount * DELIM_SIZE);
return info;
};
+//
+// 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
+ };
+}
+
// Define module
var inptSel = {};
@@ -649,6 +746,28 @@ utils.isSpecialKey = function (k) {
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;
+ }
+ }
+ }
+ }
+};
+
return Formatter;
});
Oops, something went wrong.

0 comments on commit 8ce0d48

Please sign in to comment.