/*~********************************************************************

File:	Forms.js, v1.1

Script for handling form elements and their associated value objects

Charles Macfarlane Harrison, jj@javajive.macfh.co.uk, October 2009

***********************************************************************

Charles Harrison's Work is licensed under a Creative Commons Licence -
Attribution-Non-Commercial-No Derivative Works 2.0 UK: England & Wales
http://creativecommons.org/licenses/by-nc-nd/2.0/uk/

For further details, please see:
http://www.macfh.co.uk/JavaJive/JavaJive.html

***********************************************************************

Updates:
13/06/2010	Safer Array for() loops if the Array.prototype is altered.

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

var	FORMTYPES	= [ /* "BUTTON", "FORM", */ "INPUT", "SELECT", "TEXTAREA" ];
var	RAD			= Math.PI/180;
var	DEG			= 180/Math.PI;

//	Global listeners are run once all changes are complete
var	globalListeners = new Queue();
//	Flags who must run them
var	ancestor		= null;


//	An object holding a queue
function Queue()
	{
	this.listeners	= [];

	this.addListen	= aList;
	this.delListen	= dList;
	this.runListen	= rList;
	}

//	Add, delete, and run listeners in the queue
function aList( fListener )
	{
	var	present = false;
	for( var l = 0; !present && (l < this.listeners.length); l++ )
		present = ( this.listeners[l] == fListener );
	if( !present )
		this.listeners.push( fListener );
	}

function dList( fListener )
	{
	for( var l = this.listeners.length - 1; l >= 0; l-- )
		if( this.listeners[l] == fListener )
			this.listeners.splice( l , 1 );
	}

function rList()
	{
	for( var l = 0, L = this.listeners.length; l < L; l++ )
		this.listeners[l](this);
	}


//	An object holding a String value
//	Inherits from Queue object
StrValue.prototype		= new Queue;
StrValue.prototype.types = FORMTYPES;
function StrValue( aValue, anElement, caseFlag, trimFlag, fGetVal, fSetVal, fGetDVal, fSetDVal, fGetEVal, fSetEVal, fGetElem, fSetElem, fSetURLDVal )
	{
	//	Object inheritance
	this.base = Queue;
	this.base();

	this.getDiv		= gDiv;
	this.setDiv		= sDiv;
	this.getValue	= fGetVal ? fGetVal : gVal;
	this.setValue	= fSetVal ? fSetVal : sVal;
	this.getDispVal	= fGetDVal ? fGetDVal : gDispVal;
	this.setDispVal	= fSetDVal ? fSetDVal : sDispVal;
	this.getElemVal	= fGetEVal ? fGetEVal : gElemVal;
	this.setElemVal	= fSetEVal ? fSetEVal : sElemVal;
	this.getElement	= fGetElem ? fGetElem : gElem;
	this.setElement	= fSetElem ? fSetElem : sElem;
	this.blank		= fBlank;
	this.setURLValue = fSetURLDVal;
	this.update		= fUpdate;
	this.rOrigList	= this.runListen;
	this.runListen	= rLocalList;
	this.caseFlag	= caseFlag ? caseFlag.toUpperCase().substr( 0, 1 ) : undefined;
	this.trimFlag	= trimFlag != undefined ? trimFlag : true;
	this.value;

	if( anElement )
		{
		this.div		= typeof anElement == "string" ? document.getElementById(anElement) : anElement;
		this.label		= this.div && this.div.id ? document.getElementById( "l" + this.div.id.substr(1) ) : undefined;
		this.element	= this.div && this.div.id ? document.getElementById( "i" + this.div.id.substr(1) ) : undefined;
		if( this.element )
			{
			this.element.oData = this;
			if( this.element.name )
				this.element.nameSave = this.element.name;
			}
		this.group	= TreeWalk( this.div, this.types );
//	console.log( "Group:" + this.group );
		}
	if( aValue != undefined )
		this.setValue( aValue );
	}

//	Get and set the value
//	(notifying listeners of changes)
function gVal()
	{
	return this.value;
	}

function sVal( aValue )
	{
	var	oValue = this.value;
	if( aValue == undefined )
		aValue = this.getDispVal();
	switch( this.caseFlag )
		{
		case "L":	this.value = aValue.toLowerCase();
					break;

		case "U":	this.value = aValue.toUpperCase();
					break;

		default:	this.value = aValue;
					break;
		}
	this.setDispVal( this.value );
	if( this.value != oValue )
		this.runListen();
	return oValue;
	}

function fBlank()
	{
	this.value = undefined;
	this.getElement().value = "";
	}

//	Get and set the value in the associated HTML displaying element
//	(override for inheriting objects to convert between storage and display)
function gDispVal()
	{
	return this.getElemVal();
	}

function sDispVal( aValue )
	{
	this.setElemVal( aValue );
	}

//	Default to get and set the value in the associated HTML element
function gElemVal()
	{
	var	theElem = this.getElement();
	return theElem ? (this.trimFlag ? theElem.value.trim() : theElem.value ) : undefined;
	}

function sElemVal( aValue )
	{
	var	theElem = this.getElement(), oValue;
	if( theElem )
		{
		oValue = theElem.value;
		theElem.value = aValue;
		}
	return oValue;
	}

//	Get and set the associated HTML element
function gElem()
	{
	return this.element;
	}

function sElem( aValue )
	{
	var	oValue = this.element;
	this.element = aValue;
	return oValue;
	}

//	Get and set the associated HTML container
function gDiv()
	{
	return this.div;
	}

function sDiv( aValue )
	{
	var	oValue = this.div;
	this.div = aValue;
	return oValue;
	}

//	Update the display state, active/disabled state, and what element should next receive focus
function fUpdate( srcVal, shown, active, focus, nextFocus )
	{
	var	toShow		= shown != undefined ? ( typeof shown == "boolean" ? shown : srcVal.getValue() == shown ) : false;
	var	toActivate	= toShow && ( active != undefined ? (typeof active == "boolean" ? active : srcVal.getValue() == active) : toShow );
	var	toFocus		= toActivate && ( focus != undefined ? (typeof focus == "boolean" ? focus : srcVal.getValue() == focus) : toActivate );
	if( this.div )
		{
//	console.log( this.div.id + ".update(), show:" + toShow + ", activate:" + toActivate + ", focus:" + toFocus + ", " + this.getElement()  + ", " + (typeof nextFocus == "string" ? nextFocus : document.getElementById(nextFocus) ? document.getElementById(nextFocus).id : undefined) );
		setShowHide( this.div, toShow );
		for( var i = 0, I = this.group.length; i < I; i++ )
			{
			var	theElem = this.group[i];
//	console.log( theElem.id + ".update()" );
			if( toActivate )
				{
				if( theElem.tabIndex != undefined )
					theElem.tabIndex = 0;
				theElem.removeAttribute( "readOnly" );
				if( theElem.nameSave )
					theElem.setAttribute( "name", theElem.nameSave );
				setClass( theElem, "readonly", false );
				}
			else
				{
				if( theElem.tabIndex != undefined )
					theElem.tabIndex = -1;
				theElem.removeAttribute( "name" );
				theElem.setAttribute( "readOnly", true );
				setClass( theElem, "readonly", true );
				}
			}
		try
			{
			if( toFocus )
				srcVal.getElement().next = this.getElement();
			else if( nextFocus != undefined )
				srcVal.getElement().next = nextFocus;
			}
		catch(e){}
		}
	this.runListen();
	}

//	Run local listeners and maybe global
function rLocalList()
	{
	if( ancestor == null )
		ancestor = this;
	this.rOrigList();
	if( ancestor == this )
		{
		globalListeners.runListen();
		ancestor = null;
		}
	}


//	A Checkbox value
//	Inherits from StrValue object
ChkValue.prototype = new StrValue;
function ChkValue( aValue, anElement, fGetVal, fSetVal, fGetDVal, fSetDVal, fGetEVal, fSetEVal, fGetElem, fSetElem, fSetURLDVal )
	{
	//	Object inheritance
	this.base	= StrValue;
	this.base( aValue, anElement, undefined, undefined, fGetVal, fSetVal ? fSetVal : sChkVal, fGetDVal, fSetDVal, fGetEVal ? fGetEVal : gChkEVal, fSetEVal ? fSetEVal : sChkEVal, fGetElem, fSetElem, fSetURLDVal ? fSetURLDVal : sChkURLDV );
	this.blank	= fChkBlank;
	}

function sChkVal( aValue )
	{
	var	oValue = this.value;
	switch( aValue )
		{
		case undefined:	this.value = this.getDispVal();
						break;
		case "false":
		case false:		this.value = false;
						break;
		case "true":
		case true:		this.value = true;
						break;
		default:		var	theElem = this.getElement();
						this.value = theElem ? aValue == theElem.value : false;
						break;
		}
	this.setDispVal( this.value );
//	console.log( "sChkVal: " + (typeof aValue) + ", " + aValue + ", " +  oValue + ", " + this.value );
	if( this.value != oValue )
		this.runListen();
	return oValue;
	}

function fChkBlank()
	{
	this.value = undefined;
	this.getElement().checked = false;
	}

//	Get and set the value in the associated HTML displaying element
function gChkEVal()
	{
	var	theElem = this.getElement();
	return theElem ? theElem.checked : undefined;
	}

function sChkEVal( aValue )
	{
	var	oValue = this.getElemVal();
	var	theElem = this.getElement();
	if( theElem )
		switch( aValue )
			{
			case "false":
			case false:		theElem.checked = false;
							break;
			case "true":
			case true:		theElem.checked = true;
							break;
			}
//	console.log( "sChkEVal: " + (typeof aValue) + ", " + aValue + ", " +  theElem.checked );
	return oValue;
	}

//	Set the value for a URL parameter in the associated HTML displaying element
function sChkURLDV( aValue )
	{
	var	theElem = this.getElement();
	if( theElem )
		{
		theElem.value = this.value;
		theElem.checked = true;
		}
	}


//	A Radio Button value
//	Inherits from StrValue object
RadValue.prototype = new StrValue;
function RadValue( aValue, anElement, fGetVal, fSetVal, fGetDVal, fSetDVal, fGetEVal, fSetEVal, fGetElem, fSetElem )
	{
	//	If instance, set up the buttons array
	this.buttons = [];
	if( anElement )
		{
		var	group = TreeWalk( anElement, ["INPUT"] );
		var	found = false;
		for( var b = 0, B = group.length; b < B; b++ )
			if( group[b].type == "radio" )
				{
				group[b].oData = this;
				this.buttons.push( group[b] );
				found |= group[b].checked;
				}
		if( this.buttons.length && !found )
			for( b = 0, B = this.buttons.length; b < B; b++ )
				if( this.buttons[b].defaultChecked )
					found = b;
		this.buttons[found ? found : 0].checked = true;
		}

	//	Object inheritance
	this.base	= StrValue;
	this.base( aValue, anElement, undefined, undefined, fGetVal, fSetVal ? fSetVal : sRadVal, fGetDVal, fSetDVal, fGetEVal ? fGetEVal : gRadEVal, fSetEVal ? fSetEVal : sRadEVal, fGetElem, fSetElem );
	this.blank	= fRadBlank;
	}

function sRadVal( aValue )
	{
	var	oValue = this.value;
	if( aValue == undefined )
		this.value = this.getDispVal();
	else
		{
		var	found = false;
		for( var b = 0, B = this.buttons.length; b < B; b++ )
			found |= (this.buttons[b].value == aValue);
		if( found )
			{
			this.value = aValue;
			this.setDispVal( this.value );
			}
		}
	if( this.value != oValue )
		this.runListen();
	return oValue;
	}

function fRadBlank()
	{
	var	found = 0;
	for( var b = 0, B = this.buttons.length; b < B; b++ )
		if( this.buttons[b].defaultChecked )
			found = b;
	this.setValue( this.buttons[found].value );
	}

function gRadEVal()
	{
	var	result;
	for( var b = 0, B = this.buttons.length; b < B; b++ )
		if( this.buttons[b].checked )
			result = this.buttons[b].value;
	return result;
	}

function sRadEVal( aValue )
	{
	var	oValue = this.getElemVal();
	for( var b = 0, B = this.buttons.length; b < B; b++ )
		this.buttons[b].checked = (this.buttons[b].value == aValue);
	return oValue;
	}


//	A Select value
//	Inherits from StrValue object
SelValue.prototype = new StrValue;
//	Default timeout (ms) for debouncing repeat onchange events caused by keyboard up and down arrow scrolling
//	Should be significantly more than the BIOS keyboard delay (usually 250ms), 500ms recommended.
SelValue.prototype.timeout = 500;
function SelValue( aValue, anElement, theValues, fConvert, fGetVal, fSetVal )
	{
	//	Object inheritance
	this.base = StrValue;
	this.base( aValue, anElement, undefined, false, fGetVal, fSetVal ? fSetVal : sSelVal, undefined, undefined, gSelEVal, sSelEVal );

	this.selDefault		= selDeft;
	this.getDefault		= gSelDeft;
	this.setDefault		= sSelDeft;
	this.loadOptions	= loadOpts;

	//	Onchange debouncing
	this.bounceTime		= this.timeout;
	this.bounceTimer	= undefined;
	this.bounceValue	= undefined;
	this.debounceSetValue	= debSValue;

	if( theValues != undefined )
		this.loadOptions( theValues, fConvert );
	if( aValue != undefined )
		{
		this.setDefault( aValue );
		this.selDefault();
		}
	else
		this.value = this.getDispVal();
	}

//	Debounce the onChange event
function debSValue( myVarName, timeout )
	{
	if( timeout || !this.bounceTimer )
		{
		var	cValue = this.getElemVal();
//	console.log( this.bounceValue, cValue );
		if( this.bounceValue && (cValue == this.bounceValue) )
			{
			this.bounceTimer = undefined;
			this.bounceValue = undefined;
			this.setValue();
			}
		else
			{
			this.bounceTimer = setTimeout( myVarName + ".debounceSetValue('" + myVarName + "',true);", this.bounceTime );
			this.bounceValue = cValue;
			}
		}
	}

//	Adapt and load dynamic options into the Select element
function loadOpts( theValues, fConvert )
	{
	try
		{
		if( theValues.length && (typeof theValues[0] == "object") && (theValues[0].tagName) && (theValues[0].tagName == "OPTION") )
			replaceOpts.apply( this, [theValues] );
		else
			{
			var	options = [];
			for( var o in theValues )
				{
				var	option = document.createElement("OPTION");
//	console.log( o + ", " + theValues[o]  + ", " + typeof theValues[o] );
				switch( typeof theValues[o]  )
					{
					case "object":
						if( (theValues[o].text != undefined) && (theValues[o].value != undefined) )
							{
							option.text = theValues[o].text;
							option.value = theValues[o].value;
							if( theValues[o].selected )
								option.selected = "selected";
							}
						else if( theValues[o] instanceof Array )
							{
							option.text = theValues[o][0];
							option.value = theValues[o][1];
							}
						else if( fConvert != undefined )
							{
							option.value = o;
							fConvert( theValues[o], option );
							}
						break;

					case "string":
						option.text = theValues[o];
						option.value = o;
						break;
					}
				if( (option.text != undefined) && (option.value != undefined) )
					options.push( option );
				}
			if( options.length )
				replaceOpts.apply( this, [options] );
			}
		}
	catch(e){}
	}

//	Load new dynamic options into the Select element
function replaceOpts( newOptions )
	{
	this.value		= undefined;
	this.defValue	= undefined;
	this.element.selectedIndex	= 0;
	for( var o = this.element.options.length - 1; o >= 0; o-- )
		this.element.remove( o );
	this.element.innerHTML = "";
	for( o = 0; o < newOptions.length; o++ )
		{
		try
			{
			//	FF
			this.element.add( newOptions[o], null );
			}
		catch(e)
			{
			//	IE, Opera
			this.element.add( newOptions[o] );
			}
		if( newOptions[o].selected && (newOptions[o].selected == "selected") )
			{
			this.defValue = newOptions[o].value;
			this.element.selectedIndex = o;
			}
		}
	this.selDefault();
	}

//	Set the selected value
function sSelVal( aValue )
	{
	var	oValue = this.value;
	if( aValue != undefined )
		this.setDispVal( aValue );
	this.value = this.getDispVal();
	if( this.value != oValue )
		this.runListen();
//	console.log( this.getDiv().id + "," + oValue + "," + this.value );
	return oValue;
	}

//	Get and set the value in the associated HTML SELECT element
function gSelEVal()
	{
	var	result = null;
	try
		{
		result = this.element.options[this.element.selectedIndex];
		result = result.value ? result.value : result.text ? result.text : null;
		}
	catch(e){}
	return result;
	}

function sSelEVal( aValue )
	{
	var	oValue	= this.getElemVal();
	var	found	= -1;
	for( var o = 0; (found < 0) && (o < this.element.options.length); o++ )
		if( this.element.options[o].text == aValue )
			found = o;
	for( o = 0; (found < 0) && (o < this.element.options.length); o++ )
		if( this.element.options[o].value == aValue )
			found = o;
	if( found >= 0 )
		{
		this.element.selectedIndex = found;
		this.element.value = this.getElemVal();
		}
	return oValue;
	}

//	Get, set, and select the default option
function gSelDeft()
	{
	return this.defValue;
	}

function sSelDeft( aValue )
	{
	this.defValue = aValue;
	}

function selDeft()
	{
	this.setValue( this.getDefault() );
	}


//	Number Value
//	Inherits from StrValue object
NumValue.prototype = new StrValue;
//	Matcher for leading +-
NumValue.prototype.pmMatcher = [ "(?:\\+-|-\\+|\\xB1)(\\S*)", parsePM ];
//	List of matchers for a given number type
NumValue.prototype.matchers	= [ NumValue.prototype.pmMatcher ];
NumValue.prototype.min		= -Number.MAX_VALUE;
NumValue.prototype.max		= Number.MAX_VALUE;
function NumValue( aValue, anElement, aMin, aMax, fGetDVal, fSetDVal, fSetURLDVal )
	{
	//	Object inheritance
	this.base	= StrValue;
	this.base( aValue, anElement, undefined, true, undefined, undefined, fGetDVal ? fGetDVal : gNumDV, fSetDVal, undefined, undefined, undefined, undefined, fSetURLDVal );
	this.parse	= parseNumber;
	if( aMin != undefined )
		this.min = aMin;
	if( aMax != undefined )
		this.max = aMax;
	}

//	Get and set the value in the associated HTML displaying element
//	(performing any required conversion)
function gNumDV()
	{
	return this.parse( this.getElemVal() );
	}

function parseNumber( strNumber, start )
	{
	strNumber = strNumber.toLowerCase();
	var	result = NaN;
	for( var t = start ? start : 0; strNumber && isNaN(result) && (t < this.matchers.length); t++ )
		{
		var	re = new RegExp( this.matchers[t][0].toLowerCase(), "gi" );
		result = re.exec( strNumber );
//	console.log( strNumber + "\n\n" + t + "\n" + this.matchers[t][0] + "\n" + this.matchers[t][1] + "\n\n" + result + "\n" );
		if( (result != null) && (result[0] == strNumber) )
			{
			result = this.matchers[t][1].apply( this, [strNumber,result] );
			if( (result < this.min) || (result > this.max) )
				result = NaN;
			}
		else
			result = NaN;
		}
	return result;
	}

//	Parse a number with leading +-
function parsePM( strNumber, result )
	{
	return this.parse( result[1], 1 );
	}


//	Integer Value
//	Inherits from NumValue object
IntValue.prototype				= new NumValue;
IntValue.prototype.radix		= 10;
IntValue.prototype.pcRadix		= 2;
IntValue.prototype.digits		= { 2:"0-1", 8:"0-7", 10:"0-9", 16:"0-9A-F" };
//	Matchers for binary numbers
IntValue.prototype.bPreMatcher	= [ "([+-])?(bin|0?b|#b|#?2r|2#|%)([" + IntValue.prototype.digits[2] + "]+)", parseBinInt ];
IntValue.prototype.bPstMatcher	= [ "([+-])?([" + IntValue.prototype.digits[2] + "]+)(bin|b)", parseIntBin ];
IntValue.prototype.bBr1Matcher	= [ "([+-])?(b|2)(?:'|#)([" + IntValue.prototype.digits[2] + "]+)(?:'|#)", parseBinInt ];
//	Matchers for octal numbers
IntValue.prototype.oPreMatcher	= [ "([+-])?(oct|0?o|#o|0?q|#q|#?8r|8#)([" + IntValue.prototype.digits[8] + "]+)", parseOctInt ];
IntValue.prototype.oPstMatcher	= [ "([+-])?([" + IntValue.prototype.digits[8] + "]+)(oct|o|q)", parseIntOct ];
IntValue.prototype.oBr1Matcher	= [ "([+-])?(o|8)(?:'|#)([" + IntValue.prototype.digits[8] + "]+)(?:'|#)", parseOctInt ];
//	Matchers for decimal numbers
IntValue.prototype.dPreMatcher	= [ "([+-])?(dec|0?d|#d|#?10r|10#)([" + IntValue.prototype.digits[10] + "]+)", parseDecInt ];
IntValue.prototype.dPstMatcher	= [ "([+-])?([" + IntValue.prototype.digits[10] + "]+)(dec|d)", parseIntDec ];
IntValue.prototype.dBr1Matcher	= [ "([+-])?(d|10)(?:'|#)([" + IntValue.prototype.digits[10] + "]+)(?:'|#)", parseDecInt ];
//	Matchers for hexadecimal numbers
IntValue.prototype.hPreMatcher	= [ "([+-])?(hex|0?h|0?x|\\\\x|u\\+|\\\\u|\\$|&h?|&#x|#?16r|#x?|16#|%)([" + IntValue.prototype.digits[16] + "]+)", parseHexInt ];
IntValue.prototype.hPstMatcher	= [ "([+-])?([" + IntValue.prototype.digits[16] + "]+)(hex|h|x)", parseIntHex ];
IntValue.prototype.hBr1Matcher	= [ "([+-])?(h|x|16)(?:'|#)([" + IntValue.prototype.digits[16] + "]+)(?:'|#)", parseHexInt ];
IntValue.prototype.hVrlgMatcher	= [ "([+-])?(?:[0-9]+')(h)([" + IntValue.prototype.digits[16] + "]+)", parseHexInt ];
//	Default matcher for number to the correct internal radix
IntValue.prototype.rMatcher		= [ "([+-]?[" + IntValue.prototype.digits[10] + "]+)", parseIntV ];
//	List of matchers for an integer
IntValue.prototype.matchers	=	[
									IntValue.prototype.pmMatcher,
									IntValue.prototype.bPreMatcher,
									IntValue.prototype.bBr1Matcher,
									IntValue.prototype.bPstMatcher,
									IntValue.prototype.oPreMatcher,
									IntValue.prototype.oPstMatcher,
									IntValue.prototype.oBr1Matcher,
									IntValue.prototype.dPreMatcher,
									IntValue.prototype.dPstMatcher,
									IntValue.prototype.dBr1Matcher,
									IntValue.prototype.hPreMatcher,
									IntValue.prototype.hPstMatcher,
									IntValue.prototype.hBr1Matcher,
									IntValue.prototype.hVrlgMatcher,
									IntValue.prototype.rMatcher
								];
function IntValue( aValue, anElement, aMin, aMax, theRadix, thePCRadix, fGetDVal, fSetDVal )
	{
	if( theRadix && (theRadix != 10) )
		{
		this.radix	= theRadix;
		this.matchers = new Array( IntValue.prototype.matchers.length );
		for( var i = 0; i < IntValue.prototype.matchers.length - 1; i++ )
			this.matchers[i] = IntValue.prototype.matchers[i];
		this.matchers[IntValue.prototype.matchers.length - 1] = [ "([+-]?[" + IntValue.prototype.digits[this.radix] + "]+)", parseIntV ];
		}
	if( thePCRadix )
		this.pcRadix = thePCRadix;

	//	Object inheritance
	this.base = NumValue;
	this.base( aValue, anElement, aMin, aMax, fGetDVal, fSetDVal ? fSetDVal : sIntDV );
	}

//	Set the value in the associated HTML displaying element
//	(performing any required conversion)
function sIntDV( aValue )
	{
	this.setElemVal( this.value.toString(this.radix) );
	}

//	Parse a number in a different radix
function parseIntBin( strNumber, result )
	{
	return parseBinInt.apply( this, [strNumber, result, true] );
	}

function parseBinInt( strNumber, result, swap )
	{
	return parseRadixInt.apply( this, [result, result[2] == "%" ? this.pcRadix : 2, swap] );
	}

function parseIntOct( strNumber, result, swap )
	{
	return parseOctInt.apply( this, [strNumber, result, true] );
	}

function parseOctInt( strNumber, result, swap )
	{
	return parseRadixInt.apply( this, [result, 8, swap] );
	}

function parseIntDec( strNumber, result, swap )
	{
	return parseDecInt.apply( this, [strNumber, result, true] );
	}

function parseDecInt( strNumber, result, swap )
	{
	return parseRadixInt.apply( this, [result, 10, swap] );
	}

function parseIntHex( strNumber, result, swap )
	{
	return parseHexInt.apply( this, [strNumber, result, true] );
	}

function parseHexInt( strNumber, result, swap )
	{
	return parseRadixInt.apply( this, [result, result[2] == "%" ? this.pcRadix : 16, swap] );
	}

function parseRadixInt( result, aRadix, swap )
	{
	if( swap )
		{
		var	temp = result[2];
		result[2] = result[3];
		result[3] = temp;
		}
//	console.log( result + "\n" + aRadix + "\n" + this.radix );
	return (result[2] == "b") && (aRadix == 2) && (this.radix == 16) ? NaN : parseInt( (result[1] ? result[1] : "") + result[3], aRadix != undefined ? aRadix : this.radix );
	}

//	Parse a number in the internal radix
function parseIntV( strNumber, result )
	{
//	console.log( strNumber + "\n" + result + "\n" + this.radix );
	return parseInt( strNumber, this.radix );
	}


//	Floating-Point Value
//	Inherits from NumValue object
FPValue.prototype			= new NumValue;
FPValue.prototype.normal	= "([+-]?[.0-9]+)";
FPValue.prototype.exponent	= "([+-]?[0-9]+)";
//	Matcher for scientific notation
FPValue.prototype.SciMatcher = [ FPValue.prototype.normal + "e" + FPValue.prototype.exponent, parseSci ];
//	Matcher for normal FP number
FPValue.prototype.FPMatcher	= [ FPValue.prototype.normal, parseFloat ];
//	List of matchers for a floating point number
FPValue.prototype.matchers	=	[
									FPValue.prototype.pmMatcher,
									FPValue.prototype.SciMatcher,
									FPValue.prototype.FPMatcher
								];

function FPValue( aValue, anElement, theDecimals, theURLDecimals, aMin, aMax, fGetDVal, fSetDVal, fSetURLDVal )
	{
	this.decimals		= theDecimals != undefined ? theDecimals : -1;
	this.URLdecimals	= theURLDecimals != undefined ? theURLDecimals : this.decimals != -1 ? this.decimals + 2 : -1;

	//	Object inheritance
	this.base = NumValue;
	this.base( aValue, anElement, aMin, aMax, fGetDVal, fSetDVal ? fSetDVal : sFPDV, fSetURLDVal ? fSetURLDVal : sFPURLDV );
	}

//	Set the value in the associated HTML displaying element
function sFPDV( aValue, decimals )
	{
	this.setElemVal( aValue.roundFloat(decimals ? decimals : this.decimals) );
	}

//	Set the value for a URL parameter in the associated HTML displaying element
function sFPURLDV( aValue, decimals )
	{
	this.setDispVal( aValue != undefined ? aValue : this.value, decimals ? decimals : this.URLdecimals );
	}

//	Parse a floating-point number in scientific notation
function parseSci( strNumber, result )
	{
	return parseFloat( result[1] )*Math.pow( 10, parseFloat( result[2] ) );
	}


//	ConValue which for ease of calculation is stored in one unit, perhaps kms,
//	but for convenience of the user displayed as another, perhaps miles.
//	Inherits from FPValue object
ConValue.prototype				= new FPValue;
//	The display conversion factor
ConValue.prototype.conversion	= 1;
function ConValue( aValue, anElement, theConFactor, theDecimals, theURLDecimals, aMin, aMax, fGetDVal, fSetDVal, fSetURLDVal, fGetBDVal, fSetBDVal )
	{
	//	Set the display conversion factor
	if( theConFactor != undefined )
		this.conversion	= theConFactor;

	//	Object inheritance
	//	(for descendants, need to ensure this happens only on prototype call)
	if( !this.getBDispV && !this.setBDispV )
		{
		this.getBDispV	= fGetBDVal ? fGetBDVal : this.getDispVal;
		this.setBDispV	= fSetBDVal ? fSetBDVal : this.setDispVal;
		}
	this.base = FPValue;
	this.base( aValue, anElement, theDecimals, theURLDecimals, aMin, aMax, fGetDVal ? fGetDVal : gConDV, fSetDVal ? fSetDVal : sConDV, fSetURLDVal );
	}

//	Get and set the value in the associated HTML element
//	(converting between stored and displayed units)
function gConDV()
	{
	return this.getBDispV()/this.conversion;
	}

function sConDV( aValue, decimals )
	{
	this.setBDispV( aValue*this.conversion, decimals );
	}


//	Angle which is stored and manipulated in radians, but displayed in degrees
//	Inherits from ConValue object, must set DMS output in prototype call
Angle.prototype				= new ConValue( undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, sAngDV );
//	Matcher for Degrees:Minutes:Seconds, trailing N, S, E, or W denoting Lat or Long
Angle.prototype.DMSMatcher	= [ "([+-])?([0-9]+)[:°](?:(?:([0-9]+)(?:[:']|\u2032)([.0-9]+)(?:\x22|\u2033)?)|(?:([.0-9]+)(?:'|\u2032)?))\\s*([nsew])?", parseDHMS ];
//	Matcher for decimal angle, trailing N, S, E, or W denoting Lat or Long
Angle.prototype.DecAMatcher	= [ Angle.prototype.normal + "\\s*([nsew])?", parseDecA ];
Angle.prototype.matchers	=	[
									Angle.prototype.pmMatcher,
									Angle.prototype.SciMatcher,
									Angle.prototype.DMSMatcher,
									Angle.prototype.DecAMatcher,
									Angle.prototype.FPMatcher
								];

Angle.prototype.dSym	= "°";
Angle.prototype.mSym	= "'";
Angle.prototype.sSym	= "\"";
Angle.prototype.dSep	= ":";
function Angle( aValue, anElement, theDecimals, theURLDecimals, aMin, aMax, dms, fGetDVal, fSetDVal, fSetURLDVal )
	{
	//	Set up DMS display flag
	this.setDMS	= sDMS;
	this.setDMS( dms );

	//	Object inheritance
	this.base = ConValue;
	this.base( aValue, anElement, DEG, theDecimals, theURLDecimals, aMin, aMax, fGetDVal, fSetDVal, fSetURLDVal );

	//	Return as an angle between 0 & 2*PI
	this.getNormVal	= gNormAngle;
	}

//	Set flag for whether the output should be displayed in decimals or DMS
function sDMS ( dms )
	{
	var	oValue = this.dms;
	switch( dms instanceof Array ? dms[0] : dms )
		{
		case "N":
		case "n":
		case "E":
		case "e":	this.dms = dms;
					break;

		case true:	this.dms = true;
					break;

		default:	this.dms = false;
					break;
		}
	this.setDispVal( this.value );
	return oValue;
	}

//	Set the value in the associated HTML displaying element as either decimal or DMS
function sAngDV( aValue, decimals )
	{
	var	dec = decimals ? decimals : this.decimals;
	var	res;
	if( this.dms )
		with( Math )
			{
			var	degs, mins, secs, dSym, mSym, sSym, llSym;
			res = abs( aValue );
			switch( this.dms instanceof Array ? this.dms[0] : this.dms )
				{
				case "N":
				case "S":
				case "E":
				case "W":	pmSym = "";
							dSym = this.dSym;
							mSym = this.mSym;
							sSym = this.sSym;
							llSym = this.dms[ aValue.sign() >= 0 ? 0 : 1 ];
							break;
				default:	pmSym = aValue.sign() < 0 ? "-" : "";
							dSym = this.dSep;
							mSym = this.dSep;
							sSym = "";
							llSym = "";
							break;
				}
			switch( dec )
				{
				case 0:	res = round( res );
						break;
				case 1:
				case 2:	degs = floor( res );
						res -= degs;
						mins = round( res*60 );
						res = pmSym + degs + dSym + mins.pad(2) + mSym + llSym;
						break;
				case 3:
				case 4:	degs = floor( res );
						res	= (res - degs)*60;
						mins = floor( res );
						res = (res - mins)*60;
						secs = round( res );
						res = pmSym + degs + dSym + mins.pad(2) + mSym + secs.pad(2) + sSym + llSym;
						break;
				default:degs = floor( res );
						res	= (res - degs)*60;
						mins = floor( res );
						secs = (res - mins)*60;
						if( dec != -1 )
							secs = secs.roundFloat( dec - 4 );
						res = pmSym + degs + dSym + mins.pad(2) + mSym + secs.pad(2) + sSym + llSym;
						break;
				}
			}
	else
		res = aValue.roundFloat( dec );
	this.setElemVal( res );
	}

//	Convert a -ve angle into its equivalent +ve one
function gNormAngle()
	{
	var	twoPi = 2*Math.PI;
	return (this.value >= 0 ? this.value : twoPi + this.value) % twoPi;
	}

//	Parse a decimal latitude or longitude
function parseDecA( strNumber, result )
	{
	return ( result[1] && (result[1] == "-") ? -1 : 1 )*parseFloat( result[2] )*( result[3] && result[3].toLowerCase().match(/[sw]/) ? -1 : 1 );
	}


//	Date and time value stored as milliseconds since 01/01/1970 00:00:00 GMT
//	Inherits from NumValue object
DTValue.prototype = new NumValue;
//	Matcher for Date in milliseconds since 01/01/1970 00:00:00 GMT
DTValue.prototype.msMatcher		= [ "[+-]?[0-9]+", parseMs ];
//	Matcher for Date as locale/browser dependent string
DTValue.prototype.locMatcher	= [ ".+", parseLoc ];
DTValue.prototype.matchers		=	[
										DTValue.prototype.msMatcher,
										DTValue.prototype.locMatcher
									];
DTValue.prototype.HpD	= 24;
DTValue.prototype.MpH	= 60;
DTValue.prototype.SpM	= 60;
DTValue.prototype.mSpS	= 1000;
DTValue.prototype.mSpM	= DTValue.prototype.mSpS * DTValue.prototype.SpM;
DTValue.prototype.mSpH	= DTValue.prototype.mSpM * DTValue.prototype.MpH;
DTValue.prototype.mSpD	= DTValue.prototype.mSpH * DTValue.prototype.HpD;
function DTValue( aValue, anElement, aMin, aMax, fGetDVal, fSetDVal, fSetURLDVal )
	{
	//	Object inheritance
	this.base = NumValue;
	this.base( aValue, anElement, aMin, aMax, fGetDVal, fSetDVal ? fSetDVal : sDTDV, fSetURLDVal ? fSetURLDVal : sDTURLDV );
	}

function sDTDV( aValue )
	{
	this.setElemVal( aValue.toString() );
	}

//	Set the ms since 01/01/1970 into the field for a URL
function sDTURLDV( aValue )
	{
	this.setElemVal( aValue ? aValue : this.value.getTime() );
	}

function parseMs( strNumber, result )
	{
	return new Date( parseInt(strNumber) );
	}

function parseLoc( strNumber, result )
	{
	return new Date( Date.parse(strNumber) );
	}

//	Time value stored as Date
//	Inherits from DTValue object
TimeValue.prototype = new DTValue;
//	If decimals < 0, secs are not displayed
TimeValue.prototype.decimals	= 0;
//	Matcher for Hours:Minutes:Seconds, 24Hr
TimeValue.prototype.H24Matcher	= [ "([0-2][0-9][0-5][0-9])", parseH24 ];
//	Matcher for Hours:Minutes:Seconds, optional trailing AM or PM
TimeValue.prototype.H12Matcher	= [ "([+-])?([0-9]+)[:°](?:(?:([0-9]+)(?:[:']|\u2032)([.0-9]+)(?:\x22|\u2033)?)|(?:([.0-9]+)(?:'|\u2032)?))\\s*([ap]m)?", parseDHMS ];
TimeValue.prototype.matchers	=	[
										TimeValue.prototype.H24Matcher,
										TimeValue.prototype.H12Matcher,
										TimeValue.prototype.msMatcher
									];
function TimeValue( aValue, anElement, isDay, aMin, aMax, fGetDVal, fSetDVal, fSetURLDVal )
	{
	//	Set the default day
	if( isDay == true )
		this.day = aValue;
	else
		{
		aValue	= new Date( 1970,0,1, aValue.getHours(),aValue.getMinutes(),aValue.getSeconds(),aValue.getMilliseconds() );
		this.day = new Date( 1970,0,1 );
		}

	//	Object inheritance
	this.base = DTValue;
	this.base( aValue, anElement, aMin, aMax, fGetDVal, fSetDVal ? fSetDVal : sTimeDV, fSetURLDVal );

	//	Parsing a time needs to convert the resulting decimal hours to a date
	//	(for descendants, need to ensure this happens only on prototype call)
	if( !this.bParse )
		{
		this.bParse = this.parse;
		this.parse = parseTime;
		}
	}

//	Set the value in the associated HTML element
function sTimeDV( aValue )
	{
	this.setElemVal( aValue.toLocaleTimeString() );
	}

//	Parse time in hours:minutes:sec
function parseTime( strNumber, start )
	{
	var	result = this.bParse( strNumber, start );
	if( (typeof result == "number") && !isNaN(result) )
		result = new Date( Math.floor(this.day.getTime()/this.mSpD)*this.mSpD + result*this.mSpH + this.day.getTimezoneOffset()*this.mSpM );
	return result;
	}

//	Parse a time in 0000 - 2359 format
function parseH24( strNumber, result )
	{
	return parseInt( result[1].substr(0,2), 10 ) + parseInt( result[1].substr(2,2), 10 )/60;
	}

//	Parse a latitude, longitude, or time in degrees/hours:minutes:sec
function parseDHMS( strNumber, result )
	{
	return ( result[1] && (result[1] == "-") ? -1 : 1 )*( parseInt(result[2],10) + ( result[3] ? parseInt(result[3],10)/60 + (result[4] ? parseFloat(result[4])/3600 : 0) : (result[5] ? parseFloat(result[5])/60 : 0) ) )*(result[6] && result[6].toLowerCase().match("[sw]") ? -1 : 1) + (result[6] && result[6].toLowerCase().match("pm") ? 12 : 0);
	}


//	Extend Number to pad with leading zeros
Number.prototype.pad = pad;
function pad( width )
	{
	var	sign	= "";
	var	result	= "" + this + "";
	if( (result[0] == '+') || (result[0] == '-') )
		{
		sign	= result[0];
		result	= result.substr( 1 );
		}
	while( (result.indexOf(".") >= 0 ? result.indexOf(".") : result.length) < width )
		result = "0" + result;
	return sign + result;
	}

//	Extend Number to round a floating-point number to a given number of places
Number.prototype.roundFloat = roundFloat;
function roundFloat( places )
	{
	return places == -1 ? this.valueOf() : Math.round( this.valueOf() * Math.pow(10, places) ) / Math.pow( 10, places );
	}

//	Extend Number to return the sign of a number
Number.prototype.sign = sign;
function sign()
	{
	return this == 0 ? 0 : this < 0 ? -1 : +1;
	}

//	Submit a form
function submitURLPars( aForm, aUrl, aTarget )
	{
	var	eForm		= typeof aForm == "string" ? document.getElementById(aForm) : aForm;
	var	inputs		= TreeWalk( eForm, StrValue.prototype.types );
	for( var i = 0, I = inputs.length; i < I; i++ )
		{
		try
			{
			if( inputs[i].name && (inputs[i].disabled != true) && (inputs[i].disabled != "disabled") )
				{
				if( inputs[i].oData && inputs[i].oData.setURLValue )
					inputs[i].oData.setURLValue();
				if( (inputs[i].readOnly == true) || (inputs[i].readOnly == "readonly") )
					inputs[i].setAttribute( "disabled", true );
				}
			}
		catch( e )
			{
//			alert( e );
			}
		}
	eForm.action	= aUrl ? aUrl : document.URL;
	eForm.target	= aTarget ? aTarget : "_self";
	eForm.submit();
	}

//	Read and set URL parameters
function setURLPars( thePars )
	{
	var	pars = thePars ? thePars : top.location.search != "" ? top.location.search.substr(1) : undefined;
	if( pars )
		{
		var	errMsg	= "";
		var	re		= new RegExp("(?:&amp;|&)","g");
		pars		= pars.split( re );
		for( var i = 0; i < pars.length; i++ )
			{
			re		= new RegExp("(?:=)","g");
			try
				{
				var	par		= pars[i].split( re );
				re			= new RegExp("\\+","g");
				par[1]		= decodeURIComponent( par[1] ).replace( re, " " );
				var	eList	= document.getElementsByName( par[0] );
				var	eElem	= null;
//	console.log( par[0] + ": " + par[1] + " (" + typeof par[1] + ")" );
				for( var j = 0; (j < eList.length) && !eElem; j++ )
					if( eList[j].oData )
						eElem = eList[j];
				if( !eElem )
					eElem = document.getElementById( par[0] );
				eElem.oData.setElemVal( par[1] );
				eElem.oData.setValue();
				}
			catch(e)
				{
				errMsg += par + "\n"; // + e + "\n";
//	console.log( par + "\n" + e + "\n"; );
				}
			}
		if( errMsg != "" )
			alert( "URL Parameters Error:\t" + errMsg );
		}
	}

//	Remember if <tab> or <shift-tab> used to exit <select> tag
function logKey( anElement, anEvent )
	{
	if( (anEvent.which ? anEvent.which : anEvent.keyCode) == 9 )
		anElement.lastKey = anEvent.shiftKey ? "P" : "N";
	else
		anElement.lastKey = "";
	}

//	Set next focus when structure of document has been changed
function setNext(anElement)
	{
//	console.log( "SetNext: " + (anElement.id ? anElement.id : anElement) + "; " + (anElement.next.id ? anElement.next.id : anElement.next) + "; <" + anElement.lastKey + ">\n" );
	if( (navigator.appName != "Microsoft Internet Explorer") && anElement.next && anElement.lastKey && (anElement.lastKey == "N") )
		{
		if( typeof anElement.next == "string" )
			anElement.next = document.getElementById( anElement.next );
		anElement.next.focus();
		if( anElement.next.select )
			anElement.next.select();
		}
	}


//	These are borrowed from General.js for this standalone, downloadable version

// Add or remove a class to or from an element
function setClass( anElement, aClass, set )
	{
	var theElem = typeof anElement == "string" ? document.getElementById( anElement ) : anElement;
	var	classStr = null;
	try
		{
		var	regExp = new RegExp( "\\b" + aClass + "\\b", "g" );
		classStr = theElem.className.replace(regExp,"");
		if( set )
			classStr += " " + aClass;
		theElem.className = classStr;
		}
	catch(e){}
	return classStr;
	}

// Show or hide an element
function setShowHide( anElement, show )
	{
	return setClass( anElement, "invis", !show );
	}

//	Walk a DOM subtree, looking for descendants of certain types
function TreeWalk( aNode, theTypes )
	{
	var node	= typeof aNode == "string" ? document.getElementById(aNode) : aNode;
	var types	= theTypes instanceof Array ? theTypes : [theTypes];
	var result	= [];
	var nodes;
	if( node )
		{
		if( node.tagName )
			for( var i = 0, I = types.length; i < I; i++ )
				if( node.tagName.match(new RegExp(types[i],"i") ) )
					result.push( node );
		nodes = node.childNodes;
		for( i = 0, I = nodes.length; i < I; i++ )
			result = result.concat( TreeWalk(nodes[i], types) );
		}
	return result;
	}


//	Extend String to have trim function
String.prototype.trim = strTrim;
function strTrim()
	{
    return this.replace(/(^\s+)|(\s+$)/g, "");
	}

//				| addListen  | delListen  | runListen  |  getValue  |  setValue  | getDispVal | setDispVal | getElemVal | setElemVal | getElement | setElement |   Other
//	----------------------------------------------------------------------------------------------------------------------------------------------------------------------
//	Queue		|     Y      |     Y      |     Y      |            |            |            |            |            |            |            |            |
//	StrValue	|            |            |     Y      |     Y      |     Y      |     Y      |     Y      |     Y      |     Y      |     Y      |     Y      |     Y
//	SelValue	|            |            |            |            |     Y      |            |            |     Y      |     Y      |            |            |     Y
//	NumValue	|            |            |            |            |            |     Y      |            |            |            |            |            |     Y
//	IntValue	|            |            |            |            |            |            |     Y      |            |            |            |            |
//	FPValue		|            |            |            |            |            |            |     Y      |            |            |            |            |
//	Angle		|            |            |            |            |            |     Y      |     Y      |            |            |            |            |     Y
//	TimeValue	|            |            |            |            |            |            |     Y      |            |            |            |            |
