////////////////////////////////////////////////////////////////////////////////
//
// Key management functions.
//
////////////////////////////////////////////////////////////////////////////////
//
// http://brebru.com/asciicodes.html
//
// 8 -> Backspace Key
// 9 -> Tab Key
// 13 -> Enter Key
// 32 -> Space
// 35 -> End Key
// 36 -> Home Key (objEvent.which = 0)
// 37 -> Left Arrow Key (objEvent.which = 0)
// 38 -> Up Arrow Key (objEvent.which = 0)
// 39 -> Right Arrow Key (objEvent.which = 0)
// 40 -> Right Arrow Key  (objEvent.which = 0)
// 40 -> (
// 41 -> )
// 45 -> -
// 46 -> Delete Key (objEvent.which = 0)
// 46 -> .
// 47 -> /
// 48-57 -> 1234567890
// 58 -> :
// 88 -> X
// 99 + ctrKey -> Ctrl-C (copy)
// 112 + ctrKey -> Ctrl-P (print)
// 118 + ctrKey -> Ctrl-V (paste)
// 120 -> X
// 120 + ctrKey -> Ctrl-X (cut)
//

function getKey(objEvent) {
   var nKey = objEvent.keyCode || objEvent.which;

   return nKey;
//   return(window.event) ? window.event.keyCode : objEvent.which;
}


function isPaste(objEvent) {
   return(objEvent.ctrlKey && getKey(objEvent) == 118);
}

function isActionKey(objEvent) {
   var nKey  = getKey(objEvent);
   var bCtrl = objEvent.ctrlKey;

   if (bCtrl && nKey == 118) {
      //
      // Ctrl-V was pressed... 
      //
   }
   
   return(nKey == 8 || nKey == 9 || nKey == 13 || (objEvent.which == 0 && (nKey == 35 || nKey == 36 || nKey == 37 || nKey == 378 || nKey == 39 || nKey == 46)) || bCtrl);
//          (nKey == 99 && bCtrl) || (nKey == 112 && bCtrl) || (nKey == 118 && bCtrl) || (nKey == 120 && bCtrl));
}


//
// <input type="text" onkeypress="return limitToInteger(event, false)" />
//
function limitToInteger(objEvent, bAllowNeg) {
   var nKey = getKey(objEvent);
   return((nKey >= 48 && nKey <= 57) || isActionKey(objEvent) || (bAllowNeg == true && nKey == 45));
}


//
// <input type="text" onkeypress="return limitToDashedNumber(event)" />
//
function limitToDashedNumber(objEvent) { return limitToInteger(objEvent, true); }


//
// <input type="text" onkeypress="return limitToDecimal(event, false)" />
//
function limitToDecimal(objEvent, bAllowNeg) {
   var nKey = getKey(objEvent);
   return((nKey >= 48 && nKey <= 57) || nKey == 46 || isActionKey(objEvent) || (bAllowNeg == true && nKey == 45));
}


//
// <input type="text" onkeypress="return limitToAlpha(event)" />
//
function limitToAlpha(objEvent) {
   var nKey = getKey(objEvent);
   return((nKey >= 65 && nKey <= 90) || (nKey >= 97 && nKey <= 122) || isActionKey(objEvent));
}


//
// <input type="text" onkeypress="return limitToAlphaNumeric(event)" />
//
function limitToAlphaNumeric(objEvent) {
   var nKey = getKey(objEvent);
   return(limitToAlpha(objEvent) || limitToInteger(objEvent));
}


//
// <input type="text" onkeypress="return limitToTime(event)" />
//
function limitToTime(objEvent) {
   var nKey = getKey(objEvent);
   return((nKey >= 48 && nKey <= 57) || nKey == 58 || isActionKey(objEvent));
}


//
// <input type="text" onkeypress="return limitToDate(event)" />
//
function limitToDate(objEvent) {
   var nKey = getKey(objEvent);
   return((nKey >= 48 && nKey <= 57) || nKey == 47 || isActionKey(objEvent));
}


//
// <input type="text" onkeypress="return limitToPhone(event, 'full')" />
//
function limitToPhone(objEvent, nFormat) {
   var nKey = getKey(objEvent);

   switch (nFormat) {
      case "full":    return((nKey >= 48 && nKey <= 57) || nKey == 32 || nKey == 40 || nKey == 41 || nKey == 45 || nKey == 88 || nKey == 120 || isActionKey(objEvent));
      case "dashed":  return((nKey >= 48 && nKey <= 57) || nKey == 45 || isActionKey(objEvent));
      case "compact":
      default:        return((nKey >= 48 && nKey <= 57) || isActionKey(objEvent));
   }
}

//
// <input type="text" onkeypress="return limitToCharSet(event, /CHARSET/[i])" />
//
function limitToCharSet(objEvent, objRE) {
   var nKey  = getKey(objEvent);
   
   return(objRE.test(String.fromCharCode(nKey)) || isActionKey(objEvent));
}


//
// <input type="text" onkeypress="return limitFromWeb(event)" />
//
function limitFromWeb(objEvent) {
   var nKey = getKey(objEvent);

   // " & < = >
   return((nKey != 34 && nKey != 38 && nKey != 60 && nKey != 61 && nKey != 62) || isActionKey(objEvent));
}


function formatCheck(strType, objField, strRE) {
   var objRE = new RegExp(strRE);
   var bPass = objRE.test(objField.value);
   
   if (objField.value == "")
      return true;
   
   if (! bPass) {
      alert("You entered an invalid " + strType + " format: " + objField.value + "\nPlease try again.");
      objField.value = "";
      objField.focus();
   }

   return bPass;
}


//
// <input type="text" onblur="return formatToInteger(this, false)" />
//
function formatToInteger(objField, bAllowNeg) {
   var strRE = (bAllowNeg) ? /^[-+]?\d*$/ : /^[+]?\d*$/;
   
   return formatCheck("integer", objField, strRE);
}


//
// <input type="text" onblur="return formatToDecimal(this, false)" />
//
function formatToDecimal(objField, bAllowNeg) {
   var strRE = (bAllowNeg) ? /^[-+]?\d*(\.\d+)?$/ : /^[+]?\d*(\.\d+)?$/;
   
   return formatCheck("decimal", objField, strRE);
}


//
// <input type="text" onblur="return formatToCurrency(this, false)" />
//
function formatToCurrency(objField, bAllowNeg) {
   var strRE = (bAllowNeg) ? /^[-+]?\$?\d*(\.\d{2})?$/ : /^[+]?\$?\d*(\.\d{2})?$/;
   
   return formatCheck("currency", objField, strRE);
}


function noEnter(objEvent) {
   var nKey = getKey(objEvent);

   return !(nKey == 13);
}


function submitOnEnter(objEvent, objField) {
   var nKey = getKey(objEvent);
   
   if (nKey == 13) {
      objField.form.submit();
      return false;
   }
   
   return true;
}


function actionOnEnter(objEvent, objCallback) {
   var nKey = getKey(objEvent);

   if (nKey == 13) {
      objCallback();
      return false;
   }
   
   return true;
}


function submitEnter(objEvent, objForm) {
   var nKey = getKey(objEvent);

   if (nKey == 13)
      objForm.submit();
   else
      return true;
}


function maxLength(objEvent, strID, nLen) {
   var objField = document.getElementById(strID);
   return((objField.value.length < nLen) || (isActionKey(objEvent)));
}


////////////////////////////////////////////////////////////////////////////////
//
// Miscellaneous handling form functions.
//
////////////////////////////////////////////////////////////////////////////////
function makeMask(strCC) {
   var nLen    = strCC.length;
   var strMask = strCC.substr(0, 4);
   
   for ( var i = 1 ; i <= nLen - 8 ; i++ )
      strMask += "*";
      
   strMask += strCC.substr(nLen-4, 4);

   return(strMask);
}


function formatCurrency(val) {
   var str   = String(val);
   var parts = str.split('.');
   var nInt  = parts[0];
   var nDec  = parts.length > 1 ? '.' + parts[1] : '.00';
   var rgx   = /(\d+)(\d{3})\.(\d\d)/;

   if (str.substring(0, 1) == "$")
      return str;

   var nVal = parseFloat(nInt + nDec).toFixed(2);

   while (rgx.test(nVal))
      nVal = nVal.replace(rgx, '$1' + ',' + '$2' + '.' + '$3');

   return "$" + nVal;
}


function currencyToFloat(cur) {
   var ret;
   var newcur;

   cur = cur + ""; // convert to string
   
   if (cur != "")
      return parseFloat(cur.replace(/[\$|,]/g, ""));
   else
      return cur;
}


function makeInt(val) {
   if (val == "")
      return 0;
   else
      return parseInt(val);
}


function adjustValue(strField, nDelta, nLimit) {
   var objField = document.getElementById(strField);
   
   if (objField.disabled)
      return;
   
   var nValue   = (objField.value == "") ? 0 : parseInt(objField.value);

   if (objField.value != nValue)
      return;

   if ((nDelta < 0) && (nValue == 0)) 
      return;

   if ((nDelta > 0) && (nValue + nDelta > nLimit))
      return;

   objField.value = nValue + nDelta;
}

function caseName(objField) {
   if (typeof(objField) != "object")
      return;
      
   if (objField.value == "")
      return;

   var strSpacer = " ";
   var strNewVal = "";
   var strVal    = objField.value;
       strVal    = strVal.split(strSpacer);

   for ( var c = 0 ; c < strVal.length ; c++ ) {
      if (c+1 == strVal.length)
         strSpacer = "";

      strNewVal += strVal[c].substring(0, 1).toUpperCase() + strVal[c].substring(1, strVal[c].length) + strSpacer;
   }

   objField.value = strNewVal;
}
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
//
// Form validation functions.
//
////////////////////////////////////////////////////////////////////////////////

/**********************************************************
* jvalx generic embedded form validation library
* created:  20030314
*  author:   jeff emminger
* version:  2.2.0
*
* LICENSE:
* distributed with GNU GPL license
*   http://www.gnu.org/licenses/gpl.txt
* this library is free to use, provided these credits remain in place
* donations welcomed!  paypal me:  jeff_at_jeffemminger_dot_com
************************************************************/

/******
CHANGES:
  20031112:  fixed bug in isDate() with Mozilla - multiple calls
             to regex.test() fails when global switch present
  20040603:  made XHTML-compliant using comment nodes to store form element config rules
  20040603:  changed errored-field highlighting to remain until element is validated, not upon focus
  20040604:  added "conditional" value of "required" attribute of config node
  20040604:  added "condition" attribute of config node
  20040607:  fixed bug in isAlpha:  wasn't allowing capital letters.
  20040607:  added optional "regexIgnoreCase" attribute to be used when "regex" is present
  20040608:  fixed bug in Rules(), where attempting to strip regex delimiters would fail when no regex present
  20040608:  added enhancements to isInteger() regex
  20040609:  added support for multiple dataType values, comma delimited (thanks to John Miller
             of www.DynamicDrive.com for the suggestion)
  20040713:  fixed bug with optional logical groups not validating properly.
  20041108:  fixed bug in testMinMax, hidden and password fields not validating properly
  20050201:  added ability to allow whitespace in config nodes around "=", e.g. required = "true" 
  20050327:  fixed bug with regex nodes not stripping regex delimiters
  20050506:  fixed but with "decimal" type allowing integers, added "lenient" attribute
  20050517:  renamed internal debug() method to jdebug(), was freaking mozilla out
  20051010:  fixed but with testMinMax not being called if dataType == decimal
  20060209:  changed signatures of jvalOverride() and jvalReset() to require the form
             as an argument to prevent collision of multiple forms on the same page
  20060409:  removed default invalid field highlighting - user *must* specify a class of 
             "jvalx-invalid-field" and "jvalx-invalid-label" to have formatting applied
  20060410:  fixed bug in isSelected()

/******
TODO:
  - add "dateMask" attribute for "date" dataTypes
  - allow whitespace in config nodes around "=", e.g. required = "true"
  - rewrite checkGroupRequired()
******/

/******
*  acceptable dataTypes:
*    text    : any character
*    alpha   : any letter, space, hyphen or underscore
*    integer : any integer
*    decimal : any decimal
*    email   : any valid email format
*    phone   : U.S. format phone: x-xxx-xxx-xxxx
*    zipcode : xxxxx[-xxxx]
*    ccard   : Credit Card Number (MasterCard, Discover, Visa, AmEx, Diners)
*    date    : U.S. format date: MM/DD/YYYY
*    radio   : radio buttons
*    select  : selection list
*    [null]  : the input's default "type"

  config node custom attribute definitions:
  ===================================
  required    : (true|false|conditional) whether or not the input must be given a value
  conditional : the condition to test for when required="conditional".  the result of the condition
                  determines if the field is required or not. (true=required)
  dataType    : one or more of the above acceptable dataTypes, separated by commas
  lenient     : allows integers when dataType is decimal
  min         : (dec) minimum numeric value or text length of the input's value, depending on it's dataType
  max         : (dec) maximum numeric value or text length of the input's value, depending on it's dataType
  setlen      : (dec) exact text length of the input's value
  lowerElement: if dataType="dateRange", the name of the form element representing the upper date.
  upperElement: if dataType="dateRange", the name of the form element representing the lower date.
  equalOk     : (bool) if dataType="dateRange", whether or not the two dates can be equal
  firstOk     : (bool) if dataType="select" and required="true", whether or not the first option is an acceptable choice.
                  e.g.  first option is "Please Choose", then firstOk="false"
  regex       : a regular expression to test the input's value against, without opening and closing regex delimiters "/"
  regexIgnoreCase : whether the regex should be case insensitive or not.
  errorMsg    : the error message to be displayed (if any) if the input's value fails validation.


  prototypes for form elements with custom config nodes:
  =============================

alpha:
<input type="text"
  name=""
  value=""
  title=""
  size=""
  maxlength=""/><!--
  for=""
  required=""
  min=""
  max=""
  dataType="alpha"
  regex=""
  errorMsg="" -->

date:
<input type="text"
  name=""
  value=""
  title=""
  size="10"
  maxlength=""/><!--
  for=""
  required=""
  min=""
  max=""
  dataType="date"
  lowerElement|upperElement=""
  equalOk=""
  regex=""
  errorMsg="" -->

decimal:
<input type="text"
  name=""
  value=""
  title=""
  size=""
  maxlength=""/><!--
  for=""
  required=""
  min=""
  max=""
  minval=""
  maxval=""
  dataType="decimal"
  regex=""
  errorMsg=""  -->

email:
<input type="text"
  name=""
  value=""
  title=""
  size=""
  maxlength=""/><!--
  for=""
  required=""
  min=""
  max=""
  dataType="email"
  regex=""
  errorMsg="" -->

integer:
<input type="text"
  name=""
  value=""
  title=""
  size=""
  maxlength=""/><!--
  for=""
  required=""
  min=""
  max=""
  minval=""
  maxval=""
  dataType="integer"
  regex=""
  errorMsg="" -->

phone:
<input type="text"
  name=""
  value=""
  title=""
  size=""
  maxlength=""/><!--
  for=""
  required=""
  min=""
  max=""
  dataType="phone"
  regex=""
  errorMsg="" -->
  
select:
<select name=""
  size="1"
  ondblclick=""
  onchange=""/>
  <option value=""></option>
</select>
<!--
  for=""
  required=""
  firstOk=""
  errorMsg=""
-->


Phone regex:
   Strict ###[-.]###[-.]####     ^\d{3}[\.\-]\d{3}[\.\-]\d{4}$
   Strict (###)[ ]?###[-.]####   ^(\d{3})\s?\d{3}[\.\-]\d{4}$
   Strict ##########             ^\d{7}$
   Flexible                      ^((((\d\s)|\d)?[\(\-\.\s]\s?)?\d{3}\s?[\)\-\.\s]?\s?)?\d{3}\s?[\.\-\s]?\s?\d{4}$


***********************************************************/

/*** CONSTANTS ***/
var ELEMENT_NODE = 1;
var TEXT_NODE = 3;
var COMMENT_NODE = 8;

self.jval_debugger_on = false;

/*** MAIN VALIDATION FUNCTION ***/
function jValidate(f, bAllowMultipleSubmission) {
   //  ignore NS4.x
   if (document.layers) {
      return true;
   }
   else {
      //  loop through all form elements
      if (f.bSubmitted) return false;
      if (f.jvalOverridden) return true;
      
      var els = f.elements;
      var bFlag;

      for (var x = 0; x < els.length; x++) {
         if (els[x].tagName.toLowerCase() == "fieldset" || els[x].disabled) continue;

         var el = new El(els[x]);
         var bIsValid = false;
         if (!el.dataType) continue;
         //  loop, in case el has multiple dataTypes
         for (self.DTX = 0; self.DTX < el.dataType.length; self.DTX++) {
            //  determine the element's type
            switch(el.dataType[self.DTX]) {
               case "alpha":
                  bFlag = _text(el);
                  break;
               case "integer":
                  bFlag = _numeric(el);
                  break;
               case "decimal":
                  bFlag = _numeric(el);
                  break;
               case "currency":
                  bFlag = _numeric(el);
                  break;
               case "email":
                  bFlag = _email(el);
                  break;
               case "zipcode":
                  bFlag = _zipcode(el);
                  break;
               case "phone":
                  bFlag = _phone(el);
                  break;
               case "ccard":
                  bFlag = _ccard(el);
                  break;
               case "date":
                  bFlag = _date(el);
                  break;
               case "checkbox":
                  bFlag = _checkbox(el);
                  break;
               case "file":
                  bFlag = _text(el);
                  break;
               case "hidden":
                  bFlag = _text(el);
                  break;
               case "password":
                  bFlag = _text(el);
                  break;
               case "radio":
                  bFlag = _radio(el);
                  break;
               case "select":
                  bFlag = _select(el);
                  break;
               case "text":
                  bFlag = _text(el);
                  break;
               case "textarea":
                  bFlag = _text(el);
                  break;
               default:
                  bFlag = true;
            }
            //  test the element's constraint, if any
            var bConstraintOk = el.testConstraint();

            if (bFlag && bConstraintOk) bIsValid = true;

         }

         if (!bIsValid) {
            resetOriginalRequired(f);
            
            if (!bFlag) {
               return el.throwError();
            }
            else if (!bConstraintOk) {
               return el.throwError(true);
            }
         }

         //  reset the element's backgroundColor in case still yellow
         if (/rgb\(255,\s?255,\s?0\)/.test(el.formElement.style.backgroundColor)) {
            el.formElement.style.backgroundColor = "";
         }
      }
      if (!bAllowMultipleSubmission) window.bSubmitted = true;
      return true;
   }
}

function El(el) {
   //  make an "El" object to expedite attribute retrieval
   this.form = el.form;
   this.formElement = el;
   this.type = this.type?this.type:null;
   this.name = el.name?el.name:null;
   this.value = el.value;
   this.title = el.title?el.title:null;
   this.size = el.size?el.size:null;

   //  store the rules for this element
   var ruleNode = getRuleNode(el);
   if (ruleNode) {
      ruleText                = ruleNode.nodeValue;
      this.dataType           = getRule(ruleNode, "dataType").length
                              ? getRule(ruleNode, "dataType").replace(/\s/g, "").split(",")
                              : [el.type.split("-")[0].toString().toLowerCase()];
      this.required           = getRule(ruleNode, "required");
      this.condition          = getRule(ruleNode, "condition");
      this.constraint         = getRule(ruleNode, "constraint");
      this.setlen             = getRule(ruleNode, "setlen");
      this.min                = getRule(ruleNode, "min");
      this.max                = getRule(ruleNode, "max");
      this.minval             = getRule(ruleNode, "minval");
      this.maxval             = getRule(ruleNode, "maxval");
      this.regex              = getRule(ruleNode, "regex");
      this.regexIgnoreCase    = /^true$/i.test(getRule(ruleNode, "regexIgnoreCase"));
      this.errorMsg           = getRule(ruleNode, "errorMsg").length
                              ? getRule(ruleNode, "errorMsg").replace(/[\n\r]\s+/g, " ").replace(/\\n/g, "\n")
                              : "Please enter a valid value.";
      this.constraintErrorMsg = getRule(ruleNode, "constraintErrorMsg").length
                              ? getRule(ruleNode, "constraintErrorMsg").replace(/\\n/g, "\n")
                              : this.errorMsg;
      this.lowerElement       = getRule(ruleNode, "lowerElement").length
                              ? el.form[getRule(ruleNode, "lowerElement")]
                              : null;
      this.upperElement       = getRule(ruleNode, "upperElement").length
                              ? el.form[getRule(ruleNode, "upperElement")]
                              : null;
      this.equalOk            = /^true$/i.test(getRule(ruleNode, "equalOk"));
      this.firstOk            = /^true$/i.test(getRule(ruleNode, "firstOk"));
      this.lenient            = /^true$/i.test(getRule(ruleNode, "lenient"));
   }
   return this;
}

function getRuleNode(el) {
   //  the rules node should come next...
   var nextNode = nextCommentNode(el);
   if (nextNode
      && nextNode.nodeType == COMMENT_NODE
      && getRule(nextNode, "for") == el.getAttribute("name")) {
      return nextNode;
   }
   //  ...else it should be previous...
   var previousNode = previousCommentNode(el);
   if (previousNode
      && previousNode.nodeType == COMMENT_NODE
      && getRule(previousNode, "for") == el.getAttribute("name")) {
      return previousNode;
   }
   // ...or else we can't find it
   return null;
}
function previousCommentNode(el) {
   if (!el.previousSibling) {
      return null;
   }
   else {
      if (el.previousSibling.nodeType == COMMENT_NODE) {
         return el.previousSibling;
      }
      else return previousCommentNode(el.previousSibling);
   }
}

function nextCommentNode(el) {
   if (!el.nextSibling) {
      return null;
   }
   else {
      if (el.nextSibling.nodeType == COMMENT_NODE) {
         return el.nextSibling;
      }
      else return nextCommentNode(el.nextSibling);
   }
}

function getRule(node, sRule) {
   //  extracts sRule from node.nodeValue
   var sText = node.nodeValue;
   
   if (sText.indexOf(sRule) == -1) return "";
      
   //  escape quotes and backslashes so the regex works
   sText = sText.replace(/\\\"/g, escape("\"")).replace(/\\\\/g, escape("\\"));
   /* fix text editor color coding "*/
   
   var re = new RegExp("\\b" + sRule + "\\s*=\\s*\"([^\"]*)\"", "i");
   var arr = re.exec(sText);
   
   if (arr == null || arr.length < 1) {
      return "";
   }
   else {
      //  for "regex" rule, remove regex delimiters if present
      if (sRule.toLowerCase() == "regex") {
         arr[1] = arr[1].replace(/^\/|\/$/g, "");
      }
      //  unescape the quotes and backslashes
      return unescape( arr[1] );
   }
}

function setRule(node, nKey, val) {
   var sText = node.nodeValue;
   var keyStart, valStart, valEnd, result, sKeyAndVal;
   
   var re = new RegExp("\\b" + nKey + "(\\s)*=(\\s)*", "i");
   
   if ((result = re.exec(sText)) != null) {
      //  found the nKey, need to extract whole nKey/val pair
      keyStart = result.index;
      valStart = sText.indexOf('"', keyStart) + 1;
      
      if (/regex|errormsg/i.test(nKey)) {
         //  these attributes allow quotes and backslashes
         //  end == position of ", provided the prev char is not \, unless prev char to \ is also \
         for (var x = valStart; x < sText.length; x++) {
            if ((sText.charAt(x) == '"'
               && sText.charAt(x-1) == "\\"
               && sText.charAt(x-2) == "\\")
               ||
               (sText.charAt(x) == '"'
               && sText.charAt(x-1) != "\\")) {
               valEnd = x + 1;
               break;
            }
         }
      }
      else {
         valEnd = sText.indexOf('"', valStart) + 1;
      }
      
      //  need to escape backslashes in sKeyAndVal so replace() finds them 
      sKeyAndVal = sText.substring(keyStart, valEnd).replace(/\\/g, "\\\\");
      re = new RegExp("\\b" + sKeyAndVal, "i");
      
      sText = sText.replace(re, nKey + "=\"" + val + "\"");
      
      //  create a new comment node containing sText, replace the old one with it
      var newNode = document.createComment(sText);
      node.parentNode.replaceChild(newNode, node);
   }
}

/************  class methods  ************/

/**
 * calls highlight(), alerts the errorMsg, returns false
 */
El.prototype.throwError = function _throwError(bConstraintError) {
   this.highlight();
   //  flag form as not submitted
   window.bSubmitted = false;
   try {
      this.formElement.focus();
      this.formElement.select();
   }
   catch(e) {}
   //  show error message
   if (bConstraintError) alert(this.constraintErrorMsg);
   else if (this.errorMsg) alert(this.errorMsg);

   return false;
}

/**
 * returns whether or not a date range's lower & upper values can be equal
 */
El.prototype.getEqualOk = function _getEqualOk() {
   return this.equalOk?this.equalOk == true:
      this.upperElement && this.upperElement.equalOk?
      this.upperElement.equalOk == true:
      this.lowerElement && this.lowerElement.equalOk?
      this.lowerElement.equalOk == true:
      false;
}

function jGetElementsByName(form, name) {
   var els = form.getElementsByTagName("*");
   var matches = [];
   for (var x = 0; x < els.length; x++) {
      var re = new RegExp('\\b' + name + '\\b');
      if (re.test(els[x].name)) {
         matches.push(els[x]);
      }
   }
   return matches;
}

/**
 * highlights the invalid form element
 */
El.prototype.highlight = function _highlight() {
   var els = [];
   if (window.rangeError) {
      els[0] = this.formElement;
      els[1] = this.lowerElement?this.lowerElement:this.upperElement;
      window.rangeError = false;
      this.errorMsg = "The Lower date must be less than" +
         (this.getEqualOk()?" or equal to":"") +
         " the Upper Date.";
   }
   else els = jGetElementsByName(this.form, this.name);

   for (var x = 0; x < els.length; x++) {
      //  apply user-specified class
      if (!/jvalx-invalid-field/i.test(els[x].className)) {
         els[x].className += " jvalx-invalid-field";
      }
      
      //  highlight the field's label if present
      var id = els[x].id;
      
      if (id != null && id.length > 0) {
         var labels = this.form.getElementsByTagName("label");
         labelLoop:
         for (var y = 0; y < labels.length; y++) {
            if (labels[y].attributes.getNamedItem("for") == null)
               break labelLoop;

            var labelFor = labels[y].attributes.getNamedItem("for").value;
            
            if (labelFor != null && labelFor == id) {
               labels[y].style.borderWidth = "1px";
               if (!/jvalx-invalid-label/i.test(labels[y].className)) {
                  labels[y].className += " jvalx-invalid-label";
               }
               break labelLoop;
            }
         }// end for each label
      }// end if has an id
   }// end for each el
   return true;
}
/**
 * checks if an element is part of an array of like-named elements
 * if so, if any in group have value, all become required
 */
El.prototype.checkGroupRequired = function _checkGroupRequired() {
   var els = jGetElementsByName(this.form, this.name);

   if (els.length == 1) return true;
   else {
      var bValue = false;
      for (var sgr = 0; sgr < els.length; sgr++) {
         if (this.isRequired() || els[sgr].value) bValue = true;
      }
      if (!this.isRequired()) {
         for (var sgr = 0; sgr < els.length; sgr++) {
            var ruleNode = getRuleNode(els[sgr]);
            setRule(ruleNode, "required", bValue);
         }
         var originalRequired = this.required;
         this.required = bValue;
         
         //  need to reset original "required" value after validation
         addResetOriginalRequired(this.name, originalRequired);
      }
   }
}

El.prototype.isRequired = function() {
   if (this.required.toString() == "true") return true;
   else if (this.required == "conditional") {
      return eval(this.condition);
   }
   else return false;
}

El.prototype.testConstraint = function() {
   if (this.constraint && this.constraint.length) {
      //  fix for if "this" is presented as a function arg
      return eval(this.constraint.replace(/this([\s\,\)])/g, "this.formElement$1"));
   }
   else return true;
}

/************  end class methods  ************/

/************  begin dataType validator functions ************/
window.rangeError = false;

function _text(el) {
   el.checkGroupRequired();

   if (el.isRequired() && !el.value) return false;
   else if (el.value) {
      if (el.dataType[self.DTX] == "alpha") {
         if (!isAlpha(el.value)) {
            return false;
         }
      }
      if (!testSetLen(el)) {
         el.errorMsg += "\nMust be " + el.setlen + " characters.";
         return false;
      }
      switch(testMinMax(el)) {
         case "min": el.errorMsg += "\nMinimum " + el.min + " characters.";
               return false;
         case "max": el.errorMsg += "\nMaximum " + el.max + " characters."
               return false;
      }
      if (!testRegex(el)) {
         return false;
      }
   }
   return true;
}

function _numeric(el) {
   if (el.isRequired() && el.value == "") return false;
   else if (el.value != "") {
      if (el.dataType[self.DTX] == "decimal") {
         //  lenient == allow decimal
         if (el.lenient) {
            if (isNaN(el.value)) return false;
         }
         else {
            if (!/^\d+\.\d+$/.test(el.value)) return false;
         }
      }
      
      if (el.dataType[self.DTX] == "currency") {
         //  lenient == allow currency
         if (el.lenient) {
            if (isNaN(el.value)) return false;
         }
         else {
            if (!/^\d+(\.\d+)?$/.test(el.value)) return false;
         }
      }
      
      if (el.dataType[self.DTX] == "integer")
         if (!isInteger(el.value)) return false;
         
      if (!testSetLen(el)) {
         el.errorMsg += "\nMust be " + el.setlen + " characters.";
         return false;
      }
       
      switch(testMinMax(el)) {
         case "min": el.errorMsg += "\nMinimum " + el.min + " characters.";
               return false;
         case "max": el.errorMsg += "\nMaximum " + el.max + " characters."
               return false;
      }
      
      if (!testMinMaxVal(el)) {
         el.errorMsg += ((el.minval)?"\nMinimum value " + el.minval + ".":"") +
            ((el.maxval)?"\nMaximum value " + el.maxval + ".":"");
         return false;
      }
      
      if (!testRegex(el)) return false;
   }
   return true;
}

function _email(el) {
   if (el.isRequired() && !el.value) return false;
   else if (el.value) {
      return isEmail(el.value);
   }
   return true;
}

function _select(el) {
   if (el.isRequired()) {
      return isSelected(el.formElement, el.firstOk);
   }
   else return true;
}

function _date(el) {
   if ((el.isRequired() || (el.upperElement && el.upperElement.value)) && !el.value) {
      return false;
   }
   else if (el.value) {
      if (!fixDate(el.formElement)) {
         return false;
      }
      else {
         el.value = fixDate(el.formElement);
         el.formElement.value = fixDate(el.formElement);
      }

      if (el.upperElement) el.upperElement.required = "true";
      //  if dateRange, validate range
      if (el.lowerElement) {
         if (!fixDate(el.lowerElement)) {
            return false;
         }
         else el.lowerElement.value = fixDate(el.lowerElement);

         if (el.getEqualOk()) {
            if (new Date(el.value).getTime() < new Date(el.lowerElement.value).getTime()) {
               window.rangeError = true;
               return false;
            }
         } else {
            if (new Date(el.value).getTime() <= new Date(el.lowerElement.value).getTime()) {
               window.rangeError = true;
               return false;
            }
         }
      }
   }
   return true;
}

function _zipcode(el) {
   if (el.isRequired() && !el.value) return false;
   else if (el.value) {
      if (!isZipcode(el.value)) {
         return false;
      }
      if (!testSetLen(el)) {
         el.errorMsg += "\nMust be " + el.setlen + " characters.";
         return false;
      }
      switch(testMinMax(el)) {
         case "min": el.errorMsg += "\nMinimum " + el.min + " characters.";
               return false;
         case "max": el.errorMsg += "\nMaximum " + el.max + " characters."
               return false;
      }
      if (!testRegex(el)) {
         return false;
      }
   }
   return true;
}

function _phone(el) {
   if (el.isRequired() && !el.value) return false;
   else if (el.value) {
      if (!isPhone(el.value)) {
         return false;
      }
      if (!testSetLen(el)) {
         el.errorMsg += "\nMust be " + el.setlen + " characters.";
         return false;
      }
      switch(testMinMax(el)) {
         case "min": el.errorMsg += "\nMinimum " + el.min + " characters.";
               return false;
         case "max": el.errorMsg += "\nMaximum " + el.max + " characters."
               return false;
      }
      if (!testRegex(el)) {
         return false;
      }
   }
   return true;
}

function _ccard(el) {
   if (el.isRequired() && !el.value) return false;
   else if (el.value) {
      return isCCard(getCCType(), el.value);
   }
   else return true;
}

function _checkbox(el) {
   //  same behavior as radio until "requireAll" attr done
   return _radio(el);
   /*
   if (el.isRequired()) {
      return el.formElement.checked;
   }
   else return true;
   */
}

function _radio(el) {
   if (el.isRequired()) {
      var els = jGetElementsByName(el.form, el.name);
      var bChecked = false;
      for (var x = 0; x < els.length; x++)
         if (els[x].checked) bChecked = true;
      return bChecked;
   }
   else return true;
}

/************  end dataType validator functions ************/

/************  helper functions  ************/

/**
 * overrides jValidate() to allow form to be submitted without validation
 */
function jvalOverride(form) {
   form.jvalOverridden = true;
}

/**
 * resets form to "unsubmitted" if calling jValidate() before custom validation
 */
function jvalReset(form) {
   form.bSubmitted = false;
}

function isAlpha(s) {
   return /^[a-z\s-_]+$/i.test(s);
}

function testRegex(el) {
   if (el.regex != null)
      if (el.regex.length > 0) {
         sI = el.regexIgnoreCase ? "i" : "";
         re = new RegExp(el.regex, sI);
         jdebug("regex: " + el.regex);
         jdebug("sI: " + sI);
         jdebug("test: " + /^C/.test(el.value));
         jdebug("test2: " + re.test(el.value));
         jdebug("source: " + re.source);
         return re.test(el.value);
      }
   return true;
}

function testSetLen(el) {
   if (el.dataType[self.DTX] == "text"
         || el.dataType[self.DTX] == "alpha"
         || el.dataType[self.DTX] == "textarea"
         || el.dataType[self.DTX] == "integer"
         || el.dataType[self.DTX] == "numeric") {
      if (el.setlen && el.value.length != el.setlen) return false;
   }
   return true;
}

function testMinMax(el) {
// Commented out to allow all types to be tested on length.
//   if (el.dataType[self.DTX] == "text"
//         || el.dataType[self.DTX] == "alpha"
//         || el.dataType[self.DTX] == "textarea"
//         || el.dataType[self.DTX] == "hidden"
//         || el.dataType[self.DTX] == "hidden"
//         || el.dataType[self.DTX] == "password") {
      if (el.min && el.value.length < el.min) return "min";
      if (el.max && el.value.length > el.max) return "max";
//   }
//   else {
//      if (el.min && parseFloat(el.value.replace(/\,/g, "")) < el.min) return false;
//      if (el.max && parseFloat(el.value.replace(/\,/g, "")) > el.max) return false;
//   }
   return "";
}

function testMinMaxVal(el) {
   var nValue = (el.dataType[self.DTX] == "currency") ? currencyToFloat(el.value) : el.value;
   
   if (el.dataType[self.DTX] == "integer" || el.dataType[self.DTX] == "decimal" || el.dataType[self.DTX] == "currency") {
      if (el.minval && nValue < el.minval) return false;
      if (el.maxval && nValue > el.maxval) return false;
   }
   return true;
}

function addResetOriginalRequired(name, val) {
   if (!self.jvalOriginalRequired)
      self.jvalOriginalRequired = [];
   self.jvalOriginalRequired.push([name,val]);
}

function resetOriginalRequired(form) {
   if (self.jvalOriginalRequired) {
      var OR = jvalOriginalRequired;
      for (var x = 0; x < OR.length; x++) {
         var obj = OR.pop();
         var name = obj[0];
         var val = obj[1];
         var els = jGetElementsByName(form, name);
         
         for (var y = 0; y < els.length; y++) {
            var ruleNode = getRuleNode(els[x]);
            setRule(ruleNode, "required", val);
         }         
      }
   }
}

/**********************************************************
*   general function library
***********************************************************/

function isDate(testDate) {
   if ( !(/^[\d\/\.\-]+$/.test(testDate)) ) {
      return false;
   }

   var testDate = new Date(testDate);
   if (isNaN(testDate.getTime())) {
      return false;
   }
   else {
      return true;
   }
}

function isZipcode(s) {
   var re = /^\d{5}(-\d{4})?$/;
   return re.test(s);
}

function isPhone(s) {
   var re = /^((((\d\s)|\d)?[\(\-\.\s]\s?)?\d{3}\s?[\)\-\.\s]?\s?)?\d{3}\s?[\.\-\s]?\s?\d{4}$/;
   return re.test(s);
}

function isCCard(type, ccnum) {
   if (type == "Visa")
      var re = /^4\d{3}-?\d{4}-?\d{4}-?\d{4}$/;        // Visa: length 16, prefix 4, dashes optional.
   else if (type == "MasterCard")
      var re = /^5[1-5]\d{2}-?\d{4}-?\d{4}-?\d{4}$/;   // Mastercard: length 16, prefix 51-55, dashes optional.
   else if (type == "Discover")
      var re = /^6011-?\d{4}-?\d{4}-?\d{4}$/;          // Discover: length 16, prefix 6011, dashes optional.
   else if (type == "American Express")
      var re = /^3[4,7]\d{13}$/;                       // American Express: length 15, prefix 34 or 37.
   else if (type == "Diners")
      var re = /^3[0,6,8]\d{12}$/;                     // Diners: length 14, prefix 30, 36, or 38.

   if (!re.test(ccnum))
      return false;

   // Remove all dashes for the checksum checks to eliminate negative numbers
   ccnum = ccnum.split("-").join("");

   // Checksum ("Mod 10")
   // Add even digits in even length strings or odd digits in odd length strings.
   var checksum = 0;

   for ( var i = (2-(ccnum.length % 2)) ; i <= ccnum.length ; i += 2 )
      checksum += parseInt(ccnum.charAt(i-1));

   // Analyze odd digits in even length strings or even digits in odd length strings.
   for ( var i = (ccnum.length % 2) + 1 ; i<ccnum.length ; i += 2 ) {
      var digit = parseInt(ccnum.charAt(i-1)) * 2;
      if (digit < 10)
         checksum += digit;
      else
         checksum += (digit-9);
   }

   if ((checksum % 10) == 0)
      return true;
   else
      return false;
}

function isEmail(sTest) {
   /*
   *   accepts emails like:
   *      foo@bar.com
   *      abc_123.zxy-987.etc@do_re.me-fa.so.la.ti.do
   *
   *   allows alphanumeric, underscore and hyphen
   *   ...except must be 2+ alpha only after last dot.
   *   returns boolean
   */
   var pattern = /^[\w\-]+(\.[\w\-]+)*@[\w\-]+\.([\w\-]+\.)*[a-z]{2,}$/i;
   return pattern.test(sTest);
}

function isAlphaNumeric(sTest) {
   /*
   *  tests to make sure a string is alpha-numeric only; returns boolean
   *  pattern1 ensures string contains at least one alpha character, case-insensitive
   *  pattern2 allows a-zA-Z0-9 and hypen and underscore and whitespace
   */
   var bFlag = false;

   var pattern1 = /[a-zA-Z]+/;
   if (pattern1.test(sTest)) {
      var pattern2 = /^[\w\-\s]+\s*$/;
      bFlag = pattern2.test(sTest);
   }
   return bFlag;
}

function isInteger(s) {
   // tests to make sure test string is an integer; returns boolean
   return /^-?\d{1,3}(,?\d{3})*(\.00)?$/.test(s);
}

function fixDate(oEl, bEmptyOK) {
   /*
   *   1.  format date from mmddyy or mmddyyyy to mm/dd/yyyy
   *   2.  check to see if formatted date is a valid date
   *   3.  return formatted date string
   *   *** requires isDate() ***
   */

   //  bEmptyOK allows null values in date fields
   if (bEmptyOK && !oEl.value) return true;

   var sVal = oEl.value.replace(/[\\\-\.\s\,\:\;\*\+]/gi,"/");
   var sDate = "";
   var bFlag = true;

   //  format date from mmddyy or mmddyyyy to mm/dd/yyyy

   //  format numbers < 10 to "0" + digit
   if (sVal.indexOf("/") != sVal.lastIndexOf("/")) {
      arTemp = sVal.split("/");
      for (x = 0; x < 3; x++)
         arTemp[x] = (arTemp[x] < 10)?"0" + parseInt(arTemp[x], 10):arTemp[x];
      sVal = arTemp.join("/");
   }

   //  fail if mm & dd not two digits, yy not 2 or 4 digits
   if (sVal.length != 6 && sVal.length != 8 && sVal.length != 10 ||
      (sVal.indexOf("/") == 1 || sVal.indexOf("/") == 3)) {
      bFlag = false;
   }

   else if (sVal.length < 10) {
      //  sVal is mmddyy, mmddyyyy or mm/dd/yy format
      if (sVal.indexOf("/") < 0) {
         //  mmddyy or mmddyyyy
         sDate = sVal.substring(0,2) + "/" + sVal.substring(2,4) + "/" + sVal.substring(4);
      }
      else {
         //  mm/dd/yy format:  ok already
         sDate = sVal;
      }
   }
   else {
      //  sVal is mm/dd/yyyy format already
      sDate = sVal;
   }

   sY = sDate.substring(6);

   if (sY.length == 2) {
      sY = (parseInt(sY, 10) > parseInt(new Date().getFullYear().toString().substring(2), 10) + 1)?"19" + sY:"20" + sY;
   }

   //  rebuild sDate with fixed 4-digit year
   sDate = sDate.substring(0,6) + sY;

   //  check to see if final formatted date is valid
   if (!isDate(sDate)) {
      bFlag = false;
   }

   if (!bFlag) return false;
   else {
      //  turn sDate into Date object, extract MM DD & YYYY
      dDate = new Date(sDate);
      iM = dDate.getMonth() + 1;
      iD = dDate.getDate();
      iY = dDate.getFullYear();

      //  return MM/DD/YYYY formatted string
      return((iM > 9)?iM:"0" + iM) + "/" + ((iD >9)?iD:"0" + iD) + "/" + iY;
   }
}

function isSelected(oSel, bFirstOk) {
   //  returns false if no option selected
   var x = bFirstOk?-1:0;
   return(oSel.selectedIndex > x);
}

function getDebugWin() {
   if (!window["debugWin"]) {
      var debugWin = window.open("");

      var ta = document.createElement("textarea");
      ta.setAttribute("id", "jvalDebugger");
      ta.style.width = "100%";
      ta.style.height = "35em";

      debugWin.document.body.appendChild(ta);
      
      window["debugWin"] = debugWin;
   }
   
   if (!document.getElementById("jvalDebugger")) {
      var div = document.createElement("div");
      div.setAttribute("style", "clear:both;");
      var ta = document.createElement("textarea");
      ta.setAttribute("id", "jvalDebugger");
      ta.style.width = "100%";
      ta.style.height = "35em";
      div.appendChild(ta);
      document.body.appendChild(div);
   }
}
function jdebug(s) {
   if (self.jval_debugger_on == true) {
      getDebugWin();
      document.getElementById("jvalDebugger").value += s + "\n";
      
      window["debugWin"].document.getElementById("jvalDebugger").value += s + "\n";
   }
}