/***********************************************************************
                       Masked Input version 1.1
************************************************************************
Author: Kendall Conrad
Home page: http://www.angelwatt.com/coding/masked_input.php
Created:  2008-12-16
Modified: 2010-04-14
Description:
License: This work is licensed under a Creative Commons Attribution-Share Alike
  3.0 United States License http://creativecommons.org/licenses/by-sa/3.0/us/

Argument pieces:
- elm:        [req] text input node to apply the mask on
- format:     [req] string format for the mask
- allowed:    [opt, '0123456789'] string with chars allowed to be typed
- sep:        [opt, '\/:-'] string of char(s) used as separators in mask
- typeon:     [opt, '_YMDhms'] string of chars in mask that can be typed on
- onbadkey:   [opt, null] function to run when user types a unallowed key
- badkeywait: [opt, 0] used with onbadkey. Indicates how long (in ms) to lock
  text input for onbadkey function to run
***********************************************************************/
function MaskedInput(args)
{
  if (args['elm'] === null || args['format'] === null) { return false; }
  var el     = args['elm'],
    format   = args['format'],
    allowed  = args['allowed']    || '0123456789',
    sep      = args['separator']  || '\/:-',
    open     = args['typeon']     || '_YMDhms',
    onbadkey = args['onbadkey']   || function(){},
    badwait  = args['badkeywait'] || 0;
  
  var locked = false, hold = 0;
  el.value = format;
  // Assign events
  el.onkeydown  = KeyHandlerDown;  //
  el.onkeypress = KeyHandlerPress; // add event handlers to element
  el.onkeyup    = KeyHandlerUp;    //

  function GetKey(code)
  {
    code = code || window.event, ch = '';
    var keyCode = code.which, evt = code.type;
    if (keyCode == null) { keyCode = code.keyCode; }
    if (keyCode === null) { return ''; } // no key, no play
    // deal with special keys
    switch (keyCode) {
    case 8:  ch = 'bksp'; break;
    case 46: // handle del and . both being 46
      ch = (evt == 'keydown') ? 'del' : '.'; break;
    case 16: ch = 'shift'; break;//shift
    case 0:/*CRAP*/ case 9:/*TAB*/ case 13:/*ENTER*/
      ch = 'etc'; break;
    case 37: case 38: case 39: case 40: // arrow keys
      ch = (!code.shiftKey &&
           (code.charCode != 39 && code.charCode !== undefined)) ?
        'etc' : String.fromCharCode(keyCode);
      break;
    // default to thinking it's a character or digit
    default: ch = String.fromCharCode(keyCode);
    }
    return ch;
  }
  function KeyHandlerDown(e)
  {
    e = e || event;
    if (locked) { return false; }
    var key = GetKey(e);
    if (el.value == '') { el.value = format; SetTextCursor(el,0); }
    // Only do update for bksp del
    if (key == 'bksp' || key == 'del') { Update(key); return false; }
    else if (key == 'etc' || key == 'shift') { return true; }
    else { return true; }    
  }
  function KeyHandlerPress(e)
  {
    e = e || event;
    if (locked) { return false; }
    var key = GetKey(e);
    // Check if modifier key is being pressed; command
    if (key=='etc' || e.metaKey || e.ctrlKey || e.altKey) { return true; }
    if (key != 'bksp' && key != 'del' && key != 'etc' && key != 'shift') {
      if (!GoodOnes(key)) { return false; }
      return Update(key);
    }
    else { return false; }
  }
  function KeyHandlerUp(e) { hold = 0; }
  function Update(key)
  {
    var p = GetTextCursor(el), c = el.value, val = '';
    // Handle keys now
    switch (true) {
    case (allowed.indexOf(key) != -1):
      if (++p > format.length) { return false; } // if text csor at end
      // Handle cases where user places csor before separator
      while (sep.indexOf(c.charAt(p-1)) != -1 && p <= format.length) { p++; }
      val = c.substr(0, p-1) + key + c.substr(p);
      // Move csor up a spot if next char is a separator char
      if (allowed.indexOf(c.charAt(p)) == -1
          && open.indexOf(c.charAt(p)) == -1) { p++; }
      break;
    case (key=='bksp'): // backspace
      if (--p < 0) return false; // at start of field
      // If previous char is a separator, move a little more
      while (allowed.indexOf(c.charAt(p)) == -1
             && open.indexOf(c.charAt(p)) == -1
             && p > 1) { p--; }
      val = c.substr(0, p) + format.substr(p,1) + c.substr(p+1);
      break;
    case (key=='del'): // forward delete
      if (p >= c.length) { return false; } // at end of field
      // If next char is a separator and not the end of the text field
      while (sep.indexOf(c.charAt(p)) != -1
             && c.charAt(p) != '') { p++; }
      val = c.substr(0, p) + format.substr(p,1) + c.substr(p+1);
      p++; // Move position forward
      break;
    case (key=='etc'): return true; // Catch other allowed chars
    default: return false;   // Ignore the rest
    }
    el.value = '';        // blank it first (Firefox issue)
    el.value = val;       // put updated value back in
    SetTextCursor(el, p); // Set the text cursor
    return false;
  }
  function GetTextCursor(node)
  {
    try {
      if (node.selectionStart >= 0) { return node.selectionStart; }
      else if (document.selection) {// IE
        var ntxt = node.value; // getting starting text
        var rng = document.selection.createRange();
        rng.text = '|%|';
        var start = node.value.indexOf('|%|');
        rng.moveStart('character', -3);
        rng.text = '';
        // put starting text back in,
        // fixes issue if all text was highlighted
        node.value = ntxt;
        return start;
      } return -1;
    } catch(e) { return false; }
  }
  function SetTextCursor(node, pos)
  {
    try {
      if (node.selectionStart) {
        node.focus();
        node.setSelectionRange(pos,pos);
      }
      else if (node.createTextRange) { // IE
        var rng = node.createTextRange();
        rng.move('character', pos);
        rng.select();
      }
    } catch(e) { return false; }
  }
  function GoodOnes(k)
  {
    if (allowed.indexOf(k) == -1 && k!='bksp' && k!='del' && k!='etc') {
      var p = GetTextCursor(el); // Need to ensure cursor position not lost
      locked = true; onbadkey();
      // Hold lock long enough for onbadkey function to run
      setTimeout(function(){locked=false; SetTextCursor(el,p);}, badwait);
      return false;
    } return true;
  }
  function resetField() {
    el.value = format;
  }
  function setAllowed(a) {
    allowed = a;
    resetField();
  }
  function setFormat(f) {
    format = f;
    resetField();
  }
  function setSeparator(s) {
    sep = s;
    resetField();
  }
  function setTypeon(t) {
    open = t;
    resetField();
  }
  return {
    resetField:resetField,
    setAllowed:setAllowed,
    setFormat:setFormat,
    setSeparator:setSeparator,
    setTypeon:setTypeon
  }
}

