From 8ce0d4892511c4cf93c5b24440cd236faec6b567 Mon Sep 17 00:00:00 2001
From: Mickey Reiss <mickey@venmo.com>
Date: Fri, 14 Mar 2014 17:54:51 +0000
Subject: [PATCH] Add multi-pattern support.

---
 Gruntfile.js                |   2 +
 README.md                   |   8 +++
 lib/formatter.js            | 165 ++++++++++++++++++++++++++++++++++++++------
 lib/formatter.min.js        |   2 +-
 lib/jquery.formatter.js     | 165 ++++++++++++++++++++++++++++++++++++++------
 lib/jquery.formatter.min.js |   2 +-
 src/formatter.js            |  68 ++++++++++++++----
 src/pattern-matcher.js      |  73 ++++++++++++++++++++
 src/utils.js                |  23 +++++-
 test/formatter.js           |  53 ++++++++++++--
 test/pattern-matcher.js     |  86 +++++++++++++++++++++++
 test/utils.js               |  59 +++++++++++++++-
 12 files changed, 636 insertions(+), 70 deletions(-)
 create mode 100644 src/pattern-matcher.js
 create mode 100644 test/pattern-matcher.js

diff --git a/Gruntfile.js b/Gruntfile.js
index 7fae4f3..03b7fbe 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -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'
diff --git a/README.md b/README.md
index 4cbd753..364eac2 100644
--- a/README.md
+++ b/README.md
@@ -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: {{**********}}' }
+]
+```
 
 
 
diff --git a/lib/formatter.js b/lib/formatter.js
index eb6e9ba..83c7f6d 100644
--- a/lib/formatter.js
+++ b/lib/formatter.js
@@ -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;
 
 });
\ No newline at end of file
diff --git a/lib/formatter.min.js b/lib/formatter.min.js
index 632a28c..07648b5 100644
--- a/lib/formatter.min.js
+++ b/lib/formatter.min.js
@@ -6,4 +6,4 @@
  * thanks to digitalBush/jquery.maskedinput for some of the trickier
  * keycode handling
  */
-!function(a,b,c){"undefined"!=typeof module&&module.exports?module.exports=c():"function"==typeof define&&define.amd?define(c):b[a]=c()}("Formatter",this,function(){function a(a,c){var e=this;if(e.el=a,!e.el)throw new TypeError("Must provide an existing element");if(e.opts=i.extend({},b,c),"undefined"==typeof e.opts.pattern)throw new TypeError("Must provide a pattern");var f=d.parse(e.opts.pattern);e.mLength=f.mLength,e.chars=f.chars,e.inpts=f.inpts,e.hldrs={},e.focus=0,i.addListener(e.el,"keydown",function(a){e._keyDown(a)}),i.addListener(e.el,"keypress",function(a){e._keyPress(a)}),i.addListener(e.el,"paste",function(a){e._paste(a)}),e.opts.persistent&&(e._processKey("",!1),e.el.blur(),i.addListener(e.el,"focus",function(a){e._focus(a)}),i.addListener(e.el,"click",function(a){e._focus(a)}),i.addListener(e.el,"touchstart",function(a){e._focus(a)}))}var b={persistent:!1,repeat:!1,placeholder:" "},c={9:/[0-9]/,a:/[A-Za-z]/,"*":/[A-Za-z0-9]/};a.addInptType=function(a,b){c[a]=b},a.prototype.resetPattern=function(a){a=a||this.opts.pattern,this.opts.pattern=a,this.sel=h.get(this.el),this.val=this.el.value,this.delta=0,this._removeChars();var b=d.parse(a);this.mLength=b.mLength,this.chars=b.chars,this.inpts=b.inpts,this._processKey("",!1)},a.prototype._keyDown=function(a){var b=a.which||a.keyCode;return b&&i.isDelKey(b)?(this._processKey(null,b),i.preventDefault(a)):void 0},a.prototype._keyPress=function(a){var b,c;return a.which?b=a.which:(b=a.keyCode,c=i.isSpecialKey(b)),i.isDelKey(b)||c||i.isModifier(a)?void 0:(this._processKey(String.fromCharCode(b),!1),i.preventDefault(a))},a.prototype._paste=function(a){return this._processKey(i.getClip(a),!1),i.preventDefault(a)},a.prototype._focus=function(){var a=this;setTimeout(function(){var b=h.get(a.el),c=b.end>a.focus,d=0===b.end;(c||d)&&h.set(a.el,a.focus)},0)},a.prototype._processKey=function(a,b){if(this.sel=h.get(this.el),this.val=this.el.value,this.delta=0,this.sel.begin!==this.sel.end)this.delta=-1*Math.abs(this.sel.begin-this.sel.end),this.val=i.removeChars(this.val,this.sel.begin,this.sel.end);else if(b&&46==b)this._delete();else if(b&&this.sel.begin-1>=0)this.val=i.removeChars(this.val,this.sel.end-1,this.sel.end),this.delta=-1;else if(b)return!0;b||(this.val=i.addChars(this.val,a,this.sel.begin),this.delta+=a.length),this._formatValue()},a.prototype._delete=function(){for(;this.chars[this.sel.begin];)this._nextPos();this.sel.begin<this.val.length&&(this._nextPos(),this.val=i.removeChars(this.val,this.sel.end-1,this.sel.end),this.delta=-1)},a.prototype._nextPos=function(){this.sel.end++,this.sel.begin++},a.prototype._formatValue=function(){this.newPos=this.sel.end+this.delta,this._removeChars(),this._validateInpts(),this._addChars(),this.el.value=this.val.substr(0,this.mLength),h.set(this.el,this.newPos)},a.prototype._removeChars=function(){this.sel.end>this.focus&&(this.delta+=this.sel.end-this.focus);for(var a=0,b=0;b<=this.mLength;b++){var c,d=this.chars[b],e=this.hldrs[b],f=b+a;f=b>=this.sel.begin?f+this.delta:f,c=this.val.charAt(f),(d&&d==c||e&&e==c)&&(this.val=i.removeChars(this.val,f,f+1),a--)}this.hldrs={},this.focus=this.val.length},a.prototype._validateInpts=function(){for(var a=0;a<this.val.length;a++){var b=this.inpts[a],d=!c[b],e=!d&&!c[b].test(this.val.charAt(a)),f=this.inpts[a];(d||e)&&f&&(this.val=i.removeChars(this.val,a,a+1),this.focusStart--,this.newPos--,this.delta--,a--)}},a.prototype._addChars=function(){if(this.opts.persistent){for(var a=0;a<=this.mLength;a++)this.val.charAt(a)||(this.val=i.addChars(this.val,this.opts.placeholder,a),this.hldrs[a]=this.opts.placeholder),this._addChar(a);for(;this.chars[this.focus];)this.focus++}else for(var b=0;b<=this.val.length;b++){if(this.delta<=0&&b==this.focus)return!0;this._addChar(b)}},a.prototype._addChar=function(a){var b=this.chars[a];return b?(i.isBetween(a,[this.sel.begin-1,this.newPos+1])&&(this.newPos++,this.delta++),a<=this.focus&&this.focus++,this.hldrs[a]&&(delete this.hldrs[a],this.hldrs[a+1]=this.opts.placeholder),void(this.val=i.addChars(this.val,b,a))):!0};var d={},e=4,f=new RegExp("{{([^}]+)}}","g"),g=function(a){for(var b,c=[];b=f.exec(a);)c.push(b);return c};d.parse=function(a){var b={inpts:{},chars:{}},c=g(a),d=a.length,f=0,h=0,i=0,j=function(a){for(var c=a.length,d=0;c>d;d++)b.inpts[h]=a.charAt(d),h++;f++,i+=a.length+e-1};for(i;d>i;i++)i==c[f].index?j(c[f][1]):b.chars[i-f*e]=a.charAt(i);return b.mLength=i-f*e,b};var h={};h.get=function(a){if("number"==typeof a.selectionStart)return{begin:a.selectionStart,end:a.selectionEnd};var b=document.selection.createRange();if(b&&b.parentElement()==a){var c=a.createTextRange(),d=a.createTextRange(),e=a.value.length;return c.moveToBookmark(b.getBookmark()),d.collapse(!1),c.compareEndPoints("StartToEnd",d)>-1?{begin:e,end:e}:{begin:-c.moveStart("character",-e),end:-c.moveEnd("character",-e)}}return{begin:0,end:0}},h.set=function(a,b){if(a.setSelectionRange)a.focus(),a.setSelectionRange(b,b);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0),c.moveEnd("character",b),c.moveStart("character",b),c.select()}};var i={},j="undefined"!=typeof navigator?navigator.userAgent:null,k=/iphone/i.test(j);return i.extend=function(a){for(var b=1;b<arguments.length;b++)for(var c in arguments[b])a[c]=arguments[b][c];return a},i.addChars=function(a,b,c){return a.substr(0,c)+b+a.substr(c,a.length)},i.removeChars=function(a,b,c){return a.substr(0,b)+a.substr(c,a.length)},i.isBetween=function(a,b){return b.sort(function(a,b){return a-b}),a>b[0]&&a<b[1]},i.addListener=function(a,b,c){return"undefined"!=typeof a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},i.preventDefault=function(a){return a.preventDefault?a.preventDefault():a.returnValue=!1},i.getClip=function(a){return a.clipboardData?a.clipboardData.getData("Text"):window.clipboardData?window.clipboardData.getData("Text"):void 0},i.isDelKey=function(a){return 8===a||46===a||k&&127===a},i.isSpecialKey=function(a){var b={9:"tab",13:"enter",35:"end",36:"home",37:"leftarrow",38:"uparrow",39:"rightarrow",40:"downarrow",116:"F5"};return b[a]},i.isModifier=function(a){return a.ctrlKey||a.altKey||a.metaKey},a});
\ No newline at end of file
+!function(a,b,c){"undefined"!=typeof module&&module.exports?module.exports=c():"function"==typeof define&&define.amd?define(c):b[a]=c()}("Formatter",this,function(){function a(a,d){var e=this;if(e.el=a,!e.el)throw new TypeError("Must provide an existing element");if(e.opts=k.extend({},c,d),"undefined"!=typeof e.opts.pattern&&(e.opts.patterns=e._specFromSinglePattern(e.opts.pattern),delete e.opts.pattern),"undefined"==typeof e.opts.patterns)throw new TypeError("Must provide a pattern or array of patterns");e.patternMatcher=b(e.opts.patterns),e._updatePattern(),e.hldrs={},e.focus=0,k.addListener(e.el,"keydown",function(a){e._keyDown(a)}),k.addListener(e.el,"keypress",function(a){e._keyPress(a)}),k.addListener(e.el,"paste",function(a){e._paste(a)}),e.opts.persistent&&(e._processKey("",!1),e.el.blur(),k.addListener(e.el,"focus",function(a){e._focus(a)}),k.addListener(e.el,"click",function(a){e._focus(a)}),k.addListener(e.el,"touchstart",function(a){e._focus(a)}))}function b(a){var b=[],c=[];k.forEach(a,function(a){k.forEach(a,function(a,d){var f=e.parse(a),g=i(d);return b.push(g),c.push(f),!1})});var d=function(a){var d;return k.forEach(b,function(b,c){return b.test(a)?(d=c,!1):void 0}),void 0===d?null:c[d]};return{getPattern:d,patterns:c,matchers:b}}var c={persistent:!1,repeat:!1,placeholder:" "},d={9:/[0-9]/,a:/[A-Za-z]/,"*":/[A-Za-z0-9]/};a.addInptType=function(a,b){d[a]=b},a.prototype.resetPattern=function(a){this.opts.patterns=a?this._specFromSinglePattern(a):this.opts.patterns,this.sel=j.get(this.el),this.val=this.el.value,this.delta=0,this._removeChars(),this.patternMatcher=b(this.opts.patterns);var c=this.patternMatcher.getPattern(this.val);this.mLength=c.mLength,this.chars=c.chars,this.inpts=c.inpts,this._processKey("",!1,!0)},a.prototype._updatePattern=function(){var a=this.patternMatcher.getPattern(this.val);a&&(this.mLength=a.mLength,this.chars=a.chars,this.inpts=a.inpts)},a.prototype._keyDown=function(a){var b=a.which||a.keyCode;return b&&k.isDelKey(b)?(this._processKey(null,b),k.preventDefault(a)):void 0},a.prototype._keyPress=function(a){var b,c;return a.which?b=a.which:(b=a.keyCode,c=k.isSpecialKey(b)),k.isDelKey(b)||c||k.isModifier(a)?void 0:(this._processKey(String.fromCharCode(b),!1),k.preventDefault(a))},a.prototype._paste=function(a){return this._processKey(k.getClip(a),!1),k.preventDefault(a)},a.prototype._focus=function(){var a=this;setTimeout(function(){var b=j.get(a.el),c=b.end>a.focus,d=0===b.end;(c||d)&&j.set(a.el,a.focus)},0)},a.prototype._processKey=function(a,b,c){if(this.sel=j.get(this.el),this.val=this.el.value,this.delta=0,this.sel.begin!==this.sel.end)this.delta=-1*Math.abs(this.sel.begin-this.sel.end),this.val=k.removeChars(this.val,this.sel.begin,this.sel.end);else if(b&&46==b)this._delete();else if(b&&this.sel.begin-1>=0)this.val=k.removeChars(this.val,this.sel.end-1,this.sel.end),this.delta=-1;else if(b)return!0;b||(this.val=k.addChars(this.val,a,this.sel.begin),this.delta+=a.length),this._formatValue(c)},a.prototype._delete=function(){for(;this.chars[this.sel.begin];)this._nextPos();this.sel.begin<this.val.length&&(this._nextPos(),this.val=k.removeChars(this.val,this.sel.end-1,this.sel.end),this.delta=-1)},a.prototype._nextPos=function(){this.sel.end++,this.sel.begin++},a.prototype._formatValue=function(a){this.newPos=this.sel.end+this.delta,this._removeChars(),this._updatePattern(),this._validateInpts(),this._addChars(),this.el.value=this.val.substr(0,this.mLength),("undefined"==typeof a||a===!1)&&j.set(this.el,this.newPos)},a.prototype._removeChars=function(){this.sel.end>this.focus&&(this.delta+=this.sel.end-this.focus);for(var a=0,b=0;b<=this.mLength;b++){var c,d=this.chars[b],e=this.hldrs[b],f=b+a;f=b>=this.sel.begin?f+this.delta:f,c=this.val.charAt(f),(d&&d==c||e&&e==c)&&(this.val=k.removeChars(this.val,f,f+1),a--)}this.hldrs={},this.focus=this.val.length},a.prototype._validateInpts=function(){for(var a=0;a<this.val.length;a++){var b=this.inpts[a],c=!d[b],e=!c&&!d[b].test(this.val.charAt(a)),f=this.inpts[a];(c||e)&&f&&(this.val=k.removeChars(this.val,a,a+1),this.focusStart--,this.newPos--,this.delta--,a--)}},a.prototype._addChars=function(){if(this.opts.persistent){for(var a=0;a<=this.mLength;a++)this.val.charAt(a)||(this.val=k.addChars(this.val,this.opts.placeholder,a),this.hldrs[a]=this.opts.placeholder),this._addChar(a);for(;this.chars[this.focus];)this.focus++}else for(var b=0;b<=this.val.length;b++){if(this.delta<=0&&b==this.focus)return!0;this._addChar(b)}},a.prototype._addChar=function(a){var b=this.chars[a];return b?(k.isBetween(a,[this.sel.begin-1,this.newPos+1])&&(this.newPos++,this.delta++),a<=this.focus&&this.focus++,this.hldrs[a]&&(delete this.hldrs[a],this.hldrs[a+1]=this.opts.placeholder),void(this.val=k.addChars(this.val,b,a))):!0},a.prototype._specFromSinglePattern=function(a){return[{"*":a}]};var e={},f=4,g=new RegExp("{{([^}]+)}}","g"),h=function(a){for(var b,c=[];b=g.exec(a);)c.push(b);return c};e.parse=function(a){var b={inpts:{},chars:{}},c=h(a),d=a.length,e=0,g=0,i=0,j=function(a){for(var c=a.length,d=0;c>d;d++)b.inpts[g]=a.charAt(d),g++;e++,i+=a.length+f-1};for(i;d>i;i++)i==c[e].index?j(c[e][1]):b.chars[i-e*f]=a.charAt(i);return b.mLength=i-e*f,b};var i=function(a){return"*"===a?/.*/:new RegExp(a)},j={};j.get=function(a){if("number"==typeof a.selectionStart)return{begin:a.selectionStart,end:a.selectionEnd};var b=document.selection.createRange();if(b&&b.parentElement()==a){var c=a.createTextRange(),d=a.createTextRange(),e=a.value.length;return c.moveToBookmark(b.getBookmark()),d.collapse(!1),c.compareEndPoints("StartToEnd",d)>-1?{begin:e,end:e}:{begin:-c.moveStart("character",-e),end:-c.moveEnd("character",-e)}}return{begin:0,end:0}},j.set=function(a,b){if(a.setSelectionRange)a.focus(),a.setSelectionRange(b,b);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0),c.moveEnd("character",b),c.moveStart("character",b),c.select()}};var k={},l="undefined"!=typeof navigator?navigator.userAgent:null,m=/iphone/i.test(l);return k.extend=function(a){for(var b=1;b<arguments.length;b++)for(var c in arguments[b])a[c]=arguments[b][c];return a},k.addChars=function(a,b,c){return a.substr(0,c)+b+a.substr(c,a.length)},k.removeChars=function(a,b,c){return a.substr(0,b)+a.substr(c,a.length)},k.isBetween=function(a,b){return b.sort(function(a,b){return a-b}),a>b[0]&&a<b[1]},k.addListener=function(a,b,c){return"undefined"!=typeof a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},k.preventDefault=function(a){return a.preventDefault?a.preventDefault():a.returnValue=!1},k.getClip=function(a){return a.clipboardData?a.clipboardData.getData("Text"):window.clipboardData?window.clipboardData.getData("Text"):void 0},k.isDelKey=function(a){return 8===a||46===a||m&&127===a},k.isSpecialKey=function(a){var b={9:"tab",13:"enter",35:"end",36:"home",37:"leftarrow",38:"uparrow",39:"rightarrow",40:"downarrow",116:"F5"};return b[a]},k.isModifier=function(a){return a.ctrlKey||a.altKey||a.metaKey},k.forEach=function(a,b,c){if(a.hasOwnProperty("length"))for(var d=0,e=a.length;e>d&&b.call(c,a[d],d,a)!==!1;d++);else for(var f in a)if(a.hasOwnProperty(f)&&b.call(c,a[f],f,a)===!1)break},a});
\ No newline at end of file
diff --git a/lib/jquery.formatter.js b/lib/jquery.formatter.js
index 3c1332f..95b37dc 100644
--- a/lib/jquery.formatter.js
+++ b/lib/jquery.formatter.js
@@ -41,16 +41,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 = {};
@@ -96,13 +101,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);
@@ -114,14 +117,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;
+  }
 };
 
 //
@@ -198,7 +221,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;
@@ -234,7 +257,7 @@ Formatter.prototype._processKey = function (chars, delKey) {
   }
 
   // Format el.value (also handles updating caret position)
-  this._formatValue();
+  this._formatValue(ingoreCaret);
 };
 
 //
@@ -269,24 +292,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);
+  }
 };
 
 //
@@ -415,6 +444,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 = {};
 
@@ -479,6 +518,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 = {};
 
@@ -645,6 +742,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;
+        }
+      }
+    }
+  }
+};
+
 // A really lightweight plugin wrapper around the constructor,
 // preventing against multiple instantiations
 var pluginName = 'formatter';
diff --git a/lib/jquery.formatter.min.js b/lib/jquery.formatter.min.js
index 34b08c2..ab25322 100644
--- a/lib/jquery.formatter.min.js
+++ b/lib/jquery.formatter.min.js
@@ -6,4 +6,4 @@
  * thanks to digitalBush/jquery.maskedinput for some of the trickier
  * keycode handling
  */
-!function(a,b,c){function d(a,b){var c=this;if(c.el=a,!c.el)throw new TypeError("Must provide an existing element");if(c.opts=l.extend({},e,b),"undefined"==typeof c.opts.pattern)throw new TypeError("Must provide a pattern");var d=g.parse(c.opts.pattern);c.mLength=d.mLength,c.chars=d.chars,c.inpts=d.inpts,c.hldrs={},c.focus=0,l.addListener(c.el,"keydown",function(a){c._keyDown(a)}),l.addListener(c.el,"keypress",function(a){c._keyPress(a)}),l.addListener(c.el,"paste",function(a){c._paste(a)}),c.opts.persistent&&(c._processKey("",!1),c.el.blur(),l.addListener(c.el,"focus",function(a){c._focus(a)}),l.addListener(c.el,"click",function(a){c._focus(a)}),l.addListener(c.el,"touchstart",function(a){c._focus(a)}))}var e={persistent:!1,repeat:!1,placeholder:" "},f={9:/[0-9]/,a:/[A-Za-z]/,"*":/[A-Za-z0-9]/};d.addInptType=function(a,b){f[a]=b},d.prototype.resetPattern=function(a){a=a||this.opts.pattern,this.opts.pattern=a,this.sel=k.get(this.el),this.val=this.el.value,this.delta=0,this._removeChars();var b=g.parse(a);this.mLength=b.mLength,this.chars=b.chars,this.inpts=b.inpts,this._processKey("",!1)},d.prototype._keyDown=function(a){var b=a.which||a.keyCode;return b&&l.isDelKey(b)?(this._processKey(null,b),l.preventDefault(a)):void 0},d.prototype._keyPress=function(a){var b,c;return a.which?b=a.which:(b=a.keyCode,c=l.isSpecialKey(b)),l.isDelKey(b)||c||l.isModifier(a)?void 0:(this._processKey(String.fromCharCode(b),!1),l.preventDefault(a))},d.prototype._paste=function(a){return this._processKey(l.getClip(a),!1),l.preventDefault(a)},d.prototype._focus=function(){var a=this;setTimeout(function(){var b=k.get(a.el),c=b.end>a.focus,d=0===b.end;(c||d)&&k.set(a.el,a.focus)},0)},d.prototype._processKey=function(a,b){if(this.sel=k.get(this.el),this.val=this.el.value,this.delta=0,this.sel.begin!==this.sel.end)this.delta=-1*Math.abs(this.sel.begin-this.sel.end),this.val=l.removeChars(this.val,this.sel.begin,this.sel.end);else if(b&&46==b)this._delete();else if(b&&this.sel.begin-1>=0)this.val=l.removeChars(this.val,this.sel.end-1,this.sel.end),this.delta=-1;else if(b)return!0;b||(this.val=l.addChars(this.val,a,this.sel.begin),this.delta+=a.length),this._formatValue()},d.prototype._delete=function(){for(;this.chars[this.sel.begin];)this._nextPos();this.sel.begin<this.val.length&&(this._nextPos(),this.val=l.removeChars(this.val,this.sel.end-1,this.sel.end),this.delta=-1)},d.prototype._nextPos=function(){this.sel.end++,this.sel.begin++},d.prototype._formatValue=function(){this.newPos=this.sel.end+this.delta,this._removeChars(),this._validateInpts(),this._addChars(),this.el.value=this.val.substr(0,this.mLength),k.set(this.el,this.newPos)},d.prototype._removeChars=function(){this.sel.end>this.focus&&(this.delta+=this.sel.end-this.focus);for(var a=0,b=0;b<=this.mLength;b++){var c,d=this.chars[b],e=this.hldrs[b],f=b+a;f=b>=this.sel.begin?f+this.delta:f,c=this.val.charAt(f),(d&&d==c||e&&e==c)&&(this.val=l.removeChars(this.val,f,f+1),a--)}this.hldrs={},this.focus=this.val.length},d.prototype._validateInpts=function(){for(var a=0;a<this.val.length;a++){var b=this.inpts[a],c=!f[b],d=!c&&!f[b].test(this.val.charAt(a)),e=this.inpts[a];(c||d)&&e&&(this.val=l.removeChars(this.val,a,a+1),this.focusStart--,this.newPos--,this.delta--,a--)}},d.prototype._addChars=function(){if(this.opts.persistent){for(var a=0;a<=this.mLength;a++)this.val.charAt(a)||(this.val=l.addChars(this.val,this.opts.placeholder,a),this.hldrs[a]=this.opts.placeholder),this._addChar(a);for(;this.chars[this.focus];)this.focus++}else for(var b=0;b<=this.val.length;b++){if(this.delta<=0&&b==this.focus)return!0;this._addChar(b)}},d.prototype._addChar=function(a){var b=this.chars[a];return b?(l.isBetween(a,[this.sel.begin-1,this.newPos+1])&&(this.newPos++,this.delta++),a<=this.focus&&this.focus++,this.hldrs[a]&&(delete this.hldrs[a],this.hldrs[a+1]=this.opts.placeholder),void(this.val=l.addChars(this.val,b,a))):!0};var g={},h=4,i=new RegExp("{{([^}]+)}}","g"),j=function(a){for(var b,c=[];b=i.exec(a);)c.push(b);return c};g.parse=function(a){var b={inpts:{},chars:{}},c=j(a),d=a.length,e=0,f=0,g=0,i=function(a){for(var c=a.length,d=0;c>d;d++)b.inpts[f]=a.charAt(d),f++;e++,g+=a.length+h-1};for(g;d>g;g++)g==c[e].index?i(c[e][1]):b.chars[g-e*h]=a.charAt(g);return b.mLength=g-e*h,b};var k={};k.get=function(a){if("number"==typeof a.selectionStart)return{begin:a.selectionStart,end:a.selectionEnd};var b=c.selection.createRange();if(b&&b.parentElement()==a){var d=a.createTextRange(),e=a.createTextRange(),f=a.value.length;return d.moveToBookmark(b.getBookmark()),e.collapse(!1),d.compareEndPoints("StartToEnd",e)>-1?{begin:f,end:f}:{begin:-d.moveStart("character",-f),end:-d.moveEnd("character",-f)}}return{begin:0,end:0}},k.set=function(a,b){if(a.setSelectionRange)a.focus(),a.setSelectionRange(b,b);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0),c.moveEnd("character",b),c.moveStart("character",b),c.select()}};var l={},m="undefined"!=typeof navigator?navigator.userAgent:null,n=/iphone/i.test(m);l.extend=function(a){for(var b=1;b<arguments.length;b++)for(var c in arguments[b])a[c]=arguments[b][c];return a},l.addChars=function(a,b,c){return a.substr(0,c)+b+a.substr(c,a.length)},l.removeChars=function(a,b,c){return a.substr(0,b)+a.substr(c,a.length)},l.isBetween=function(a,b){return b.sort(function(a,b){return a-b}),a>b[0]&&a<b[1]},l.addListener=function(a,b,c){return"undefined"!=typeof a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},l.preventDefault=function(a){return a.preventDefault?a.preventDefault():a.returnValue=!1},l.getClip=function(a){return a.clipboardData?a.clipboardData.getData("Text"):b.clipboardData?b.clipboardData.getData("Text"):void 0},l.isDelKey=function(a){return 8===a||46===a||n&&127===a},l.isSpecialKey=function(a){var b={9:"tab",13:"enter",35:"end",36:"home",37:"leftarrow",38:"uparrow",39:"rightarrow",40:"downarrow",116:"F5"};return b[a]},l.isModifier=function(a){return a.ctrlKey||a.altKey||a.metaKey};var o="formatter";a.fn[o]=function(b){return"object"==typeof b&&this.each(function(){a.data(this,"plugin_"+o)||a.data(this,"plugin_"+o,new d(this,b))}),this.resetPattern=function(b){return this.each(function(){var c=a.data(this,"plugin_"+o);c&&c.resetPattern(b)}),this},this},a.fn[o].addInptType=function(a,b){d.addInptType(a,b)}}(jQuery,window,document);
\ No newline at end of file
+!function(a,b,c,d){function e(a,b){var c=this;if(c.el=a,!c.el)throw new TypeError("Must provide an existing element");if(c.opts=o.extend({},g,b),"undefined"!=typeof c.opts.pattern&&(c.opts.patterns=c._specFromSinglePattern(c.opts.pattern),delete c.opts.pattern),"undefined"==typeof c.opts.patterns)throw new TypeError("Must provide a pattern or array of patterns");c.patternMatcher=f(c.opts.patterns),c._updatePattern(),c.hldrs={},c.focus=0,o.addListener(c.el,"keydown",function(a){c._keyDown(a)}),o.addListener(c.el,"keypress",function(a){c._keyPress(a)}),o.addListener(c.el,"paste",function(a){c._paste(a)}),c.opts.persistent&&(c._processKey("",!1),c.el.blur(),o.addListener(c.el,"focus",function(a){c._focus(a)}),o.addListener(c.el,"click",function(a){c._focus(a)}),o.addListener(c.el,"touchstart",function(a){c._focus(a)}))}function f(a){var b=[],c=[];o.forEach(a,function(a){o.forEach(a,function(a,d){var e=i.parse(a),f=m(d);return b.push(f),c.push(e),!1})});var e=function(a){var e;return o.forEach(b,function(b,c){return b.test(a)?(e=c,!1):void 0}),e===d?null:c[e]};return{getPattern:e,patterns:c,matchers:b}}var g={persistent:!1,repeat:!1,placeholder:" "},h={9:/[0-9]/,a:/[A-Za-z]/,"*":/[A-Za-z0-9]/};e.addInptType=function(a,b){h[a]=b},e.prototype.resetPattern=function(a){this.opts.patterns=a?this._specFromSinglePattern(a):this.opts.patterns,this.sel=n.get(this.el),this.val=this.el.value,this.delta=0,this._removeChars(),this.patternMatcher=f(this.opts.patterns);var b=this.patternMatcher.getPattern(this.val);this.mLength=b.mLength,this.chars=b.chars,this.inpts=b.inpts,this._processKey("",!1,!0)},e.prototype._updatePattern=function(){var a=this.patternMatcher.getPattern(this.val);a&&(this.mLength=a.mLength,this.chars=a.chars,this.inpts=a.inpts)},e.prototype._keyDown=function(a){var b=a.which||a.keyCode;return b&&o.isDelKey(b)?(this._processKey(null,b),o.preventDefault(a)):void 0},e.prototype._keyPress=function(a){var b,c;return a.which?b=a.which:(b=a.keyCode,c=o.isSpecialKey(b)),o.isDelKey(b)||c||o.isModifier(a)?void 0:(this._processKey(String.fromCharCode(b),!1),o.preventDefault(a))},e.prototype._paste=function(a){return this._processKey(o.getClip(a),!1),o.preventDefault(a)},e.prototype._focus=function(){var a=this;setTimeout(function(){var b=n.get(a.el),c=b.end>a.focus,d=0===b.end;(c||d)&&n.set(a.el,a.focus)},0)},e.prototype._processKey=function(a,b,c){if(this.sel=n.get(this.el),this.val=this.el.value,this.delta=0,this.sel.begin!==this.sel.end)this.delta=-1*Math.abs(this.sel.begin-this.sel.end),this.val=o.removeChars(this.val,this.sel.begin,this.sel.end);else if(b&&46==b)this._delete();else if(b&&this.sel.begin-1>=0)this.val=o.removeChars(this.val,this.sel.end-1,this.sel.end),this.delta=-1;else if(b)return!0;b||(this.val=o.addChars(this.val,a,this.sel.begin),this.delta+=a.length),this._formatValue(c)},e.prototype._delete=function(){for(;this.chars[this.sel.begin];)this._nextPos();this.sel.begin<this.val.length&&(this._nextPos(),this.val=o.removeChars(this.val,this.sel.end-1,this.sel.end),this.delta=-1)},e.prototype._nextPos=function(){this.sel.end++,this.sel.begin++},e.prototype._formatValue=function(a){this.newPos=this.sel.end+this.delta,this._removeChars(),this._updatePattern(),this._validateInpts(),this._addChars(),this.el.value=this.val.substr(0,this.mLength),("undefined"==typeof a||a===!1)&&n.set(this.el,this.newPos)},e.prototype._removeChars=function(){this.sel.end>this.focus&&(this.delta+=this.sel.end-this.focus);for(var a=0,b=0;b<=this.mLength;b++){var c,d=this.chars[b],e=this.hldrs[b],f=b+a;f=b>=this.sel.begin?f+this.delta:f,c=this.val.charAt(f),(d&&d==c||e&&e==c)&&(this.val=o.removeChars(this.val,f,f+1),a--)}this.hldrs={},this.focus=this.val.length},e.prototype._validateInpts=function(){for(var a=0;a<this.val.length;a++){var b=this.inpts[a],c=!h[b],d=!c&&!h[b].test(this.val.charAt(a)),e=this.inpts[a];(c||d)&&e&&(this.val=o.removeChars(this.val,a,a+1),this.focusStart--,this.newPos--,this.delta--,a--)}},e.prototype._addChars=function(){if(this.opts.persistent){for(var a=0;a<=this.mLength;a++)this.val.charAt(a)||(this.val=o.addChars(this.val,this.opts.placeholder,a),this.hldrs[a]=this.opts.placeholder),this._addChar(a);for(;this.chars[this.focus];)this.focus++}else for(var b=0;b<=this.val.length;b++){if(this.delta<=0&&b==this.focus)return!0;this._addChar(b)}},e.prototype._addChar=function(a){var b=this.chars[a];return b?(o.isBetween(a,[this.sel.begin-1,this.newPos+1])&&(this.newPos++,this.delta++),a<=this.focus&&this.focus++,this.hldrs[a]&&(delete this.hldrs[a],this.hldrs[a+1]=this.opts.placeholder),void(this.val=o.addChars(this.val,b,a))):!0},e.prototype._specFromSinglePattern=function(a){return[{"*":a}]};var i={},j=4,k=new RegExp("{{([^}]+)}}","g"),l=function(a){for(var b,c=[];b=k.exec(a);)c.push(b);return c};i.parse=function(a){var b={inpts:{},chars:{}},c=l(a),d=a.length,e=0,f=0,g=0,h=function(a){for(var c=a.length,d=0;c>d;d++)b.inpts[f]=a.charAt(d),f++;e++,g+=a.length+j-1};for(g;d>g;g++)g==c[e].index?h(c[e][1]):b.chars[g-e*j]=a.charAt(g);return b.mLength=g-e*j,b};var m=function(a){return"*"===a?/.*/:new RegExp(a)},n={};n.get=function(a){if("number"==typeof a.selectionStart)return{begin:a.selectionStart,end:a.selectionEnd};var b=c.selection.createRange();if(b&&b.parentElement()==a){var d=a.createTextRange(),e=a.createTextRange(),f=a.value.length;return d.moveToBookmark(b.getBookmark()),e.collapse(!1),d.compareEndPoints("StartToEnd",e)>-1?{begin:f,end:f}:{begin:-d.moveStart("character",-f),end:-d.moveEnd("character",-f)}}return{begin:0,end:0}},n.set=function(a,b){if(a.setSelectionRange)a.focus(),a.setSelectionRange(b,b);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0),c.moveEnd("character",b),c.moveStart("character",b),c.select()}};var o={},p="undefined"!=typeof navigator?navigator.userAgent:null,q=/iphone/i.test(p);o.extend=function(a){for(var b=1;b<arguments.length;b++)for(var c in arguments[b])a[c]=arguments[b][c];return a},o.addChars=function(a,b,c){return a.substr(0,c)+b+a.substr(c,a.length)},o.removeChars=function(a,b,c){return a.substr(0,b)+a.substr(c,a.length)},o.isBetween=function(a,b){return b.sort(function(a,b){return a-b}),a>b[0]&&a<b[1]},o.addListener=function(a,b,c){return"undefined"!=typeof a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},o.preventDefault=function(a){return a.preventDefault?a.preventDefault():a.returnValue=!1},o.getClip=function(a){return a.clipboardData?a.clipboardData.getData("Text"):b.clipboardData?b.clipboardData.getData("Text"):void 0},o.isDelKey=function(a){return 8===a||46===a||q&&127===a},o.isSpecialKey=function(a){var b={9:"tab",13:"enter",35:"end",36:"home",37:"leftarrow",38:"uparrow",39:"rightarrow",40:"downarrow",116:"F5"};return b[a]},o.isModifier=function(a){return a.ctrlKey||a.altKey||a.metaKey},o.forEach=function(a,b,c){if(a.hasOwnProperty("length"))for(var d=0,e=a.length;e>d&&b.call(c,a[d],d,a)!==!1;d++);else for(var f in a)if(a.hasOwnProperty(f)&&b.call(c,a[f],f,a)===!1)break};var r="formatter";a.fn[r]=function(b){return"object"==typeof b&&this.each(function(){a.data(this,"plugin_"+r)||a.data(this,"plugin_"+r,new e(this,b))}),this.resetPattern=function(b){return this.each(function(){var c=a.data(this,"plugin_"+r);c&&c.resetPattern(b)}),this},this},a.fn[r].addInptType=function(a,b){e.addInptType(a,b)}}(jQuery,window,document);
\ No newline at end of file
diff --git a/src/formatter.js b/src/formatter.js
index bda9d9e..590f8a8 100644
--- a/src/formatter.js
+++ b/src/formatter.js
@@ -6,7 +6,7 @@
  */
 
 /* ExcludeStart */
-var pattern = require('./pattern'),
+var patternMatcher = require('./pattern-matcher'),
     inptSel = require('./inpt-sel'),
     utils   = require('./utils');
 
@@ -46,16 +46,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 = {};
@@ -105,8 +110,7 @@ Formatter.addInptType = function (chr, reg) {
 //
 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,11 +122,13 @@ 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, true);
@@ -130,6 +136,24 @@ Formatter.prototype.resetPattern = function (str) {
 
 //
 // @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.
 //
@@ -281,12 +305,16 @@ Formatter.prototype._formatValue = function (ignoreCaret) {
 
   // 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 
+  // Set value and adhere to maxLength
   this.el.value = this.val.substr(0, this.mLength);
 
   // Set new caret position
@@ -420,3 +448,13 @@ Formatter.prototype._addChar = function (i) {
   // 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 }];
+};
+
diff --git a/src/pattern-matcher.js b/src/pattern-matcher.js
new file mode 100644
index 0000000..b35c926
--- /dev/null
+++ b/src/pattern-matcher.js
@@ -0,0 +1,73 @@
+/*
+* pattern-matcher.js
+*
+* Parses a pattern specification and determines appropriate pattern for an
+* input string
+*
+*/
+
+/* ExcludeStart */
+var pattern = require('./pattern'),
+    utils = require('./utils');
+
+// Define module
+module.exports = patternMatcher;
+/* ExcludeEnd */
+
+//
+// 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
+  };
+}
diff --git a/src/utils.js b/src/utils.js
index fc352ea..979db86 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -102,4 +102,25 @@ utils.isSpecialKey = function (k) {
 //
 utils.isModifier = function (evt) {
   return evt.ctrlKey || evt.altKey || evt.metaKey;
-};
\ No newline at end of file
+};
+
+//
+// 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;
+        }
+      }
+    }
+  }
+};
diff --git a/test/formatter.js b/test/formatter.js
index 0bac3b2..6c2463e 100644
--- a/test/formatter.js
+++ b/test/formatter.js
@@ -4,7 +4,7 @@
  * (C) 2013 First Opinion
  * MIT LICENCE
  *
- */ 
+ */
 
 // 3rd party
 var restore = require('sinon').restore,
@@ -69,7 +69,7 @@ describe('formatter.js', function () {
     it('Should set init values and merge defaults', function () {
       createInstance('({{999}}) {{999}}-{{9999}}');
       // Check opts
-      assert.equal(formatted.opts.pattern, '({{999}}) {{999}}-{{9999}}');
+      assert.equal(formatted.opts.patterns[0]['*'], '({{999}}) {{999}}-{{9999}}');
       assert.isTrue(formatted.opts.persistent);
       // Check pattern
       assert.isObject(formatted.chars);
@@ -137,6 +137,51 @@ describe('formatter.js', function () {
     });
   });
 
+  //
+  // Formatter with value dependent patterns
+  //
+  describe('value dependent patterns', function () {
+    it('Should apply the default format', function (done) {
+      formatted = new Formatter(el, {
+        patterns: [
+          { '*': '!{{9}}' }
+        ]
+      });
+
+      user.keySeq('1', function () {
+        assert.equal(formatted.el.value, '!1');
+        done();
+      });
+    });
+
+    it('Should apply appropriate format based on current value', function (done) {
+      formatted = new Formatter(el, {
+        patterns: [
+          { '^0': '!{{9999}}' },
+          {  '*': '{{9999}}' }
+        ]
+      });
+
+      user.keySeq('0123', function () {
+        assert.equal(formatted.el.value, '!0123');
+        done();
+      });
+    });
+
+    it('Should apply the first appropriate format that matches the current value', function (done) {
+      formatted = new Formatter(el, {
+        patterns: [
+          {  '^0': 'first:{{9999}}' },
+          { '^00': 'second:{{9999}}' }
+        ]
+      });
+
+      user.keySeq('00', function () {
+        assert.equal(formatted.el.value, 'first:00');
+        done();
+      });
+    });
+  });
 
   //
   // Formatter with persistence
@@ -293,7 +338,7 @@ describe('formatter.js', function () {
         done();
       });
     });
- 
+
     it('Should update value when resetPattern method is called without changing pattern', function (done) {
       user.keySeq('2456789013', function () {
         formatted.resetPattern();
@@ -312,5 +357,5 @@ describe('formatter.js', function () {
       });
     });
   });
-  
+
 });
diff --git a/test/pattern-matcher.js b/test/pattern-matcher.js
new file mode 100644
index 0000000..d2d0e61
--- /dev/null
+++ b/test/pattern-matcher.js
@@ -0,0 +1,86 @@
+/*
+* test/pattern-matcher.js
+*
+* MIT LICENCE
+*
+*/
+
+// 3rd party
+var should = require('chai').should(),
+    assert = require('chai').assert,
+    sinon = require('sinon');
+
+// first party
+var patternMatcher = require('../src/pattern-matcher'),
+    pattern = require("../src/pattern");
+
+//
+// pattern-matcher.js tests
+//
+describe('pattern-matcher.js', function () {
+  it('Should parse each matcher as a regex', function () {
+    var myPatternMatcher = patternMatcher([ { '^abc$': '{{*}}-{{*}}-{{*}}' } ]);
+
+    assert.isTrue(myPatternMatcher.matchers[0].test("abc"));
+    assert.isFalse(myPatternMatcher.matchers[0].test("xyz"));
+  });
+
+  it('Should parse each pattern as a pattern', function () {
+    var myPatternMatcher = patternMatcher([ { '^abc$': '{{***}}' } ]);
+
+    assert.deepEqual(myPatternMatcher.patterns[0], pattern.parse('{{***}}'));
+  });
+
+  describe('getPattern', function  () {
+    it('Should return the appropriate pattern for the input', function () {
+      var patternStringA = '!{{*}}{{*}}',
+      patternStringB = '@{{*}}{{*}}',
+      patternA = pattern.parse(patternStringA),
+      patternB = pattern.parse(patternStringB);
+
+      var myPatternMatcher = patternMatcher([ { '^a': patternStringA },
+                                            { '^b': patternStringB } ]);
+
+      assert.deepEqual(myPatternMatcher.getPattern("a"), patternA);
+      assert.deepEqual(myPatternMatcher.getPattern("abc"), patternA);
+      assert.deepEqual(myPatternMatcher.getPattern("bac"), patternB);
+    });
+
+    it('Should return the first matching pattern', function () {
+      var patternStringA = '!{{*}}{{*}}',
+      patternStringB = '@{{*}}{{*}}',
+      patternA = pattern.parse(patternStringA),
+      patternB = pattern.parse(patternStringB);
+
+      var myPatternMatcher = patternMatcher([ { '^a': patternStringA },
+                                            { '.*': patternStringB } ]);
+
+      assert.deepEqual(myPatternMatcher.getPattern("a"), patternA);
+      assert.deepEqual(myPatternMatcher.getPattern("aa"), patternA);
+    });
+
+    it('Should return the wildcard pattern "*" if no other matches', function () {
+      var patternStringA = '!{{*}}{{*}}',
+      patternStringB = '@{{*}}{{*}}',
+      patternA = pattern.parse(patternStringA),
+      patternB = pattern.parse(patternStringB);
+
+      var myPatternMatcher = patternMatcher([ { 'wont-match': patternStringA },
+                                            { '*': patternStringB } ]);
+
+      assert.deepEqual(myPatternMatcher.getPattern("a"), patternB);
+    });
+
+    it('Should return null if no pattern matches', function () {
+      var patternStringA = '!{{*}}{{*}}',
+      patternStringB = '@{{*}}{{*}}',
+      patternA = pattern.parse(patternStringA),
+      patternB = pattern.parse(patternStringB);
+
+      var myPatternMatcher = patternMatcher([ { 'wont-match': patternStringA },
+                                            { '*': patternStringB } ]);
+
+      assert.deepEqual(myPatternMatcher.getPattern("a"), patternB);
+    });
+  });
+});
diff --git a/test/utils.js b/test/utils.js
index 0e6112d..2bf3eed 100644
--- a/test/utils.js
+++ b/test/utils.js
@@ -4,7 +4,7 @@
  * (C) 2013 First Opinion
  * MIT LICENCE
  *
- */ 
+ */
 
 // 3rd party
 var should = require('chai').should(),
@@ -92,4 +92,59 @@ describe('utils.js', function () {
     });
   });
 
-});
\ No newline at end of file
+
+  // utils.forEach
+  // Iterate over a collection
+  describe('forEach', function () {
+    it('Should iterate over an array', function () {
+      var result = [];
+      utils.forEach(['a','b','c'], function (val, key) {
+        result.push(key);
+        result.push(val);
+      });
+
+      assert.deepEqual(result, [0,'a',1,'b',2,'c']);
+    });
+    it('Should iterate over an object', function () {
+      var result = [];
+      utils.forEach({ 'first': 'a', second: 'b' }, function (val, key) {
+        result.push(key);
+        result.push(val);
+      });
+
+      assert.deepEqual(result, ['first', 'a', 'second', 'b']);
+    });
+    it('Should ignore prototypically inherited properties', function () {
+      var parent = function () {
+        this.property = 'property';
+      };
+      parent.prototype = { protoProperty: 'protoProperty' } ;
+
+      var result = [];
+      utils.forEach(new parent(), function (val) {
+        result.push(val);
+      });
+
+      assert.deepEqual(result, ['property']);
+    });
+    it('Should stop short when callback returns false', function () {
+      var result = [];
+      utils.forEach(['a','b','c'], function (val, key) {
+        result.push(key);
+        result.push(val);
+        return false;
+      });
+
+      assert.deepEqual(result, [0,'a']);
+    });
+    it('Should bind the callback to the given thisArg', function () {
+      var result = [];
+      utils.forEach(['a','b','c'], function (val, key) {
+        this.push(key);
+        this.push(val);
+      }, result);
+
+      assert.deepEqual(result, [0,'a',1,'b',2,'c']);
+    });
+  });
+});
