﻿/*
* jQuery UI Mask @VERSION
*
* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
*
* Based on the jquery.maskedinput.js plugin by Josh Bush (digitalbush.com)
*
* http://docs.jquery.com/UI/Mask
*
* Depends:
*   ui.core.js
*/
(function($) {

  var pasteEventName = ($.browser.msie ? 'paste' : 'input') + ".mask";

  $.widget("ui.mask", {

    _init: function() {

      var options = this.options, self = this;

      $.extend(this, { caret: function(begin, end) { return $.ui.mask.caret(self.element, begin, end); } });

      if (!options.mask || !options.mask.length) { return; } //no mask pattern defined. no point in continuing.
      if (!options.placeholder || !options.placeholder.length) {
        options.placeholder = '_'; //in case the user decided to nix a placeholder. 
      }

      this._prepareBuffer();
      this._bindEvents();
      this._checkVal((this.element.val().length && options.allowPartials)); //Perform initial check for existing values
    },

    destroy: function() {
      this.element
			.unbind('.mask')
			.removeData('mask');
    },

    value: function() {
      var input = this.element,
			self = this,
			res = $.map(self.buffer, function(c, i) { return self.tests[i] ? c : null; }).join(''),
			r = new RegExp('\\' + this.options.placeholder, 'gi');
      return res.replace(r, '');
    },

    formatted: function() {
      var r = new RegExp('\\' + this.options.placeholder, 'gi'),
			res = this.element.val();
      return res.replace(r, '');
    },

    apply: function() {
      this.element.trigger('apply.mask');
    },

    _setData: function(key, value) {

      $.widget.prototype._setData.apply(this, arguments);
      var options = this.options;

      switch (key) {
        case 'mask':
          //no mask pattern defined. no point in continuing.
          if (!options.mask || !options.mask.length) {
            this.element.unbind('.mask')
            break;
          }
        case 'placeholder':
          if (!options.placeholder || !options.placeholder.length) {
            options.placeholder = '_'; //in case the user decided to nix a placeholder.
          }
          this.element.val('');
          this._prepareBuffer();
          !this.eventsBound && this._bindEvents();
          break;
      }

    },

    _prepareBuffer: function() {

      this._escapeMask();

      var self = this,
			input = this.element,
			options = this.options,
			mask = options.mask,
			defs = $.ui.mask.definitions,
			tests = [],
			partialPosition = mask.length,
			firstNonMaskPos = null,
			len = mask.length;

      //if we're applying the mask to an element which is not an input, it won't have a val() method. fake one for our purposes.
      if (!input.is(':input')) input.val = input.html;

      $.each(mask.split(""), function(i, c) {
        if (c == '?') {
          len--;
          partialPosition = i;
        }
        else if (defs[c]) {
          tests.push(new RegExp(defs[c]));
          if (firstNonMaskPos == null)
            firstNonMaskPos = tests.length - 1;
        }
        else {
          tests.push(null);
        }
      });

      $.extend(this, {
        buffer: $.map(mask.split(""), function(c, i) {
          if (c != '?') {
            return defs[c] ? options.placeholder : c;
          }
        }),
        tests: tests,
        firstNonMaskPos: firstNonMaskPos,
        partialPosition: partialPosition
      });

      this.buffer = $.map(this.buffer, function(c, i) {
        if (c == "\t") {
          return self.maskEscaped[i];
        }
        return c;
      });

      this.options.mask = mask = this.maskEscaped;
    },

    _bindEvents: function() {

      var self = this,
			input = this.element,
			ignore = false,  			//Variable for ignoring control keys
			focusText = input.val();

      function keydownEvent(e) {
        e = e || window.event;
        var pos = self.caret(),
				k = e.keyCode,
				keyCode = $.ui.keyCode;

        ignore = (k < keyCode.SHIFT || (k > keyCode.SHIFT && k < keyCode.SPACE) || (k > keyCode.SPACE && k < 41));

        //delete selection before proceeding
        if ((pos.begin - pos.end) != 0 && (!ignore || k == keyCode.BACKSPACE || k == keyCode.DELETE))
          self._clearBuffer(pos.begin, pos.end);

        //backspace, delete, and escape get special treatment
        if (k == keyCode.BACKSPACE || k == keyCode.DELETE) {//backspace/delete
          self._shiftL(pos.begin + ((k == keyCode.DELETE || (k == keyCode.BACKSPACE && pos.begin != pos.end)) ? 0 : -1), Math.abs(pos.begin - pos.end));
          return false;
        }
        else if (k == keyCode.ESCAPE) {//escape
          input.val(focusText);
          self.caret(0, self._checkVal());
          return false;
        }
      };

      function keypressEvent(e) {

        e = e || window.event;

        var k = e.charCode || e.keyCode || e.which,
				keyCode = $.ui.keyCode,
				len = self.options.mask.length;

        if (ignore) {
          ignore = false;
          //Fixes Mac FF bug on backspace
          return (e.keyCode == keyCode.BACKSPACE) ? false : null;
        }

        var pos = self.caret();

        if (e.ctrlKey || e.altKey || e.metaKey) {//Ignore
          return true;
        }
        else if ((k >= keyCode.SPACE && k <= 125) || k > 186) {//typeable characters
          var p = self._seekNext(pos.begin - 1);
          if (p < len) {
            var c = String.fromCharCode(k);
            if (self.tests[p] && self.tests[p].test(c)) {
              self._shiftR(p);
              self.buffer[p] = c;
              self._writeBuffer();
              var next = self._seekNext(p);
              self.caret(next);
              self.options.completed && next == len && self.options.completed.call(input);
            }
          }
        }
        return false;
      };

      if (!input.attr("readonly")) {
        input
				.bind("focus.mask", function() {
				  focusText = input.val();
				  var pos = self._checkVal();
				  self._writeBuffer();
				  setTimeout(function() {
				    if (pos == self.options.mask.length)
				      self.caret(0, pos);
				    else
				      self.caret(pos);
				  }, 0);
				})
				.bind("blur.mask", function() {
				  self._checkVal();
				  if (input.val() != focusText)
				    input.change();
				})
				.bind('apply.mask', function() { //changing the value of an input without keyboard input requires re-applying the mask.
				  focusText = input.val();
				  var pos = self._checkVal();
				  self._writeBuffer();
				})
				.bind("keydown.mask", keydownEvent)
				.bind("keypress.mask", keypressEvent)
				.bind(pasteEventName, function() {
				  setTimeout(function() { self.caret(self._checkVal(true)); }, 0);
				});
        this.eventsBound = true;
      }
    },

    _writeBuffer: function() {
      return this.element.val(this.buffer.join('')).val();
    },

    _clearBuffer: function(start, end) {
      var len = this.options.mask.length;
      for (var i = start; i < end && i < len; i++) {
        if (this.tests[i])
          this.buffer[i] = this.options.placeholder;
      }
    },

    _seekNext: function(pos) {
      var len = this.options.mask.length;
      while (++pos <= len && !this.tests[pos]);
      return pos;
    },

    _shiftR: function(pos) {
      var len = this.options.mask.length;
      for (var i = pos, c = this.options.placeholder; i < len; i++) {
        if (this.tests[i]) {
          var j = this._seekNext(i);
          var t = this.buffer[i];
          this.buffer[i] = c;
          if (j < len && this.tests[j].test(t)) {
            c = t;
          }
          else {
            break;
          }
        }
      }
    },

    _shiftL: function(pos, length) {

      while (!this.tests[pos] && --pos >= 0);

      var originalPos = pos,
			len = this.options.mask.length,
			placeholder = this.options.placeholder;

      for (var i = pos; i < len && (i >= 0 || length > 1); i++) {
        if (this.tests[i]) {
          this.buffer[i] = placeholder;
          var j = this._seekNext(i);
          if (j < len && this.tests[i].test(this.buffer[j])) {
            this.buffer[pos] = this.buffer[j];
            this.buffer[j] = placeholder;
            pos++;
            while (!this.tests[pos]) pos++;
          }
        }
      }
      this._writeBuffer();
      this.caret(Math.max(this.firstNonMaskPos, originalPos));
    },

    _checkVal: function(allow) {
      //try to place characters where they belong
      var input = this.element,
			test = input.val(),
			len = this.options.mask.length,
			lastMatch = -1;

      for (var i = 0, pos = 0; i < len; i++) {
        if (this.tests[i]) {
          this.buffer[i] = this.options.placeholder;
          while (pos++ < test.length) {
            var c = test.charAt(pos - 1);
            if (this.tests[i].test(c)) {
              this.buffer[i] = c;
              lastMatch = i;
              break;
            }
          }
          if (pos > test.length)
            break;
        }
        else if (this.buffer[i] == test[pos] && i != this.partialPosition) {
          pos++;
          lastMatch = i;
        }
      }
      if (!allow && lastMatch + 1 < this.partialPosition) {
        if (!this.options.allowPartials || !this.value().length) {
          input.val("");
          this._clearBuffer(0, len);
        }
        else //if we're allowing partial input/inital values, and the element we're masking isnt an input, then we need to allow the mask to apply.
          if (!input.is(':input')) this._writeBuffer();

      }
      else if (allow || lastMatch + 1 >= this.partialPosition) {
        this._writeBuffer();
        if (!allow) input.val(input.val().substring(0, lastMatch + 1));
      }
      return (this.partialPosition ? i : this.firstNonMaskPos);
    },

    _escapeMask: function() {
      var mask = this.options.mask,
			literals = [],
			replacements = [];

      for (var i = 0; i < mask.length; i++) {
        var c, temp = mask[i] || mask.charAt(i);
        if (temp != "\\" || mask[i - 1] == "\\") {
          if (mask[i - 1] == "\\") {
            c = "\t";
            replacements[literals.length] = temp;
          }
          else {
            c = temp;
          }
          literals[literals.length] = c;
        }
      }

      this.options.mask = literals.join('');

      for (var i = 0; i < literals.length; i++) {
        if (replacements[i] !== undefined) {
          literals[i] = replacements[i];
        }
      }

      this.maskEscaped = literals.join('');
    }

  });

  $.extend($.ui.mask, {
    version: "@VERSION",
    getter: "value formatted",
    defaults: {
      mask: '',
      placeholder: '_',
      completed: null,
      allowPartials: false
    },
    definitions: { //Predefined character definitions
      '#': "[\\d]",
      'a': "[A-Za-z]",
      '*': "[A-Za-z0-9]"
    },
    caret: function(element, begin, end) {	//Helper Function for Caret positioning
      var input = element[0];
      if (typeof begin == 'number') {
        end = (typeof end == 'number') ? end : begin;
        if (input.setSelectionRange) {
          input.focus();
          input.setSelectionRange(begin, end);
        } else if (input.createTextRange) {
          var range = input.createTextRange();
          range.collapse(true);
          range.moveEnd('character', end);
          range.moveStart('character', begin);
          range.select();
        }
        return element;
      } else {
        if (input.setSelectionRange) {
          begin = input.selectionStart;
          end = input.selectionEnd;
        }
        else if (document.selection && document.selection.createRange) {
          var range = document.selection.createRange();
          begin = 0 - range.duplicate().moveStart('character', -100000);
          end = begin + range.text.length;
        }
        return { begin: begin, end: end };
      }
    }
  });

})(jQuery);