From fdf3c926d99a95f3c524bf35cae44d0eca766ce0 Mon Sep 17 00:00:00 2001
From: Jarid Margolin <jaridmargolin@gmail.com>
Date: Thu, 8 May 2014 17:02:24 -0700
Subject: [PATCH] Updates

---
 _layouts/master.html                |  69 ++++
 demos.html                          |  39 ++
 index.md                            | 150 +++++++
 javascripts/formatter.js            | 766 +++++++++++++++++++++++++++++++++++
 javascripts/formatter.min.js        |   1 +
 javascripts/jquery.formatter.js     | 787 ++++++++++++++++++++++++++++++++++++
 javascripts/jquery.formatter.min.js |   1 +
 javascripts/scale.fix.js            |  17 +
 stylesheets/pygment_trac.css        |  69 ++++
 stylesheets/styles.css              | 343 ++++++++++++++++
 10 files changed, 2242 insertions(+)
 create mode 100644 _layouts/master.html
 create mode 100644 demos.html
 create mode 100644 index.md
 create mode 100644 javascripts/formatter.js
 create mode 100644 javascripts/formatter.min.js
 create mode 100644 javascripts/jquery.formatter.js
 create mode 100644 javascripts/jquery.formatter.min.js
 create mode 100644 javascripts/scale.fix.js
 create mode 100644 stylesheets/pygment_trac.css
 create mode 100644 stylesheets/styles.css

diff --git a/_layouts/master.html b/_layouts/master.html
new file mode 100644
index 0000000..ec4b91b
--- /dev/null
+++ b/_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/demos.html b/demos.html
new file mode 100644
index 0000000..9d8e014
--- /dev/null
+++ b/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/index.md b/index.md
new file mode 100644
index 0000000..42f3b0b
--- /dev/null
+++ b/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/javascripts/formatter.js b/javascripts/formatter.js
new file mode 100644
index 0000000..551d30d
--- /dev/null
+++ b/javascripts/formatter.js
@@ -0,0 +1,766 @@
+/*!
+ * v0.0.9
+ * 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/javascripts/formatter.min.js b/javascripts/formatter.min.js
new file mode 100644
index 0000000..d64eb54
--- /dev/null
+++ b/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/javascripts/jquery.formatter.js b/javascripts/jquery.formatter.js
new file mode 100644
index 0000000..fdd3de0
--- /dev/null
+++ b/javascripts/jquery.formatter.js
@@ -0,0 +1,787 @@
+/*!
+ * v0.0.9
+ * 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/javascripts/jquery.formatter.min.js b/javascripts/jquery.formatter.min.js
new file mode 100644
index 0000000..9418837
--- /dev/null
+++ b/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/javascripts/scale.fix.js b/javascripts/scale.fix.js
new file mode 100644
index 0000000..87a40ca
--- /dev/null
+++ b/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/stylesheets/pygment_trac.css b/stylesheets/pygment_trac.css
new file mode 100644
index 0000000..9aed55e
--- /dev/null
+++ b/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/stylesheets/styles.css b/stylesheets/styles.css
new file mode 100644
index 0000000..20d2502
--- /dev/null
+++ b/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
