

/**********************************************************
*	jval generic embedded form validation library
*	created:	20030314
*	author:		jeff emminger
*
*	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
*		dateRange	:
*		[null]		: the input's default "type"

******
CHANGES:
	20031112:  fixed bug in isDate() with Mozilla - multiple calls
							to regex.test() fails when global switch present
******


	form element custom attribute definitions:
	===================================
	required	: (bool) whether or not the input must be given a value
	dataType	: any of the above acceptable dataTypes
	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 "/"
	errorMsg	: the error message to be displayed (if any) if the input's value fails validation.


	prototypes for form elements with custom attributes:
	=============================

alpha:
<input type="text"
	name=""
	value=""
	title=""
	size=""
	required=""
	min=""
	max=""
	maxlength=""
	dataType="alpha"
	regex=""
	errorMsg=""/>

date:
<input type="text"
	name=""
	value=""
	title=""
	size="10"
	required=""
	min=""
	max=""
	maxlength=""
	dataType="date"
	lowerElement|upperElement=""
	equalOk=""
	regex=""
	errorMsg=""/>

decimal:
<input type="text"
	name=""
	value=""
	title=""
	size=""
	required=""
	min=""
	max=""
	maxlength=""
	dataType="decimal"
	regex=""
	errorMsg=""/>

email:
<input type="text"
	name=""
	value=""
	title=""
	size=""
	required=""
	min=""
	max=""
	maxlength=""
	dataType="email"
	regex=""
	errorMsg=""/>

integer:
<input type="text"
	name=""
	value=""
	title=""
	size=""
	required=""
	min=""
	max=""
	maxlength=""
	dataType="integer"
	regex=""
	errorMsg=""/>

phone:
<input type="text"
	name=""
	value=""
	title=""
	size=""
	required=""
	min=""
	max=""
	maxlength=""
	dataType="phone"
	regex=""
	errorMsg=""/>

select:
<select name=""
	size="1"
	ondblclick=""
	onchange=""
	required=""
	firstOk=""
	errorMsg=""/>
	<option value=""></option>
</select>


***********************************************************/

function jValidate(f, bAllowMultipleSubmission) {
	//  loop through all form elements
	if (window.bSubmitted) return false;
	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]);

		//  determine the element's type
		switch(el.dataType) {
			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;
		}
		if (!bFlag) {
			return el.throwError();
		}
		//  reset the element's backgroundColor in case still yellow
		if (el.formElement.style.backgroundColor == "#ffff00") {
			el.formElement.style.backgroundColor = "";
		}
	}
	if (!bAllowMultipleSubmission) window.bSubmitted = true;
	return true;
}

function El(el) {
	//  make an "El" object to expedite attribute retrieval
	this.formElement = el;
	this.type = this.type?this.type:null;
	dataType = el.getAttribute("dataType");
		this.dataType = (dataType && dataType.length > 0)?
			dataType.toLowerCase():
			el.type.split("-")[0].toString().toLowerCase();
	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;
	this.required = el.getAttribute("required")?
		el.getAttribute("required").toLowerCase() == "true":false;
	this.min = el.getAttribute("min")?el.getAttribute("min"):null;
	this.max = el.getAttribute("max")?el.getAttribute("max"):null;
	this.maxlength = el.maxlength?el.maxlength:null;
	this.regex = el.getAttribute("regex")?el.getAttribute("regex"):null;
	this.errorMsg = el.getAttribute("errorMsg")?
		el.getAttribute("errorMsg"):"Please enter a valid value.";
	this.lowerElement = el.getAttribute("lowerElement")?
		el.form[el.getAttribute("lowerElement")]:null;
	this.upperElement = el.getAttribute("upperElement")?
		el.form[el.getAttribute("upperElement")]:null;
	this.equalOk = el.getAttribute("equalOk")?
		el.getAttribute("equalOk").toLowerCase() == "true":false;
	this.firstOk = el.getAttribute("firstOk")?
		el.getAttribute("firstOk").toLowerCase() == "true":true;
	return this;
}

/************  class methods  ************/

El.prototype.throwError = function _throwError() {
	//  calls highlight(), alerts the errorMsg, returns false
	this.highlight();
	if (this.errorMsg) alert(this.errorMsg);
	window.bSubmitted = false;
	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 = "#ff0000";
		els[x].onfocus = function(){
			for (var x = 0; x < els.length; x++)
				els[x].style.backgroundColor = "";
		}
	}
	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 (els[sgr].value) bValue = true;
		}
		for (var sgr = 0; sgr < els.length; sgr++) {
			els[sgr].setAttribute("required", bValue.toString());
		}
		this.required = bValue;
	}
}

/************  end class methods  ************/

/************  begin dataType validator functions ************/
window.rangeError = false;

function _text(el) {
	el.checkGroupRequired();

	if (el.required && !el.value) return false;
	else if (el.value) {
		if (el.dataType == "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.required && !el.value) return false;
	else if (el.value) {
		if (el.dataType == "decimal")
			if (isNaN(el.value)) return false;
		if (el.dataType == "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.required && !el.value) return false;
	else if (el.value) {
		return isEmail(el.value);
	}
	return true;
}

function _select(el) {
	if (el.required) return isSelected(el.formElement, el.firstOk);
	else return true;
}

function _date(el) {
	if ((el.required || (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.required || el.value) {return isPhone(el.value);}
	else return true;
}

function _checkbox(el) {
/*	if (el.required) return el.formElement.checked;
	else return true;*/
    if (el.required) {
		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.required) {
		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 jvalReset() {
	//  resets form to "unsubmitted" if calling jValidate() before custom validation
	window.bSubmitted = false;
}

function isAlpha(s) {
	return /^[a-z\s-_]+$/.test(s);
}

function testRegex(el) {
	if (el.regex != null)
		if (el.regex.length > 0) {
			re = new RegExp(el.regex, "gi");
			return re.test(el.value);
		}
	return true;
}

function testMinMax(el) {
	if (el.dataType == "text" ||
		el.dataType == "alpha" ||
		el.dataType == "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, 10) < el.min) return false;
		if (el.max && parseFloat(el.value, 10) > el.max) return false;
	}
	return true;
}

/**********************************************************
*	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 pat = /^((((\d\s)|\d)?[\(\-\.\s]\s?)?\d{3}\s?[\)\-\.\s]?\s?)?\d{3}\s?[\.\-\s]?\s?\d{4}$/;
	return pat.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+$/.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;
	if (oSel.selectedIndex == x) return false;
	else return true;
}

function dateDiff(oDateLower,oDateUpper) {
	//  returns difference in days between oDateLower & oDateUpper
	return Math.floor((oDateUpper.getTime() - oDateLower.getTime()) / 1000 / 60 / 60 / 24);
}

function showRangePicker(o1, o2, fAction, sPath) {
	window.fieldFrom = o1;
	window.fieldTo = o2;
	window.action = fAction || null;
	//  default path to root
	sPath = sPath || "/";
	dpWin = window.open(sPath + "dateRangePicker.html","dpWin","width=300,height=250");
}

function showDatePicker(oEl, onchange, sPath) {
	window.field = oEl;
	window.onchange = onchange || null;
	//  default path to root
	sPath = sPath || "/";
	dpWin = window.open(sPath + "datePicker.html","dpWin","width=300,height=250");
}

function jump(iLen, oFrom, oTo) {
	if (event.keyCode != 46 && event.keyCode != 8)
		if (oFrom.value && oFrom.value.length == iLen) {
			oTo.focus();
			return true;
		}
}

function changeEnter() {
	//  makes the enter key work as tab key on forms
	if (event.keyCode == 13) {
		try {
			this.form.elements[window.tabOrder[(this.index + 1)]].focus();
		} catch(E) {}
		return false;
	}
}

function setChangeEnter(f) {
	//  attaches changeEnter() to form elements, except SUBMIT, BUTTON and TEXTAREA
	if (f) {
		window.tabOrder = [];
		var els = f.elements;
		for (var x = 0, ndx = 0; x < els.length; x++) {
			if (!els[x].type || els[x].type.toLowerCase().contains("submit","button","hidden")) {
				continue;
			}
			else {
				window.tabOrder[window.tabOrder.length] = x;
				els[x].index = ndx++;
				if (els[x].type.toLowerCase() != "textarea")
					els[x].onkeydown = changeEnter;
			}
		}
	}
}

function dateDiff(sDateLower, sDateUpper) {
	//  returns difference in days between sDateLower & sDateUpper
	var dateLower = new Date(sDateLower);
	var dateUpper = new Date(sDateUpper);
	return (dateUpper.getTime() - dateLower.getTime()) / 1000 / 60 / 60 / 24;
}

String.prototype.contains = function () {
	//  call me on a String, pass me String args of values to test the String for
	//  e.g.:   "foo".contains("boo","goo","foo") == true;
	for (x = 0; x < arguments.length; x++)
		if (this == arguments[x]) return true;
	return false;
}

Array.prototype.pull = function (arg) {
	//  pulls an element out of an array, returns the single element.
	//  array.length becomes one less.
	//  may pass the index or value of element to remove
	var oEl, tmp;

	if (typeof arg == "number") {
		oEl = this[arg];
		tmp = this.slice(0,arg).concat(this.slice(arg + 1));
	}
	else if (typeof arg == "string") {
		for (var x = 0; x < this.length; x++) {
			if (this[x] == arg) {
				oEl = this[x];
				tmp = this.slice(0,x).concat(this.slice(x + 1));
				break;
			}
		}
	}
	if (typeof oEl != "undefined") {
		this.length = 0;
		for (var x = 0; x < tmp.length; x++)
			this[x] = tmp[x];
		return oEl;
	}
	else return null;
}

Array.prototype.replace = function (id, val) {
	//  replaces the element in Array at index "id" with value "val"
	var tmp = this.slice(0,id).concat(val, this.slice(id+1));
	this.length = 0;
	for (var x = 0; x < tmp.length; x++) {
		this[x] = tmp[x];
	}
	return this;
}

Array.prototype.insert = function (id, val) {
	//  inserts value "val" into Array at index "id"
	//  Array.length increases by one
	tmp = this.slice(0,id).concat(val,this.slice(id));
	this.length = 0;
	for (var x = 0; x < tmp.length; x++) {
		this[x] = tmp[x];
	}
	return this;
}

function hideMsgBanner() {
	var msg = document.getElementById("sMsg");
	if (msg) window.setTimeout("document.getElementById('sMsg').style.visibility = 'hidden';", 10000);
}

function goTo(sURL) {
	if (sURL) location.href = sURL;
}

/************************************************
*	DYNAMIC WINDOW.ONLOAD
*	allows a dynamic number of scripts to be called onload
*
*	useful if your header does something onload,
*		and you need another file
*		to do something onload also
*
*	e.g.
*	window.addOnload( foo );
*	window.addOnload( function() { foo(arg); } );
*	window.addOnload( function() { bar(a, b, c); } );
*/

window.addOnload = function (fn) {
	if (!window.OnloadCache) window.OnloadCache = [];
	var ol = window.OnloadCache;
	ol[ol.length] = fn;
}

window.onload = function () {
	var ol = window.OnloadCache;
	if (ol)
		for (var x = 0; x < ol.length; x++)
			this.action = ol[x]();
}
/*
*
**************************************************/

/************************************************
*	DYNAMIC WINDOW.ONUNLOAD
*	allows a dynamic number of scripts to be called onunload
*
*	useful if your header does something onunload,
*		and you need another file
*		to do something onunload also
*
*	e.g.
*	window.addOnunload( foo );
*	window.addOnunload( function() { foo(arg); } );
*	window.addOnunload( function() { bar(a, b, c); } );
*/

window.addOnunload = function (fn) {
	if (!window.OnunloadCache) window.OnunloadCache = [];
	var ol = window.OnunloadCache;
	ol[ol.length] = fn;
}

window.onunload = function () {
	var ol = window.OnunloadCache;
	if (ol)
		for (var x = 0; x < ol.length; x++)
			this.action = ol[x]();
}
/*
*
**************************************************/

/************************************************
*	SMART SELECT
*
*	allows searching beyond first keystroke in <select> lists
*	assumes the <option>s are sorted alphabetically
*
*	implement with window.onload = attachSmartSelect;
*/

function SS_selKeyPress(e) {
	var kc = e ? e.which : event.keyCode;

	//  get char from keycode, add to cache
	var key = String.fromCharCode(kc);
	window.keyCache.push(key);

	//  calling SS_findMatch on enter key (kc 13) results in improper index being selected
	if (kc != 13) SS_findMatch(this);

	//  clear old timer, set new timer to expire the cache
	window.clearTimeout(window.keyTimer);
	window.keyTimer = window.setTimeout("SS_resetKeyCache();", 3000);
	return false;
}

function SS_resetKeyCache(e) {
	if (navigator.appName.toLowerCase().indexOf("microsoft") > -1) {
		e = event ? event : null;
		var kc = e ? e.keyCode : null;
	}
	else {
		if (e) {
			kc = e.which;
		}
	}

	if (!e || kc == 0 || kc == 46) {
		//  delete key pressed
		window.keyCache.length = 0;
		window.MX = null;
		return false;
	}
	return false;
}
function SS_findMatch(el) {
	//  select the option most closely matching cache

	//  loop through cache
	var oCache = window.keyCache;
	mx = window.MX?window.MX:0;

	cacheLoop:for (var cx = 0; cx < oCache.length; cx++) {
		//  get substring of cached chars to match
		subCache = oCache.join("").substring(0, cx + 1);
		//  loop through options, starting at last match
		optLoop:for (var ox = mx; ox < el.options.length; ox++) {
			subTxt = el.options[ox].text.substring(0, cx + 1);
			//  quit if previous n - 1 chars don't match
			if (subTxt.substring(0, cx - 1).toLowerCase() !=
				subCache.substring(0, cx - 1).toLowerCase()) {
				//  remove last bad char from cache in case it was a miskey
				oCache.pop();
				break cacheLoop;
			}

			//  see if cached chars == option chars
			if (subCache.toLowerCase() == subTxt.toLowerCase()) {
				//  we have a match!  store the index
				mx = ox;
				break optLoop;
			}
		}
	}
	//  select the best match
	el.selectedIndex = mx;
	window.MX = mx;
}

function attachSmartSelect() {
	//  set up the keyCache
	window.keyCache = [];
	window.MX = 0;

	//  attach to all select lists
	var f = document.forms;
	for (var fx = 0; fx < f.length; fx++) {
		var els = f[fx].elements;
		for (var ex = 0; ex < els.length; ex++) {
			if (els[ex].tagName.toLowerCase() == "select") {
				els[ex].onkeypress = SS_selKeyPress;
				els[ex].onkeyup = SS_resetKeyCache;
				els[ex].onblur = SS_resetKeyCache;
			}
		}
	}
}

/*
*
**************************************************/

function debug(s) {
	document.getElementById("debug").value += s + "\n";
}

function ProgressBar() {
	this.cssFile = "ProgressBar.css";
}

ProgressBar.prototype.start = function() {
	document.write('<style type="text/css">@import url('+this.cssFile+');</style>' +
		'<div id="ProgressBar">' +
		'<input type="text" class="PB_box" name="PB_box" readonly/>' +
		'<input type="text" class="PB_box" name="PB_box" readonly/>' +
		'<input type="text" class="PB_box" name="PB_box" readonly/>' +
		'<input type="text" class="PB_box" name="PB_box" readonly/>' +
		'<input type="text" class="PB_box" name="PB_box" readonly/>' +
		'<input type="text" class="PB_box" name="PB_box" readonly/>' +
		'<input type="text" class="PB_box" name="PB_box" readonly/>' +
		'</div>');
	window.PB_timer = window.setInterval("pbrun();", 150);
}

ProgressBar.prototype.run = function pbrun() {
	pbx = window.pbx?window.pbx:0;
	try{
		var b = document.getElementsByName("PB_box");

		for (y = 0; y < b.length; y++)
			b[y].style.backgroundColor = "threedhighlight";

		b[pbx++ % b.length].style.backgroundColor = "threedface";
		if (pbx > 1) b[(pbx-2) % b.length].style.backgroundColor = "threedface";
	} catch(E){};
}

function formatNumber(num, places) {
	num = Math.round(num * Math.pow(10, places)) / Math.pow(10, places);
	var ar = num.toString().split(".");
	ar[1] = ar[1]?ar[1]:"0";
	while (ar[1].length < places) {
		ar[1] += "0";
	}
	return ar[0] + "." + ar[1];
}

function setScrollbars() {
	//  adds vertical scrollbar to window if necessary
	if (document.body.scrollHeight > document.body.offsetHeight)
		document.body.scroll = "yes";
	else document.body.scroll = "no";
}

/************************************************
*	Key Listener
*	allows attaching a keystroke combination to a
*	function or form button, like "CTRL+S" = Save
*
*	implement with window.onkeydown = listenToKeys;
*	add keylisteners after onload
*/
function KeyListener(oTarget, sKey, sModifier) {
	//  oTarget can be a function or form element
	//  sKey must be one or two characters, two being an F key e.g. "F5"
	//  sModifier must be [ALT|CTRL|SHIFT]
	var arFKeyCodes = [null,112,113,114,115,116,117,118,119,120,121,122,123];

	window.document.onkeydown = listenToKeys;
	this.obj = oTarget;
	this.key = sKey.toUpperCase();
	this.keyCode = (sKey.length == 1)?
		this.key.charCodeAt(0):
		arFKeyCodes[parseInt(this.key.substring(1),10)];
	this.modifier = sModifier || null;
	if (!window.KLCache) window.KLCache = [];
	KLCache.push(this);
}

function listenToKeys() {
	//  onkeydown, search the keyListener cache for the pressed key
	var KL = window.KLCache;
	if (KL) {
		var kc = window.event.keyCode;
		if (kc > 111 && kc < 124) event.keyCode = null;
		var ak = window.event.altKey;
		var ck = window.event.ctrlKey;
		var sk = window.event.shiftKey;
		var bKLFound;

		for (var x = 0; x < KL.length; x++) {
			bKLFound = false;

			if (KL[x].keyCode == kc) {
				if (KL[x].modifier) {
					switch(KL[x].modifier.toUpperCase()) {
						case "ALT":
							if (ak) bKLFound = true;
							break;
						case "CTRL":
							if (ck) bKLFound = true;
							break;
						case "SHIFT":
							if (sk) bKLFound = true;
							break;
					}
				}
				else bKLFound = true;
			}
			if (bKLFound) {
				if (typeof KL[x].obj == "function") { KL[x].obj(); }
				if (typeof KL[x].obj == "object") { KL[x].obj.click(); }
				//return false;
			}
		}
	}
	//  cancel the default action if not a form element
	event.returnValue = event.srcElement.tagName.toLowerCase().contains("input", "select", "textarea");
	event.cancelBubble = true;
	return event.returnValue;
}

/************************************************
*	document.getElementsByClassName(sClassName[ , sTagName])
*
*	allows retreiving elements from DOM by className
*	returns Array
* note: can provide sTagName to speed up retrieval of array...
*		will only search through sTagName type of tags
*/
document.getElementsByClassName = function(sClassName, sTagName) {
	var tag = sTagName || "*";
	var els = document.getElementsByTagName(tag);
	var ar = [];
	for (var x = 0; x < els.length; x++) {
		if (els[x].className && els[x].className == sClassName) {
			ar.push(els[x]);
		}
	}
	return ar;
}

function switchTabs(oTab) {
	self.panels = self.panels?panels:document.getElementsByClassName("panel", "table");
	self.tabs = self.tabs?tabs:document.getElementsByClassName("tab", "td");
	var sPanel = oTab.getAttribute("tabfor");
	//  show the active panel, hide the rest
	for (var x = 0; x < panels.length; x++) {
		panels[x].style.display = "none";
	}
	document.getElementById(sPanel).style.display = "block";

	//  set the active tab
	for (var x = 0; x < tabs.length; x++) {
		tabs[x].id = "";
	}
	oTab.id = "active_tab";
	//  see if window needs scrollbars
	setScrollbars();
}
