From f90acacb7c7d2c0bef27fc079699305b8f94431e Mon Sep 17 00:00:00 2001
From: Jarid Margolin <jaridmargolin@gmail.com>
Date: Thu, 8 May 2014 17:07:22 -0700
Subject: [PATCH] Refactor to use AMD. Overhaul build process.

---
 .gitignore                                         |  11 +-
 .jshintrc                                          |  28 +
 Gruntfile.js                                       | 402 ++++++++---
 Makefile                                           |   6 -
 bower.json                                         |  10 +
 {lib => dist/amd}/formatter.js                     | 340 +--------
 dist/amd/inpt-sel.js                               |  91 +++
 dist/amd/pattern-matcher.js                        |  79 +++
 dist/amd/pattern.js                                |  82 +++
 dist/amd/utils.js                                  | 137 ++++
 .../common/formatter.js                            | 368 +---------
 dist/common/inpt-sel.js                            |  90 +++
 dist/common/pattern-matcher.js                     |  76 ++
 dist/common/pattern.js                             |  81 +++
 dist/common/utils.js                               | 136 ++++
 dist/formatter.js                                  | 766 ++++++++++++++++++++
 dist/formatter.min.js                              |   1 +
 dist/jquery.formatter.js                           | 787 +++++++++++++++++++++
 dist/jquery.formatter.min.js                       |   1 +
 docs/_layouts/master.html                          |  69 ++
 docs/demos.html                                    |  39 +
 docs/index.md                                      | 150 ++++
 docs/javascripts/formatter.js                      | 766 ++++++++++++++++++++
 docs/javascripts/formatter.min.js                  |   1 +
 docs/javascripts/jquery.formatter.js               | 787 +++++++++++++++++++++
 docs/javascripts/jquery.formatter.min.js           |   1 +
 docs/javascripts/scale.fix.js                      |  17 +
 docs/stylesheets/pygment_trac.css                  |  69 ++
 docs/stylesheets/styles.css                        | 343 +++++++++
 lib/formatter.min.js                               |   9 -
 lib/jquery.formatter.min.js                        |   9 -
 package.json                                       |  36 +-
 src/formatter.js                                   |  22 +-
 src/inpt-sel.js                                    |  38 +-
 src/pattern-matcher.js                             |  18 +-
 src/pattern.js                                     |  15 +-
 src/tmpls/intro.js                                 |   5 -
 src/tmpls/jquery.hbs                               |  62 ++
 src/tmpls/jquery.intro.js                          |   1 -
 src/tmpls/jquery.outro.js                          |  36 -
 src/tmpls/outro.js                                 |   3 -
 src/tmpls/umd.hbs                                  |  41 ++
 src/utils.js                                       |  17 +-
 test/_runner.html                                  |  43 ++
 test/fake-user/events.js                           |  63 --
 test/fake-user/keys.js                             | 247 -------
 test/fake-user/user.js                             |  92 ---
 test/formatter.js                                  | 313 ++++----
 test/pattern-matcher.js                            | 115 +--
 test/pattern.js                                    |  26 +-
 test/utils.js                                      |  45 +-
 51 files changed, 5480 insertions(+), 1510 deletions(-)
 create mode 100644 .jshintrc
 delete mode 100644 Makefile
 create mode 100644 bower.json
 rename {lib => dist/amd}/formatter.js (58%)
 create mode 100644 dist/amd/inpt-sel.js
 create mode 100644 dist/amd/pattern-matcher.js
 create mode 100644 dist/amd/pattern.js
 create mode 100644 dist/amd/utils.js
 rename lib/jquery.formatter.js => dist/common/formatter.js (56%)
 create mode 100644 dist/common/inpt-sel.js
 create mode 100644 dist/common/pattern-matcher.js
 create mode 100644 dist/common/pattern.js
 create mode 100644 dist/common/utils.js
 create mode 100644 dist/formatter.js
 create mode 100644 dist/formatter.min.js
 create mode 100644 dist/jquery.formatter.js
 create mode 100644 dist/jquery.formatter.min.js
 create mode 100644 docs/_layouts/master.html
 create mode 100644 docs/demos.html
 create mode 100644 docs/index.md
 create mode 100644 docs/javascripts/formatter.js
 create mode 100644 docs/javascripts/formatter.min.js
 create mode 100644 docs/javascripts/jquery.formatter.js
 create mode 100644 docs/javascripts/jquery.formatter.min.js
 create mode 100644 docs/javascripts/scale.fix.js
 create mode 100644 docs/stylesheets/pygment_trac.css
 create mode 100644 docs/stylesheets/styles.css
 delete mode 100644 lib/formatter.min.js
 delete mode 100644 lib/jquery.formatter.min.js
 delete mode 100644 src/tmpls/intro.js
 create mode 100644 src/tmpls/jquery.hbs
 delete mode 100644 src/tmpls/jquery.intro.js
 delete mode 100644 src/tmpls/jquery.outro.js
 delete mode 100644 src/tmpls/outro.js
 create mode 100644 src/tmpls/umd.hbs
 create mode 100644 test/_runner.html
 delete mode 100644 test/fake-user/events.js
 delete mode 100644 test/fake-user/keys.js
 delete mode 100644 test/fake-user/user.js

diff --git a/.gitignore b/.gitignore
index 822d612..7f45d07 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-.DS_STORE
+.DS_Store
 
 lib-cov
 *.seed
@@ -12,7 +12,12 @@ lib-cov
 pids
 logs
 results
-node_modules
+tmp
+
 npm-debug.log
+node_modules
+bower_components
+.grunt
+_site
 
-_site
\ No newline at end of file
+sauce_connect.log
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..7c75e5e
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,28 @@
+{
+  "node": true,
+  "camelcase": true,
+  "curly": true,
+  "eqeqeq": true,
+  "immed": true,
+  "indent": false,
+  "latedef": true,
+  "newcap": true,
+  "noarg": true,
+  "quotmark": "single",
+  "undef": false,
+  "unused": false,
+  "trailing": true,
+  "smarttabs": true,
+  "laxbreak": true,
+  "boss": true,
+  "sub": true,
+  "immed": false,
+  "predef": {
+    "describe": true,
+    "it": true,
+    "before": true,
+    "beforeEach": true,
+    "after": true,
+    "afterEach": true
+  }
+}
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
index 03b7fbe..1e6731c 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,99 +1,327 @@
-module.exports = function(grunt) {
-
-  grunt.initConfig({
-    pkg: grunt.file.readJSON('package.json'),
-    jshint: {
-      all: [
-        'Gruntfile.js',
-        'src/**/*.js',
-        'test/**/*.js',
-        'test/**/**/*.js',
-        'lib/formatter.js',
-        'lib/jquery.formatter.js'
-      ],
+/*!
+ * Gruntfile.js
+ * 
+ * Copyright (c) 2014
+ */
+
+
+module.exports = function (grunt) {
+
+
+// Load tasks
+require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
+
+
+// Browsers
+var browsers = [
+  // Latest Versions
+  { browserName: 'firefox', platform: 'WIN8' },
+  { browserName: 'chrome', platform: 'WIN8' },
+  { browserName: 'opera', platform: 'WIN7' },
+
+  // Internet Explorer
+  { browserName: 'internet explorer', platform: 'WIN8', version: '10' },
+  { browserName: 'internet explorer', platform: 'VISTA', version: '9' },
+  { browserName: 'internet explorer', platform: 'XP', version: '8' }
+];
+
+
+// Config
+grunt.initConfig({
+
+  // --------------------------------------------------------------------------
+  // PKG CONFIG
+  // --------------------------------------------------------------------------
+
+  'pkg': grunt.file.readJSON('package.json'),
+
+
+  // --------------------------------------------------------------------------
+  // JSHINT
+  // --------------------------------------------------------------------------
+
+  'jshint': {
+    src: [
+      'Gruntfile.js',
+      'src/**/*.js',
+      'test/**/*.js'
+    ],
+    build: [
+      'dist/**/*.js',
+      '!dist/**/*.min.js'
+    ],
+    options: {
+      jshintrc: '.jshintrc',
+      force: true
+    }
+  },
+
+
+  // --------------------------------------------------------------------------
+  // CLEAN (EMPTY DIRECTORY)
+  // --------------------------------------------------------------------------
+
+  'clean': {
+    dist: [
+      'dist'
+    ],
+    docs: [
+      'docs/javascripts/*formatter.js',
+      'docs/javascripts/*formatter.min.js',
+      'docs/index.md'
+    ]
+  },
+
+
+  // --------------------------------------------------------------------------
+  // REQUIREJS BUILD
+  // --------------------------------------------------------------------------
+
+  'requirejs': {
+    compile: {
       options: {
-        ignores: [
-          'src/tmpls/intro.js',
-          'src/tmpls/outro.js',
-          'src/tmpls/jquery.intro.js',
-          'src/tmpls/jquery.outro.js'
-        ],
-        force: true,
-        es3: true,
-        smarttabs: true,
-        // Bad line breaking before '?'.
-        '-W014': true,
-        // Expected a conditional expression and instead saw an assignment.
-        '-W084': true
+        name: 'formatter',
+        baseUrl: 'src',
+        out: 'dist/formatter.js',
+        optimize: 'none',
+        skipModuleInsertion: true,
+        onBuildWrite: function(name, path, contents) {
+          return require('amdclean').clean({
+            code: contents,
+            prefixMode: 'camelCase',
+            escodegen: {
+              format: {
+                indent: { style: '  ' }
+              }
+            }
+          });
+        }
       }
+    }
+  },
+
+
+  // --------------------------------------------------------------------------
+  // UMD WRAP
+  // --------------------------------------------------------------------------
+
+  'umd': {
+    jquery: {
+      src: 'dist/formatter.js',
+      dest: 'dist/jquery.formatter.js',
+      template: 'src/tmpls/jquery.hbs',
+      deps: { 'default': ['jQuery'] }
+    },
+    umd: {
+      src: 'dist/formatter.js',
+      objectToExport: 'formatter',
+      globalAlias: 'Formatter',
+      template: 'src/tmpls/umd.hbs',
+      dest: 'dist/formatter.js'
+    }
+  },
+
+
+  // --------------------------------------------------------------------------
+  // ADD BANNER
+  // --------------------------------------------------------------------------
+
+  'concat': {
+    options: {
+      banner: '/*!\n' +
+        ' * v<%= pkg.version %>\n' +
+        ' * Copyright (c) 2014 First Opinion\n' +
+        ' * formatter.js is open sourced under the MIT license.\n' +
+        ' *\n' +
+        ' * thanks to digitalBush/jquery.maskedinput for some of the trickier\n' +
+        ' * keycode handling\n' +
+        ' */ \n\n',
+      stripBanners: true
+    },
+    umd: {
+      src: 'dist/formatter.js',
+      dest: 'dist/formatter.js'
     },
-    concat: {
+    jquery: {
+      src: 'dist/jquery.formatter.js',
+      dest: 'dist/jquery.formatter.js'
+    }
+  },
+
+
+  // --------------------------------------------------------------------------
+  // MINIFY JS
+  // --------------------------------------------------------------------------
+
+  'uglify': {
+    umd: {
+      src: 'dist/formatter.js',
+      dest: 'dist/formatter.min.js'
+    },
+    jquery: {
+      src: 'dist/jquery.formatter.js',
+      dest: 'dist/jquery.formatter.min.js'
+    }
+  },
+
+
+  // --------------------------------------------------------------------------
+  // CREATE COMMONJS VERSION IN DIST
+  // --------------------------------------------------------------------------
+
+  'nodefy': {
+    all: {
+      expand: true,
+      src: ['**/*.js'],
+      cwd: 'src/',
+      dest: 'dist/common'
+    }
+  },
+
+
+  // --------------------------------------------------------------------------
+  // COPY AMD TO DIST
+  // --------------------------------------------------------------------------
+
+  'copy': {
+    amd: {
+      expand: true,
+      src: ['**/*.js'],
+      cwd: 'src/',
+      dest: 'dist/amd'
+    },
+    javascripts: {
+      expand: true,
+      src: ['*.js'],
+      cwd: 'dist',
+      dest: 'docs/javascripts'
+    },
+    readme: {
+      src: 'README.md',
+      dest: 'docs/index.md'
+    }
+  },
+
+
+  // --------------------------------------------------------------------------
+  // WRAP
+  // --------------------------------------------------------------------------
+
+  'wrap': {
+    readme: {
+      src: ['docs/index.md'],
+      dest: 'docs/index.md',
+      options: {
+        wrapper: ['---\nlayout: master\n---\n{% raw %}', '{% endraw %}']
+      }
+    }
+  },
+
+
+  // --------------------------------------------------------------------------
+  // WATCH FILES
+  // --------------------------------------------------------------------------
+
+  'watch': {
+    options: { spawn: true },
+    build: {
+      files: ['Gruntfile.js'],
+      tasks: ['build', 'docs'],
+      options: { livereload: true }
+    },
+    src: {
+      files: ['src/**/*.js'],
+      tasks: ['build'],
+      options: { livereload: true }
+    },
+    docs: {
+      files: ['docs/**/*'],
+      tasks: ['jekyll'],
+      options: { livereload: true }
+    },
+    test: {
+      files: ['test/**/*'],
+      options: { livereload: true }
+    }
+  },
+
+
+  // --------------------------------------------------------------------------
+  // STATIC SERVER
+  // --------------------------------------------------------------------------
+
+  'connect': {
+    docs: {
+      options: { base: '_site', port: 9998 }
+    },
+    test: {
+      options: { base: '', port: 9999 }
+    }
+  },
+
+
+  // --------------------------------------------------------------------------
+  // BUILD AND SERVE JEKYLL DOCS
+  // --------------------------------------------------------------------------
+
+  'jekyll': {
+    all: {
       options: {
-        banner: '/*!\n' +
-          ' * v<%= pkg.version %>\n' +
-          ' * Copyright (c) 2013 First Opinion\n' +
-          ' * formatter.js is open sourced under the MIT license.\n' +
-          ' *\n' +
-          ' * thanks to digitalBush/jquery.maskedinput for some of the trickier\n' +
-          ' * keycode handling\n' +
-          ' */ \n\n',
-        process: function(src, filepath) {
-          if (!/tmpls\/.*\.js/.test(filepath)) {
-            // Remove contents between Exclude Start and Exclude End
-            src = src.replace( /\/\*\s*ExcludeStart\s*\*\/[\w\W]*?\/\*\s*ExcludeEnd\s*\*\//ig, '');
-            // Rewrite module.exports to local var
-            src = src.replace(/module.exports\s=/g, 'var');
-          }
-          // Return final
-          return src;
-        },
-        stripBanners: true
-      },
-      vanilla: {
-        src: [
-          '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'
-        ],
-        dest: 'lib/formatter.js'
-      },
-      jquery: {
-        src: [
-          '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'
-        ],
-        dest: 'lib/jquery.formatter.js'
+        src : 'docs',
+        dest: '_site'
       }
+    }
+  },
+
+
+  // --------------------------------------------------------------------------
+  // PUSH DOCS LIVE
+  // --------------------------------------------------------------------------
+
+  'gh-pages': {
+    options: {
+      base: 'docs'
     },
-    uglify: {
+    src: ['**']
+  },
+
+
+  // --------------------------------------------------------------------------
+  // TESTS
+  // --------------------------------------------------------------------------
+
+  'saucelabs-mocha': {
+    all: {
       options: {
-        preserveComments: 'some'
-      },
-      vanilla: {
-        src: 'lib/formatter.js',
-        dest: 'lib/formatter.min.js'
-      },
-      jquery: {
-        src: 'lib/jquery.formatter.js',
-        dest: 'lib/jquery.formatter.min.js'
+        urls: ['http://127.0.0.1:9999/test/_runner.html'],
+        build: process.env.TRAVIS_JOB_ID || '<%= pkg.version %>',
+        tunnelTimeout: 5,
+        concurrency: 3,
+        browsers: browsers,
+        testname: 'formatter.js'
       }
     }
-  });
+  },
+
+
+  // --------------------------------------------------------------------------
+  // MOCHA
+  // --------------------------------------------------------------------------
+
+  'mocha_phantomjs': {
+    all: ['test/_runner.html']
+  }
+
+});
+
+
+// Tasks
+grunt.registerTask('default', ['build']);
+grunt.registerTask('dev', ['build', 'docs', 'connect', 'watch']);
+grunt.registerTask('test', ['build', 'mocha_phantomjs']);
+grunt.registerTask('test-cloud', ['build', 'connect:test', 'saucelabs-mocha']);
+grunt.registerTask('docs', ['clean:docs', 'copy:javascripts', 'copy:readme', 'wrap:readme', 'jekyll']);
+grunt.registerTask('build', ['jshint:src', 'clean:dist', 'requirejs', 'umd:jquery', 'umd:umd', 'concat:umd', 'concat:jquery', 'uglify:umd', 'uglify:jquery', 'nodefy', 'copy:amd']);
 
-  // Load plugins
-  grunt.loadNpmTasks('grunt-contrib-jshint');
-  grunt.loadNpmTasks('grunt-contrib-concat');
-  grunt.loadNpmTasks('grunt-contrib-uglify');
 
-  // Tasks
-  grunt.registerTask('default', ['concat', 'uglify', 'jshint']);
 };
\ No newline at end of file
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 6962297..0000000
--- a/Makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-test:
-	@NODE_ENV=test grunt uglify concat && \
-	grunt jshint && \
-	./node_modules/.bin/mocha --reporter spec --check-leaks
-
-.PHONY: test
\ No newline at end of file
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..43f3d96
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,10 @@
+{
+  "name": "formatter.js",
+  "version": "0.1.0",
+  "devDependencies": {
+    "easy-amdtest": "~0.0.1",
+    "requirejs": "~2.1.11",
+    "jquery": "~1.11.0",
+    "fakey": "~0.0.7"
+  }
+}
\ No newline at end of file
diff --git a/lib/formatter.js b/dist/amd/formatter.js
similarity index 58%
rename from lib/formatter.js
rename to dist/amd/formatter.js
index 7cfa184..6330bf8 100644
--- a/lib/formatter.js
+++ b/dist/amd/formatter.js
@@ -1,17 +1,16 @@
-/*!
- * v0.0.9
- * Copyright (c) 2013 First Opinion
- * formatter.js is open sourced under the MIT license.
+/*
+ * formatter.js
  *
- * thanks to digitalBush/jquery.maskedinput for some of the trickier
- * keycode handling
- */ 
+ * Class used to format input based on passed pattern
+ *
+ */
+
+define([
+  'pattern-matcher',
+  'inpt-sel',
+  'utils'
+], function (patternMatcher, inptSel, utils) {
 
-;(function (name, context, definition) {
-  if (typeof module !== 'undefined' && module.exports) { module.exports = definition(); }
-  else if (typeof define === 'function' && define.amd) { define(definition); }
-  else { context[name] = definition(); }
-})('Formatter', this, function () {
 
 // Defaults
 var defaults = {
@@ -240,7 +239,7 @@ Formatter.prototype._processKey = function (chars, delKey,ingoreCaret) {
   }
 
   // Delete key (moves opposite direction)
-  else if (delKey && delKey == 46) {
+  else if (delKey && delKey === 46) {
     this._delete();
 
   // or Backspace and not at start
@@ -357,7 +356,7 @@ Formatter.prototype._removeChars = function () {
     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) {
+    if (curChar && curChar === val || curHldr && curHldr === val) {
       this.val = utils.removeChars(this.val, pos, pos + 1);
       shift--;
     }
@@ -469,317 +468,8 @@ Formatter.prototype._specFromSinglePattern = function (patternStr) {
 };
 
 
-// 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;
-};
-
-//
-// 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 = {};
-
-//
-// 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) {
-  // If normal browser
-  if (el.setSelectionRange) {
-    el.focus();
-    el.setSelectionRange(pos,pos);
-
-  // IE = TextRange fun
-  } else if (el.createTextRange) {
-    var range = el.createTextRange();
-    range.collapse(true);
-    range.moveEnd('character', pos);
-    range.moveStart('character', pos);
-    range.select();
-  }
-};
-// Define module
-var utils = {};
-
-// Useragent info for keycode handling
-var uAgent = (typeof navigator !== 'undefined') ? navigator.userAgent : null,
-    iPhone = /iphone/i.test(uAgent);
-
-//
-// 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'); }
-};
-
-//
-// Returns true/false if k is a del key
-//
-utils.isDelKey = function (k) {
-  return k === 8 || k === 46 || (iPhone && k === 127);
-};
-
-//
-// Returns true/false if k is an arrow key
-//
-utils.isSpecialKey = function (k) {
-  var codes = {
-    '9' : 'tab',
-    '13': 'enter',
-    '35': 'end',
-    '36': 'home',
-    '37': 'leftarrow',
-    '38': 'uparrow',
-    '39': 'rightarrow',
-    '40': 'downarrow',
-    '116': 'F5'
-  };
-  // If del or special key
-  return codes[k];
-};
-
-//
-// 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 Formatter;
 
+
 });
\ No newline at end of file
diff --git a/dist/amd/inpt-sel.js b/dist/amd/inpt-sel.js
new file mode 100644
index 0000000..9f1f771
--- /dev/null
+++ b/dist/amd/inpt-sel.js
@@ -0,0 +1,91 @@
+/*
+ * inpt-sel.js
+ *
+ * Cross browser implementation to get and set input selections
+ *
+ */
+
+
+define(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);
+
+  // IE = TextRange fun
+  } 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;
+
+
+});
\ No newline at end of file
diff --git a/dist/amd/pattern-matcher.js b/dist/amd/pattern-matcher.js
new file mode 100644
index 0000000..780726b
--- /dev/null
+++ b/dist/amd/pattern-matcher.js
@@ -0,0 +1,79 @@
+/*
+* pattern-matcher.js
+*
+* Parses a pattern specification and determines appropriate pattern for an
+* input string
+*
+*/
+
+
+define([
+  'pattern',
+  'utils'
+], 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;
+
+
+});
\ No newline at end of file
diff --git a/dist/amd/pattern.js b/dist/amd/pattern.js
new file mode 100644
index 0000000..4d61efa
--- /dev/null
+++ b/dist/amd/pattern.js
@@ -0,0 +1,82 @@
+/*
+ * pattern.js
+ *
+ * Utilities to parse str pattern and return info
+ *
+ */
+
+
+define(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;
+
+
+});
\ No newline at end of file
diff --git a/dist/amd/utils.js b/dist/amd/utils.js
new file mode 100644
index 0000000..8995e80
--- /dev/null
+++ b/dist/amd/utils.js
@@ -0,0 +1,137 @@
+/*
+ * utils.js
+ *
+ * Independent helper methods (cross browser, etc..)
+ *
+ */
+
+
+define(function () {
+
+
+// Define module
+var utils = {};
+
+// Useragent info for keycode handling
+var uAgent = (typeof navigator !== 'undefined') ? navigator.userAgent : null,
+    iPhone = /iphone/i.test(uAgent);
+
+//
+// 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'); }
+};
+
+//
+// Returns true/false if k is a del key
+//
+utils.isDelKey = function (k) {
+  return k === 8 || k === 46 || (iPhone && k === 127);
+};
+
+//
+// Returns true/false if k is an arrow key
+//
+utils.isSpecialKey = function (k) {
+  var codes = {
+    '9' : 'tab',
+    '13': 'enter',
+    '35': 'end',
+    '36': 'home',
+    '37': 'leftarrow',
+    '38': 'uparrow',
+    '39': 'rightarrow',
+    '40': 'downarrow',
+    '116': 'F5'
+  };
+  // If del or special key
+  return codes[k];
+};
+
+//
+// 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;
+
+
+});
\ No newline at end of file
diff --git a/lib/jquery.formatter.js b/dist/common/formatter.js
similarity index 56%
rename from lib/jquery.formatter.js
rename to dist/common/formatter.js
index b0fd537..f6b0068 100644
--- a/lib/jquery.formatter.js
+++ b/dist/common/formatter.js
@@ -1,13 +1,14 @@
-/*!
- * v0.0.9
- * Copyright (c) 2013 First Opinion
- * formatter.js is open sourced under the MIT license.
+/*
+ * formatter.js
  *
- * thanks to digitalBush/jquery.maskedinput for some of the trickier
- * keycode handling
- */ 
+ * Class used to format input based on passed pattern
+ *
+ */
+
+var patternMatcher = require('pattern-matcher');
+var inptSel = require('inpt-sel');
+var utils = require('utils');
 
-;(function ($, window, document, undefined) {
 
 // Defaults
 var defaults = {
@@ -236,7 +237,7 @@ Formatter.prototype._processKey = function (chars, delKey,ingoreCaret) {
   }
 
   // Delete key (moves opposite direction)
-  else if (delKey && delKey == 46) {
+  else if (delKey && delKey === 46) {
     this._delete();
 
   // or Backspace and not at start
@@ -353,7 +354,7 @@ Formatter.prototype._removeChars = function () {
     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) {
+    if (curChar && curChar === val || curHldr && curHldr === val) {
       this.val = utils.removeChars(this.val, pos, pos + 1);
       shift--;
     }
@@ -465,350 +466,7 @@ Formatter.prototype._specFromSinglePattern = function (patternStr) {
 };
 
 
-// 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;
-};
-
-//
-// 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 = {};
-
-//
-// 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) {
-  // If normal browser
-  if (el.setSelectionRange) {
-    el.focus();
-    el.setSelectionRange(pos,pos);
-
-  // IE = TextRange fun
-  } else if (el.createTextRange) {
-    var range = el.createTextRange();
-    range.collapse(true);
-    range.moveEnd('character', pos);
-    range.moveStart('character', pos);
-    range.select();
-  }
-};
-// Define module
-var utils = {};
-
-// Useragent info for keycode handling
-var uAgent = (typeof navigator !== 'undefined') ? navigator.userAgent : null,
-    iPhone = /iphone/i.test(uAgent);
+// Expose
+module.exports = Formatter;
 
-//
-// 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'); }
-};
-
-//
-// Returns true/false if k is a del key
-//
-utils.isDelKey = function (k) {
-  return k === 8 || k === 46 || (iPhone && k === 127);
-};
-
-//
-// Returns true/false if k is an arrow key
-//
-utils.isSpecialKey = function (k) {
-  var codes = {
-    '9' : 'tab',
-    '13': 'enter',
-    '35': 'end',
-    '36': 'home',
-    '37': 'leftarrow',
-    '38': 'uparrow',
-    '39': 'rightarrow',
-    '40': 'downarrow',
-    '116': 'F5'
-  };
-  // If del or special key
-  return codes[k];
-};
-
-//
-// 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;
-        }
-      }
-    }
-  }
-};
-
-// 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);
-};
 
-})( jQuery, window, document);
\ No newline at end of file
diff --git a/dist/common/inpt-sel.js b/dist/common/inpt-sel.js
new file mode 100644
index 0000000..3f11c3e
--- /dev/null
+++ b/dist/common/inpt-sel.js
@@ -0,0 +1,90 @@
+/*
+ * inpt-sel.js
+ *
+ * Cross browser implementation to get and set input selections
+ *
+ */
+
+
+
+
+
+// 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);
+
+  // IE = TextRange fun
+  } else if (el.createTextRange) {
+    var range = el.createTextRange();
+    range.collapse(true);
+    range.moveEnd('character', pos.end);
+    range.moveStart('character', pos.begin);
+    range.select();
+  }
+};
+
+
+// Expose
+module.exports = inptSel;
+
+
diff --git a/dist/common/pattern-matcher.js b/dist/common/pattern-matcher.js
new file mode 100644
index 0000000..cdbae78
--- /dev/null
+++ b/dist/common/pattern-matcher.js
@@ -0,0 +1,76 @@
+/*
+* pattern-matcher.js
+*
+* Parses a pattern specification and determines appropriate pattern for an
+* input string
+*
+*/
+
+
+var pattern = require('pattern');
+var utils = require('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
+module.exports = patternMatcher;
+
+
diff --git a/dist/common/pattern.js b/dist/common/pattern.js
new file mode 100644
index 0000000..3e2613d
--- /dev/null
+++ b/dist/common/pattern.js
@@ -0,0 +1,81 @@
+/*
+ * pattern.js
+ *
+ * Utilities to parse str pattern and return info
+ *
+ */
+
+
+
+
+
+// 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
+module.exports = pattern;
+
+
diff --git a/dist/common/utils.js b/dist/common/utils.js
new file mode 100644
index 0000000..b78e837
--- /dev/null
+++ b/dist/common/utils.js
@@ -0,0 +1,136 @@
+/*
+ * utils.js
+ *
+ * Independent helper methods (cross browser, etc..)
+ *
+ */
+
+
+
+
+
+// Define module
+var utils = {};
+
+// Useragent info for keycode handling
+var uAgent = (typeof navigator !== 'undefined') ? navigator.userAgent : null,
+    iPhone = /iphone/i.test(uAgent);
+
+//
+// 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'); }
+};
+
+//
+// Returns true/false if k is a del key
+//
+utils.isDelKey = function (k) {
+  return k === 8 || k === 46 || (iPhone && k === 127);
+};
+
+//
+// Returns true/false if k is an arrow key
+//
+utils.isSpecialKey = function (k) {
+  var codes = {
+    '9' : 'tab',
+    '13': 'enter',
+    '35': 'end',
+    '36': 'home',
+    '37': 'leftarrow',
+    '38': 'uparrow',
+    '39': 'rightarrow',
+    '40': 'downarrow',
+    '116': 'F5'
+  };
+  // If del or special key
+  return codes[k];
+};
+
+//
+// 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
+module.exports = utils;
+
+
diff --git a/dist/formatter.js b/dist/formatter.js
new file mode 100644
index 0000000..f8cb1c2
--- /dev/null
+++ b/dist/formatter.js
@@ -0,0 +1,766 @@
+/*!
+ * v0.1.0
+ * Copyright (c) 2014 First Opinion
+ * formatter.js is open sourced under the MIT license.
+ *
+ * thanks to digitalBush/jquery.maskedinput for some of the trickier
+ * keycode handling
+ */ 
+
+//
+// Uses Node, AMD or browser globals to create a module. This example creates
+// a global even when AMD is used. This is useful if you have some scripts
+// that are loaded by an AMD loader, but they still want access to globals.
+// If you do not need to export a global for the AMD case,
+// see returnExports.js.
+//
+// If you want something that will work in other stricter CommonJS environments,
+// or if you need to create a circular dependency, see commonJsStrictGlobal.js
+//
+// Defines a module "returnExportsGlobal" that depends another module called
+// "b". Note that the name of the module is implied by the file name. It is
+// best if the file name and the exported global have matching names.
+//
+// If the 'b' module also uses this type of boilerplate, then
+// in the browser, it will create a global .b that is used below.
+//
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define([], function () {
+      return (root.returnExportsGlobal = factory());
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like enviroments that support module.exports,
+    // like Node.
+    module.exports = factory();
+  } else {
+    root['Formatter'] = factory();
+  }
+}(this, function () {
+
+
+/*
+ * pattern.js
+ *
+ * Utilities to parse str pattern and return info
+ *
+ */
+var pattern = function () {
+    // Define module
+    var pattern = {};
+    // Match information
+    var DELIM_SIZE = 4;
+    // Our regex used to parse
+    var regexp = new RegExp('{{([^}]+)}}', 'g');
+    //
+    // Helper method to parse pattern str
+    //
+    var getMatches = function (pattern) {
+      // Populate array of matches
+      var matches = [], match;
+      while (match = regexp.exec(pattern)) {
+        matches.push(match);
+      }
+      return matches;
+    };
+    //
+    // Create an object holding all formatted characters
+    // with corresponding positions
+    //
+    pattern.parse = function (pattern) {
+      // Our obj to populate
+      var info = {
+          inpts: {},
+          chars: {}
+        };
+      // Pattern information
+      var matches = getMatches(pattern), pLength = pattern.length;
+      // Counters
+      var mCount = 0, iCount = 0, i = 0;
+      // Add inpts, move to end of match, and process
+      var processMatch = function (val) {
+        var valLength = val.length;
+        for (var j = 0; j < valLength; j++) {
+          info.inpts[iCount] = val.charAt(j);
+          iCount++;
+        }
+        mCount++;
+        i += val.length + DELIM_SIZE - 1;
+      };
+      // Process match or add chars
+      for (i; i < pLength; i++) {
+        if (mCount < matches.length && i === matches[mCount].index) {
+          processMatch(matches[mCount][1]);
+        } else {
+          info.chars[i - mCount * DELIM_SIZE] = pattern.charAt(i);
+        }
+      }
+      // Set mLength and return
+      info.mLength = i - mCount * DELIM_SIZE;
+      return info;
+    };
+    // Expose
+    return pattern;
+  }();
+/*
+ * utils.js
+ *
+ * Independent helper methods (cross browser, etc..)
+ *
+ */
+var utils = function () {
+    // Define module
+    var utils = {};
+    // Useragent info for keycode handling
+    var uAgent = typeof navigator !== 'undefined' ? navigator.userAgent : null, iPhone = /iphone/i.test(uAgent);
+    //
+    // 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');
+      }
+    };
+    //
+    // Returns true/false if k is a del key
+    //
+    utils.isDelKey = function (k) {
+      return k === 8 || k === 46 || iPhone && k === 127;
+    };
+    //
+    // Returns true/false if k is an arrow key
+    //
+    utils.isSpecialKey = function (k) {
+      var codes = {
+          '9': 'tab',
+          '13': 'enter',
+          '35': 'end',
+          '36': 'home',
+          '37': 'leftarrow',
+          '38': 'uparrow',
+          '39': 'rightarrow',
+          '40': 'downarrow',
+          '116': 'F5'
+        };
+      // If del or special key
+      return codes[k];
+    };
+    //
+    // 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.isDelKey(k)) {
+        // 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.
+      if (evt.which) {
+        k = evt.which;
+      } else {
+        k = evt.keyCode;
+        isSpecial = utils.isSpecialKey(k);
+      }
+      // Process the keyCode and prevent default
+      if (!utils.isDelKey(k) && !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, ingoreCaret) {
+      // 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.delta -= 1;
+        // Count number of additional format chars to be deleted. (A group of multiple format chars should be deleted like one value char.)
+        while (this.chars[this.focus - 1]) {
+          this.delta--;
+          this.focus--;
+        }
+        this.val = utils.removeChars(this.val, this.sel.end + this.delta, this.sel.end);
+      } 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(ingoreCaret);
+    };
+    //
+    // @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 and this.focus, as they may change in _addChar.
+        for (var j = 0; j <= this.val.length; j++) {
+          // When moving backwards, i.e. delting characters, don't add format characters past focus point.
+          if (this.delta <= 0 && j === this.focus && this.chars[j] === undefined || this.focus === 0) {
+            return true;
+          }
+          // Place character in current position of the formatted string.
+          this._addChar(j);
+        }
+      }
+    };
+    //
+    // @private
+    // Add formattted char at position
+    //
+    Formatter.prototype._addChar = function (i) {
+      // If char exists at position
+      var chr = this.chars[i];
+      if (!chr) {
+        return true;
+      }
+      // If chars are added in between the old pos and new pos
+      // we need to increment pos and delta
+      if (utils.isBetween(i, [
+          this.sel.begin - 1,
+          this.newPos + 1
+        ])) {
+        this.newPos++;
+        this.delta++;
+      }
+      // If character added before focus, incr
+      if (i <= this.focus) {
+        this.focus++;
+      }
+      // Updateholder
+      if (this.hldrs[i]) {
+        delete this.hldrs[i];
+        this.hldrs[i + 1] = this.opts.placeholder;
+      }
+      // Update value
+      this.val = utils.addChars(this.val, chr, i);
+    };
+    //
+    // @private
+    // Create a patternSpec for passing into patternMatcher that
+    // has exactly one catch all pattern.
+    //
+    Formatter.prototype._specFromSinglePattern = function (patternStr) {
+      return [{ '*': patternStr }];
+    };
+    // Expose
+    return Formatter;
+  }(patternMatcher, inptSel, utils);
+
+
+return formatter;
+
+
+
+}));
\ No newline at end of file
diff --git a/dist/formatter.min.js b/dist/formatter.min.js
new file mode 100644
index 0000000..d64eb54
--- /dev/null
+++ b/dist/formatter.min.js
@@ -0,0 +1 @@
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return a.returnExportsGlobal=b()}):"object"==typeof exports?module.exports=b():a.Formatter=b()}(this,function(){var a=function(){var a={},b=4,c=new RegExp("{{([^}]+)}}","g"),d=function(a){for(var b,d=[];b=c.exec(a);)d.push(b);return d};return a.parse=function(a){var c={inpts:{},chars:{}},e=d(a),f=a.length,g=0,h=0,i=0,j=function(a){for(var d=a.length,e=0;d>e;e++)c.inpts[h]=a.charAt(e),h++;g++,i+=a.length+b-1};for(i;f>i;i++)g<e.length&&i===e[g].index?j(e[g][1]):c.chars[i-g*b]=a.charAt(i);return c.mLength=i-g*b,c},a}(),b=function(){var a={},b="undefined"!=typeof navigator?navigator.userAgent:null,c=/iphone/i.test(b);return a.extend=function(a){for(var b=1;b<arguments.length;b++)for(var c in arguments[b])a[c]=arguments[b][c];return a},a.addChars=function(a,b,c){return a.substr(0,c)+b+a.substr(c,a.length)},a.removeChars=function(a,b,c){return a.substr(0,b)+a.substr(c,a.length)},a.isBetween=function(a,b){return b.sort(function(a,b){return a-b}),a>b[0]&&a<b[1]},a.addListener=function(a,b,c){return"undefined"!=typeof a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},a.preventDefault=function(a){return a.preventDefault?a.preventDefault():a.returnValue=!1},a.getClip=function(a){return a.clipboardData?a.clipboardData.getData("Text"):window.clipboardData?window.clipboardData.getData("Text"):void 0},a.isDelKey=function(a){return 8===a||46===a||c&&127===a},a.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]},a.isModifier=function(a){return a.ctrlKey||a.altKey||a.metaKey},a.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}(),c=function(a,b){function c(c){var e=[],f=[];b.forEach(c,function(c){b.forEach(c,function(b,c){var g=a.parse(b),h=d(c);return e.push(h),f.push(g),!1})});var g=function(a){var c;return b.forEach(e,function(b,d){return b.test(a)?(c=d,!1):void 0}),void 0===c?null:f[c]};return{getPattern:g,patterns:f,matchers:e}}var d=function(a){return"*"===a?/.*/:new RegExp(a)};return c}(a,b),d=function(){var a={};return a.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}},a.set=function(a,b){if("object"!=typeof b&&(b={begin:b,end:b}),a.setSelectionRange)a.focus(),a.setSelectionRange(b.begin,b.end);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0),c.moveEnd("character",b.end),c.moveStart("character",b.begin),c.select()}},a}(),e=function(a,b,c){function d(b,d){var f=this;if(f.el=b,!f.el)throw new TypeError("Must provide an existing element");if(f.opts=c.extend({},e,d),"undefined"!=typeof f.opts.pattern&&(f.opts.patterns=f._specFromSinglePattern(f.opts.pattern),delete f.opts.pattern),"undefined"==typeof f.opts.patterns)throw new TypeError("Must provide a pattern or array of patterns");f.patternMatcher=a(f.opts.patterns),f._updatePattern(),f.hldrs={},f.focus=0,c.addListener(f.el,"keydown",function(a){f._keyDown(a)}),c.addListener(f.el,"keypress",function(a){f._keyPress(a)}),c.addListener(f.el,"paste",function(a){f._paste(a)}),f.opts.persistent&&(f._processKey("",!1),f.el.blur(),c.addListener(f.el,"focus",function(a){f._focus(a)}),c.addListener(f.el,"click",function(a){f._focus(a)}),c.addListener(f.el,"touchstart",function(a){f._focus(a)}))}var e={persistent:!1,repeat:!1,placeholder:" "},f={9:/[0-9]/,a:/[A-Za-z]/,"*":/[A-Za-z0-9]/};return d.addInptType=function(a,b){f[a]=b},d.prototype.resetPattern=function(c){this.opts.patterns=c?this._specFromSinglePattern(c):this.opts.patterns,this.sel=b.get(this.el),this.val=this.el.value,this.delta=0,this._removeChars(),this.patternMatcher=a(this.opts.patterns);var d=this.patternMatcher.getPattern(this.val);this.mLength=d.mLength,this.chars=d.chars,this.inpts=d.inpts,this._processKey("",!1,!0)},d.prototype._updatePattern=function(){var a=this.patternMatcher.getPattern(this.val);a&&(this.mLength=a.mLength,this.chars=a.chars,this.inpts=a.inpts)},d.prototype._keyDown=function(a){var b=a.which||a.keyCode;return b&&c.isDelKey(b)?(this._processKey(null,b),c.preventDefault(a)):void 0},d.prototype._keyPress=function(a){var b,d;return a.which?b=a.which:(b=a.keyCode,d=c.isSpecialKey(b)),c.isDelKey(b)||d||c.isModifier(a)?void 0:(this._processKey(String.fromCharCode(b),!1),c.preventDefault(a))},d.prototype._paste=function(a){return this._processKey(c.getClip(a),!1),c.preventDefault(a)},d.prototype._focus=function(){var a=this;setTimeout(function(){var c=b.get(a.el),d=c.end>a.focus,e=0===c.end;(d||e)&&b.set(a.el,a.focus)},0)},d.prototype._processKey=function(a,d,e){if(this.sel=b.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=c.removeChars(this.val,this.sel.begin,this.sel.end);else if(d&&46===d)this._delete();else if(d&&this.sel.begin-1>=0){for(this.delta-=1;this.chars[this.focus-1];)this.delta--,this.focus--;this.val=c.removeChars(this.val,this.sel.end+this.delta,this.sel.end)}else if(d)return!0;d||(this.val=c.addChars(this.val,a,this.sel.begin),this.delta+=a.length),this._formatValue(e)},d.prototype._delete=function(){for(;this.chars[this.sel.begin];)this._nextPos();this.sel.begin<this.val.length&&(this._nextPos(),this.val=c.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(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)&&b.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 d,e=this.chars[b],f=this.hldrs[b],g=b+a;g=b>=this.sel.begin?g+this.delta:g,d=this.val.charAt(g),(e&&e===d||f&&f===d)&&(this.val=c.removeChars(this.val,g,g+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],d=!f[b],e=!d&&!f[b].test(this.val.charAt(a)),g=this.inpts[a];(d||e)&&g&&(this.val=c.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=c.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&&void 0===this.chars[b]||0===this.focus)return!0;this._addChar(b)}},d.prototype._addChar=function(a){var b=this.chars[a];return b?(c.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=c.addChars(this.val,b,a))):!0},d.prototype._specFromSinglePattern=function(a){return[{"*":a}]},d}(c,d,b);return e});
\ No newline at end of file
diff --git a/dist/jquery.formatter.js b/dist/jquery.formatter.js
new file mode 100644
index 0000000..9dbde16
--- /dev/null
+++ b/dist/jquery.formatter.js
@@ -0,0 +1,787 @@
+/*!
+ * v0.1.0
+ * 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, iPhone = /iphone/i.test(uAgent);
+    //
+    // 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');
+      }
+    };
+    //
+    // Returns true/false if k is a del key
+    //
+    utils.isDelKey = function (k) {
+      return k === 8 || k === 46 || iPhone && k === 127;
+    };
+    //
+    // Returns true/false if k is an arrow key
+    //
+    utils.isSpecialKey = function (k) {
+      var codes = {
+          '9': 'tab',
+          '13': 'enter',
+          '35': 'end',
+          '36': 'home',
+          '37': 'leftarrow',
+          '38': 'uparrow',
+          '39': 'rightarrow',
+          '40': 'downarrow',
+          '116': 'F5'
+        };
+      // If del or special key
+      return codes[k];
+    };
+    //
+    // 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.isDelKey(k)) {
+        // 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.
+      if (evt.which) {
+        k = evt.which;
+      } else {
+        k = evt.keyCode;
+        isSpecial = utils.isSpecialKey(k);
+      }
+      // Process the keyCode and prevent default
+      if (!utils.isDelKey(k) && !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, ingoreCaret) {
+      // 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.delta -= 1;
+        // Count number of additional format chars to be deleted. (A group of multiple format chars should be deleted like one value char.)
+        while (this.chars[this.focus - 1]) {
+          this.delta--;
+          this.focus--;
+        }
+        this.val = utils.removeChars(this.val, this.sel.end + this.delta, this.sel.end);
+      } 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(ingoreCaret);
+    };
+    //
+    // @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 and this.focus, as they may change in _addChar.
+        for (var j = 0; j <= this.val.length; j++) {
+          // When moving backwards, i.e. delting characters, don't add format characters past focus point.
+          if (this.delta <= 0 && j === this.focus && this.chars[j] === undefined || this.focus === 0) {
+            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);
+};
+
+
+}));
\ No newline at end of file
diff --git a/dist/jquery.formatter.min.js b/dist/jquery.formatter.min.js
new file mode 100644
index 0000000..9418837
--- /dev/null
+++ b/dist/jquery.formatter.min.js
@@ -0,0 +1 @@
+!function(a,b){"function"==typeof define&&define.amd?define(["jQuery"],b):b("object"==typeof exports?require("jQuery"):a.jQuery)}(this,function(){var a=function(){var a={},b=4,c=new RegExp("{{([^}]+)}}","g"),d=function(a){for(var b,d=[];b=c.exec(a);)d.push(b);return d};return a.parse=function(a){var c={inpts:{},chars:{}},e=d(a),f=a.length,g=0,h=0,i=0,j=function(a){for(var d=a.length,e=0;d>e;e++)c.inpts[h]=a.charAt(e),h++;g++,i+=a.length+b-1};for(i;f>i;i++)g<e.length&&i===e[g].index?j(e[g][1]):c.chars[i-g*b]=a.charAt(i);return c.mLength=i-g*b,c},a}(),b=function(){var a={},b="undefined"!=typeof navigator?navigator.userAgent:null,c=/iphone/i.test(b);return a.extend=function(a){for(var b=1;b<arguments.length;b++)for(var c in arguments[b])a[c]=arguments[b][c];return a},a.addChars=function(a,b,c){return a.substr(0,c)+b+a.substr(c,a.length)},a.removeChars=function(a,b,c){return a.substr(0,b)+a.substr(c,a.length)},a.isBetween=function(a,b){return b.sort(function(a,b){return a-b}),a>b[0]&&a<b[1]},a.addListener=function(a,b,c){return"undefined"!=typeof a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},a.preventDefault=function(a){return a.preventDefault?a.preventDefault():a.returnValue=!1},a.getClip=function(a){return a.clipboardData?a.clipboardData.getData("Text"):window.clipboardData?window.clipboardData.getData("Text"):void 0},a.isDelKey=function(a){return 8===a||46===a||c&&127===a},a.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]},a.isModifier=function(a){return a.ctrlKey||a.altKey||a.metaKey},a.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}(),c=function(a,b){function c(c){var e=[],f=[];b.forEach(c,function(c){b.forEach(c,function(b,c){var g=a.parse(b),h=d(c);return e.push(h),f.push(g),!1})});var g=function(a){var c;return b.forEach(e,function(b,d){return b.test(a)?(c=d,!1):void 0}),void 0===c?null:f[c]};return{getPattern:g,patterns:f,matchers:e}}var d=function(a){return"*"===a?/.*/:new RegExp(a)};return c}(a,b),d=function(){var a={};return a.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}},a.set=function(a,b){if("object"!=typeof b&&(b={begin:b,end:b}),a.setSelectionRange)a.focus(),a.setSelectionRange(b.begin,b.end);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0),c.moveEnd("character",b.end),c.moveStart("character",b.begin),c.select()}},a}(),e=function(a,b,c){function d(b,d){var f=this;if(f.el=b,!f.el)throw new TypeError("Must provide an existing element");if(f.opts=c.extend({},e,d),"undefined"!=typeof f.opts.pattern&&(f.opts.patterns=f._specFromSinglePattern(f.opts.pattern),delete f.opts.pattern),"undefined"==typeof f.opts.patterns)throw new TypeError("Must provide a pattern or array of patterns");f.patternMatcher=a(f.opts.patterns),f._updatePattern(),f.hldrs={},f.focus=0,c.addListener(f.el,"keydown",function(a){f._keyDown(a)}),c.addListener(f.el,"keypress",function(a){f._keyPress(a)}),c.addListener(f.el,"paste",function(a){f._paste(a)}),f.opts.persistent&&(f._processKey("",!1),f.el.blur(),c.addListener(f.el,"focus",function(a){f._focus(a)}),c.addListener(f.el,"click",function(a){f._focus(a)}),c.addListener(f.el,"touchstart",function(a){f._focus(a)}))}var e={persistent:!1,repeat:!1,placeholder:" "},f={9:/[0-9]/,a:/[A-Za-z]/,"*":/[A-Za-z0-9]/};return d.addInptType=function(a,b){f[a]=b},d.prototype.resetPattern=function(c){this.opts.patterns=c?this._specFromSinglePattern(c):this.opts.patterns,this.sel=b.get(this.el),this.val=this.el.value,this.delta=0,this._removeChars(),this.patternMatcher=a(this.opts.patterns);var d=this.patternMatcher.getPattern(this.val);this.mLength=d.mLength,this.chars=d.chars,this.inpts=d.inpts,this._processKey("",!1,!0)},d.prototype._updatePattern=function(){var a=this.patternMatcher.getPattern(this.val);a&&(this.mLength=a.mLength,this.chars=a.chars,this.inpts=a.inpts)},d.prototype._keyDown=function(a){var b=a.which||a.keyCode;return b&&c.isDelKey(b)?(this._processKey(null,b),c.preventDefault(a)):void 0},d.prototype._keyPress=function(a){var b,d;return a.which?b=a.which:(b=a.keyCode,d=c.isSpecialKey(b)),c.isDelKey(b)||d||c.isModifier(a)?void 0:(this._processKey(String.fromCharCode(b),!1),c.preventDefault(a))},d.prototype._paste=function(a){return this._processKey(c.getClip(a),!1),c.preventDefault(a)},d.prototype._focus=function(){var a=this;setTimeout(function(){var c=b.get(a.el),d=c.end>a.focus,e=0===c.end;(d||e)&&b.set(a.el,a.focus)},0)},d.prototype._processKey=function(a,d,e){if(this.sel=b.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=c.removeChars(this.val,this.sel.begin,this.sel.end);else if(d&&46===d)this._delete();else if(d&&this.sel.begin-1>=0){for(this.delta-=1;this.chars[this.focus-1];)this.delta--,this.focus--;this.val=c.removeChars(this.val,this.sel.end+this.delta,this.sel.end)}else if(d)return!0;d||(this.val=c.addChars(this.val,a,this.sel.begin),this.delta+=a.length),this._formatValue(e)},d.prototype._delete=function(){for(;this.chars[this.sel.begin];)this._nextPos();this.sel.begin<this.val.length&&(this._nextPos(),this.val=c.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(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)&&b.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 d,e=this.chars[b],f=this.hldrs[b],g=b+a;g=b>=this.sel.begin?g+this.delta:g,d=this.val.charAt(g),(e&&e===d||f&&f===d)&&(this.val=c.removeChars(this.val,g,g+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],d=!f[b],e=!d&&!f[b].test(this.val.charAt(a)),g=this.inpts[a];(d||e)&&g&&(this.val=c.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=c.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&&void 0===this.chars[b]||0===this.focus)return!0;this._addChar(b)}},d.prototype._addChar=function(a){var b=this.chars[a];return b?(c.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=c.addChars(this.val,b,a))):!0},d.prototype._specFromSinglePattern=function(a){return[{"*":a}]},d}(c,d,b),f="formatter";$.fn[f]=function(a){return"object"==typeof a&&this.each(function(){$.data(this,"plugin_"+f)||$.data(this,"plugin_"+f,new e(this,a))}),this.resetPattern=function(a){return this.each(function(){var b=$.data(this,"plugin_"+f);b&&b.resetPattern(a)}),this},this},$.fn[f].addInptType=function(a,b){e.addInptType(a,b)}});
\ No newline at end of file
diff --git a/docs/_layouts/master.html b/docs/_layouts/master.html
new file mode 100644
index 0000000..ec4b91b
--- /dev/null
+++ b/docs/_layouts/master.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="chrome=1">
+    <title>formatter.js by firstopinion</title>
+
+    <link rel="stylesheet" href="stylesheets/styles.css">
+    <link rel="stylesheet" href="stylesheets/pygment_trac.css">
+    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+    <!--[if lt IE 9]>
+    <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+    <![endif]-->
+    <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+  </head>
+  <body>
+    <div class="wrapper">
+      <header>
+        <h1>formatter.js</h1>
+        <p>Format user input to match a specified pattern</p>
+
+        <p class="view"><a href="https://github.com/firstopinion/formatter.js">View the Project on GitHub <small>firstopinion/formatter.js</small></a></p>
+        
+        <div class="menu">
+          <p class="menu-item"><a href="index.html">+ Docs</a></p>
+          <p class="menu-item"><a href="demos.html">+ Demos</a></p>
+        </div>
+
+        <ul>
+          <li><a href="https://github.com/firstopinion/formatter.js/zipball/master">Download <strong>ZIP File</strong></a></li>
+          <li><a href="https://github.com/firstopinion/formatter.js/tarball/master">Download <strong>TAR Ball</strong></a></li>
+          <li><a href="https://github.com/firstopinion/formatter.js">View On <strong>GitHub</strong></a></li>
+        </ul>
+      </header>
+      <section>
+
+        {{ content }}
+
+      </section>
+    </div>
+    <script src="http://code.jquery.com/jquery-1.10.1.js"></script>
+    <script src="javascripts/scale.fix.js"></script>
+    <script src="javascripts/formatter.min.js"></script>
+    {% raw %}
+    <script>
+      // $('#phone-input').formatter({
+      //   'pattern': '({{999}}) {{999}}.{{9999}}',
+      //   'persistent': true
+      // });
+      // $('#credit-input').formatter({
+      //   'pattern': '{{9999}}-{{9999}}-{{9999}}-{{9999}}'
+      // });
+      var creditInput = document.getElementById('credit-input');
+      if (creditInput) {
+        new Formatter(creditInput, {
+            'pattern': '{{9999}}-{{9999}}-{{9999}}-{{9999}}'
+        });
+      }
+      var phoneInput = document.getElementById('phone-input');
+      if (creditInput) {
+        new Formatter(phoneInput, {
+            'pattern': '({{999}}) {{999}}.{{9999}}',
+            'persistent': true
+        });
+      }
+    </script>
+    {% endraw %}
+  </body>
+</html>
\ No newline at end of file
diff --git a/docs/demos.html b/docs/demos.html
new file mode 100644
index 0000000..9d8e014
--- /dev/null
+++ b/docs/demos.html
@@ -0,0 +1,39 @@
+---
+layout: master
+---
+
+{% raw %}
+
+<h1>Demos</h1>
+
+<h2 id="credit">Credit Card</h2>
+<p><strong>Ex</strong>: 4242-4242-4242-4242</p>
+<div class="demo-input">
+  <div class="input-wrap input-s1 input-white">
+    <input type="text" class="input" id="credit-input" maxlength="19" pattern="\d*">
+  </div>
+</div>
+<pre><code>new Formatter(document.getElementById('credit-input'), {
+  'pattern': '{{9999}}-{{9999}}-{{9999}}-{{9999}}'
+});</code></pre>
+<pre><code>$('#credit-input').formatter({
+  'pattern': '{{9999}}-{{9999}}-{{9999}}-{{9999}}'
+});</code></pre>
+
+<h2 id="phone">Phone Number</h2>
+<p><strong>Ex</strong>: (802) 415-3411</p>
+<div class="demo-input">
+  <div class="input-wrap input-s1 input-white">
+    <input type="text" class="input" id="phone-input" maxlength="14" pattern="\d*">
+  </div>
+</div>
+<pre><code>new Formatter(document.getElementById('phone-input'), {
+  'pattern': '({{999}}) {{999}}.{{9999}}',
+  'persistent': true
+});</code></pre>
+<pre><code>$('#phone-input').formatter({
+  'pattern': '({{999}}) {{999}}-{{9999}}',
+  'persistent': true
+});</code></pre>
+
+{% endraw %}
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..42f3b0b
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,150 @@
+---
+layout: master
+---
+{% raw %}
+formatter.js [![Build Status](https://travis-ci.org/firstopinion/formatter.js.png)](https://travis-ci.org/firstopinion/formatter.js)
+============
+
+       ___                    __  __              _   
+      / _/__  ______ _  ___ _/ /_/ /____ ____    (_)__
+     / _/ _ \/ __/  ' \/ _ `/ __/ __/ -_) __/   / (_-<
+    /_/ \___/_/ /_/_/_/\_,_/\__/\__/\__/_/ (_)_/ /___/
+                                            |___/     
+                                            
+Format user input to match a specified pattern
+
+
+
+Demos/Examples
+--------------
+
+[view demo](http://firstopinion.github.io/formatter.js/demos.html)
+
+
+
+Why?
+----
+
+Sometimes it is useful to format user input as they type. Existing libraries lacked proper functionality / flexibility. Formatter was built from the ground up with no dependencies. There is however a jquery wrapper version for quick use.
+
+
+
+On Bower
+--------
+
+    bower install formatter
+
+
+
+Usage
+-----
+
+### Vanilla Javascript
+
+* **uncompressed**: formatter.js
+* **compressed**: formatter.min.js
+
+#### new Formatter(el, opts)
+
+    var formatted = new Formatter(document.getElementById('credit-input'), {
+      'pattern': '{{999}}-{{999}}-{{999}}-{{9999}}',
+      'persistent': true
+    });
+
+
+### Jquery
+
+* **uncompressed**: jquery.formatter.js
+* **compressed**: jquery.formatter.min.js
+
+#### $(selector).formatter(opts)
+
+    $('#credit-input').formatter({
+      'pattern': '{{999}}-{{999}}-{{999}}-{{9999}}',
+      'persistent': true
+    });
+
+
+
+Opts
+----
+
+* **pattern** (required): String representing the pattern of your formatted input. User input areas begin with `{{` and end with `}}`. For example, a phone number may be represented: `({{999}}) {{999}}-{{999}}`. You can specify numbers, letters, or numbers and letters.
+  * 9: [0-9]
+  * 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: {{**********}}' }
+]
+```
+
+
+
+Class Methods
+-------------
+
+#### addInptType(char, regexp)
+
+Add regular expressions for different input types.
+
+**Vanilla Javascript**
+
+    Formatter.addInptType('L', /[A-Z]/);
+
+**Jquery**
+
+    $.fn.formatter.addInptType('L', /[A-Z]/);
+
+
+
+Instance Methods
+----------------
+
+#### resetPattern(pattern)
+
+Fairly self explanatory here :) reset the pattern on an existing Formatter instance.
+
+**Vanilla Javascript**
+
+(assuming you already created a new instance and saved it to the var `formatted`)
+
+    formatted.resetPattern('{{999}}.{{999}}.{{9999}}');
+
+**Jquery**
+
+(assuming you already initiated formatter on `#selector`)
+
+    $('#selector').formatter().resetPattern();
+
+
+
+Tests
+-----
+
+Install Dependencies:
+    
+    npm install
+
+Run Tests:
+    
+    npm test
+
+
+
+License
+-------
+
+The MIT License (MIT) Copyright (c) 2013 First Opinion
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+{% endraw %}
\ No newline at end of file
diff --git a/docs/javascripts/formatter.js b/docs/javascripts/formatter.js
new file mode 100644
index 0000000..f8cb1c2
--- /dev/null
+++ b/docs/javascripts/formatter.js
@@ -0,0 +1,766 @@
+/*!
+ * v0.1.0
+ * Copyright (c) 2014 First Opinion
+ * formatter.js is open sourced under the MIT license.
+ *
+ * thanks to digitalBush/jquery.maskedinput for some of the trickier
+ * keycode handling
+ */ 
+
+//
+// Uses Node, AMD or browser globals to create a module. This example creates
+// a global even when AMD is used. This is useful if you have some scripts
+// that are loaded by an AMD loader, but they still want access to globals.
+// If you do not need to export a global for the AMD case,
+// see returnExports.js.
+//
+// If you want something that will work in other stricter CommonJS environments,
+// or if you need to create a circular dependency, see commonJsStrictGlobal.js
+//
+// Defines a module "returnExportsGlobal" that depends another module called
+// "b". Note that the name of the module is implied by the file name. It is
+// best if the file name and the exported global have matching names.
+//
+// If the 'b' module also uses this type of boilerplate, then
+// in the browser, it will create a global .b that is used below.
+//
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define([], function () {
+      return (root.returnExportsGlobal = factory());
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like enviroments that support module.exports,
+    // like Node.
+    module.exports = factory();
+  } else {
+    root['Formatter'] = factory();
+  }
+}(this, function () {
+
+
+/*
+ * pattern.js
+ *
+ * Utilities to parse str pattern and return info
+ *
+ */
+var pattern = function () {
+    // Define module
+    var pattern = {};
+    // Match information
+    var DELIM_SIZE = 4;
+    // Our regex used to parse
+    var regexp = new RegExp('{{([^}]+)}}', 'g');
+    //
+    // Helper method to parse pattern str
+    //
+    var getMatches = function (pattern) {
+      // Populate array of matches
+      var matches = [], match;
+      while (match = regexp.exec(pattern)) {
+        matches.push(match);
+      }
+      return matches;
+    };
+    //
+    // Create an object holding all formatted characters
+    // with corresponding positions
+    //
+    pattern.parse = function (pattern) {
+      // Our obj to populate
+      var info = {
+          inpts: {},
+          chars: {}
+        };
+      // Pattern information
+      var matches = getMatches(pattern), pLength = pattern.length;
+      // Counters
+      var mCount = 0, iCount = 0, i = 0;
+      // Add inpts, move to end of match, and process
+      var processMatch = function (val) {
+        var valLength = val.length;
+        for (var j = 0; j < valLength; j++) {
+          info.inpts[iCount] = val.charAt(j);
+          iCount++;
+        }
+        mCount++;
+        i += val.length + DELIM_SIZE - 1;
+      };
+      // Process match or add chars
+      for (i; i < pLength; i++) {
+        if (mCount < matches.length && i === matches[mCount].index) {
+          processMatch(matches[mCount][1]);
+        } else {
+          info.chars[i - mCount * DELIM_SIZE] = pattern.charAt(i);
+        }
+      }
+      // Set mLength and return
+      info.mLength = i - mCount * DELIM_SIZE;
+      return info;
+    };
+    // Expose
+    return pattern;
+  }();
+/*
+ * utils.js
+ *
+ * Independent helper methods (cross browser, etc..)
+ *
+ */
+var utils = function () {
+    // Define module
+    var utils = {};
+    // Useragent info for keycode handling
+    var uAgent = typeof navigator !== 'undefined' ? navigator.userAgent : null, iPhone = /iphone/i.test(uAgent);
+    //
+    // 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');
+      }
+    };
+    //
+    // Returns true/false if k is a del key
+    //
+    utils.isDelKey = function (k) {
+      return k === 8 || k === 46 || iPhone && k === 127;
+    };
+    //
+    // Returns true/false if k is an arrow key
+    //
+    utils.isSpecialKey = function (k) {
+      var codes = {
+          '9': 'tab',
+          '13': 'enter',
+          '35': 'end',
+          '36': 'home',
+          '37': 'leftarrow',
+          '38': 'uparrow',
+          '39': 'rightarrow',
+          '40': 'downarrow',
+          '116': 'F5'
+        };
+      // If del or special key
+      return codes[k];
+    };
+    //
+    // 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.isDelKey(k)) {
+        // 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.
+      if (evt.which) {
+        k = evt.which;
+      } else {
+        k = evt.keyCode;
+        isSpecial = utils.isSpecialKey(k);
+      }
+      // Process the keyCode and prevent default
+      if (!utils.isDelKey(k) && !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, ingoreCaret) {
+      // 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.delta -= 1;
+        // Count number of additional format chars to be deleted. (A group of multiple format chars should be deleted like one value char.)
+        while (this.chars[this.focus - 1]) {
+          this.delta--;
+          this.focus--;
+        }
+        this.val = utils.removeChars(this.val, this.sel.end + this.delta, this.sel.end);
+      } 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(ingoreCaret);
+    };
+    //
+    // @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 and this.focus, as they may change in _addChar.
+        for (var j = 0; j <= this.val.length; j++) {
+          // When moving backwards, i.e. delting characters, don't add format characters past focus point.
+          if (this.delta <= 0 && j === this.focus && this.chars[j] === undefined || this.focus === 0) {
+            return true;
+          }
+          // Place character in current position of the formatted string.
+          this._addChar(j);
+        }
+      }
+    };
+    //
+    // @private
+    // Add formattted char at position
+    //
+    Formatter.prototype._addChar = function (i) {
+      // If char exists at position
+      var chr = this.chars[i];
+      if (!chr) {
+        return true;
+      }
+      // If chars are added in between the old pos and new pos
+      // we need to increment pos and delta
+      if (utils.isBetween(i, [
+          this.sel.begin - 1,
+          this.newPos + 1
+        ])) {
+        this.newPos++;
+        this.delta++;
+      }
+      // If character added before focus, incr
+      if (i <= this.focus) {
+        this.focus++;
+      }
+      // Updateholder
+      if (this.hldrs[i]) {
+        delete this.hldrs[i];
+        this.hldrs[i + 1] = this.opts.placeholder;
+      }
+      // Update value
+      this.val = utils.addChars(this.val, chr, i);
+    };
+    //
+    // @private
+    // Create a patternSpec for passing into patternMatcher that
+    // has exactly one catch all pattern.
+    //
+    Formatter.prototype._specFromSinglePattern = function (patternStr) {
+      return [{ '*': patternStr }];
+    };
+    // Expose
+    return Formatter;
+  }(patternMatcher, inptSel, utils);
+
+
+return formatter;
+
+
+
+}));
\ No newline at end of file
diff --git a/docs/javascripts/formatter.min.js b/docs/javascripts/formatter.min.js
new file mode 100644
index 0000000..d64eb54
--- /dev/null
+++ b/docs/javascripts/formatter.min.js
@@ -0,0 +1 @@
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return a.returnExportsGlobal=b()}):"object"==typeof exports?module.exports=b():a.Formatter=b()}(this,function(){var a=function(){var a={},b=4,c=new RegExp("{{([^}]+)}}","g"),d=function(a){for(var b,d=[];b=c.exec(a);)d.push(b);return d};return a.parse=function(a){var c={inpts:{},chars:{}},e=d(a),f=a.length,g=0,h=0,i=0,j=function(a){for(var d=a.length,e=0;d>e;e++)c.inpts[h]=a.charAt(e),h++;g++,i+=a.length+b-1};for(i;f>i;i++)g<e.length&&i===e[g].index?j(e[g][1]):c.chars[i-g*b]=a.charAt(i);return c.mLength=i-g*b,c},a}(),b=function(){var a={},b="undefined"!=typeof navigator?navigator.userAgent:null,c=/iphone/i.test(b);return a.extend=function(a){for(var b=1;b<arguments.length;b++)for(var c in arguments[b])a[c]=arguments[b][c];return a},a.addChars=function(a,b,c){return a.substr(0,c)+b+a.substr(c,a.length)},a.removeChars=function(a,b,c){return a.substr(0,b)+a.substr(c,a.length)},a.isBetween=function(a,b){return b.sort(function(a,b){return a-b}),a>b[0]&&a<b[1]},a.addListener=function(a,b,c){return"undefined"!=typeof a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},a.preventDefault=function(a){return a.preventDefault?a.preventDefault():a.returnValue=!1},a.getClip=function(a){return a.clipboardData?a.clipboardData.getData("Text"):window.clipboardData?window.clipboardData.getData("Text"):void 0},a.isDelKey=function(a){return 8===a||46===a||c&&127===a},a.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]},a.isModifier=function(a){return a.ctrlKey||a.altKey||a.metaKey},a.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}(),c=function(a,b){function c(c){var e=[],f=[];b.forEach(c,function(c){b.forEach(c,function(b,c){var g=a.parse(b),h=d(c);return e.push(h),f.push(g),!1})});var g=function(a){var c;return b.forEach(e,function(b,d){return b.test(a)?(c=d,!1):void 0}),void 0===c?null:f[c]};return{getPattern:g,patterns:f,matchers:e}}var d=function(a){return"*"===a?/.*/:new RegExp(a)};return c}(a,b),d=function(){var a={};return a.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}},a.set=function(a,b){if("object"!=typeof b&&(b={begin:b,end:b}),a.setSelectionRange)a.focus(),a.setSelectionRange(b.begin,b.end);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0),c.moveEnd("character",b.end),c.moveStart("character",b.begin),c.select()}},a}(),e=function(a,b,c){function d(b,d){var f=this;if(f.el=b,!f.el)throw new TypeError("Must provide an existing element");if(f.opts=c.extend({},e,d),"undefined"!=typeof f.opts.pattern&&(f.opts.patterns=f._specFromSinglePattern(f.opts.pattern),delete f.opts.pattern),"undefined"==typeof f.opts.patterns)throw new TypeError("Must provide a pattern or array of patterns");f.patternMatcher=a(f.opts.patterns),f._updatePattern(),f.hldrs={},f.focus=0,c.addListener(f.el,"keydown",function(a){f._keyDown(a)}),c.addListener(f.el,"keypress",function(a){f._keyPress(a)}),c.addListener(f.el,"paste",function(a){f._paste(a)}),f.opts.persistent&&(f._processKey("",!1),f.el.blur(),c.addListener(f.el,"focus",function(a){f._focus(a)}),c.addListener(f.el,"click",function(a){f._focus(a)}),c.addListener(f.el,"touchstart",function(a){f._focus(a)}))}var e={persistent:!1,repeat:!1,placeholder:" "},f={9:/[0-9]/,a:/[A-Za-z]/,"*":/[A-Za-z0-9]/};return d.addInptType=function(a,b){f[a]=b},d.prototype.resetPattern=function(c){this.opts.patterns=c?this._specFromSinglePattern(c):this.opts.patterns,this.sel=b.get(this.el),this.val=this.el.value,this.delta=0,this._removeChars(),this.patternMatcher=a(this.opts.patterns);var d=this.patternMatcher.getPattern(this.val);this.mLength=d.mLength,this.chars=d.chars,this.inpts=d.inpts,this._processKey("",!1,!0)},d.prototype._updatePattern=function(){var a=this.patternMatcher.getPattern(this.val);a&&(this.mLength=a.mLength,this.chars=a.chars,this.inpts=a.inpts)},d.prototype._keyDown=function(a){var b=a.which||a.keyCode;return b&&c.isDelKey(b)?(this._processKey(null,b),c.preventDefault(a)):void 0},d.prototype._keyPress=function(a){var b,d;return a.which?b=a.which:(b=a.keyCode,d=c.isSpecialKey(b)),c.isDelKey(b)||d||c.isModifier(a)?void 0:(this._processKey(String.fromCharCode(b),!1),c.preventDefault(a))},d.prototype._paste=function(a){return this._processKey(c.getClip(a),!1),c.preventDefault(a)},d.prototype._focus=function(){var a=this;setTimeout(function(){var c=b.get(a.el),d=c.end>a.focus,e=0===c.end;(d||e)&&b.set(a.el,a.focus)},0)},d.prototype._processKey=function(a,d,e){if(this.sel=b.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=c.removeChars(this.val,this.sel.begin,this.sel.end);else if(d&&46===d)this._delete();else if(d&&this.sel.begin-1>=0){for(this.delta-=1;this.chars[this.focus-1];)this.delta--,this.focus--;this.val=c.removeChars(this.val,this.sel.end+this.delta,this.sel.end)}else if(d)return!0;d||(this.val=c.addChars(this.val,a,this.sel.begin),this.delta+=a.length),this._formatValue(e)},d.prototype._delete=function(){for(;this.chars[this.sel.begin];)this._nextPos();this.sel.begin<this.val.length&&(this._nextPos(),this.val=c.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(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)&&b.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 d,e=this.chars[b],f=this.hldrs[b],g=b+a;g=b>=this.sel.begin?g+this.delta:g,d=this.val.charAt(g),(e&&e===d||f&&f===d)&&(this.val=c.removeChars(this.val,g,g+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],d=!f[b],e=!d&&!f[b].test(this.val.charAt(a)),g=this.inpts[a];(d||e)&&g&&(this.val=c.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=c.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&&void 0===this.chars[b]||0===this.focus)return!0;this._addChar(b)}},d.prototype._addChar=function(a){var b=this.chars[a];return b?(c.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=c.addChars(this.val,b,a))):!0},d.prototype._specFromSinglePattern=function(a){return[{"*":a}]},d}(c,d,b);return e});
\ No newline at end of file
diff --git a/docs/javascripts/jquery.formatter.js b/docs/javascripts/jquery.formatter.js
new file mode 100644
index 0000000..9dbde16
--- /dev/null
+++ b/docs/javascripts/jquery.formatter.js
@@ -0,0 +1,787 @@
+/*!
+ * v0.1.0
+ * 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, iPhone = /iphone/i.test(uAgent);
+    //
+    // 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');
+      }
+    };
+    //
+    // Returns true/false if k is a del key
+    //
+    utils.isDelKey = function (k) {
+      return k === 8 || k === 46 || iPhone && k === 127;
+    };
+    //
+    // Returns true/false if k is an arrow key
+    //
+    utils.isSpecialKey = function (k) {
+      var codes = {
+          '9': 'tab',
+          '13': 'enter',
+          '35': 'end',
+          '36': 'home',
+          '37': 'leftarrow',
+          '38': 'uparrow',
+          '39': 'rightarrow',
+          '40': 'downarrow',
+          '116': 'F5'
+        };
+      // If del or special key
+      return codes[k];
+    };
+    //
+    // 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.isDelKey(k)) {
+        // 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.
+      if (evt.which) {
+        k = evt.which;
+      } else {
+        k = evt.keyCode;
+        isSpecial = utils.isSpecialKey(k);
+      }
+      // Process the keyCode and prevent default
+      if (!utils.isDelKey(k) && !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, ingoreCaret) {
+      // 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.delta -= 1;
+        // Count number of additional format chars to be deleted. (A group of multiple format chars should be deleted like one value char.)
+        while (this.chars[this.focus - 1]) {
+          this.delta--;
+          this.focus--;
+        }
+        this.val = utils.removeChars(this.val, this.sel.end + this.delta, this.sel.end);
+      } 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(ingoreCaret);
+    };
+    //
+    // @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 and this.focus, as they may change in _addChar.
+        for (var j = 0; j <= this.val.length; j++) {
+          // When moving backwards, i.e. delting characters, don't add format characters past focus point.
+          if (this.delta <= 0 && j === this.focus && this.chars[j] === undefined || this.focus === 0) {
+            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);
+};
+
+
+}));
\ No newline at end of file
diff --git a/docs/javascripts/jquery.formatter.min.js b/docs/javascripts/jquery.formatter.min.js
new file mode 100644
index 0000000..9418837
--- /dev/null
+++ b/docs/javascripts/jquery.formatter.min.js
@@ -0,0 +1 @@
+!function(a,b){"function"==typeof define&&define.amd?define(["jQuery"],b):b("object"==typeof exports?require("jQuery"):a.jQuery)}(this,function(){var a=function(){var a={},b=4,c=new RegExp("{{([^}]+)}}","g"),d=function(a){for(var b,d=[];b=c.exec(a);)d.push(b);return d};return a.parse=function(a){var c={inpts:{},chars:{}},e=d(a),f=a.length,g=0,h=0,i=0,j=function(a){for(var d=a.length,e=0;d>e;e++)c.inpts[h]=a.charAt(e),h++;g++,i+=a.length+b-1};for(i;f>i;i++)g<e.length&&i===e[g].index?j(e[g][1]):c.chars[i-g*b]=a.charAt(i);return c.mLength=i-g*b,c},a}(),b=function(){var a={},b="undefined"!=typeof navigator?navigator.userAgent:null,c=/iphone/i.test(b);return a.extend=function(a){for(var b=1;b<arguments.length;b++)for(var c in arguments[b])a[c]=arguments[b][c];return a},a.addChars=function(a,b,c){return a.substr(0,c)+b+a.substr(c,a.length)},a.removeChars=function(a,b,c){return a.substr(0,b)+a.substr(c,a.length)},a.isBetween=function(a,b){return b.sort(function(a,b){return a-b}),a>b[0]&&a<b[1]},a.addListener=function(a,b,c){return"undefined"!=typeof a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},a.preventDefault=function(a){return a.preventDefault?a.preventDefault():a.returnValue=!1},a.getClip=function(a){return a.clipboardData?a.clipboardData.getData("Text"):window.clipboardData?window.clipboardData.getData("Text"):void 0},a.isDelKey=function(a){return 8===a||46===a||c&&127===a},a.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]},a.isModifier=function(a){return a.ctrlKey||a.altKey||a.metaKey},a.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}(),c=function(a,b){function c(c){var e=[],f=[];b.forEach(c,function(c){b.forEach(c,function(b,c){var g=a.parse(b),h=d(c);return e.push(h),f.push(g),!1})});var g=function(a){var c;return b.forEach(e,function(b,d){return b.test(a)?(c=d,!1):void 0}),void 0===c?null:f[c]};return{getPattern:g,patterns:f,matchers:e}}var d=function(a){return"*"===a?/.*/:new RegExp(a)};return c}(a,b),d=function(){var a={};return a.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}},a.set=function(a,b){if("object"!=typeof b&&(b={begin:b,end:b}),a.setSelectionRange)a.focus(),a.setSelectionRange(b.begin,b.end);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0),c.moveEnd("character",b.end),c.moveStart("character",b.begin),c.select()}},a}(),e=function(a,b,c){function d(b,d){var f=this;if(f.el=b,!f.el)throw new TypeError("Must provide an existing element");if(f.opts=c.extend({},e,d),"undefined"!=typeof f.opts.pattern&&(f.opts.patterns=f._specFromSinglePattern(f.opts.pattern),delete f.opts.pattern),"undefined"==typeof f.opts.patterns)throw new TypeError("Must provide a pattern or array of patterns");f.patternMatcher=a(f.opts.patterns),f._updatePattern(),f.hldrs={},f.focus=0,c.addListener(f.el,"keydown",function(a){f._keyDown(a)}),c.addListener(f.el,"keypress",function(a){f._keyPress(a)}),c.addListener(f.el,"paste",function(a){f._paste(a)}),f.opts.persistent&&(f._processKey("",!1),f.el.blur(),c.addListener(f.el,"focus",function(a){f._focus(a)}),c.addListener(f.el,"click",function(a){f._focus(a)}),c.addListener(f.el,"touchstart",function(a){f._focus(a)}))}var e={persistent:!1,repeat:!1,placeholder:" "},f={9:/[0-9]/,a:/[A-Za-z]/,"*":/[A-Za-z0-9]/};return d.addInptType=function(a,b){f[a]=b},d.prototype.resetPattern=function(c){this.opts.patterns=c?this._specFromSinglePattern(c):this.opts.patterns,this.sel=b.get(this.el),this.val=this.el.value,this.delta=0,this._removeChars(),this.patternMatcher=a(this.opts.patterns);var d=this.patternMatcher.getPattern(this.val);this.mLength=d.mLength,this.chars=d.chars,this.inpts=d.inpts,this._processKey("",!1,!0)},d.prototype._updatePattern=function(){var a=this.patternMatcher.getPattern(this.val);a&&(this.mLength=a.mLength,this.chars=a.chars,this.inpts=a.inpts)},d.prototype._keyDown=function(a){var b=a.which||a.keyCode;return b&&c.isDelKey(b)?(this._processKey(null,b),c.preventDefault(a)):void 0},d.prototype._keyPress=function(a){var b,d;return a.which?b=a.which:(b=a.keyCode,d=c.isSpecialKey(b)),c.isDelKey(b)||d||c.isModifier(a)?void 0:(this._processKey(String.fromCharCode(b),!1),c.preventDefault(a))},d.prototype._paste=function(a){return this._processKey(c.getClip(a),!1),c.preventDefault(a)},d.prototype._focus=function(){var a=this;setTimeout(function(){var c=b.get(a.el),d=c.end>a.focus,e=0===c.end;(d||e)&&b.set(a.el,a.focus)},0)},d.prototype._processKey=function(a,d,e){if(this.sel=b.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=c.removeChars(this.val,this.sel.begin,this.sel.end);else if(d&&46===d)this._delete();else if(d&&this.sel.begin-1>=0){for(this.delta-=1;this.chars[this.focus-1];)this.delta--,this.focus--;this.val=c.removeChars(this.val,this.sel.end+this.delta,this.sel.end)}else if(d)return!0;d||(this.val=c.addChars(this.val,a,this.sel.begin),this.delta+=a.length),this._formatValue(e)},d.prototype._delete=function(){for(;this.chars[this.sel.begin];)this._nextPos();this.sel.begin<this.val.length&&(this._nextPos(),this.val=c.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(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)&&b.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 d,e=this.chars[b],f=this.hldrs[b],g=b+a;g=b>=this.sel.begin?g+this.delta:g,d=this.val.charAt(g),(e&&e===d||f&&f===d)&&(this.val=c.removeChars(this.val,g,g+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],d=!f[b],e=!d&&!f[b].test(this.val.charAt(a)),g=this.inpts[a];(d||e)&&g&&(this.val=c.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=c.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&&void 0===this.chars[b]||0===this.focus)return!0;this._addChar(b)}},d.prototype._addChar=function(a){var b=this.chars[a];return b?(c.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=c.addChars(this.val,b,a))):!0},d.prototype._specFromSinglePattern=function(a){return[{"*":a}]},d}(c,d,b),f="formatter";$.fn[f]=function(a){return"object"==typeof a&&this.each(function(){$.data(this,"plugin_"+f)||$.data(this,"plugin_"+f,new e(this,a))}),this.resetPattern=function(a){return this.each(function(){var b=$.data(this,"plugin_"+f);b&&b.resetPattern(a)}),this},this},$.fn[f].addInptType=function(a,b){e.addInptType(a,b)}});
\ No newline at end of file
diff --git a/docs/javascripts/scale.fix.js b/docs/javascripts/scale.fix.js
new file mode 100644
index 0000000..87a40ca
--- /dev/null
+++ b/docs/javascripts/scale.fix.js
@@ -0,0 +1,17 @@
+var metas = document.getElementsByTagName('meta');
+var i;
+if (navigator.userAgent.match(/iPhone/i)) {
+  for (i=0; i<metas.length; i++) {
+    if (metas[i].name == "viewport") {
+      metas[i].content = "width=device-width, minimum-scale=1.0, maximum-scale=1.0";
+    }
+  }
+  document.addEventListener("gesturestart", gestureStart, false);
+}
+function gestureStart() {
+  for (i=0; i<metas.length; i++) {
+    if (metas[i].name == "viewport") {
+      metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6";
+    }
+  }
+}
\ No newline at end of file
diff --git a/docs/stylesheets/pygment_trac.css b/docs/stylesheets/pygment_trac.css
new file mode 100644
index 0000000..9aed55e
--- /dev/null
+++ b/docs/stylesheets/pygment_trac.css
@@ -0,0 +1,69 @@
+.highlight  { background: #ffffff; }
+.highlight .c { color: #999988; font-style: italic } /* Comment */
+.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
+.highlight .k { font-weight: bold } /* Keyword */
+.highlight .o { font-weight: bold } /* Operator */
+.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
+.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
+.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
+.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .gr { color: #aa0000 } /* Generic.Error */
+.highlight .gh { color: #999999 } /* Generic.Heading */
+.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
+.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
+.highlight .go { color: #888888 } /* Generic.Output */
+.highlight .gp { color: #555555 } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */
+.highlight .gt { color: #aa0000 } /* Generic.Traceback */
+.highlight .kc { font-weight: bold } /* Keyword.Constant */
+.highlight .kd { font-weight: bold } /* Keyword.Declaration */
+.highlight .kn { font-weight: bold } /* Keyword.Namespace */
+.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
+.highlight .kr { font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
+.highlight .m { color: #009999 } /* Literal.Number */
+.highlight .s { color: #d14 } /* Literal.String */
+.highlight .na { color: #008080 } /* Name.Attribute */
+.highlight .nb { color: #0086B3 } /* Name.Builtin */
+.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
+.highlight .no { color: #008080 } /* Name.Constant */
+.highlight .ni { color: #800080 } /* Name.Entity */
+.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
+.highlight .nn { color: #555555 } /* Name.Namespace */
+.highlight .nt { color: #000080 } /* Name.Tag */
+.highlight .nv { color: #008080 } /* Name.Variable */
+.highlight .ow { font-weight: bold } /* Operator.Word */
+.highlight .w { color: #bbbbbb } /* Text.Whitespace */
+.highlight .mf { color: #009999 } /* Literal.Number.Float */
+.highlight .mh { color: #009999 } /* Literal.Number.Hex */
+.highlight .mi { color: #009999 } /* Literal.Number.Integer */
+.highlight .mo { color: #009999 } /* Literal.Number.Oct */
+.highlight .sb { color: #d14 } /* Literal.String.Backtick */
+.highlight .sc { color: #d14 } /* Literal.String.Char */
+.highlight .sd { color: #d14 } /* Literal.String.Doc */
+.highlight .s2 { color: #d14 } /* Literal.String.Double */
+.highlight .se { color: #d14 } /* Literal.String.Escape */
+.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
+.highlight .si { color: #d14 } /* Literal.String.Interpol */
+.highlight .sx { color: #d14 } /* Literal.String.Other */
+.highlight .sr { color: #009926 } /* Literal.String.Regex */
+.highlight .s1 { color: #d14 } /* Literal.String.Single */
+.highlight .ss { color: #990073 } /* Literal.String.Symbol */
+.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
+.highlight .vc { color: #008080 } /* Name.Variable.Class */
+.highlight .vg { color: #008080 } /* Name.Variable.Global */
+.highlight .vi { color: #008080 } /* Name.Variable.Instance */
+.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
+
+.type-csharp .highlight .k { color: #0000FF }
+.type-csharp .highlight .kt { color: #0000FF }
+.type-csharp .highlight .nf { color: #000000; font-weight: normal }
+.type-csharp .highlight .nc { color: #2B91AF }
+.type-csharp .highlight .nn { color: #000000 }
+.type-csharp .highlight .s { color: #A31515 }
+.type-csharp .highlight .sc { color: #A31515 }
\ No newline at end of file
diff --git a/docs/stylesheets/styles.css b/docs/stylesheets/styles.css
new file mode 100644
index 0000000..20d2502
--- /dev/null
+++ b/docs/stylesheets/styles.css
@@ -0,0 +1,343 @@
+/* 1. theme
+----------------------------------------------------------------------------------------------------*/
+@import url(https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700);
+
+body {
+  padding:50px;
+  font:14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif;
+  color:#777;
+  font-weight:300;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  color:#222;
+  margin:0 0 20px;
+}
+
+p, ul, ol, table, pre, dl {
+  margin:0 0 20px;
+}
+
+h1, h2, h3 {
+  line-height:1.1;
+}
+
+h1 {
+  font-size:28px;
+}
+
+h2 {
+  color:#393939;
+}
+
+h3, h4, h5, h6 {
+  color:#494949;
+}
+
+a {
+  color:#39c;
+  font-weight:400;
+  text-decoration:none;
+}
+
+a small {
+  font-size:11px;
+  color:#777;
+  margin-top:-0.6em;
+  display:block;
+}
+
+.wrapper {
+  width:860px;
+  margin:0 auto;
+}
+
+blockquote {
+  border-left:1px solid #e5e5e5;
+  margin:0;
+  padding:0 0 0 20px;
+  font-style:italic;
+}
+
+code, pre {
+  font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
+  color:#333;
+  font-size:12px;
+}
+
+pre {
+  padding:8px 15px;
+  background: #f8f8f8;  
+  border-radius:5px;
+  border:1px solid #e5e5e5;
+  overflow-x: auto;
+}
+
+table {
+  width:100%;
+  border-collapse:collapse;
+}
+
+th, td {
+  text-align:left;
+  padding:5px 10px;
+  border-bottom:1px solid #e5e5e5;
+}
+
+dt {
+  color:#444;
+  font-weight:700;
+}
+
+th {
+  color:#444;
+}
+
+img {
+  max-width:100%;
+}
+
+header {
+  width:270px;
+  float:left;
+  position:fixed;
+}
+
+header ul {
+  list-style:none;
+  height:40px;
+  
+  padding:0;
+  
+  background: #eee;
+  background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%);
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));
+  background: -webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+  background: -o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+  background: -ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+  background: linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+  
+  border-radius:5px;
+  border:1px solid #d2d2d2;
+  box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0;
+  width:270px;
+}
+
+header li {
+  width:89px;
+  float:left;
+  border-right:1px solid #d2d2d2;
+  height:40px;
+}
+
+header ul a {
+  line-height:1;
+  font-size:11px;
+  color:#999;
+  display:block;
+  text-align:center;
+  padding-top:6px;
+  height:40px;
+}
+
+strong {
+  color:#222;
+  font-weight:700;
+}
+
+header ul li + li {
+  width:88px;
+  border-left:1px solid #fff;
+}
+
+header ul li + li + li {
+  border-right:none;
+  width:89px;
+}
+
+header ul a strong {
+  font-size:14px;
+  display:block;
+  color:#222;
+}
+
+section {
+  width:500px;
+  float:right;
+  padding-bottom:50px;
+}
+
+small {
+  font-size:11px;
+}
+
+hr {
+  border:0;
+  background:#e5e5e5;
+  height:1px;
+  margin:0 0 20px;
+}
+
+footer {
+  width:270px;
+  float:left;
+  position:fixed;
+  bottom:50px;
+}
+
+@media print, screen and (max-width: 960px) {
+  
+  div.wrapper {
+    width:auto;
+    margin:0;
+  }
+  
+  header, section, footer {
+    float:none;
+    position:static;
+    width:auto;
+  }
+  
+  header {
+    padding-right:320px;
+  }
+  
+  section {
+    border:1px solid #e5e5e5;
+    border-width:1px 0;
+    padding:20px 0;
+    margin:0 0 20px;
+  }
+  
+  header a small {
+    display:inline;
+  }
+  
+  header ul {
+    position:absolute;
+    right:50px;
+    top:52px;
+  }
+}
+
+@media print, screen and (max-width: 720px) {
+  body {
+    word-wrap:break-word;
+  }
+  
+  header {
+    padding:0;
+  }
+  
+  header ul, header p.view {
+    position:static;
+  }
+  
+  pre, code {
+    word-wrap:normal;
+  }
+}
+
+@media print, screen and (max-width: 480px) {
+  body {
+    padding:15px;
+  }
+  
+  header ul {
+    display:none;
+  }
+}
+
+@media print {
+  body {
+    padding:0.4in;
+    font-size:12pt;
+    color:#444;
+  }
+}
+
+.menu {
+  margin: 0 0 20px 0;
+  padding: 0;
+  border-top: 1px solid #ddd;
+}
+.menu-item {
+  margin: 0;
+  border-bottom: 1px solid #ddd;
+}
+.menu-item > a {
+  display: block;
+  padding: 10px 0;
+}
+.demo-input {
+  margin-bottom: 20px;
+  padding: 15px;
+  border-radius: 4px;
+  background: #fafafa;
+}
+/* 2. form elements
+----------------------------------------------------------------------------------------------------*/
+input {
+  display: block;
+  margin: 0;
+  padding: 0;
+  border: none;
+  outline: none;
+  border: 0;
+  outline: none;
+  background: none;
+  font-family: "Lucida Console", Monaco, monospace;
+}
+input[type=file] {
+  width: 1px;
+}
+input[type=number]::-webkit-inner-spin-button,
+input[type=number]::-webkit-outer-spin-button {
+    -webkit-appearance: none;
+    margin: 0;
+}
+
+/* 3. input
+----------------------------------------------------------------------------------------------------*/
+.input-wrap {
+  display: block;
+  position: relative;
+  *position: static;
+  overflow: hidden;
+  line-height: 0;
+  vertical-align: middle;
+}
+.input {
+  width: 100%;
+  border: medium none;
+}
+
+.input-s1 {
+  padding: 21px 21px 17px 21px;
+  height: 16px;
+  line-height: 1;
+  font-size: 16px;
+  font-weight: light;
+  border-radius: 4px;
+  border: 1px solid rgb(250, 250, 250);
+}
+.input-s1 > .input {
+  margin: -19px -21px -17px -21px;
+  padding: 19px 21px 17px 21px;
+  height: 16px;
+  border-radius: 4px;
+}
+.input-white {
+  background: #d3d3ce; /* Old browsers */
+  background: linear-gradient(to bottom, #d3d3ce 0%,#fff 100%); /* W3C */
+}
+.input-white > .input {
+  color: #676a6c;
+  background: #fff;
+}
+.input-white > .input::-webkit-input-placeholder {
+  color: #bcbec0;
+}
+.input-white > .input:-moz-placeholder {  
+  color: #bcbec0;
+}
\ No newline at end of file
diff --git a/lib/formatter.min.js b/lib/formatter.min.js
deleted file mode 100644
index 3c8c8d1..0000000
--- a/lib/formatter.min.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/*!
- * v0.0.9
- * Copyright (c) 2013 First Opinion
- * formatter.js is open sourced under the MIT license.
- *
- * 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,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=(require("util"),{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){for(this.delta-=1;this.chars[this.focus-1];)this.delta--,this.focus--;this.val=k.removeChars(this.val,this.sel.end+this.delta,this.sel.end)}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&&void 0===this.chars[b]||0===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++)e<c.length&&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.min.js b/lib/jquery.formatter.min.js
deleted file mode 100644
index 4751766..0000000
--- a/lib/jquery.formatter.min.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/*!
- * v0.0.9
- * Copyright (c) 2013 First Opinion
- * formatter.js is open sourced under the MIT license.
- *
- * thanks to digitalBush/jquery.maskedinput for some of the trickier
- * keycode handling
- */
-!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=(require("util"),{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){for(this.delta-=1;this.chars[this.focus-1];)this.delta--,this.focus--;this.val=o.removeChars(this.val,this.sel.end+this.delta,this.sel.end)}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&&this.chars[b]===d||0===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++)e<c.length&&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/package.json b/package.json
index 49bd33d..d5f61d6 100644
--- a/package.json
+++ b/package.json
@@ -1,23 +1,41 @@
 {
   "name": "formatter.js",
-  "version": "0.0.9",
+  "version": "0.1.0",
   "description": "Format user input to match a specified pattern",
   "author": "Jarid Margolin <jarid@firstopinion.com>",
   "repository": {
     "type": "git",
     "url": "https://github.com/firstopinion/formatter.js"
   },
+  "bugs": {
+    "url": "https://github.com/easy-js/easy-amdtest/issues"
+  },
   "scripts": {
-    "test": "make test"
+    "test": "grunt test"
   },
+  "main": "./dist/common/formatter.js",
   "devDependencies": {
-    "grunt-contrib-concat": "~0.3.0",
-    "grunt-contrib-uglify": "~0.3.3",
-    "grunt-contrib-jshint": "~0.8.0",
-    "grunt": "~0.4.1",
-    "mocha": "~1.17.1",
-    "sinon": "~1.8.2",
-    "chai": "~1.9.0"
+    "amdclean": "~1.5.0",
+    "grunt": "~0.4.3",
+    "grunt-contrib-clean": "~0.5.0",
+    "grunt-contrib-concat": "~0.4.0",
+    "grunt-contrib-connect": "~0.7.1",
+    "grunt-contrib-copy": "~0.5.0",
+    "grunt-contrib-jshint": "~0.10.0",
+    "grunt-contrib-nodefy": "~0.2.1",
+    "grunt-contrib-requirejs": "~0.4.1",
+    "grunt-contrib-uglify": "~0.4.0",
+    "grunt-contrib-watch": "~0.6.1",
+    "grunt-gh-pages": "~0.9.1",
+    "grunt-jekyll": "~0.4.2",
+    "grunt-mocha-phantomjs": "~0.5.0",
+    "grunt-saucelabs": "~5.1.0",
+    "grunt-umd": "~1.7.3",
+    "grunt-wrap": "~0.3.0",
+    "matchdep": "~0.3.0",
+    "mocha": "~1.18.2",
+    "proclaim": "~2.0.0",
+    "sinon": "~1.9.0"
   },
   "license": "MIT"
 }
diff --git a/src/formatter.js b/src/formatter.js
index 581d5ca..6330bf8 100644
--- a/src/formatter.js
+++ b/src/formatter.js
@@ -5,14 +5,12 @@
  *
  */
 
-/* ExcludeStart */
-var patternMatcher = require('./pattern-matcher'),
-    inptSel = require('./inpt-sel'),
-    utils   = require('./utils');
+define([
+  'pattern-matcher',
+  'inpt-sel',
+  'utils'
+], function (patternMatcher, inptSel, utils) {
 
-// Define module
-module.exports = Formatter;
-/* ExcludeEnd */
 
 // Defaults
 var defaults = {
@@ -241,7 +239,7 @@ Formatter.prototype._processKey = function (chars, delKey,ingoreCaret) {
   }
 
   // Delete key (moves opposite direction)
-  else if (delKey && delKey == 46) {
+  else if (delKey && delKey === 46) {
     this._delete();
 
   // or Backspace and not at start
@@ -358,7 +356,7 @@ Formatter.prototype._removeChars = function () {
     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) {
+    if (curChar && curChar === val || curHldr && curHldr === val) {
       this.val = utils.removeChars(this.val, pos, pos + 1);
       shift--;
     }
@@ -469,3 +467,9 @@ Formatter.prototype._specFromSinglePattern = function (patternStr) {
   return [{ '*': patternStr }];
 };
 
+
+// Expose
+return Formatter;
+
+
+});
\ No newline at end of file
diff --git a/src/inpt-sel.js b/src/inpt-sel.js
index f191ce2..9f1f771 100644
--- a/src/inpt-sel.js
+++ b/src/inpt-sel.js
@@ -1,12 +1,16 @@
 /*
- * inpt-selector.js
+ * inpt-sel.js
  *
  * Cross browser implementation to get and set input selections
  *
  */
 
+
+define(function () {
+
+
 // Define module
-module.exports = inptSel = {};
+var inptSel = {};
 
 //
 // Get begin and end positions of selected input. Return 0's
@@ -14,7 +18,7 @@ module.exports = inptSel = {};
 //
 inptSel.get = function (el) {
   // If normal browser return with result
-  if (typeof el.selectionStart == "number") {
+  if (typeof el.selectionStart === 'number') {
     return { 
       begin: el.selectionStart,
       end: el.selectionEnd
@@ -24,7 +28,7 @@ inptSel.get = function (el) {
   // 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) {
+  if (range && range.parentElement() === el) {
     var inputRange = el.createTextRange(),
         endRange   = el.createTextRange(),
         length     = el.value.length;
@@ -37,7 +41,7 @@ inptSel.get = function (el) {
     
     // 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) {
+    if (inputRange.compareEndPoints('StartToEnd', endRange) > -1) {
       return { begin: length, end: length };
     }
 
@@ -46,8 +50,8 @@ inptSel.get = function (el) {
     // 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)
+      begin: -inputRange.moveStart('character', -length),
+      end: -inputRange.moveEnd('character', -length)
     };
   }
 
@@ -59,17 +63,29 @@ inptSel.get = function (el) {
 // 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,pos);
+    el.setSelectionRange(pos.begin, pos.end);
 
   // IE = TextRange fun
   } else if (el.createTextRange) {
     var range = el.createTextRange();
     range.collapse(true);
-    range.moveEnd('character', pos);
-    range.moveStart('character', pos);
+    range.moveEnd('character', pos.end);
+    range.moveStart('character', pos.begin);
     range.select();
   }
-};
\ No newline at end of file
+};
+
+
+// Expose
+return inptSel;
+
+
+});
\ No newline at end of file
diff --git a/src/pattern-matcher.js b/src/pattern-matcher.js
index b35c926..780726b 100644
--- a/src/pattern-matcher.js
+++ b/src/pattern-matcher.js
@@ -6,13 +6,12 @@
 *
 */
 
-/* ExcludeStart */
-var pattern = require('./pattern'),
-    utils = require('./utils');
 
-// Define module
-module.exports = patternMatcher;
-/* ExcludeEnd */
+define([
+  'pattern',
+  'utils'
+], function (pattern, utils) {
+
 
 //
 // Parse a matcher string into a RegExp. Accepts valid regular
@@ -71,3 +70,10 @@ function patternMatcher (patternSpec) {
     matchers: matchers
   };
 }
+
+
+// Expose
+return patternMatcher;
+
+
+});
\ No newline at end of file
diff --git a/src/pattern.js b/src/pattern.js
index 5463b66..4d61efa 100644
--- a/src/pattern.js
+++ b/src/pattern.js
@@ -5,8 +5,12 @@
  *
  */
 
+
+define(function () {
+
+
 // Define module
-module.exports = pattern = {};
+var pattern = {};
 
 // Match information
 var DELIM_SIZE = 4;
@@ -58,7 +62,7 @@ pattern.parse = function (pattern) {
 
   // Process match or add chars
   for (i; i < pLength; i++) {
-    if (mCount < matches.length && i == matches[mCount].index) {
+    if (mCount < matches.length && i === matches[mCount].index) {
       processMatch(matches[mCount][1]);
     } else {
       info.chars[i - (mCount * DELIM_SIZE)] = pattern.charAt(i);
@@ -69,3 +73,10 @@ pattern.parse = function (pattern) {
   info.mLength = i - (mCount * DELIM_SIZE);
   return info;
 };
+
+
+// Expose
+return pattern;
+
+
+});
\ No newline at end of file
diff --git a/src/tmpls/intro.js b/src/tmpls/intro.js
deleted file mode 100644
index d123869..0000000
--- a/src/tmpls/intro.js
+++ /dev/null
@@ -1,5 +0,0 @@
-;(function (name, context, definition) {
-  if (typeof module !== 'undefined' && module.exports) { module.exports = definition(); }
-  else if (typeof define === 'function' && define.amd) { define(definition); }
-  else { context[name] = definition(); }
-})('Formatter', this, function () {
diff --git a/src/tmpls/jquery.hbs b/src/tmpls/jquery.hbs
new file mode 100644
index 0000000..a6a791c
--- /dev/null
+++ b/src/tmpls/jquery.hbs
@@ -0,0 +1,62 @@
+//
+// 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({{#if amdModuleId}}'{{amdModuleId}}', {{/if}}[{{{amdDependencies}}}], factory);
+  } else if (typeof exports === 'object') {
+    factory({{{cjsDependencies}}});
+  } else {
+    // Browser globals
+    factory({{{globalDependencies}}});
+  }
+}(this, function ({{dependencies}}) {
+
+
+{{{code}}}
+
+
+// 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);
+};
+
+
+}));
\ No newline at end of file
diff --git a/src/tmpls/jquery.intro.js b/src/tmpls/jquery.intro.js
deleted file mode 100644
index 31520cd..0000000
--- a/src/tmpls/jquery.intro.js
+++ /dev/null
@@ -1 +0,0 @@
-;(function ($, window, document, undefined) {
diff --git a/src/tmpls/jquery.outro.js b/src/tmpls/jquery.outro.js
deleted file mode 100644
index 5b1877b..0000000
--- a/src/tmpls/jquery.outro.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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);
-};
-
-})( jQuery, window, document);
\ No newline at end of file
diff --git a/src/tmpls/outro.js b/src/tmpls/outro.js
deleted file mode 100644
index bc1b197..0000000
--- a/src/tmpls/outro.js
+++ /dev/null
@@ -1,3 +0,0 @@
-return Formatter;
-
-});
\ No newline at end of file
diff --git a/src/tmpls/umd.hbs b/src/tmpls/umd.hbs
new file mode 100644
index 0000000..4fd3751
--- /dev/null
+++ b/src/tmpls/umd.hbs
@@ -0,0 +1,41 @@
+//
+// Uses Node, AMD or browser globals to create a module. This example creates
+// a global even when AMD is used. This is useful if you have some scripts
+// that are loaded by an AMD loader, but they still want access to globals.
+// If you do not need to export a global for the AMD case,
+// see returnExports.js.
+//
+// If you want something that will work in other stricter CommonJS environments,
+// or if you need to create a circular dependency, see commonJsStrictGlobal.js
+//
+// Defines a module "returnExportsGlobal" that depends another module called
+// "b". Note that the name of the module is implied by the file name. It is
+// best if the file name and the exported global have matching names.
+//
+// If the 'b' module also uses this type of boilerplate, then
+// in the browser, it will create a global .b that is used below.
+//
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define({{#if amdModuleId}}'{{amdModuleId}}', {{/if}}[{{{amdDependencies}}}], function () {
+      return (root.returnExportsGlobal = factory({{{amdDependencies}}}));
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like enviroments that support module.exports,
+    // like Node.
+    module.exports = factory({{{cjsDependencies}}});
+  } else {
+    {{#if globalAlias}}root['{{{globalAlias}}}'] = {{else}}{{#if objectToExport}}root['{{{objectToExport}}}'] = {{/if}}{{/if}}factory({{{globalDependencies}}});
+  }
+}(this, function ({{dependencies}}) {
+
+
+{{{code}}}
+{{#if objectToExport}}
+{{indent}}return {{{objectToExport}}};
+{{/if}}
+
+
+}));
\ No newline at end of file
diff --git a/src/utils.js b/src/utils.js
index 979db86..8995e80 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -5,8 +5,12 @@
  *
  */
 
+
+define(function () {
+
+
 // Define module
-module.exports = utils = {};
+var utils = {};
 
 // Useragent info for keycode handling
 var uAgent = (typeof navigator !== 'undefined') ? navigator.userAgent : null,
@@ -50,7 +54,7 @@ utils.isBetween = function (num, bounds) {
 // Helper method for cross browser event listeners
 //
 utils.addListener = function (el, evt, handler) {
-  return (typeof el.addEventListener != "undefined")
+  return (typeof el.addEventListener !== 'undefined')
     ? el.addEventListener(evt, handler, false)
     : el.attachEvent('on' + evt, handler);
 };
@@ -108,7 +112,7 @@ utils.isModifier = function (evt) {
 // Iterates over each property of object or array.
 //
 utils.forEach = function (collection, callback, thisArg) {
-  if (collection.hasOwnProperty("length")) {
+  if (collection.hasOwnProperty('length')) {
     for (var index = 0, len = collection.length; index < len; index++) {
       if (callback.call(thisArg, collection[index], index, collection) === false) {
         break;
@@ -124,3 +128,10 @@ utils.forEach = function (collection, callback, thisArg) {
     }
   }
 };
+
+
+// Expose
+return utils;
+
+
+});
\ No newline at end of file
diff --git a/test/_runner.html b/test/_runner.html
new file mode 100644
index 0000000..98ccbf6
--- /dev/null
+++ b/test/_runner.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <!-- meta -->
+    <meta charset="utf-8" />
+    <title>formatter.js</title>
+  </head>
+
+  <body>
+    <!-- content -->
+    <div id="workboard"></div>
+    <div id="mocha"></div>
+
+    <!-- templates -->
+    <script id="tmpl-input-text" type="text/tmpl">
+      <input type="text">
+    </script>
+
+    <!-- tests -->
+    <script src="../bower_components/requirejs/require.js"></script>
+    <script src="../bower_components/easy-amdtest/dist/easy-amdtest.js"></script>
+    <script>
+      var testSuite = new EasyAMDTest({
+        baseUrl: '../src',
+        urlArgs: 'bust=' + (new Date()).getTime(),
+        paths: {
+          'proclaim' : '/node_modules/proclaim/lib/proclaim',
+          "jquery"   : '/bower_components/jquery/dist/jquery',
+          'mocha'    : '/node_modules/mocha/mocha',
+          'fakey'    : '/bower_components/fakey/dist/fakey'
+        }
+      }).run({
+        name: 'mocha',
+        tests: [
+          './utils.js',
+          './pattern.js',
+          './pattern-matcher.js',
+          './formatter.js'
+        ]
+      });
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/test/fake-user/events.js b/test/fake-user/events.js
deleted file mode 100644
index 702c098..0000000
--- a/test/fake-user/events.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * test/fake-user/events.js:
- *
- * (C) 2013 First Opinion
- * MIT LICENCE
- *
- */ 
-
-// define module
-module.exports = events = {};
-
-
-// Base event that all events inherit from
-var Event = function () {};
-Event.prototype.preventDefault = function () {};
-
-
-//
-// Event passed on keyboard related event
-//
-events.KeyEvent = function (opts) {
-  // Defaults
-  this.altKey = false;
-  this.ctrlKey = false;
-  this.metaKey = false;
-  this.shiftKey = false;
-  // Merge
-  extend(this, opts);
-};
-
-// Inherits from Event
-events.KeyEvent.prototype = Object.create(events.KeyEvent.prototype);
-events.KeyEvent.prototype.constructor = events.KeyEvent;
-
-
-//
-// Event passed on paste event
-//
-events.PasteEvent = function (str) {
-  // Defaults
-  this.clipboardData = {
-    getData: function (type) {
-      return str;
-    }
-  };
-};
-
-// Inherits from Event
-events.PasteEvent.prototype = Object.create(events.PasteEvent.prototype);
-events.PasteEvent.prototype.constructor = events.PasteEvent;
-
-
-//
-// Helper - shallow copy properties
-//
-var extend = function (destObj) {
-  for (var i = 1; i < arguments.length; i++) {
-    for (var key in arguments[i]) {
-      destObj[key] = arguments[i][key];
-    }
-  }
-  return destObj;
-};
\ No newline at end of file
diff --git a/test/fake-user/keys.js b/test/fake-user/keys.js
deleted file mode 100644
index 1860e7d..0000000
--- a/test/fake-user/keys.js
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * test/fake-user/keys.js:
- *
- * (C) 2013 First Opinion
- * MIT LICENCE
- *
- */
-
-module.exports = keys = {};
-
-
-//
-// Helper method to fix characters that cannot
-// be store as a json key
-//
-var fixChar = function (chr) {
-  if (chr == '"') {
-    chr = 'doublequote';
-  } else if (chr == "\\") {
-    chr = 'forwardslash';
-  }
-  return chr;
-};
-
-//
-// Method to get key press obj
-//
-keys.getPress = function (chr) {
-  return keys.press[fixChar(chr)];
-};
-
-//
-// Method to get key down obj
-//
-keys.getDown = function (chr) {
-  return keys.down[fixChar(chr)];
-};
-
-// Press obj
-keys.press = {
-  "shift": { "which": 16, "keyCode": 16, "shiftKey": true },
-  "!": { "which": 33, "keyCode": 33, "shiftKey": true },
-  "doublequote": { "which": 34, "keyCode": 34, "shiftKey": true },
-  "#": { "which": 35, "keyCode": 35, "shiftKey": true },
-  "$": { "which": 36, "keyCode": 36, "shiftKey": true },
-  "%": { "which": 37, "keyCode": 37, "shiftKey": true },
-  "&": { "which": 38, "keyCode": 38, "shiftKey": true },
-  "'": { "which": 39, "keyCode": 39, "shiftKey": false },
-  "(": { "which": 40, "keyCode": 40, "shiftKey": true },
-  ")": { "which": 41, "keyCode": 41, "shiftKey": true },
-  "*": { "which": 42, "keyCode": 42, "shiftKey": true },
-  "+": { "which": 43, "keyCode": 43, "shiftKey": true },
-  ",": { "which": 44, "keyCode": 44, "shiftKey": false },
-  "-": { "which": 45, "keyCode": 45, "shiftKey": false },
-  ".": { "which": 46, "keyCode": 46, "shiftKey": false },
-  "/": { "which": 47, "keyCode": 47, "shiftKey": false },
-  "0": { "which": 48, "keyCode": 48, "shiftKey": false },
-  "1": { "which": 49, "keyCode": 49, "shiftKey": false },
-  "2": { "which": 50, "keyCode": 50, "shiftKey": false },
-  "3": { "which": 51, "keyCode": 51, "shiftKey": false },
-  "4": { "which": 52, "keyCode": 52, "shiftKey": false },
-  "5": { "which": 53, "keyCode": 53, "shiftKey": false },
-  "6": { "which": 54, "keyCode": 54, "shiftKey": false },
-  "7": { "which": 55, "keyCode": 55, "shiftKey": false },
-  "8": { "which": 56, "keyCode": 56, "shiftKey": false },
-  "9": { "which": 57, "keyCode": 57, "shiftKey": false },
-  ":": { "which": 58, "keyCode": 58, "shiftKey": true },
-  ";": { "which": 59, "keyCode": 59, "shiftKey": false },
-  "<": { "which": 60, "keyCode": 60, "shiftKey": true },
-  "=": { "which": 61, "keyCode": 61, "shiftKey": false },
-  ">": { "which": 62, "keyCode": 62, "shiftKey": true },
-  "?": { "which": 63, "keyCode": 63, "shiftKey": true },
-  "@": { "which": 64, "keyCode": 64, "shiftKey": true },
-  "A": { "which": 65, "keyCode": 65, "shiftKey": true },
-  "B": { "which": 66, "keyCode": 66, "shiftKey": true },
-  "C": { "which": 67, "keyCode": 67, "shiftKey": true },
-  "D": { "which": 68, "keyCode": 68, "shiftKey": true },
-  "E": { "which": 69, "keyCode": 69, "shiftKey": true },
-  "F": { "which": 70, "keyCode": 70, "shiftKey": true },
-  "G": { "which": 71, "keyCode": 71, "shiftKey": true },
-  "H": { "which": 72, "keyCode": 72, "shiftKey": true },
-  "I": { "which": 73, "keyCode": 73, "shiftKey": true },
-  "J": { "which": 74, "keyCode": 74, "shiftKey": true },
-  "K": { "which": 75, "keyCode": 75, "shiftKey": true },
-  "L": { "which": 76, "keyCode": 76, "shiftKey": true },
-  "M": { "which": 77, "keyCode": 77, "shiftKey": true },
-  "N": { "which": 78, "keyCode": 78, "shiftKey": true },
-  "O": { "which": 79, "keyCode": 79, "shiftKey": true },
-  "P": { "which": 80, "keyCode": 80, "shiftKey": true },
-  "Q": { "which": 81, "keyCode": 81, "shiftKey": true },
-  "R": { "which": 82, "keyCode": 82, "shiftKey": true },
-  "S": { "which": 83, "keyCode": 83, "shiftKey": true },
-  "T": { "which": 84, "keyCode": 84, "shiftKey": true },
-  "U": { "which": 85, "keyCode": 85, "shiftKey": true },
-  "V": { "which": 86, "keyCode": 86, "shiftKey": true },
-  "W": { "which": 87, "keyCode": 87, "shiftKey": true },
-  "X": { "which": 88, "keyCode": 88, "shiftKey": true },
-  "Y": { "which": 89, "keyCode": 89, "shiftKey": true },
-  "Z": { "which": 90, "keyCode": 90, "shiftKey": true },
-  "[": { "which": 91, "keyCode": 91, "shiftKey": false },
-  "forwardslash": { "which": 92, "keyCode": 92, "shiftKey": false },
-  "]": { "which": 93, "keyCode": 93, "shiftKey": false },
-  "^": { "which": 94, "keyCode": 94, "shiftKey": true },
-  "_": { "which": 95, "keyCode": 95, "shiftKey": true },
-  "`": { "which": 96, "keyCode": 96, "shiftKey": false },
-  "a": { "which": 97, "keyCode": 97, "shiftKey": false },
-  "b": { "which": 98, "keyCode": 98, "shiftKey": false },
-  "c": { "which": 99, "keyCode": 99, "shiftKey": false },
-  "d": { "which": 100, "keyCode": 100, "shiftKey": false },
-  "e": { "which": 101, "keyCode": 101, "shiftKey": false },
-  "f": { "which": 102, "keyCode": 102, "shiftKey": false },
-  "g": { "which": 103, "keyCode": 103, "shiftKey": false },
-  "h": { "which": 104, "keyCode": 104, "shiftKey": false },
-  "i": { "which": 105, "keyCode": 105, "shiftKey": false },
-  "j": { "which": 106, "keyCode": 106, "shiftKey": false },
-  "k": { "which": 107, "keyCode": 107, "shiftKey": false },
-  "l": { "which": 108, "keyCode": 108, "shiftKey": false },
-  "m": { "which": 109, "keyCode": 109, "shiftKey": false },
-  "n": { "which": 110, "keyCode": 110, "shiftKey": false },
-  "o": { "which": 111, "keyCode": 111, "shiftKey": false },
-  "p": { "which": 112, "keyCode": 112, "shiftKey": false },
-  "q": { "which": 113, "keyCode": 113, "shiftKey": false },
-  "r": { "which": 114, "keyCode": 114, "shiftKey": false },
-  "s": { "which": 115, "keyCode": 115, "shiftKey": false },
-  "t": { "which": 116, "keyCode": 116, "shiftKey": false },
-  "u": { "which": 117, "keyCode": 117, "shiftKey": false },
-  "v": { "which": 118, "keyCode": 118, "shiftKey": false },
-  "w": { "which": 119, "keyCode": 119, "shiftKey": false },
-  "x": { "which": 120, "keyCode": 120, "shiftKey": false },
-  "y": { "which": 121, "keyCode": 121, "shiftKey": false },
-  "z": { "which": 122, "keyCode": 122, "shiftKey": false },
-  "{": { "which": 123, "keyCode": 123, "shiftKey": true },
-  "|": { "which": 124, "keyCode": 124, "shiftKey": true },
-  "}": { "which": 125, "keyCode": 125, "shiftKey": true },
-  "~": { "which": 126, "keyCode": 126, "shiftKey": true },
-  /* Mozilla Specific */
-  "end": { "which": 0, "keyCode": 35, "shiftKey": false },
-  "home": { "which": 0, "keyCode": 36, "shiftKey": false },
-  "leftarrow": { "which": 0, "keyCode": 37, "shiftKey": false },
-  "uparrow": { "which": 0, "keyCode": 38, "shiftKey": false },
-  "rightarrow": { "which": 0, "keyCode": 39, "shiftKey": false },
-  "downarrow": { "which": 0, "keyCode": 40, "shiftKey": false },
-  "delete": { "which": 0, "keyCode": 46, "shiftKey": false }
-};
-
-// Down obj
-keys.down = {
-  "backspace": { "which": 8, "keyCode": 8, "shiftKey": false },
-  "tab": { "which": 9, "keyCode": 9, "shiftKey": false },
-  "enter": { "which": 13, "keyCode": 13, "shiftKey": false },
-  "shift": { "which": 16, "keyCode": 16, "shiftKey": false },
-  "ctrl": { "which": 17, "keyCode": 17, "shiftKey": false },
-  "alt": { "which": 18, "keyCode": 18, "shiftKey": false },
-  "capslock": { "which": 20, "keyCode": 20, "shiftKey": false },
-  "escape": { "which": 27, "keyCode": 27, "shiftKey": false },
-  "end": { "which": 35, "keyCode": 35, "shiftKey": false },
-  "home": { "which": 36, "keyCode": 36, "shiftKey": false },
-  "leftarrow": { "which": 37, "keyCode": 37, "shiftKey": false },
-  "uparrow": { "which": 38, "keyCode": 38, "shiftKey": false },
-  "rightarrow": { "which": 39, "keyCode": 39, "shiftKey": false },
-  "downarrow": { "which": 40, "keyCode": 40, "shiftKey": false },
-  "delete": { "which": 46, "keyCode": 46, "shiftKey": false },
-  "0": { "which": 48, "keyCode": 48, "shiftKey": false },
-  "1": { "which": 49, "keyCode": 49, "shiftKey": false },
-  "2": { "which": 50, "keyCode": 50, "shiftKey": false },
-  "3": { "which": 51, "keyCode": 51, "shiftKey": false },
-  "4": { "which": 52, "keyCode": 52, "shiftKey": false },
-  "5": { "which": 53, "keyCode": 53, "shiftKey": false },
-  "6": { "which": 54, "keyCode": 54, "shiftKey": false },
-  "7": { "which": 55, "keyCode": 55, "shiftKey": false },
-  "8": { "which": 56, "keyCode": 56, "shiftKey": false },
-  "9": { "which": 57, "keyCode": 57, "shiftKey": false },
-  "a": { "which": 65, "keyCode": 65, "shiftKey": false },
-  "b": { "which": 66, "keyCode": 66, "shiftKey": false },
-  "c": { "which": 67, "keyCode": 67, "shiftKey": false },
-  "d": { "which": 68, "keyCode": 68, "shiftKey": false },
-  "e": { "which": 69, "keyCode": 69, "shiftKey": false },
-  "f": { "which": 70, "keyCode": 70, "shiftKey": false },
-  "g": { "which": 71, "keyCode": 71, "shiftKey": false },
-  "h": { "which": 72, "keyCode": 72, "shiftKey": false },
-  "i": { "which": 73, "keyCode": 73, "shiftKey": false },
-  "j": { "which": 74, "keyCode": 74, "shiftKey": false },
-  "k": { "which": 75, "keyCode": 75, "shiftKey": false },
-  "l": { "which": 76, "keyCode": 76, "shiftKey": false },
-  "m": { "which": 77, "keyCode": 77, "shiftKey": false },
-  "n": { "which": 78, "keyCode": 78, "shiftKey": false },
-  "o": { "which": 79, "keyCode": 79, "shiftKey": false },
-  "p": { "which": 80, "keyCode": 80, "shiftKey": false },
-  "q": { "which": 81, "keyCode": 81, "shiftKey": false },
-  "r": { "which": 82, "keyCode": 82, "shiftKey": false },
-  "s": { "which": 83, "keyCode": 83, "shiftKey": false },
-  "t": { "which": 84, "keyCode": 84, "shiftKey": false },
-  "u": { "which": 85, "keyCode": 85, "shiftKey": false },
-  "v": { "which": 86, "keyCode": 86, "shiftKey": false },
-  "w": { "which": 87, "keyCode": 87, "shiftKey": false },
-  "x": { "which": 88, "keyCode": 88, "shiftKey": false },
-  "y": { "which": 89, "keyCode": 89, "shiftKey": false },
-  "z": { "which": 90, "keyCode": 90, "shiftKey": false },
-  "A": { "which": 65, "keyCode": 65, "shiftKey": true },
-  "B": { "which": 66, "keyCode": 66, "shiftKey": true },
-  "C": { "which": 67, "keyCode": 67, "shiftKey": true },
-  "D": { "which": 68, "keyCode": 68, "shiftKey": true },
-  "E": { "which": 69, "keyCode": 69, "shiftKey": true },
-  "F": { "which": 70, "keyCode": 70, "shiftKey": true },
-  "G": { "which": 71, "keyCode": 71, "shiftKey": true },
-  "H": { "which": 72, "keyCode": 72, "shiftKey": true },
-  "I": { "which": 73, "keyCode": 73, "shiftKey": true },
-  "J": { "which": 74, "keyCode": 74, "shiftKey": true },
-  "K": { "which": 75, "keyCode": 75, "shiftKey": true },
-  "L": { "which": 76, "keyCode": 76, "shiftKey": true },
-  "M": { "which": 77, "keyCode": 77, "shiftKey": true },
-  "N": { "which": 78, "keyCode": 78, "shiftKey": true },
-  "O": { "which": 79, "keyCode": 79, "shiftKey": true },
-  "P": { "which": 80, "keyCode": 80, "shiftKey": true },
-  "Q": { "which": 81, "keyCode": 81, "shiftKey": true },
-  "R": { "which": 82, "keyCode": 82, "shiftKey": true },
-  "S": { "which": 83, "keyCode": 83, "shiftKey": true },
-  "T": { "which": 84, "keyCode": 84, "shiftKey": true },
-  "U": { "which": 85, "keyCode": 85, "shiftKey": true },
-  "V": { "which": 86, "keyCode": 86, "shiftKey": true },
-  "W": { "which": 87, "keyCode": 87, "shiftKey": true },
-  "X": { "which": 88, "keyCode": 88, "shiftKey": true },
-  "Y": { "which": 89, "keyCode": 89, "shiftKey": true },
-  "Z": { "which": 90, "keyCode": 90, "shiftKey": true },
-  "`": { "which": 192, "keyCode": 192, "shiftKey": false },
-  "~": { "which": 192, "keyCode": 192, "shiftKey": true },
-  "-": { "which": 189, "keyCode": 189, "shiftKey": false },
-  "_": { "which": 189, "keyCode": 189, "shiftKey": true },
-  "=": { "which": 187, "keyCode": 187, "shiftKey": false },
-  "+": { "which": 187, "keyCode": 187, "shiftKey": true },
-  "[": { "which": 219, "keyCode": 219, "shiftKey": false },
-  "{": { "which": 219, "keyCode": 219, "shiftKey": true },
-  "]": { "which": 221, "keyCode": 221, "shiftKey": false },
-  "}": { "which": 221, "keyCode": 221, "shiftKey": true },
-  "forwardslash": { "which": 220, "keyCode": 220, "shiftKey": false },
-  "|": { "which": 220, "keyCode": 220, "shiftKey": true },
-  ";": { "which": 186, "keyCode": 186, "shiftKey": false },
-  ":": { "which": 186, "keyCode": 186, "shiftKey": true },
-  "'": { "which": 222, "keyCode": 222, "shiftKey": false },
-  "doublequote": { "which": 222, "keyCode": 222, "shiftKey": true },
-  ",": { "which": 188, "keyCode": 188, "shiftKey": false },
-  "<": { "which": 188, "keyCode": 188, "shiftKey": true },
-  ".": { "which": 190, "keyCode": 190, "shiftKey": false },
-  ">": { "which": 190, "keyCode": 190, "shiftKey": true },
-  "/": { "which": 191, "keyCode": 191, "shiftKey": false },
-  "?": { "which": 191, "keyCode": 191, "shiftKey": true }
-};
\ No newline at end of file
diff --git a/test/fake-user/user.js b/test/fake-user/user.js
deleted file mode 100644
index e491264..0000000
--- a/test/fake-user/user.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * test/fake-user/user.js:
- *
- * (C) 2013 First Opinion
- * MIT LICENCE
- *
- */
-
-// stdlib
-var EventEmitter = require('events').EventEmitter;
-
-// first party
-var events = require('./events'),
-    keys   = require('./keys');
-
-// define module
-module.exports = User;
-
-
-//
-// User Class
-//
-function User() {}
-
-// Inherits from EventEmitter
-User.prototype = Object.create(EventEmitter.prototype);
-User.prototype.constructor = User;
-
-//
-// Mock keyboard input
-//
-User.prototype.key = function (chr, opts) {
-  // Determine what we should trigger by looking for
-  // existence within keys.press, and keys.down
-  var keyPress = keys.getPress(chr),
-      keyDown  = keys.getDown(chr);
-
-  // If no opts passed, create blank obj
-  if (!opts) { opts = {}; }
-
-  if (keyPress) {
-    opts.which = keyPress.which;
-    opts.keyCode = keyPress.keyCode;
-    opts.shiftKey = keyPress.shiftKey;
-    this.emit('keypress', new events.KeyEvent(opts));
-  }
-  if (keyDown.which) {
-    opts.which = keyDown.which;
-    opts.keyCode = keyDown.keyCode;
-    opts.shiftKey = keyDown.shiftKey;
-    this.emit('keydown', new events.KeyEvent(opts));
-  }
-};
-
-//
-// Fire off a series of keys
-//
-User.prototype.keySeq = function (str, callback) {
-  // Cache this
-  var self = this;
-
-  var singleKey = function (i) {
-    setTimeout(function () {
-      self.key(str[i]);
-      return (i == strLength - 1)
-        ? callback()
-        : true;
-    }, 1);
-  };
-
-  var strLength = str.length;
-  for (var i = 0; i < strLength; i++) {
-    singleKey(i);
-  }
-};
-
-//
-// Mock focus evt
-//
-User.prototype.focus = function () {
-  this.emit('focus');
-};
-
-//
-// Mock paste event
-//
-User.prototype.paste = function (str, callback) {
-  this.emit('paste', new events.PasteEvent(str));
-  setTimeout(function () {
-    callback();
-  }, 1);
-};
\ No newline at end of file
diff --git a/test/formatter.js b/test/formatter.js
index fbb8fb1..f34d355 100644
--- a/test/formatter.js
+++ b/test/formatter.js
@@ -1,66 +1,55 @@
-/*
- * test/utils.js:
- *
- * (C) 2013 First Opinion
- * MIT LICENCE
- *
+/*!
+ * test/formatter.js
+ * 
+ * Copyright (c) 2014 First Opinion
  */
 
-// 3rd party
-var restore = require('sinon').restore,
-    mock    = require('sinon').mock,
-    stub    = require('sinon').stub;
 
-var should  = require('chai').should(),
-    assert  = require('chai').assert;
-
-// first party
-var Formatter = require('../src/formatter'),
-    inptSel   = require('../src/inpt-sel'),
-    utils     = require('../src/utils'),
-    User      = require('./fake-user/user');
+define([
+  'jquery',
+  'proclaim',
+  'fakey',
+  'formatter',
+  'inpt-sel',
+  'pattern-matcher',
+  'pattern',
+  'utils'
+], function ($, assert, fakey, Formatter, inptSel, utils) {
 
 
 //
-// pattern.js tests
+// formatter.js tests
 //
 describe('formatter.js', function () {
 
-  // Scope vars
-  var formatted, user, sel, el;
+  // Global vars
+  var $workboard = $('#workboard');
+
+  // Test vars
+  var formatted, $el, el;
 
+  // Add fresh element
   beforeEach(function () {
-    // Fake user to carry out keyboard and mouse events
-    user = new User();
-    // Set init el
-    el = { value: '', blur: function () {} };
-    // Set init caret position
-    sel = { begin: 0, end: 0 };
-    // Stub
-    stub(utils, 'addListener', function (evt, type, handler) {
-      user.on(type, handler);
-    });
-    stub(inptSel, 'get', function () {
-      return sel;
-    });
-    stub(inptSel, 'set', function (el, pos) {
-      sel.begin = sel.end = pos;
-    });
+    var html = $('#tmpl-input-text').html();
+    $el = $(html);
+    el = $el[0];
+
+    $('#workboard').append($el);
   });
 
+  // Remove element
   afterEach(function () {
-    utils.addListener.restore();
-    inptSel.get.restore();
-    inptSel.set.restore();
+    $el.remove();
   });
 
   //
   // Formatter global tests
   //
   describe('global', function () {
+
     // Create new instance
     var createInstance = function (str) {
-      formatted = new Formatter(el, {
+      formatted = new Formatter($el[0], {
         pattern: str,
         persistent: true
       });
@@ -68,6 +57,7 @@ describe('formatter.js', function () {
 
     it('Should set init values and merge defaults', function () {
       createInstance('({{999}}) {{999}}-{{9999}}');
+
       // Check opts
       assert.equal(formatted.opts.patterns[0]['*'], '({{999}}) {{999}}-{{9999}}');
       assert.isTrue(formatted.opts.persistent);
@@ -80,48 +70,56 @@ describe('formatter.js', function () {
       assert.isNumber(formatted.focus);
     });
 
-    it('Should natively handle home, end, and arrow keys', function () {
-      createInstance('({{999}}) {{999}}-{{9999}}');
-      user.key('leftarrow');
-      user.key('rightarrow');
-      user.key('uparrow');
-      user.key('downarrow');
-      user.key('home');
-      user.key('end');
-      user.key('enter');
-      user.key('tab');
-      assert.equal(formatted.focus, 1);
-    });
+    // it('Should natively handle home, end, and arrow keys', function (done) {
+    //   createInstance('({{999}}) {{999}}-{{9999}}');
+
+    //   fakey.seq(el, [
+    //     { key: 'leftarrow' },
+    //     { key: 'rightarrow' },
+    //     { key: 'uparrow' },
+    //     { key: 'downarrow' },
+    //     { key: 'home' },
+    //     { key: 'end' },
+    //     { key: 'enter' },
+    //     { key: 'tab' }
+    //   ], function () {
+    //     assert.equal(formatted.focus, 1);
+    //     done();
+    //   });      
+    // });
 
     it('Should update value when resetPattern method is called', function (done) {
       createInstance('({{999}}) {{999}}-{{9999}}');
-      user.keySeq('24567890', function () {
+
+      fakey.str(el, '24567890', function () {
         formatted.resetPattern('{{999}}.{{999}}.{{9999}}');
         assert.equal(formatted.el.value, '245.678.90  ');
         done();
       });
     });
 
-    describe('input focus', function () {
-      it('Should focus to the next available inpt position', function (done) {
-        createInstance('({{999}}) {{999}}-{{9999}}');
-        user.keySeq('1237890', function () {
-          assert.equal(formatted.focus, 11);
-          done();
-        });
+    it('Should focus to the next available inpt position', function (done) {
+      createInstance('({{999}}) {{999}}-{{9999}}');
+
+      fakey.str(el, '1237890', function () {
+        assert.equal(formatted.focus, 11);
+        done();
       });
-      it('Should not focus on a formatted char', function (done) {
-        createInstance('({{999}}) {{999}}-{{9999}}');
-        user.keySeq('123', function () {
-          assert.equal(formatted.focus, 6);
-          done();
-        });
+    });
+
+    it('Should not focus on a formatted char', function (done) {
+      createInstance('({{999}}) {{999}}-{{9999}}');
+
+      fakey.str(el, '123', function () {
+        assert.equal(formatted.focus, 6);
+        done();
       });
     });
 
     it('Should enforce pattern maxLength', function (done) {
       createInstance('({{999}}) {{999}}-{{9999}}');
-      user.keySeq('12345678901', function () {
+      
+      fakey.str(el, '12345678901', function () {
         assert.equal(formatted.el.value, '(123) 456-7890');
         done();
       });
@@ -130,17 +128,20 @@ describe('formatter.js', function () {
     it('Should add regex inpts', function (done) {
       Formatter.addInptType('L', /[A-Z]/);
       createInstance('{{LLL}}');
-      user.keySeq('AaAaA', function () {
+
+      fakey.str(el, 'AaAaA', function () {
         assert.equal(formatted.el.value, 'AAA');
         done();
       });
     });
+
   });
 
   //
   // Formatter with value dependent patterns
   //
   describe('value dependent patterns', function () {
+
     it('Should apply the default format', function (done) {
       formatted = new Formatter(el, {
         patterns: [
@@ -148,7 +149,7 @@ describe('formatter.js', function () {
         ]
       });
 
-      user.keySeq('1', function () {
+      fakey.key(el, '1', function () {
         assert.equal(formatted.el.value, '!1');
         done();
       });
@@ -162,7 +163,7 @@ describe('formatter.js', function () {
         ]
       });
 
-      user.keySeq('0123', function () {
+      fakey.str(el, '0123', function () {
         assert.equal(formatted.el.value, '!0123');
         done();
       });
@@ -176,7 +177,7 @@ describe('formatter.js', function () {
         ]
       });
 
-      user.keySeq('00', function () {
+      fakey.str(el, '00', function () {
         assert.equal(formatted.el.value, 'first:00');
         done();
       });
@@ -187,8 +188,8 @@ describe('formatter.js', function () {
   // Formatter with persistence
   //
   describe('persistent: true', function () {
+
     beforeEach(function () {
-      // New instance
       formatted = new Formatter(el, {
         pattern: '({{999}}) {{999}}-{{9999}}',
         persistent: true
@@ -196,16 +197,16 @@ describe('formatter.js', function () {
     });
 
     it('Should format chars as they are entered', function (done) {
-      user.keySeq('1237890', function () {
+      fakey.str(el, '1237890', function () {
         assert.equal(formatted.el.value, '(123) 789-0   ');
         done();
       });
     });
 
     it('Should fromat chars entered mid str', function (done) {
-      user.keySeq('1237890', function () {
-        sel = { begin: 6, end: 6 };
-        user.keySeq('456', function () {
+      fakey.str(el, '1237890', function () {
+        inptSel.set(el, 6);
+        fakey.str(el, '456', function () {
           assert.equal(formatted.el.value, '(123) 456-7890');
           done();
         });
@@ -213,65 +214,69 @@ describe('formatter.js', function () {
     });
 
     it('Should delete chars when highlighted', function (done) {
-      user.keySeq('1234567890', function () {
-        sel = { begin: 2, end: 8 };
-        user.key('backspace');
-        assert.equal(formatted.el.value, '(167) 890-    ');
-        done();
-      });
-    });
-
-    it('Should handle pasting multiple characters', function (done) {
-      user.keySeq('167890', function () {
-        sel = { begin: 2, end: 2 };
-        user.paste('2345', function () {
-          assert.equal(formatted.el.value, '(123) 456-7890');
+      fakey.str(el, '1234567890', function () {
+        inptSel.set(el, { begin: 2, end: 8 });
+        fakey.key(el, 'backspace', function () {
+          assert.equal(formatted.el.value, '(167) 890-    ');
           done();
         });
       });
     });
 
+    // it('Should handle pasting multiple characters', function (done) {
+    //   user.keySeq('167890', function () {
+    //     sel = { begin: 2, end: 2 };
+    //     user.paste('2345', function () {
+    //       assert.equal(formatted.el.value, '(123) 456-7890');
+    //       done();
+    //     });
+    //   });
+    // });
+
     it('Should remove previous character on backspace key', function (done) {
-      user.keySeq('1234567890', function () {
-        sel = { begin: 2, end: 2 };
-        user.key('backspace');
-        assert.equal(formatted.el.value, '(234) 567-890 ');
-        done();
+      fakey.str(el, '1234567890', function () {
+        inptSel.set(el, 2);
+        fakey.key(el, 'backspace', function () {
+          assert.equal(formatted.el.value, '(234) 567-890 ');
+          done();
+        });
       });
     });
 
     it('Should remove next character on delete key', function (done) {
-      user.keySeq('234567890', function () {
-        sel = { begin: 2, end: 2 };
-        user.key('delete');
-        assert.equal(formatted.el.value, '(245) 678-90  ');
-        done();
+      fakey.str(el, '234567890', function () {
+        inptSel.set(el, 2);
+        fakey.key(el, 'delete', function () {
+          assert.equal(formatted.el.value, '(245) 678-90  ');
+          done();
+        });
       });
     });
+
   });
 
   //
   // Formatter without persistence
   //
   describe('persistent: false', function () {
+
     beforeEach(function () {
-      // New instance
       formatted = new Formatter(el, {
         pattern: '({{999}}) {{999}}-{{9999}}'
       });
     });
 
     it('Should format chars as they are entered', function (done) {
-      user.keySeq('1237890', function () {
+      fakey.str(el, '1237890', function () {
         assert.equal(formatted.el.value, '(123) 789-0');
         done();
       });
     });
 
     it('Should fromat chars entered mid str', function (done) {
-      user.keySeq('1237890', function () {
-        sel = { begin: 6, end: 6 };
-        user.keySeq('456', function () {
+      fakey.str(el, '1237890', function () {
+        inptSel.set(el, 6);
+        fakey.str(el, '456', function () {
           assert.equal(formatted.el.value, '(123) 456-7890');
           done();
         });
@@ -279,76 +284,83 @@ describe('formatter.js', function () {
     });
 
     it('Should delete chars when highlighted', function (done) {
-      user.keySeq('1234567890', function () {
-        sel = { begin: 2, end: 8 };
-        user.key('backspace');
-        assert.equal(formatted.el.value, '(167) 890-');
-        done();
-      });
-    });
-
-    it('Should handle pasting multiple characters', function (done) {
-      user.keySeq('167890', function () {
-        sel = { begin: 2, end: 2 };
-        user.paste('2345', function () {
-          assert.equal(formatted.el.value, '(123) 456-7890');
+      fakey.str(el, '1234567890', function () {
+        inptSel.set(el, { begin: 2, end: 8 });
+        fakey.key(el, 'backspace', function () {
+          assert.equal(formatted.el.value, '(167) 890-');
           done();
         });
       });
     });
 
+    // it('Should handle pasting multiple characters', function (done) {
+    //   fakey.str(el, '167890', function () {
+    //     inptSel.set(el, 2);
+    //     user.paste('2345', function () {
+    //       assert.equal(formatted.el.value, '(123) 456-7890');
+    //       done();
+    //     });
+    //   });
+    // });
+
     it('Should remove previous character on backspace key', function (done) {
-      user.keySeq('1234567890', function () {
-        sel = { begin: 2, end: 2 };
-        user.key('backspace');
-        assert.equal(formatted.el.value, '(234) 567-890');
-        done();
+      fakey.str(el, '1234567890', function () {
+        inptSel.set(el, 2);
+        fakey.key(el, 'backspace', function () {
+          assert.equal(formatted.el.value, '(234) 567-890');
+          done();
+        });
       });
     });
 
     it('Should leave a trailing format preceding the deleted character on backspace key', function (done) {
-      user.keySeq('1234', function () {
-        user.key('backspace');
-        assert.equal(formatted.el.value, '(123) ');
-        done();
+      fakey.str(el, '1234', function () {
+        fakey.key(el, 'backspace', function () {
+          assert.equal(formatted.el.value, '(123) ');
+          done();
+        });
       });
     });
 
     it('Should remove a format character when it is the last character on backspace key', function (done) {
-      user.keySeq('123', function () {
-        user.key('backspace');
-        assert.equal(formatted.el.value, '(12');
-        done();
+      fakey.str(el, '123', function () {
+        fakey.key(el, 'backspace', function () {
+          assert.equal(formatted.el.value, '(12');
+          done();
+        });
       });
     });
 
     it('Should completely empty field', function (done) {
-      user.keySeq('1', function () {
-        user.key('backspace');
-        assert.equal(formatted.el.value, '');
-        done();
+      fakey.key(el, '1', function () {
+        fakey.key(el, 'backspace', function () {
+          assert.equal(formatted.el.value, '');
+          done();
+        });
       });
     });
 
     it('Should not add chars past focus location if deleting', function (done) {
-      user.keySeq('1234567', function () {
-        user.key('backspace');
-        assert.equal(formatted.el.value, '(123) 456-');
-        done();
+      fakey.str(el, '1234567', function () {
+        fakey.key(el, 'backspace', function () {
+          assert.equal(formatted.el.value, '(123) 456-');
+          done();
+        });
       });
     });
 
     it('Should remove next character on delete key', function (done) {
-      user.keySeq('234567890', function () {
-        sel = { begin: 2, end: 2 };
-        user.key('delete');
-        assert.equal(formatted.el.value, '(245) 678-90');
-        done();
+      fakey.str(el, '234567890', function () {
+        inptSel.set(el, 2);
+        fakey.key(el, 'delete', function () {
+          assert.equal(formatted.el.value, '(245) 678-90');
+          done();
+        });
       });
     });
 
     it('Should update value when resetPattern method is called', function (done) {
-      user.keySeq('24567890', function () {
+      fakey.str(el, '24567890', function () {
         formatted.resetPattern('{{999}}.{{999}}.{{9999}}');
         assert.equal(formatted.el.value, '245.678.90');
         done();
@@ -356,7 +368,7 @@ describe('formatter.js', function () {
     });
 
     it('Should update value when resetPattern method is called without changing pattern', function (done) {
-      user.keySeq('2456789013', function () {
+      fakey.str(el, '2456789013', function () {
         formatted.resetPattern();
         assert.equal(formatted.el.value, '(245) 678-9013');
         done();
@@ -364,14 +376,17 @@ describe('formatter.js', function () {
     });
 
     it('Should not reset caret position on format', function (done) {
-      user.keySeq('24567890', function () {
-        sel = { begin: 2, end: 2 };
+      fakey.str(el, '24567890', function () {
         formatted.resetPattern();
         assert.equal(formatted.el.value, '(245) 678-90');
-        assert.deepEqual(sel, inptSel.get());
+        assert.deepEqual({ begin: 12, end: 12 }, inptSel.get(el));
         done();
       });
     });
+
   });
 
 });
+
+
+});
\ No newline at end of file
diff --git a/test/pattern-matcher.js b/test/pattern-matcher.js
index d2d0e61..54a376f 100644
--- a/test/pattern-matcher.js
+++ b/test/pattern-matcher.js
@@ -1,86 +1,91 @@
-/*
-* 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");
+/*!
+ * test/pattern-matcher.js
+ * 
+ * Copyright (c) 2014 First Opinion
+ */
+
+
+define([
+  'proclaim',
+  'pattern-matcher',
+  'pattern'
+], function (assert, patternMatcher, pattern) {
+
 
 //
 // pattern-matcher.js tests
 //
 describe('pattern-matcher.js', function () {
+
+  // Pattern Strings
+  var patternStringA = '!{{*}}{{*}}',
+      patternStringB = '@{{*}}{{*}}';
+
+  // Parsed Patterns
+  var patternA = pattern.parse(patternStringA),
+      patternB = pattern.parse(patternStringB);
+
   it('Should parse each matcher as a regex', function () {
-    var myPatternMatcher = patternMatcher([ { '^abc$': '{{*}}-{{*}}-{{*}}' } ]);
+    var myPatternMatcher = patternMatcher([
+      { '^abc$': '{{*}}-{{*}}-{{*}}' }
+    ]);
 
-    assert.isTrue(myPatternMatcher.matchers[0].test("abc"));
-    assert.isFalse(myPatternMatcher.matchers[0].test("xyz"));
+    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$': '{{***}}' } ]);
+    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 appropriate pattern for the input', function () {
+      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 } ]);
+      var myPatternMatcher = patternMatcher([
+        { '^a': patternStringA },
+        { '.*': patternStringB }
+      ]);
 
-      assert.deepEqual(myPatternMatcher.getPattern("a"), patternA);
-      assert.deepEqual(myPatternMatcher.getPattern("aa"), patternA);
+      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 }
+      ]);
 
-      var myPatternMatcher = patternMatcher([ { 'wont-match': patternStringA },
-                                            { '*': patternStringB } ]);
-
-      assert.deepEqual(myPatternMatcher.getPattern("a"), patternB);
+      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 }
+      ]);
 
-      var myPatternMatcher = patternMatcher([ { 'wont-match': patternStringA },
-                                            { '*': patternStringB } ]);
-
-      assert.deepEqual(myPatternMatcher.getPattern("a"), patternB);
+      assert.deepEqual(myPatternMatcher.getPattern('a'), patternB);
     });
+
   });
+
 });
+
+
+});
\ No newline at end of file
diff --git a/test/pattern.js b/test/pattern.js
index 4fdac9f..8a6d81e 100644
--- a/test/pattern.js
+++ b/test/pattern.js
@@ -1,17 +1,14 @@
-/*
- * test/utils.js:
- *
- * (C) 2013 First Opinion
- * MIT LICENCE
- *
+/*!
+ * test/pattern.js
+ * 
+ * Copyright (c) 2014 First Opinion
  */
 
-// 3rd party
-var should = require('chai').should(),
-    assert = require('chai').assert;
 
-// first party
-var pattern = require('../src/pattern');
+define([
+  'proclaim',
+  'pattern'
+], function (assert, pattern) {
 
 
 //
@@ -23,6 +20,7 @@ describe('pattern.js', function () {
   // Create an object holding all formatted characters
   // with corresponding positions
   describe('parse', function () {
+
     it('Should return an obj with pattern info', function () {
       var result = pattern.parse('({{9A*}}) {{9A*}}-{{AAAA}}');
       assert.deepEqual(result.chars, {
@@ -47,6 +45,7 @@ describe('pattern.js', function () {
     });
 
     describe('edge cases', function () {
+
       it('Should parse a pattern with a leading char', function () {
         var result = pattern.parse('_{{**}}');
 
@@ -72,7 +71,12 @@ describe('pattern.js', function () {
         });
         assert.equal(result.mLength, 3);
       });
+
     });
+    
   });
 
 });
+
+
+});
\ No newline at end of file
diff --git a/test/utils.js b/test/utils.js
index 2bf3eed..7edebc9 100644
--- a/test/utils.js
+++ b/test/utils.js
@@ -1,17 +1,14 @@
-/*
- * test/utils.js:
- *
- * (C) 2013 First Opinion
- * MIT LICENCE
- *
+/*!
+ * test/utils.js
+ * 
+ * Copyright (c) 2014 First Opinion
  */
 
-// 3rd party
-var should = require('chai').should(),
-    assert = require('chai').assert;
 
-// first party
-var utils = require('../src/utils');
+define([
+  'proclaim',
+  'utils'
+], function (assert, utils) {
 
 
 //
@@ -22,6 +19,7 @@ describe('utils.js', function () {
   // utils.extend
   // Shallow copy properties from n objects to destObj
   describe('extend', function () {
+
     it('Should return an obj with merged props', function () {
       // Setup Data
       var defaults, opts;
@@ -55,47 +53,57 @@ describe('utils.js', function () {
         'the'      : 'destObj'
       }, opts);
     });
+
   });
 
   // utils.addChars
   // Add a given character to a string at a defined pos
   describe('addChars', function () {
+
     it('Should add chars to str starting at pos', function () {
       var result = utils.addChars('add the str', 'inbetween ', 4);
       assert.equal(result, 'add inbetween the str');
     });
+
   });
 
   // utils.removeChars
   // Remove a span of characters
   describe('removeChars', function () {
+
     it('Should remove span of chars', function () {
     var result = utils.removeChars('remove uneccesary chars', 6, 17);
       assert.equal(result, 'remove chars');
     });
+
   });
 
   // utils.isBetween
   // Return true/false is num false between bounds
   describe('removeChars', function () {
+
     it('Should return true when between range', function () {
       var result = utils.isBetween(2, [1, 3]);
       assert.isTrue(result);
     });
+
     it('Should return true regardless of order', function () {
       var result = utils.isBetween(2, [3, 1]);
       assert.isTrue(result);
     });
+
     it('Should return false when not between range', function () {
       var result = utils.isBetween(4, [1, 3]);
       assert.isFalse(result);
     });
+
   });
 
 
   // 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) {
@@ -105,6 +113,7 @@ describe('utils.js', function () {
 
       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) {
@@ -114,19 +123,21 @@ describe('utils.js', function () {
 
       assert.deepEqual(result, ['first', 'a', 'second', 'b']);
     });
+
     it('Should ignore prototypically inherited properties', function () {
-      var parent = function () {
+      var Parent = function () {
         this.property = 'property';
       };
-      parent.prototype = { protoProperty: 'protoProperty' } ;
+      Parent.prototype = { protoProperty: 'protoProperty' } ;
 
       var result = [];
-      utils.forEach(new parent(), function (val) {
+      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) {
@@ -137,6 +148,7 @@ describe('utils.js', function () {
 
       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) {
@@ -146,5 +158,10 @@ describe('utils.js', function () {
 
       assert.deepEqual(result, [0,'a',1,'b',2,'c']);
     });
+
   });
+
 });
+
+
+});
\ No newline at end of file
