
/**********************************************************
* 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.

/******
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.debuggerOn = false;

/*** MAIN VALIDATION FUNCTION ***/
function jValidate(f, bAllowMultipleSubmission) {
	//  ignore NS4.x
	if (document.layers) {
		return true;
	}
	else {
		//  loop through all form elements
		if (window.bSubmitted) return false;
		if (self.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();
				
				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"));
	}
	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 "";
	
	if (!/regex|errormsg/i.test(sRule)) {
		var start = (sText.indexOf(sRule + "=") + sRule.length + 2);
		var end = sText.indexOf('"', start);
		return sText.substring(start, end);
	}
	else {
		var start = (sText.indexOf(sRule + "=") + sRule.length + 2);
		//  end == position of ", provided the prev char is not \, unless prev char to \ is also \
		var value = [];
		for (var x = start; x < sText.length; x++) {
			if ((sText.charAt(x) == '"'
				&& sText.charAt(x-1) == "\\"
				&& sText.charAt(x-2) == "\\")
				||
				(sText.charAt(x) == '"'
				&& sText.charAt(x-1) != "\\")) {
				if (/errormsg/i.test(sRule)) {
					return value.join("").replace(/\\\"/g,"\"").replace(/\\\\/g,"\\");
				}
				else
					return value.join("").replace(/^\/|\/$/g, "");
				break;
			}
			else value.push(sText.charAt(x));
		}
	}
}

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  ************/

El.prototype.throwError = function _throwError(bConstraintError) {
	//  calls highlight(), alerts the errorMsg, returns false
	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;
}

El.prototype.getEqualOk = function _getEqualOk() {
	//  returns whether or not a date range's lower & upper values can be equal
	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;
}

El.prototype.highlight = function _highlight() {
	//  highlights the form element
	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 = document.getElementsByName(this.name);

	for (var x = 0; x < els.length; x++) {
		els[x].style.backgroundColor = "rgb(255,255,0)";
	}
	return true;
}

El.prototype.checkGroupRequired = function _checkGroupRequired() {
	//  checks if an element is part of an array of like-named elements
	//  if so, if any in group have value, all become required
	var els = document.getElementsByName(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) {
		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")
			if (isNaN(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) {
	//if (el.isRequired()) {
	//	return el.formElement.checked;
	//}
	//else return true;
	
	if (el.isRequired()) {
		var els = document.getElementsByName(el.name);
		var bChecked = false;
		for (var x = 0; x < els.length; x++)
			if (els[x].checked) bChecked = true;
		return bChecked;
	}
	else return true;
}

function _radio(el) {
	if (el.isRequired()) {
		var els = document.getElementsByName(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  ************/

function jvalOverride() {
	//  overrides jValidate() to allow form to be submitted without validation
	self.jvalOverridden = true;
}

function jvalReset() {
	window.bSubmitted = false;
	//  resets form to "unsubmitted" if calling jValidate() before custom validation
}

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);
			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") {
		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() {
	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 = document.getElementsByName(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 (!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 debug(s) {
	if (self.debuggerOn) {
		getDebugWin();
		document.getElementById("jvalDebugger").value += s + "\n";
	}
}
