
/**********************************************************
* jvalx generic embedded form validation library
* created:  20030314
*  author:   jeff emminger
* version:  2.2.20061024
*
* LICENSE:

This program is free software; you can redistribute it and/or modify it
 under the terms of the GNU General Public License as published by the
 Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 This program is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.

 You should have received a copy of the GNU General Public License along
 with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

* 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
*    date    : U.S. format date: MM/DD/YYYY
*    [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
  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
  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=""
  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=""
  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=""
-->

***********************************************************/

/*** 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 "email":
						bFlag = _email(el);
						break;
					case "phone":
						bFlag = _phone(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.min = getRule(ruleNode, "min");
		this.max = getRule(ruleNode, "max");
		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, key, val) {
	var sText = node.nodeValue;
	var keyStart, valStart, valEnd, result, sKeyAndVal;
	
	var re = new RegExp("\\b" + key + "(\\s)*=(\\s)*", "i");
	
	if ((result = re.exec(sText)) != null) {
		//  found the key, need to extract whole key/val pair
		keyStart = result.index;
		valStart = sText.indexOf('"', keyStart) + 1;
		
		if (/regex|errormsg/i.test(key)) {
			//  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, key + "=\"" + 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++) {
				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 (!testMinMax(el)) {
			el.errorMsg += ((el.min)?"\nMinimum " + el.min + " characters.":"") +
				((el.max)?"\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 integers
			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 (!testMinMax(el)) {
			el.errorMsg += ((el.min)?"\nMinimum value " + el.min + ".":"") +
				((el.max)?"\nMaximum value " + el.max + ".":"");
			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 _phone(el) {
	if (el.isRequired() || el.value) {
		return isPhone(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 testMinMax(el) {
	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] == "password") {
		if (el.min && el.value.length < el.min) return false;
		if (el.max && el.value.length > el.max) return false;
	}
	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 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 noEnter() {
	//  disables the enter key in form fields
	if (event.keyCode == 13) return false;
	else return true;
}

function isDate(testDate) {
	if ( !(/^[\d\/\.\-]+$/.test(testDate)) ) {
		return false;
	}

	var testDate = new Date(testDate);
	if (isNaN(testDate.getTime())) {
		return false;
	}
	else {
		return true;
	}
}

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 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";
	}
}
