/**
 * @author Cory
 * @copyright Dambach Corporation
 */

	 //JavaScript File - ECMAScript - As per ECMA 262 with W3C Language Bindings as per Appendix E of the Document Object Model Level 1 Spec
	function ParseResponseXml( HtmlToAssign )//find any error messages, and display them
	{
		var tempDiv = document.createElement("div"); //create ghost div to house our Xml
		tempDiv.innerHTML = HtmlToAssign;
		var ErrList = tempDiv.getElementsByTagName("span"); //detect any span elements
		if( ErrList[0] != null && ErrList[0].id == "err" ) //it should always be the first span
		{
			DisplayMsg( ErrList[0].innerHTML );
			ErrList[0].parentNode.removeChild( ErrList[0] ); //gets the error out of the innerHTML
		}
		return tempDiv.innerHTML;
	}
	
	//WARNING: Global Variable
	var ErrorIsDisplayed = false; //is an Error msg displayed right now?	
	
	//depends upon GetInnerText in file Html.js
	function DisplayMsg( Msg )
	{
		new Rico.Effect.FadeTo(	"ContextInfo", .1, 500, 15, { complete: function() { return; } } ); //hide the element by making its opacity 0
		
		if( Msg == GetInnerText( Find("ContextInfo") ) ) //don't display the same message twice
			return;
			
		if( ErrorIsDisplayed == true ) //Find("ErrorMsg").style.display != "none"
		{
			Msg = Msg.replace( /'/g, "\"" ); //gets into trouble if single quotes are in the value Thursday September 28, 2006 CAD
			setTimeout( "DisplayMsg('" + Msg + "');", 500 );
			return;
		}
		ErrorIsDisplayed = true;	
		Find("ContextInfo").innerHTML = Msg;
			
		new Rico.Effect.FadeTo( 'ContextInfo', 1, 500, 15, { complete: function() { return; } } );		
		setTimeout( "FadeOutMsg();", 5000 );
	}
	
	//Depends on OpenRico to fade out a message
	//TODO: Use scriptaculous instead
	function FadeOutMsg()
	{
		new Rico.Effect.FadeTo(	"ContextInfo", .1, 500, 15, { complete: function() { return; } } ); //hide the element by making its opacity 0
		setTimeout( "Find('ContextInfo').innerHTML = '&#160;';", 600 ); //run this just a little while after the element is invisible
		setTimeout( "ErrorIsDisplayed = false;", 600 );
	}		
	
	//returns the QueryString part of the current URL
	function GetQueryString()
	{		
		var qs = location.href.replace( /.*\?/, "" );
		return qs;
	}
	
	//Mimics RequestVars from Dambach's C# Web Library
	function GetRequestVariables()
	{
		var qs = location.href.replace( /.*\?/, "" );
		return NameValueCollection.CreateFromQueryString( qs );
	}
	
		
	//case "int": case "double": case "single": case "float": case "long": case "decimal":
	//case "date": case "datetime": case "smalldatetime":
	//Constraints shall henceforth be defined to check one thing about one piece of data, therefore if you wish to check type and length, well, your boned for now!
	function Constraint() //abstract object constructor, includes some fallbacks
	{
		this.Evaluate = function( Value ) { return true; }
		this.ErrorMsg = "does not meet the contraints applied to it."; //user should never see this error message
	}
	
	var ConstraintType = {
		DataType	: 0,
		Function	: 1,
		Regex		: 2
	};
	function RegexConstraint( RegexObj ) {
		Constraint.call( this ); //inherits from Constraint
		this.__RegexObj	= RegexObj;
		this.ErrorMsg = "does not match the Regex filter of " + this.__RegexObj.toString();
		this.Evaluate = function( value ) //only makes sense for single valued inputs
		{
			return ( value.match( this.__RegexObj ) != null );
		}
	}
	function FunctionConstraint( BooleanFunction ) {
		Constraint.call( this );
		this.__FunctionObj = BooleanFunction;
		this.Evaluate = function( values )
		{
			return this.__FunctionObj( values );
		}
	}
		
	function MaxLengthConstraint( maxlen ) { //dmin
		Constraint.call( this );
		this.ErrorMsg = "exceeds the maximum length of " + maxlen.toString() + " for this field";
		this.Evaluate = function( values ) {
			return ( values.length <= maxlen );
		};
	}	
	function MinLengthConstraint( minlen ) { //dmin
		Constraint.call( this );
		this.ErrorMsg			= "";
		this.SingularErrorMsg	= "requires at least " + minlen.toString() + " characters for this field";
		this.PluralErrorMsg		= "needs at least " + minlen.toString() + " values";
		this.Evaluate = function( values ) {
			if( values instanceof Array ){
				this.ErrorMsg = this.PluralErrorMsg;
			} else {
				this.ErrorMsg = this.SingularErrorMsg;
			}		
			return ( values.length >= minlen );
		};
	}	
	
	//Unless otherwise noted, all regular expressions were created, tested and debugged by Cory Dambach
	var Constraints = new Object();
	
	//most common constraint is defined first
	Constraints.Required = new FunctionConstraint( function( val ) {		
		if( val == "" )		
			return false;
		return true;
	} );
	Constraints.Required.ErrorMsg = "is a required field";
	
	//then datatype constraints	
	//Constraints.Email				= new RegexConstraint( /^[\w]+@[\w]+\.[\w]+$/ );
	Constraints.Email					= new RegexConstraint( /^[A-Z0-9._%-]+@[A-Z0-9._%-]+\.[A-Z]{2,4}$/i );
	Constraints.Email.ErrorMsg			= "is not a valid email address.";
		
	Constraints.Money					= new RegexConstraint( /^\$\d+\.\d{0,2}$|^\$\d+$|^\d+\.\d{0,2}$|^\d+$/ );
	Constraints.Money.ErrorMsg			= "is not a valid value for physical money, please, use no more than two decimal places.";

	Constraints.Url						= new RegexConstraint( /^http:\/\/(\w+\.)+\w+$/ );
	Constraints.Url.ErrorMsg			= "is not a valid Http(RFC 2616) Web Address. e.g., http://example.com";
	
	//RegexBuddy's Library - Modified by Cory
	Constraints.IpAddress				= new RegexConstraint( /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ );
	Constraints.IpAddress.ErrorMsg		= "is not a valid IP Address. e.g., 64.233.187.99";

	//RegexBuddy's Library
	Constraints.PhoneNumber				= new RegexConstraint( /^\(?[0-9]{3}\)?[-. ]?[0-9]{3}[-. ]?[0-9]{4}$/ );
	Constraints.PhoneNumber.ErrorMsg	= "is not a valid North American phone number. e.g., 555-555-5555";
	
	Constraints.Zip						= new RegexConstraint( /^[0-9]{5}(?:-[0-9]{4})?$/ );
	Constraints.Zip.ErrorMsg			= "is not a valid Zip Code. e.g., 36106";
	
	Constraints.Number = new FunctionConstraint( function ( val ) {
		return ( !isNaN( val ) ); //if the value is (not not) a number.
	} );
	Constraints.Number.ErrorMsg = "is not a valid Number";
	
	Constraints.Integer = new FunctionConstraint( function ( val ) {
		var isNumber 		= ( !isNaN( val ) ); //if the value is (not not) a number
		var hasNoDecimals	= ( val.toString().indexOf('.') == -1 ); //prep is done
				
		return ( isNumber && hasNoDecimals ); //true if isNumber is true and hasNoDecimals is true
		//return false;
	} );
	Constraints.Integer.ErrorMsg = "is not a valid Integer.";
	
	Constraints.Date = new FunctionConstraint( function ( val ) {
		if( val.trim() == "" )
			return true;
		return ( IsDate( val ) ); //if the value is a date return true for passed!
	} );
	Constraints.Date.ErrorMsg = "is not a valid Date";
	
	var dtCh	=	"/";
	var minYear	=	1900;
	var maxYear	=	2100;

	function isInteger(s){
		var i;
		for (i = 0; i < s.length; i++)
		 {
			var c = s.charAt(i);
			if (((c < "0") || (c > "9"))) return false;
		}
		return true;
	}

	function stripCharsInBag(s, bag) {
		var i;
		var returnString = "";	
		for (i = 0; i < s.length; i++)
		{
			var c = s.charAt(i);
			if (bag.indexOf(c) == -1)
				returnString += c;
		}
		return returnString;
	}

	function daysInFebruary( year )
	{
		return ( ( ( year % 4 == 0 ) && ( ( !( year % 100 == 0 )) || ( year % 400 == 0 ) ) ) ? 29 : 28 );
	}
	function DaysArray(n)
	{
		for (var i = 1; i <= n; i++)
		{
			this[i] = 31;
			if (i==4 || i==6 || i==9 || i==11)
				this[i] = 30;		
			if (i==2)		
				this[i] = 29;
		}
		return this;
	}

	function IsDate(dtStr)
	{
		var daysInMonth = DaysArray(12);
		var pos1=dtStr.indexOf(dtCh);
		var pos2=dtStr.indexOf(dtCh,pos1+1);
		var strMonth=dtStr.substring(0,pos1);
		var strDay=dtStr.substring(pos1+1,pos2);
		var strYear=dtStr.substring(pos2+1)
		strYr = strYear;
		if (strDay.charAt(0)=="0" && strDay.length>1)
		{	strDay=strDay.substring(1);	}
		if (strMonth.charAt(0)=="0" && strMonth.length>1)
		{	strMonth=strMonth.substring(1);	}
		
		for (var i = 1; i <= 3; i++)
		{
			if (strYr.charAt(0)=="0" && strYr.length>1)
				strYr=strYr.substring(1);
		}
		month=parseInt(strMonth)
		day=parseInt(strDay)
		year=parseInt(strYr)
		if (pos1==-1 || pos2==-1){
			//alert("The date format should be : mm/dd/yyyy")
			return false
		}
		if (strMonth.length<1 || month<1 || month>12)
		{
			//alert("Please enter a valid month")
			return false;
		}
		if( strDay.length < 1 || day < 1 || day > 31 || ( month == 2 && day > daysInFebruary( year ) ) || day > daysInMonth[month] )
		{
			//alert("Please enter a valid day");
			return false;
		}
		if( strYear.length != 4 || year == 0 || year < minYear || year > maxYear )
		{
			//alert("Please enter a valid 4 digit year between "+minYear+" and "+maxYear);
			return false;
		}
		if( dtStr.indexOf( dtCh, pos2+1 ) != -1 || isInteger( stripCharsInBag( dtStr, dtCh ) ) == false )
		{
			//alert("Please enter a valid date");
			return false;
		}
		return true;
	}


	/* System.Data			*/
	/* Table of Contents	*/
	/*
		Class: DataTable
			Member Methods:
				AddColumn	( ColumnName );
				AddRow		( object[] Values );
				GetValueAt	( RowIndex, ColumnIndex );
				GetRow		( rowIndex );
				Sort		( col );
				ToXml		();
				GetXml		();
			Static Methods:
				DataTable.CreateFromQueryString();
				DataTable.GetXmlFromQueryString();
	*/

/**
 * DataTable seeks to emulate the .Net class of the same name
 * Has methods for serializing and generating and reading Xml, as well as deserializing from the CoffeePaper ASP.NET Component XmlHttp
 */
function DataTable()
{
	this.Name		= "DataTable"; //just a string
	this.Rows		= new Array(); //Multidimensional String Array
	this.Columns	= new Array(); //String Array: Rules no column name may be a numeric value only, however, name123 would be permitted
	
	this.AddColumn = function( ColName )
	{
		this.Columns.push( ColName );
		return;
	}
	this.AddRow = function( DataArray /* Should be a string array */ )
	{
		if( this.Columns.length == DataArray.length )
		{
			this.Rows.push( DataArray );
		}
		return;
	}
	this.InsertRowAt = function( row, rowIndex ) {
		this.Rows.splice( rowIndex, 0, row ); //start at rowIndex, remove 0 elements, and add the 1 row
	}
	this.GetValueAt = function( RowIndex, ColumnIndex )
	{
		try {
			return this.Rows[RowIndex][ColumnIndex];
		}
		catch( ex ) {
			throw( "The DataPoint at Row " + RowIndex + " Column " + ColumnIndex + " does not exist." );
		}
	}	
	this.GetRow = function( RowIndex ) //returns an array
	{
		return this.Rows[RowIndex];
	};
	this.GetRowAsNVC = function( RowIndex )
	{
		var vals	= this.GetRow( RowIndex );
		var nvc		= new NVC();
		for( var i = 0; i < this.Columns.length; i++ )
		{
			nvc.Add( this.Columns[i], vals[i] );
		}
		return nvc;
	};
	this.SetRow = function( RowIndex, NewRow )
	{
		this.Rows[RowIndex] = NewRow;
	};	
	this.GetOrdinalFromColumnName = function( colName )
	{
		for( var i = 0; i < this.Columns.length; i++ ) //iterate over all columns in our dataTable
			if( this.Columns[i] == colName ) //comparing it to the passed in one
				return i; //if a column's name matches the passed in one, return its index
	};
	this.ForEach = function( rowFunction )		
	{
		var result; //no idea what this object will be
		for (var i = 0; i < this.Rows.length; i++)
		{
			var row = this.Rows[i]; //get the row and place it in temp var
			var innerResult = rowFunction(row);
			if (innerResult != null) //only if they return something do we want to add it
				result += result; //add whatever the hell the row function returns to the result						
		}
		return result; //returns the results of each rowfunction into a single var
	};
	this.Sort = function( col ) //may be an ordinal or a string may have a use for instance of here and the === operator
	{
		var colOrdinal = -1;
		if( typeof(col) == "string" ) //instance of only works for Functions
			colOrdinal = this.GetOrdinalFromColumnName( col ); //does what it says, It's Called Mathematics, LOOK IT UP Dummy! A WhipCracka!
		else if( parseInt( col ).toString() == "NaN" ) //it better be a number, otherwise this won't work
			throw( "DataTable.Sort: argument was not a column name or an ordinal, cannot continue." );
		//continue on!
			
		var comparer = this.__probeAndGetComparator( colOrdinal );
		
		if( comparer != null )					
			this.Rows.sort( comparer );
		else
			throw new ("I could not figure out a way to sort the data in that column, please contact technical support");
	};
	this.Reverse = function()
	{
		this.Rows.reverse();
	};
	this.__probeAndGetComparator = function( columnOrdinal )
	{			
		var x = this.Rows[0][columnOrdinal];		
		if( Date.parse( x ).toString() != "NaN" ) {
			return function( a, b ) { //DateTime based sort
				var x = Date.parse( a[columnOrdinal] );
				var y = Date.parse( b[columnOrdinal] );
				
				if( x.toString() == "NaN" )
					x = 0;
				if( y.toString() == "NaN" )
					y = 0;
					
				return DataTable.baseCompare( x, y );
			};
		}
		if( parseFloat( x ).toString() != "NaN" )		
		{
			return function( a, b ) { //numerical sort
				var x = parseFloat( a[columnOrdinal] );
				var y = parseFloat( b[columnOrdinal] );
				
				if( x.toString() == "NaN" )
					x = 0;
				if( y.toString() == "NaN" )
					y = 0;
				
				return DataTable.baseCompare( x, y );
			};
		}		
		return function( a, b ) { //lexicographical sort
			var x = new String( a[columnOrdinal] );
			var y = new String( b[columnOrdinal] );			
			
			return DataTable.baseCompare( x, y );
		};
	}	
	
	this.ToAttributalXml = function( method )
	{
		var outXml = "<" + this.Name + ">"; //xml
		for( var i = 0; i < this.Rows.length; i++ )
		{
			outXml += '<r' + i.toString() + " "; //xml
			for( var j = 0; j < this.Columns.length; j++ )
			{
				outXml += this.Columns[j] + '="' + this.Rows[i][j] + '" ';
			}
			outXml += ' />'; //xml			
		}
		outXml += "</" + this.Name + ">"; //xml
		return outXml;
	};

	//This one manually uses the DOM 
	this.ToAttributalXmlDom = function( method )
	{
		var domDiv	= document.createElement( "div" );
		var xroot	= domDiv.appendChild( document.createElement( this.Name ) );
		
		for( var i = 0; i < this.Rows.length; i++ )
		{
			var rowEl = xroot.appendChild( document.createElement( "r" + i.toString() ) );
			for( var j = 0; j < this.Columns.length; j++ )			
				rowEl.setAttribute( this.Columns[j], this.Rows[i][j] );
		}
		return domDiv.innerHTML;
	};
	this.ToElementalXml = function( method )
	{
		if( !method ) 
		var outXml = "<" + this.Name + ">"; //xml
		for( var i = 0; i < this.Rows.length; i++ )
		{
			//outXml += '<r' + i.toString() + '>'; //xml
			outXml += '<row>'; //xml
			for( var j = 0; j < this.Columns.length; j++ )
			{
				outXml += "<" + this.Columns[j] + ">" + this.Rows[i][j] + "</" + this.Columns[j] + ">\n";
			}
			outXml += '</row>'; //xml			
		}
		outXml += "</" + this.Name + ">"; //xml
		return outXml;
	};
	this.ToXml	= this.ToElementalXml;
	this.GetXml	= this.ToXml; //just two names for the same thing

	
	/*
	this.QuickSorter = function()
	{
		//var comparedColumnIndex = 0;
		this.Sort = function( dt, colOrdinal )
		{
			this.__QuickSort( dt, 0, dt.Rows.length, colOrdinal ); //sort all rows, and use the specified column ordinal
		}	
		
		this.__QuickSort = function(dt, begin, end, ord)
		{
			if( end-1 > begin ) {
				var pivot	= begin + Math.floor( Math.random()*( end - begin ) );
				pivot		= this.partition( dt, begin, end, pivot, ord );
		
				this.__QuickSort( dt, begin, pivot, ord );
				this.__QuickSort( dt, pivot+1, end, ord );
			}
		}
		
		this.partition = function( dt, begin, end, pivot, ord )
		{
			var piv	= dt.Rows[pivot][ord]; //get the value of the compared column using its ordinal
			//this.swap( dt, pivot, end-1 );
			var store = begin;
			var ix;
			for( ix = begin; ix < end - 1; ++ix )
			{
				if( dt.Rows[ix][ord] <= piv ){ //compare them correctly again
					this.swap( dt, store, ix );					
					++store;
				}
			}
			this.swap( dt, end-1, store );
			return store;
		}
		
		this.swap = function( dt, a, b)
		{
			var rowA	= dt.GetRow( a );
			//var copyA	= rowA; //tmp Var for A
			//for( var i = 0; i < rowA.length; i++ ) //make a copy in memory of the row
			//	copyA.push( rowA[i] ); //not sure that this is necessary, see the note below
			dt.SetRow( a, dt.GetRow( b ) ); //this IS necessary
			dt.SetRow( b, rowA ); //because rowA is gone(GC)...likely...playing it safe!
		}		
	}; //quicksorter
	*/
}; //DataTable

DataTable.CreateFromQueryString = function( QueryString )
{
	var outDT = new DataTable();
	if( QueryString == "" ) { return outDT; }
	var arrQueryString	= QueryString.split( '&' );
	
	for( var i = 0; i < arrQueryString.length; i++ )
	{
		var arrPair	= arrQueryString[i].split('=');
		var Name	= arrPair[0];
		var Value	= arrPair[1];
		if( i == 0 ) //First row
		{
			outDT.Name = Name;
			var arrColumns = Value.split(',');
			for( var j = 0; j < arrColumns.length; j++ )			
				outDT.AddColumn( decodeURIComponent( arrColumns[j] ) );
		}
		else
		{	
			var NewRow = Value.split( ',' );
			for( var j = 0; j < NewRow.length; j++ )	
				NewRow[j] = decodeURIComponent( NewRow[j] );
			outDT.AddRow( NewRow );
		}
	}
	return outDT;
};

//TODO: implement a method for attributal Xml generation, the below method is currently Elemental Xml generation June 8
DataTable.GetXmlFromQueryString = function( QueryString ) /* Returns Xml, because sometimes, you don't need no stinking datatable */
{
	var ColArray	= new Array();
	var outString	= new String();
	var DTName		= new String();
	if( QueryString == "" )	{ return "<DataTable />"; }
	var arrQueryString = QueryString.split( '&' );
	
	for( var i = 0; i < arrQueryString.length; i++ )
	{
		var arrPair	= arrQueryString[i].split('=');
		var Name	= arrPair[0];
		var Value	= arrPair[1];
		if( i == 0 ) //First row, has the Name of the DataTable and the Fields/Columns
		{
			DTName = Name;
			outString += '<' + Name + '>'; //xml
			var arrColumns = Value.split(',');
			for( var j = 0; j < arrColumns.length; j++ ) //gets all the field names
				ColArray.push( decodeURIComponent( arrColumns[j] ) );
		}
		else //the other cases
		{
			var NewRow = Value.split( ',' );
			outString += '<' + Name + '>'; //xml
			for( var j = 0; j < NewRow.length; j++ )
			{
				NewRow[j] = decodeURIComponent( NewRow[j] );	
				outString += '<' + ColArray[j] + '>' + NewRow[j] + '</' + ColArray[j] + '>\n'; //xml
			}
			outString += '</' + Name + '>'; //xml
		}
	}
	outString += '</' + DTName + '>'; //xml
	return outString;
};

//for use in sorting, didn't fit well anywhere else
DataTable.baseCompare = function( x, y )
{
	if( x == y	)
		return 0;
	if( x < y )
		return -1;
	return 1;
};

//Compiled at 5:30 PM Saturday March 26, 2010 by Cory Dambach


/* Cross-Browser Event Model Utility */
/* Gives programmers the ability to set up event handlers for different browsers */
/*
var sUserAgent = navigator.userAgent;
var fAppVersion = parseFloat(navigator.appVersion);

function compareVersions(sVersion1, sVersion2) {

    var aVersion1 = sVersion1.split(".");
    var aVersion2 = sVersion2.split(".");
    
    if (aVersion1.length > aVersion2.length) {
        for (var i=0; i < aVersion1.length - aVersion2.length; i++) {
            aVersion2.push("0");
        }
    } else if (aVersion1.length < aVersion2.length) {
        for (var i=0; i < aVersion2.length - aVersion1.length; i++) {
            aVersion1.push("0");
        }    
    }
    
    for (var i=0; i < aVersion1.length; i++) {
 
        if (aVersion1[i] < aVersion2[i]) {
            return -1;
        } else if (aVersion1[i] > aVersion2[i]) {
            return 1;
        }    
    }
    
    return 0;
}

var isOpera = sUserAgent.indexOf("Opera") > -1;
var isMinOpera4 = isMinOpera5 = isMinOpera6 = isMinOpera7 = isMinOpera7_5 = false;

if (isOpera) {
    var fOperaVersion;
    if(navigator.appName == "Opera") {
        fOperaVersion = fAppVersion;
    } else {
        var reOperaVersion = new RegExp("Opera (\\d+\\.\\d+)");
        reOperaVersion.test(sUserAgent);
        fOperaVersion = parseFloat(RegExp["$1"]);
    }

    isMinOpera4 = fOperaVersion >= 4;
    isMinOpera5 = fOperaVersion >= 5;
    isMinOpera6 = fOperaVersion >= 6;
    isMinOpera7 = fOperaVersion >= 7;
    isMinOpera7_5 = fOperaVersion >= 7.5;
}

var isKHTML = sUserAgent.indexOf("KHTML") > -1 
              || sUserAgent.indexOf("Konqueror") > -1 
              || sUserAgent.indexOf("AppleWebKit") > -1; 
              
var isMinSafari1 = isMinSafari1_2 = false;
var isMinKonq2_2 = isMinKonq3 = isMinKonq3_1 = isMinKonq3_2 = false;

if (isKHTML) {
    isSafari = sUserAgent.indexOf("AppleWebKit") > -1;
    isKonq = sUserAgent.indexOf("Konqueror") > -1;

    if (isSafari) {
        var reAppleWebKit = new RegExp("AppleWebKit\\/(\\d+(?:\\.\\d*)?)");
        reAppleWebKit.test(sUserAgent);
        var fAppleWebKitVersion = parseFloat(RegExp["$1"]);

        isMinSafari1 = fAppleWebKitVersion >= 85;
        isMinSafari1_2 = fAppleWebKitVersion >= 124;
    } else if (isKonq) {

        var reKonq = new RegExp("Konqueror\\/(\\d+(?:\\.\\d+(?:\\.\\d)?)?)");
        reKonq.test(sUserAgent);
        isMinKonq2_2 = compareVersions(RegExp["$1"], "2.2") >= 0;
        isMinKonq3 = compareVersions(RegExp["$1"], "3.0") >= 0;
        isMinKonq3_1 = compareVersions(RegExp["$1"], "3.1") >= 0;
        isMinKonq3_2 = compareVersions(RegExp["$1"], "3.2") >= 0;
    } 
    
}

var isIE = sUserAgent.indexOf("compatible") > -1 
           && sUserAgent.indexOf("MSIE") > -1
           && !isOpera;
           
var isMinIE4 = isMinIE5 = isMinIE5_5 = isMinIE6 = false;

if (isIE) {
    var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
    reIE.test(sUserAgent);
    var fIEVersion = parseFloat(RegExp["$1"]);

    isMinIE4 = fIEVersion >= 4;
    isMinIE5 = fIEVersion >= 5;
    isMinIE5_5 = fIEVersion >= 5.5;
    isMinIE6 = fIEVersion >= 6.0;
}

var isMoz = sUserAgent.indexOf("Gecko") > -1
            && !isKHTML;

var isMinMoz1 = sMinMoz1_4 = isMinMoz1_5 = false;

if (isMoz) {
    var reMoz = new RegExp("rv:(\\d+\\.\\d+(?:\\.\\d+)?)");
    reMoz.test(sUserAgent);
    isMinMoz1 = compareVersions(RegExp["$1"], "1.0") >= 0;
    isMinMoz1_4 = compareVersions(RegExp["$1"], "1.4") >= 0;
    isMinMoz1_5 = compareVersions(RegExp["$1"], "1.5") >= 0;
}

var isNS4 = !isIE && !isOpera && !isMoz && !isKHTML 
            && (sUserAgent.indexOf("Mozilla") == 0) 
            && (navigator.appName == "Netscape") 
            && (fAppVersion >= 4.0 && fAppVersion < 5.0);

var isMinNS4 = isMinNS4_5 = isMinNS4_7 = isMinNS4_8 = false;

if (isNS4) {
    isMinNS4 = true;
    isMinNS4_5 = fAppVersion >= 4.5;
    isMinNS4_7 = fAppVersion >= 4.7;
    isMinNS4_8 = fAppVersion >= 4.8;
}

var isWin = (navigator.platform == "Win32") || (navigator.platform == "Windows");
var isMac = (navigator.platform == "Mac68K") || (navigator.platform == "MacPPC") 
            || (navigator.platform == "Macintosh");

var isUnix = (navigator.platform == "X11") && !isWin && !isMac;

var isWin95 = isWin98 = isWinNT4 = isWin2K = isWinME = isWinXP = false;
var isMac68K = isMacPPC = false;
var isSunOS = isMinSunOS4 = isMinSunOS5 = isMinSunOS5_5 = false;

if (isWin) {
    isWin95 = sUserAgent.indexOf("Win95") > -1 
              || sUserAgent.indexOf("Windows 95") > -1;
    isWin98 = sUserAgent.indexOf("Win98") > -1 
              || sUserAgent.indexOf("Windows 98") > -1;
    isWinME = sUserAgent.indexOf("Win 9x 4.90") > -1 
              || sUserAgent.indexOf("Windows ME") > -1;
    isWin2K = sUserAgent.indexOf("Windows NT 5.0") > -1 
              || sUserAgent.indexOf("Windows 2000") > -1;
    isWinXP = sUserAgent.indexOf("Windows NT 5.1") > -1 
              || sUserAgent.indexOf("Windows XP") > -1;
    isWinNT4 = sUserAgent.indexOf("WinNT") > -1 
              || sUserAgent.indexOf("Windows NT") > -1 
              || sUserAgent.indexOf("WinNT4.0") > -1 
              || sUserAgent.indexOf("Windows NT 4.0") > -1 
              && (!isWinME && !isWin2K && !isWinXP);
} 

if (isMac) {
    isMac68K = sUserAgent.indexOf("Mac_68000") > -1 
               || sUserAgent.indexOf("68K") > -1;
    isMacPPC = sUserAgent.indexOf("Mac_PowerPC") > -1 
               || sUserAgent.indexOf("PPC") > -1;  
}

if (isUnix) {
    isSunOS = sUserAgent.indexOf("SunOS") > -1;

    if (isSunOS) {
        var reSunOS = new RegExp("SunOS (\\d+\\.\\d+(?:\\.\\d+)?)");
        reSunOS.test(sUserAgent);
        isMinSunOS4 = compareVersions(RegExp["$1"], "4.0") >= 0;
        isMinSunOS5 = compareVersions(RegExp["$1"], "5.0") >= 0;
        isMinSunOS5_5 = compareVersions(RegExp["$1"], "5.5") >= 0;
    }
}*/

var EventUtil = {};
EventUtil.addEventHandler = function (oTarget, sEventType, fnHandler)
{
    if( oTarget.addEventListener )
        oTarget.addEventListener( sEventType, fnHandler, false );
    else if( oTarget.attachEvent )
        oTarget.attachEvent("on" + sEventType, fnHandler);
    else //last resort
        oTarget["on" + sEventType] = fnHandler;
};
        
EventUtil.removeEventHandler = function (oTarget, sEventType, fnHandler)
{
    if( oTarget.removeEventListener )
        oTarget.removeEventListener(sEventType, fnHandler, false);
    else if( oTarget.detachEvent )
		oTarget.detachEvent("on" + sEventType, fnHandler);
    else
        oTarget["on" + sEventType] = null;
};

EventUtil.formatEvent = function (oEvent)
{
    if (isIE && isWin)
	{
        oEvent.charCode = (oEvent.type == "keypress") ? oEvent.keyCode : 0;
        oEvent.eventPhase = 2;
        oEvent.isChar = ( oEvent.charCode > 0 );
        oEvent.pageX = oEvent.clientX + document.body.scrollLeft;
        oEvent.pageY = oEvent.clientY + document.body.scrollTop;
        oEvent.preventDefault	= function(){ this.returnValue = false; };
		
        if( oEvent.type == "mouseout" )
            oEvent.relatedTarget = oEvent.toElement;
		else if( oEvent.type == "mouseover" )
            oEvent.relatedTarget = oEvent.fromElement;
			
        oEvent.stopPropagation	= function(){ this.cancelBubble = true; };
        oEvent.target			= oEvent.srcElement;
        oEvent.time				= (new Date).getTime();
    }
    return oEvent;
};

EventUtil.getEvent = function()
{
    if (window.event) {
        return this.formatEvent(window.event);
    }
	else {
        return EventUtil.getEvent.caller.arguments[0];
    }
};


	function FormWatcher( formObj, OnChangeHandler ) //catches all clicks, keyups, changes, for every type of form element
	{
		this.FormObj	= formObj;					//HTMLFormElement object
	var frmInputs	= this.FormObj.getElementsByTagName("*");	//such a long reference
		
		this.SetOnChange = function( OnChangeHandler )
		{
		for( var i = 0; i < frmInputs.length; i++ )
		{
			FormWatcher.MonitorInput( frmInputs[i], OnChangeHandler );
		}
	};		
	 //nothing
	if( OnChangeHandler != null )
	{
		this.SetOnChange( OnChangeHandler );
	}
}
FormWatcher.MonitorInput = function( el, OnChangeHandler )
{
	switch( el.nodeName )
	{
		case HtmlForm.ControlType.Input:			
			switch( el.type )
			{
				case HtmlForm.InputType.Text:	
				case HtmlForm.InputType.Password:
					EventUtil.addEventHandler( el, "keyup",		OnChangeHandler );
					//EventUtil.addEventHandler( el, "change",	OnChangeHandler );
					break;
				case HtmlForm.InputType.Submit:
				case HtmlForm.InputType.Image:
				case HtmlForm.InputType.Reset:
				case HtmlForm.InputType.Button:
					EventUtil.addEventHandler( el, "click", OnChangeHandler );
					break;
					
				case HtmlForm.InputType.Radio:
				case HtmlForm.InputType.CheckBox:
					EventUtil.addEventHandler( el, "mousedown", OnChangeHandler );
					EventUtil.addEventHandler( el, "click", OnChangeHandler );
					EventUtil.addEventHandler( el, "change", OnChangeHandler );
					break;
			}
			break;
			
		case HtmlForm.ControlType.Select:
			EventUtil.addEventHandler( el, "keyup", OnChangeHandler );
			EventUtil.addEventHandler( el, "change", OnChangeHandler );
			break;
						
		case HtmlForm.ControlType.TextArea:
			EventUtil.addEventHandler( el, "keyup", OnChangeHandler );
			EventUtil.addEventHandler( el, "change", OnChangeHandler );
			break;
	}
}

	/***********************************\
	|*	Html.js						   *|
	|*  For Working with the DOM	   *|
	\***********************************/
	
	//I would consider omitting this if prototype were less of a monster...
	this.Find = function( id ) {
		return document.getElementById( id );
	};
	this.Tag = Find; //For backwards compatibility...
	
	this.TagsByName = function( name ) {
		return document.body.getElementsByTagName( name ); //getElementsByTagName implemented in DOM Level 1 Core
	};
	
	//Gets only the inner text nodes from an XHTML Node
	this.GetInnerText = function( xNode )
	{
		if( xNode.textContent )
			return xNode.textContent;
		else if( xNode.innerText )
			return xNode.innerText;
		else
			return null;
	}
	
	this.Coffee = function( el ) { //we are here to make coffee metal, we will make everything metal...blacker than the blackest black times infinity
		if( typeof(el) == "string" ) { /* find the element using doc.getElById */ 
			CoffeePaperElement.call( Tag(el) );
			return el;
		}
		else { //it had better be an element already
			return CoffeePaperElement.call( el );
		}
	}
	
	/*String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g,""); }
	String.prototype.ltrim = function() { return this.replace(/^\s+/,""); }
	String.prototype.rtrim = function() { return this.replace(/\s+$/,""); }*/
	
//As far as I know, this is the latest and greatest HtmlForm.js
this.HtmlForm = function()
{
	var constraints = new Map( {} ); //muhahahahahahahahahahahahahahahahahahahaha
	//constraints contains functions that can be used to evaluate constraints, oh yeah		
	constraints["email"		] = Constraints.Email		; //whatever@whatever.com
	constraints["url"		] = Constraints.Url			; //http://whatever.com
	constraints["money"		] = Constraints.Money		; //includes dollar sign or not
	constraints["ip"		] = Constraints.IpAddress	; //255.255.255.255
	constraints["phone"		] = Constraints.PhoneNumber	; //North American Phone Number
	constraints["zip"		] = Constraints.Zip			; //36106, doesn't support two-part zipcodes
	constraints["number"	] = Constraints.Number		; //any number, including decimals
	constraints["int"		] = Constraints.Integer		; //any integer, decimals are not allowed
	constraints["date"		] = Constraints.Date		; //any Date, decimals are not allowed	
	
	function InputConstraintPair( el, c ) { //private inner class of objects meant to only be used inside!	
		this.Element	= el;
		this.Constraint	= c;
	}
	
	//Specialized Member Initialization
	this.parents		= new Array();	//private
	this.constrained	= [];			//private...but less so		
	
	//input to be constrained, constraint
	this.AddConstraint = function( input, constraint ) //called only once for any given element so performance of with keyword is not an issue!
	{
		this.constrained.push( new InputConstraintPair( input, constraint ) ); //sweet!		
		with( HtmlForm.InputType ) 	{
			if( input.type == CheckBox || input.type == Radio ) {
				for( var i = 0; i < this.parents.length; i++ ) {				
					var inputs = this.parents[i].getElementsByTagName( "input" );
					for( var j = 0; j < inputs.length; j++ )					
						if(	inputs[j].name == input.name && ( inputs[j].type == CheckBox || inputs[j].type == Radio ) )
							FormWatcher.MonitorInput( inputs[j], this.formInputChanged );					
				}
			}			
			else
				FormWatcher.MonitorInput( input, this.formInputChanged );		
		}
	};
	
	/* Setting the internal Use FormInput Getters */
	this.GetRadioGroupValue		= RadioGroup.GetValue;
	this.GetCheckGroupValue		= CheckBoxGroup.GetValue;
	this.GetSimpleInputValue	= function ( InputObj ) { return InputObj.value; } 
	this.GetSelectValue			= MultiSelect.GetValue;
	this.GetTextAreaValue		= function ( InputObj ) { return InputObj.value; }
	/* End Internal Use Getters */
			
	this.InputValueLookupCol = new NVC(); //TODO: rename InputValueLookupCol
	with( this.InputValueLookupCol )
	{
		Add( HtmlForm.InputType.Text,		this.GetSimpleInputValue );
		Add( HtmlForm.InputType.Password,	this.GetSimpleInputValue );
		Add( HtmlForm.InputType.CheckBox,	this.GetCheckGroupValue  );
		Add( HtmlForm.InputType.Radio,		this.GetRadioGroupValue  );
		Add( HtmlForm.InputType.Submit,		this.GetSimpleInputValue );
		Add( HtmlForm.InputType.Image,		this.GetSimpleInputValue );
		Add( HtmlForm.InputType.Reset,		this.GetSimpleInputValue );
		Add( HtmlForm.InputType.Button,		this.GetSimpleInputValue );
	    Add( HtmlForm.InputType.Hidden,		this.GetSimpleInputValue );
	    Add( HtmlForm.InputType.File,		this.GetSimpleInputValue );
	}
	
	this.GetControlValue = function( ControlObj )
	{
		switch( ControlObj.nodeName )
		{
			case HtmlForm.ControlType.Input:
				return this.GetInputValue( ControlObj );
			case HtmlForm.ControlType.Button:
				return this.GetInputValue( ControlObj );
			case HtmlForm.ControlType.Select:
				return this.GetSelectValue( ControlObj );
			case HtmlForm.ControlType.TextArea:
				return this.GetTextAreaValue( ControlObj );
			default:
				throw ("The parameter 'ControlObj' is not a W3C Recognized Form Control");
		}
	};
	
	this.GetInputValue = function( InputObj )
	{	//the InputValueLookupCollection returns a function, which is passed the InputObj
		return this.InputValueLookupCol.Get( InputObj.type )( InputObj );
	};
	
	//this has no comments!!!	
	this.ToNVC = function() {
		var formValues = new NVC();
		for( var i = 0; i < this.parents.length; i++ )		
			formValues.Add( Element.ToNVC( this.parents[i] ) );
		return formValues;
	};
	
	this.Validate = function( onComplete ) //validate just takes one
	{
		var ErrorMsgs = new Array(); //start out with empty error msgs
		function fail_accumulate( el, ex, errMsg, i ) {
			ErrorMsgs[i] = HtmlForm.PascalToEnglish( el.name ) + " " + errMsg + "\n";
		}
		function onCompleteWrap()
		{
			if( !onComplete ) //if onComplete is not set
				return;
			onComplete( ErrorMsgs.join("") ); //calling the function we have wrapped with the errors we have accrued
		}
		this.ErrorMsgs = ""; //reset musty old msgs
		if( !this.CheckUserConstraints( fail_accumulate, null, null, null ) )	 {
			onCompleteWrap();
			return false;
		}
		return true;
	};
	
	var onFormChanged = new Array();
	this.formInputChanged = function()
	{
		var e = EventUtil.getEvent(); //get the even for this function
		for( var i = 0; i < onFormChanged.length; i++ )	
			onFormChanged[i]( e ); //call the function returned by the indexing function of the array, use the event as a single argument
	};

	this.Constrain = function( id ) //for help in validating a non-HtmlFormElement object
	{	//requires the Surf lib
		var htmlForm	= this; //necessary, since surf's surf function's "this" is the parent element
		var constrainer	= Surf( id );
		this.parents.push( constrainer ); //very important, necessary that this happens before AddConstraint is called because
		
		constrainer.descend( function( el ) //addConstraint puts some listeners into place to allow for form colorization
		{
			if( el.getAttribute("required") == "true" || el.getAttribute("required") == "required" )	
				htmlForm.AddConstraint( el, Constraints.Required );
			if( el.getAttribute("dtype") != null && el.getAttribute("dtype") != "" )
				htmlForm.AddConstraint( el, constraints[el.getAttribute("dtype")] ); //constraints is a map of constraints
			if( el.getAttribute("dmax") != null && el.getAttribute("dmax") != "" )
				htmlForm.AddConstraint( el, new MaxLengthConstraint( parseInt( el.getAttribute("dmax" ) ) ) );
			if( el.getAttribute("dmin") != null && el.getAttribute("dmin") != "" )
				htmlForm.AddConstraint( el, new MinLengthConstraint( parseInt( el.getAttribute("dmin" ) ) ) );			
			return false; //go through all the children
		} );
	};		

	this.CheckUserConstraints = function( fail, pass, onComplete, e ) //OnInputPass is never used
	{
		try { //in case the validation code itself throws an error, we can't have that data being submitted		
			if( fail == null ) //we must set up the correct event handlers
				fail = HtmlForm.ValidateExceptionHandler;
			if( pass == null )
				pass = HtmlForm.ValidateSuccessHandler;
	
			var errNum = 0;
			var errors = new NVC(); //holds errors, woopsey, gotta bring it out of the loop! DOH!
			for( var i = 0; i < this.constrained.length; i++ ) {
				if( !e || e.target == null || e.target.name == this.constrained[i].Element.name ) { //if the event was missing do it anyway
					var pair		= this.constrained[i];							//grab the right pair
					var el			= pair.Element;									//just allows me to re-use my old code
					var constraint	= pair.Constraint;								//get the constraint...muhahaha!
					if( !constraint.Evaluate( this.GetControlValue( el ) ) )
					{
						errNum++; //increment the errorCount
						errors.Set( el.name, true ); //indicate that an error has occurred for this element
						fail( el, HtmlForm.CustomException, constraint.ErrorMsg, i ); //TODO: reimplement control lookup using a map for more efficiency
					}
					else				//if an error for that form name exists
						pass( el, i );	//do not let it pass
				}						//more than one constraint can be validated at a time
			}
			
			if( errNum > 0 ) {
				if( onComplete )
					onComplete();
				return false;
			}
		}
		catch( ex ) {
			alert( "An error occurred during validation.  Form cannot submit.  Contact an administrator or technical support.\nError Details: " + ex );
			return false; //make sure the form doesn't submit
		}
		return true; //form validation completed
	};
	
	this.Colorize = function( color )
	{
		if( !color )
			color = "#ffeded";
		
		var ErrorMsgs = new Array();				
		function fail( a, b, c, i ) { //Override the current one with our own!
			ErrorMsgs[i] = HtmlForm.PascalToEnglish( a.name ) + " " + c + "<br/>";
			a.style.backgroundColor = color; //TODO: replace this with a className assignment
			a.setAttribute( "title", "This " + c );
		}
		function pass( el, i ) {
			ErrorMsgs[i] = "";
			el.setAttribute( "title", "" ); //this one should just remove the classname
			el.style.backgroundColor = "";
		}
		var _form = this;
		function ColorForm_Validate( e ) {
			_form.CheckUserConstraints( fail, pass, null, e );
			var errList = Tag("ErrorList"); //good happy fun time go juice!
			if( errList != null )
				errList.innerHTML = ErrorMsgs.join("");
		}
		//for( var i = 0; i < this.constrained.length; i++ )
		onFormChanged.push( ColorForm_Validate ); //now do it the first time
		this.formInputChanged(); //validate all fields
	};
	
	this.ToString = function() { return this.ToNVC().ToString(); }

	//Optimized by Cory Dambach - 1:39 PM 6/19/2006 
	this.Fill = function( formValues ) //TODO: Fix, does not handle multi-selects
	{
		for( var i = 0; i < this.parents.length; i++ )
		{
			var els = this.parents[i].getElementsByTagName("*");
			for( var j = 0; j < els.length; j++ )
			{
				var el = els[j]; //sit! stay!
		    	if(!(el.tagName == "INPUT" || el.tagName == "SELECT" || el.tagName == "TEXTAREA"))
		    	    continue; //don't try to fill itmove on!
				
				if( !formValues.ContainsKey( el.name ) )
					continue; //FIXES: CheckVals is null in case HtmlForm.InputType.CheckBox
				//adding a comment to test SVN, delete if you find and I forgot about it.
				//the below switch is where the element is actually filled with a value
				switch( el.type )
				{
					case HtmlForm.InputType.Radio:
					case HtmlForm.InputType.CheckBox:
						var CheckVals = formValues.GetValues( el.name );
						for( var k = 0; k < CheckVals.length; k++ )						
							if( el.value == CheckVals[k] )							
								el.checked = true;						
						break;
						
					case HtmlForm.ControlType.Select: //implement multi-select support!!!
						if( IsSet( el.getAttribute("multiple") ) )
							alert("Does not work with multi-select!");
						break;
					
					default:
						el.value = formValues.Get( el.name );
						break;
				}
			}
		}
	};
			
	this.Clear = function()
	{
		for( var i = 0; i < this.parents.length; i++ ) {
			var els = this.parents[i].getElementsByTagName("*");
			for( var j = 0; j < els.length; j++ )
			{
				var el = els[j];
		    	if(!(el.tagName == "INPUT" || el.tagName == "SELECT" || el.tagName == "TEXTAREA"))
		    	    continue; //don't try to fill itmove on!				
				
				switch( el.type ) //the below switch is where the element is actually filled with a value
				{
					case HtmlForm.InputType.Radio:
					case HtmlForm.InputType.CheckBox:
						el.checked = false;
						break;
						
					case HtmlForm.ControlType.Select: //implement multi-select support!!!
						if( IsSet( el.getAttribute("multiple") ) )
							alert("Does not work with multi-select! or maybe it does!?!?!?");
						break;
					
					default:
						el.value = ""; //set it with nothing
						break;
				}
			}
		}	
		this.formInputChanged();
	};
	
	//is not accessible outside of this constructor
	function IsSet( obj ) { //returns true if the object is set to a meaningful value
		return( obj != null && obj.toString() != "" );
	}
	
	//And now initialization of the HtmlForm itself	
	for( var i = 0; i < arguments.length; i++ )
	{
		if( typeof(arguments[i]) == "string" ) //the argument is an id
		{
			var el = Tag( arguments[i] );
			this.Constrain( el );
		}	
		else
			this.Constrain( arguments[i] ); //the argument is an element
	}
};

	/* Default Exception Handler for Form.Validate */
	HtmlForm.ValidateExceptionHandler = function( InputObj, ExceptionType, Msg )
	{
		switch( ExceptionType )
		{
			case HtmlForm.RequiredException:								
			case HtmlForm.TypeException:								
			case HtmlForm.LengthException:
				alert( HtmlForm.PascalToEnglish( InputObj.name + " " + Msg ) );
				InputObj.focus();
				break;
		}
		return;
	}
	/* Default Input Succes Handler for Form.Validate */
	HtmlForm.ValidateSuccessHandler = function( InputObj )	{
		InputObj.style.backgroundColor = ""; //clear color
	}
	
	/* Converts PascalCaseIdentifiers to User Friendly English */
	HtmlForm.PascalToEnglish = function(str) {
		str = str.replace( /(.*?[a-z])([A-Z].*?)/g, "$1 $2"				);
		str = str.replace( /([A-Z][A-Z].*?)([A-Z][a-z].*?)/g, "$1 $2"	);
		return str;
	}

	HtmlForm.ControlType = {		//W3C Canonical Uppercase
		Input		: "INPUT",		//See Form.InputType
		Button		: "BUTTON",		//See Form.ButtonType
		Select		: "SELECT",
		TextArea	: "TEXTAREA"
	};
	HtmlForm.InputType = {
		Text		: "text",		// Simple
		Password	: "password",   // Simple
		CheckBox	: "checkbox",   // CheckBoxGroup.GetValue	( CheckBoxObj.name );
		Radio		: "radio",      // RadioGroup.GetValue		( RadioObj.name );
		Submit		: "submit",     // Simple
		Image		: "image",      // Not sure about this one
		Reset		: "reset",      // Not sure if this should be used for its value
		Button		: "button",     // Simple
		Hidden		: "hidden",     // Simple
		File		: "file"		// Simple
	};
	HtmlForm.ButtonType = { /* <button type="submit"><img src="fubar.gif"/></button> */
		Button		: "button",
		Submit		: "submit", 	
		Reset		: "reset"
	};

	function MultiSelect() { /* useless constructor for now */ }
	MultiSelect.GetValue = function( select ) /* static */
	{
		if( select.multiple ) { //if its a multi-select return an array
			var values = new Array();
			for(var i = 0; i < select.length; i++)
			{
				if( select.options[i].selected )					
				{
					if( select.options[i].value == null || select.options[i].value == "" )
						values.push( select.options[i].innerHTML.trim() ); //do what firefox does automatically for IE
					else
						values.push( select.options[i].value );
				}
			}
			return values;
		} // : array
		return select.value; // : string
	};

	function RadioGroup( formObj, radioInputName ) /* constructor */
	{	
		/* Members */
		this.RadioItems = new Array();	//HTMLElement Array
		this.Value		= new String();	//Currently Selected RadioButton's Value
		this.OnChange	= null;			//EventHandler( object Sender, EventArgs e );
		
		/* Member Methods */
		with( this )
		{
			//event handler, is not called with the context of the RadioGroup object...
			//So I used With! screw you IE
			//no need to use this. resolver inside of here
			OnValueChanged = function()
			{
				for( var i = 0; i < RadioItems.length; i++ )			
					if( RadioItems[i].checked == true )				
						Value = RadioItems[i].value;
									
				if( OnChange != null )
					OnChange( this, Value );
			}
		}
	
		/* Initialization Code */
		var allInputs = formObj.getElementsByTagName( "input" ); //AllRadioButtons is an Html-Node-List	
		for( var i = 0; i < allInputs.length; i++ )
		{
			if( allInputs[i].type == "radio" && allInputs[i].name == radioInputName )
			{
				this.RadioItems.push( allInputs[i] );
				allInputs[i].onclick = OnValueChanged; //My own event handler, so that Value is always correct
				OnValueChanged(); //get the initial value for the group
			}
		}
	}; //End Class RadioGroup
	
	RadioGroup.GetValue = function( RadioInputObj ) { /* Utility Function for use by the Form Object */
		var AllInputs = document.getElementsByTagName( "input" ); //AllRadioButtons is an Html-Node-List	
		for( var i = 0; i < AllInputs.length; i++ )
		{
			if(	AllInputs[i].type == HtmlForm.InputType.Radio &&	//if its a radiobutton 
				AllInputs[i].name == RadioInputObj.name &&			//&& it is in the desired group 
				AllInputs[i].checked == true ) {					//&& its selected
					return AllInputs[i].value; //return the singly selected radiobutton's value
				} //returns a string
			//end of if
		}
		return ""; //if the if doesn't happen, return false...
	};

	function CheckBoxGroup() { 
		/* useless constructor for now */
	}
	CheckBoxGroup.GetValue = function( input ) {
		var values = new Array();
		var inputs = TagsByName( "input" ); //AllRadioButtons is an Html-Node-List	
		for( var i = 0; i < inputs.length; i++ )
		{
			if( inputs[i].type == HtmlForm.InputType.CheckBox && inputs[i].name == input.name && inputs[i].checked == true ) {		
				values.push( inputs[i].value ); /* if its a checkbox && it is in the desired group && its checked */
			} //returns an array
		}
		return values;
	}


function Map( initialMap )
{
	this.AddObject = function( obj ) { //for adding an object mapping to it, for internal use	
		for( var p in obj ) //p = propertyName : String; add the properties to the map
		{
			if( obj[p] instanceof Function || obj[p].DontEnum == true ) //beautiful!!! only want named objects
				continue; //don't pay attention to functions
			this[p] = obj[p]; //will be using the oldAdd
		}
	};	
	this.Add = function( key, value ) { //add a key and a value to the map	
		if( typeof( key ) == "string" )
			this[key] = value;
		else if( typeof(key) == "object" ) //add the object as if we were combining two maps
			this.AddObject( key );
		else if( key == null )
			return; //allow for no object to be passed in
		else
			throw new Exception( "The object could not be added to the map!" );
	};	
	this.ToNVC = function() { //implementing some useful conversions
		var nvc = new NVC();
		for( var p in this )
		{
			if( this[p] instanceof Function || this[p].DontEnum == true ) //beautiful!!! only want named objects
				continue; //don't pay attention to functions
			nvc.Add( p, this[p].toString() ); //add every enumerable property of 'this' to an NVC
		}
		return nvc; //return the new NVC
	};
	this.URLEncode = function()
	{	//to allow this object to be sent with any of the XmlHttp classes
		return this.ToNVC().URLEncode();
	};
	this.ToObjectString = function( obj )
	{
		if( arguments.length == 0 ) { //first call		
			return "{" + this.ToObjectString( this ) + "}"; //will return the rest of the strings
		}
		else {
			for( var p in this )
			{
				if( this[p].DontEnum == true /* || this[p] instanceof Function */  ) //don't exclude functions this time
					continue; //exclude the DontEnums
				else
				{
					switch( typeof(p) )
					{
						case "number":
						case "string":
							break;												
						default:
							break; //TODO: Finish Later...
					}
				}			
			}
		}
	};

	this.AddObject	.DontEnum = true; //ahhhhh yeah
	this.Add		.DontEnum = true; //ahhhhh yeah
	this.ToNVC		.DontEnum = true; //ahhhhh yeah
	this.URLEncode	.DontEnum = true; //ahhhhh yeah
	this.Add( initialMap );
}
/**
 * @author Cory
 */

	this.XmlHttp = {};
	
	XmlHttp.ReadyStates = { //These are the states as defined by
		Loading		: 1,
		Loaded		: 2,
		Interactive	: 3,
		Completed	: 4
	};
	
	XmlHttp.StatusCodes = {	//just the most common ones, feel free to add one if you need one
		Continue	: 100,	//if you need more follow the link below
		OK			: 200,	//http://msdn2.microsoft.com/en-us/library/ms767625.aspx
		Ok			: 200,	//just allow people to use both
		NotFound	: 404
	};
	
	//original I made to wrap the XMLHttpRequest and Msxml.XMLHTTP object
	this.XmlHttpClient = function() /* Cross browser wrapper for XMLHttpRequest and Msxml.XMLHTTP */
	{
		var XmlObj;
		if( window.XMLHttpRequest ) 
			return new XMLHttpRequest();	// Create XMLHttpRequest for Firefox, Opera, Safari, Konqueror, and Webkit
		else if ( window.ActiveXObject )	// Check for IE XMLHTTP Object
		{
			XmlObj = new ActiveXObject( "msxml2.XMLHTTP.3.0" );
			if ( XmlObj )	
				return XmlObj;			
		}
	}
	
	this.XmlClient = function( OnResponseComplete, OnResponseError )
	{
		var client		= new XmlHttpClient(); //inner scope, this is what makes it all happen, no access to this outside
		var ReadyState	= XmlHttp.ReadyStates; //to give us shorthand access
		var Status		= XmlHttp.StatusCodes; //again gives us shorthand access
		
		this.HttpStateChanged = function()
		{
			if( client.readyState == ReadyState.Completed ) //ReadyStates 1: Loading; 2: Loaded; 3: Interactive; Completed
			{
				if ( client.status != null && client.status == Status.OK )				
					if( OnResponseComplete )
						return OnResponseComplete(); //return to end processing since it is a callback the user has no access to the callback's return value anyway
				if( OnResponseError ) //if an event handler was provided
					OnResponseError();//execute it
			}
		};
		client.onreadystatechange = this.HttpStateChanged; //set up our callback
		
		this.Abort				= function	() { client.abort();				};
		this.GetResponseText	= function	() { return client.responseText;	};
		this.GetResponseXml		= function	() { return client.responseXML;		};
		this.GetResponseXML		= this.GetResponseXml;
		
		this.Send = function( values, optionalURL ) //can accept more than an NVC
		{
			if( optionalURL )
				client.open( "POST", optionalURL, true );
			else
				client.open( "POST", location.pathname, true ); //don't include the QueryString of the current page
			client.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );
			client.send( values.URLEncode() );
		}
		this.SendNVC = this.Send; //backward compatibility
	}//End XmlClient

	/* One time Xml over Http Request */
	this.Post = function( inNVC, optionalURL ) //consider importing these functions into XmlHttp namespace
	{
		var httpClient = new XmlHttpClient();
		if( optionalURL )
			httpClient.open( "POST", optionalURL, false );		
		else		
			httpClient.open( "POST", location.pathname, false );

		httpClient.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );
		httpClient.send( inNVC.URLEncode() );
		return httpClient.responseText;
	}
	
	this.PostQueryString = function( QueryString, optionalURL )
	{
		var httpClient = new XmlHttpClient();
		if( optionalURL )
			httpClient.open( "POST", optionalURL, false );
		else
			httpClient.open( "POST", location.pathname, false );
			
		httpClient.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );
		httpClient.send( QueryString );
		return httpClient.responseText;
	};
	this.SendNVC			= Post;	
	this.Send				= Post;
	this.SendQueryString	= PostQueryString;

	/* Performs an HTTP Get of the document specified by the URL */
	this.GetDocument = function( optionalURL )
	{
		var httpClient = new XmlHttpClient();
		if( optionalURL )
		{
			httpClient.open( "GET", optionalURL, false );
		}
		else
		{
			httpClient.open( "GET", location.pathname, false );
		}
		httpClient.send( null );
		return httpClient.responseText;
	};

	/* this.Request = function( OnSuccess, OnFailure )
	{
		var Success = OnSuccess;
		var Failure = OnFailure;
		var client	= this; //maintain a lexical reference to the current object
		
		//and apply the construction to the current execution object
		//the methods below are callbacks that call the users callbacks passing the responseText to them
		function OnResponseComplete() {
			var responseText = client.GetResponseText();
			Success( responseText ); //call the users event handler
		}
		function OnResponseFailure() {	//context of this function gets fucked up in IE anyway, 
			var responseText = client.GetResponseText();
			Failure( responseText ); //call the users event handler
		}
		NVC.call		( this ); //inherit all methods and properties from the NVC constructor
		XmlClient.call	( this, OnResponseComplete, OnResponseFailure ); //construct an XmlClient using the arguments passed to this function	
		//var GetResponseText = this.GetResponseText;

		this.oldSend = this.Send;		//store the old send, cause we are gonna use it
		this.Send = function( url )	//now override the old one, and...
		{ //don't need to give me a NVC, I am the NVC
			this.oldSend( this, url ); //call the oldSend, with the argument of the new one
		};
	};	*/
	
	//Depends on NVC.js - it inherits from it
	this.Request = function( OnSuccess, OnFailure )
	{		
		var success			= OnSuccess;		//maintain a lexical reference to the current object
		var failure			= OnFailure;		//and apply the construction to the current execution object
		var client			= this; 			//the methods below are callbacks that call the users callbacks passing the responseText to them
		var Expectations	= new Array();
		
		//internal use class - so far as I know, it is only used in Expects( a, b ) 
		//below so I moved it inside here to prevent confusion
		function Expectation( object, block )
		{
			this.whatToExpect	= object;
			this.whatToDo		= block;
		}
				
		function OnResponseComplete() {
			var responseText = client.GetResponseText();
			var expectationsResult = EvaluateExpectations( responseText ); //parse, evaluate, and remove my Great Expectations			
			if( success != null )
				success( responseText ); //call the users event handler 
		}
		function OnResponseFailure() {	//context of this function gets messed up in IE anyway, 
			var responseText = client.GetResponseText();
			if( failure != null )
				failure( responseText ); //call the users event handler
		}
		
		function EvaluateExpectations( response ) //find any error messages, and display them
		{
			var respDiv = document.createElement("div"); //create ghost div to house our Xml
			respDiv.innerHTML = response;
			var spans = respDiv.getElementsByTagName( "span" );
			for( var i = 0; i < spans.length; i++ )
			{
				for( var j = 0; j < Expectations.length; j++ ) //go through each expectation
				{	//
					if( Expectations[j].whatToExpect == spans[i].id ) //should be a string comparison
					{
						Expectations[j].whatToDo( spans[i].innerHTML ); //run the expectation
						spans[j].parentNode.removeChild( spans[j] ); //gets the XmlMsg out of the innerHTML
					}					
				}
			}
			return respDiv.innerHTML;
		}
		
		NVC.call		( this ); //inherit all methods and properties from the NVC constructor
		XmlClient.call	( this, OnResponseComplete, OnResponseFailure ); //construct an XmlClient using the arguments passed to this function

		this.Expects = function( id, block )
		{
			var expectation = new Expectation( id, block );
			Expectations.push( expectation );
		};
		
		//if( DisplayMsg )
		//	this.Expects( "err", DisplayMsg ); //display the msg inside of the 'err' Span
		//TODO: change the name of the err span to something more generic and meaningful

		this.oldSend = this.Send;		//store the old send, cause we are gonna use it
		this.Send = function( url )		//now override the old one, and...
		{								//don't give me an NVC, I am the NVC
			this.oldSend( this, url );	//call the oldSend, with the argument of the new one
		};
	}; 
	

	/* JavaScript.Net											*/
	/* System.Collections.Specialized.js						*/
	/* An Implementation of the NameValueCollection Class		*/
	/* Last Modified by Cory Dambach June 2, 2006				*/
	/* Get() no longer throws an error when key doesn't exist	*/	
	function NVC()
	{
		this.Keys		= new Array();
		this.Values		= new Array();
		this.longestKey	= 0;

		this.Add = function( key, value )
		{
			if( key instanceof NVC ) {			//is key a NVC? if so...
				return this.AddNVC( key );		//add the keys and values of the nvc
			}
			
			if( key.length > this.longestKey )
				this.longestKey = key.length;
			
			var keyIndex = this.IndexOfKey( key ); //get the index if the key already exists
			if( keyIndex == -1 ) //is -1 when the key isn't held in the NVC
			{	//lets add it then
				var values = new Array();			//create new array
				values.push( value );				//add the value to the new values array
				//TODO:Perf Testing with hashtable for key Index lookup 
				//next is the parallel array goodness
				this.Keys.push		( key		);	//key added
				this.Values.push	( values	);	//array can safetly hold more than one value using the array
			}
			else
			{
				if( value instanceof Array )
					for( var i = 0; i < value.length; i++ )
						this.Values[keyIndex].push( value[i] ); //add each value in the array to the array that already exists for that key
				else
					this.Values[keyIndex].push( value ); //add the value to the end of the array of values that already exists for that key	
			}			
			return;
		}
		this.AddNVC = function( nvc )
		{
			for( var i = 0; i < nvc.Keys.length; i++ )
			{
				var values = nvc.GetValuesAt( i );
				this.Add( nvc.GetKeyAt( i ), nvc.GetValueAt( i ) );
			}
		}
		this.Set = function( key, value )
		{
			if( key.length > this.longestKey )
				this.longestKey = key.length;						
			
			var values = new Array();		//create new array
			if( value instanceof Array )	//see if the value passed in is an array itself
				values = value;				//set the sub-array to the passed in array			
			else							//the value is not an array so prolly a string, so treat it normally
				values.push( value );		//add the value to the end of the array of values that already exists for that key
				
			var keyIndex = this.IndexOfKey( key );
			if( keyIndex == -1 ) //if key does not already exist
			{
				this.Keys.push		( key   );		//parallel array goodness
				this.Values.push	( values );		//forgot to change 
			}
			else //the keyIndex was not equal to -1 and so we can use it
			{	//fixed typo; keyIndex was KeyIndex...			
				this.Values[keyIndex] = values;
			}
			return;
		};
		
		/* [Was Unsafe] - [Now Safe] */
		this.GetValues = function( key ) {
			var pairIndex = this.IndexOfKey( key );
			if( pairIndex > -1 )		
				return this.Values[pairIndex]; //is now an array, so we are good
			return null;
		};
		
		/* [Was Unsafe] - [Now Safe] */
		this.GetValuesAt = function( index ) {
			return this.Values[index]; //is now an array, so we are good
		};
		
		//Used to throw an error if the key did not exists
		//Changed it to mirror the functionality of the .NET Framework
		//Now it returns null if the key does not exist
		this.Get = function( key ) {
			var keyIndex = this.IndexOfKey( key );
			if( keyIndex > -1 )	//do we have an entry for it?
			{
				if( this.Values[keyIndex].length == 1 )			
					return this.Values[keyIndex][0]; //fixes a problem with storing things other than strings				
				else				
					return this.Values[keyIndex].join(','); //if there is more than one value, returns a comma delimited list of the values				
			}
			return null; //return null, used to throw error, didn't match the .NET Framework
		};
		
		this.GetLongestKeyLength = function() //might need it for string replacement ninja action!!! Hiya!!!!!	
		{
			return this.longestKey;
		}
		
		this.ForEach = function( keyfunction ) {			
			for( var i = 0; i < this.Keys.length; i++ ) {
				keyfunction( this.Keys[i] );
			}	
		}
		
		this.RemoveKey = function( key )
		{
			var PairIndex = this.IndexOfKey( Key );
			if( PairIndex > -1 )
			{
				 this.Values.splice( PairIndex, 1 ); //remove the keys
				 this.Keys.splice( PairIndex, 1 );//and the values for that entry
			}
			return null;			
		};		
		
		//simple utility functions
		this.GetKeys	= function()			{ return this.Keys;			}
		this.GetValue	= function( Key )		{ return this.Get( Key );		}
		this.GetKeyAt	= function( Index )	{ return this.Keys[Index];		} // : String
		this.GetValueAt	= function( Index )	{ return this.Values[Index];	} // : Array [Unsafe]
		//end utility functions
		
		this.URLEncode = function() {
			var QueryString = new String();
			for( var i = 0; i < this.Keys.length; i++ )
			{
				QueryString += encodeURIComponent( this.GetKeyAt(i) ) + "=" + encodeURIComponent( this.GetValueAt(i) );
				if( (i+1) != this.Keys.length ) {
					QueryString = QueryString + "&";
				}
			}
			return QueryString;
		};	
		this.ContainsKey = function( Key ) {
			for( var i = 0; i < this.Keys.length; i++ )
				if( this.Keys[i] == Key )
					return true;
			return false;
		};
		this.IndexOfKey = function( Key )
		{
			//internal
			for( var i = 0; i < this.Keys.length; i++ )			
				if( this.Keys[i] == Key )			
					return i;			
			return -1;
		};		
		this.ToString = function() { //outputs a nicely formatted string
			var myKeys = this.GetKeys();
			var Result = new String();
			for( var i = 0; i < myKeys.length; i++ )
				Result = Result + myKeys[i] + " = " + this.Get( myKeys[i] ) + "\n";
			return Result;
		};
		
		this.ToElementalXml = function()
		{
			alert("Not yet implemented");
		}
	}//end of NVC Constructor
	
	/* Static Methods */
	NVC.PrintNVC = function( pNVC ) //[Obsolete] This method is a candidate for deletion
	{
		var myKeys	= pNVC.GetKeys();
		var res		= "";
		for( var i = 0; i < myKeys.length; i++ )
		{
			res += myKeys[i] + " = " + pNVC.Get( myKeys[i] ) + "<br />";			
		}
		document.write( res );
	};
	
	NVC.CreateFromQueryString = function( QueryString )
	{
		var outNVC			= new NVC();
		var arrQueryString	= QueryString.split( '&' );
		for( var i = 0; i < arrQueryString.length; i++ )
		{
			var arrPair	= arrQueryString[i].split('=');
			var Name	= arrPair[0];
			var Value	= arrPair[1];			
			outNVC.Add( decodeURIComponent(Name), decodeURIComponent(Value) );
		}
		return outNVC;
	};	
	this.NameValueCollection = NVC;

	//Element.ToNVC: get all input inside a Tag
	if(!Element) var Element = new Object();
        
	Element.ToNVC = function(frmObj) {
		var el = frmObj.getElementsByTagName( "*" );		
		var resultNVC = new NameValueCollection();
		
		var ElementValue = new String();			
		var ElementName	= new String();
		var ElementType	= new String();
		
		for( var i=0; i < el.length; i++ ) {
		    if(!(el[i].tagName == "INPUT" || el[i].tagName == "SELECT" || el[i].tagName == "TEXTAREA"))
		        continue;
		
			ElementValue = "";
			ElementName  = "";
			ElementType  = "";
			
			if(el[i].getAttribute("name") != null)
				ElementName = el[i].getAttribute("name");
			if (el[i].multiple) {//if it is a multiple select
			    //alert("This is a mutiple selection : " + el[i].getAttribute("name"));
			    for (var ii = 0; ii < el[i].length; ii++)
			        if( el[i].options[ii].selected )
			            resultNVC.Add(el[i].name, el[i].options[ii].value);
			    continue;
			}			
				
			if (el[i].getAttribute("value") != null)
				ElementValue = el[i].getAttribute("value");
			if (el[i].getAttribute("type") != null)
				ElementType = el[i].getAttribute("type");
				
			if ((ElementName.length > 0) && (ElementValue.length >= 0))	{
				switch (ElementType)	{
					case "radio":
					case "checkbox":
						if (el[i].checked) { 
							resultNVC.Add(el[i].name, el[i].value);
						}
						break;
					default:
						resultNVC.Add(el[i].name, el[i].value);
						break;
				}
			}			
		}
		return resultNVC;
	}


	/* System.Xml.Serialization */

	/*
	Object.prototype.Type		= "Object";
	String.prototype.Type		= "String";
	Number.prototype.Type		= "Number";
	Array.prototype.Type		= "Array";
	Date.prototype.Type			= "Date";
	Function.prototype.Type		= "Function";
	*/

/*
function XmlSerialize( Name, SourceObj, resArray )
{
	if( SourceObj.Type == "String" )
	{
		resArray.push( "<" + Name + ">" + SourceObj + "</" + Name + ">" );
		return;
	}
	else if( SourceObj.Type == "Number" )
	{
		resArray.push( "<" + Name + ">" + SourceObj.toString() + "</" + Name + ">" );
		return;
	}
	else if( SourceObj.Type == "Date" )
	{
		resArray.push( "<" + Name + ">" + SourceObj + "</" + Name + ">" );
		return;
	}
	else if( SourceObj.Type == "Function" ) { return; }
	
	if( SourceObj.Type == "Array" )
	{
		resArray.push( "<" + Name + ">" );
		for( var i = 0; i < SourceObj.length; i++ )
		{
			XmlSerialize( "Item:" + i.toString(), SourceObj[i], resArray );
		}
		resArray.push( "</" + Name + ">" );
		return;
	}
	else if( SourceObj.Type == "Object" )
	{
		resArray.push( "<" + Name + ">" );
		for( var Member in SourceObj )
		{
			XmlSerialize( Member, SourceObj[Member], resArray );
		}
		resArray.push( "</" + Name + ">" );
	}
}
*/

//Created By Cory Dambach : Last Modified August 18, 2005
//<summary>
//Serializes any javascript object into Xml, for transmission, it is recommended that you escape the xml first!
//</summary>
function __XmlSerialize( Name, SourceObj, resArray )
{
	switch( SourceObj.Type )
	{
		case "Date": //TODO: break this into its own thing later...
		case "String":
			resArray.push( "<" + Name + ">" + SourceObj + "</" + Name + ">" );
			return;
		case "Number":
			resArray.push( "<" + Name + ">" + SourceObj.toString() + "</" + Name + ">" );
			return;
		case "Function":
			return;
		//End of Simple Types
		case "Array":
			resArray.push( "<" + Name + ">" );
			for( var i = 0; i < SourceObj.length; i++ )
			{
				__XmlSerialize( "Item:" + i.toString(), SourceObj[i], resArray );
			}
			resArray.push( "</" + Name + ">" );
			return;
		case "Object":
		default:
			resArray.push( "<" + Name + ">" );
			for( var Member in SourceObj )
			{
				__XmlSerialize( Member, SourceObj[Member], resArray );
			}
			resArray.push( "</" + Name + ">" );
			return;
	}
}

function XmlSerialize( Name, SourceObj ) //: string
{
		var outArray = new Array();
		__XmlSerialize( Name, SourceObj, outArray );
		return outArray.join( "\n" );
}
/* This is a library that aims to make navigation of the DOM much easier */

function Surf( id ){
	var el;
	if( typeof( id ) == "string" )
		el = document.getElementById( id );		
	else
		el = id;
	Surfer.call( el );
	return el;
}

//A surfer surfs to an element or elements via ascent or descent, and then back to its original position 
function Surfer() //meant to be called with the context of an HTMLElement an extender if you will
{
	this.ascend = function( block ) //block should be an anonymous function
	{
		if( block instanceof Function )
		{
			var parent = this.parentNode;
			while( !block( parent ) )
				parent = parent.parentNode;
			return Surf( parent );
		}
	}
	
	this.ascendToFirst = function( tagName )
	{
		var parent = this.parentNode;
		while( parent.tagName != tagName.toUpperCase() )
			parent = parent.parentNode;	
		return Surf( parent );
	}		
	
	this.ascendToClass = function( className ) {
		return Surf( this.ascend( function ( el ) {
			if( el.className == className )
				return true;
			return false;				
		} ) );
	}
	
	this.descend = function( block ) {
		var children = this.getElementsByTagName( "*" );
		for( var i = 0; i < children.length; i++ )
			if( block( children[i] ) == true )
				return Surf( children[i] );
	}	
	
	this.descendToFirst = function( tagName ) {
		var children = this.getElementsByTagName( "*" );
		for( var i = 0; i < children.length; i++ )
			if( children[i].tagName == tagName.toUpperCase() )
				return Surf( children[i] );	
	}	
	
	
	this.descendToClass = function( className ) 
	{
		var children = this.getElementsByTagName( "*" );
		for( var i = 0; i < children.length; i++ )
			if( children[i].className == className )
				return Surf( children[i] );	
	}
	
	this.descendToClassNodes = function( className ) //can return either, a single node or many
	{
		var res = new Array();	
		var children = this.getElementsByTagName( "*" );
		for( var i = 0; i < children.length; i++ )
			if( children[i].className == className )
				res.push( Surf( children[i] ) );
		return res;
	}
}
//Coded by Cory Dambach, Inspired by the C# used in the Mono Projects Developer Blog

this.TemplateRenderer = function( parentEl )
{
	this.templatedEl		= parentEl;
	this.templates			= new Array(); //will be assigned to again later, see GetTemplates
	this.currentTemplate	= 0;
	
	this.RenderFromDataTable = function( dt ) {
		var maxKeyLength = 0; //find the max key length
		for( var i = 0; i < dt.Columns.length; i++ )
			if( dt.Columns[i].length > maxKeyLength )
				maxKeyLength = dt.Columns[i].length;
		
		for( var i = 0; i < dt.Rows.length; i++ )		
			this.Add( dt.GetRowAsNVC( i ), maxKeyLength );		
	}
		
	this.Add = function( nvc, maxKeyLen ) {
		this.templatedEl.appendChild( this.FillTemplate( this.GetNextTemplate(), nvc, maxKeyLen ) );
	}
	   
	this.Clear = function() {
		//this.templatedEl.style.display = "none";
		
		var newParent = this.templatedEl.cloneNode( true );
		document.replaceChild( newParent, this.templatedEl );
		this.templatedEl = newParent;
		return;
		
		while( this.templatedEl.childNodes.length > 0 )
			this.templatedEl.removeChild( this.templatedEl.firstChild );			
		
		if( this.templatedEl.rows )
		{
			if( this.templatedEl.rows.length == 0 ) //this is actually for a wierd firefox behavior
			{
				while( this.templatedEl.firstChild.rows.length != 0 )
					this.templatedEl.firstChild.rows.deleteRow( 0 );
				return;
			}
			else
			{
				while( this.templatedEl.rows.length != 0 )
					this.templatedEl.rows.deleteRow( 0 );
				return;
			}
		}
		this.templatedEl.style.display = "";
	};
	
	this.FillTemplate = function( template, valueNVC, maxKeyLen ) //Works in Firefox2, and IE7
	{
		/* Old stuffy slow way, much worse than any of the others */
//		for( var i = 0; i < valueNVC.Keys.length; i++ )	
//		{ 
//			while( template.innerHTML.indexOf( '{' + valueNVC.Keys[i] + '}' ) > -1 ) //if the {string} still exists in the HTML		
//				template.innerHTML = template.innerHTML.replace( "{" + valueNVC.Keys[i] + "}", valueNVC.Get( valueNVC.Keys[i] ) ); //get by index );
//		}
//		return template;
		
		if( !maxKeyLen ) //then it must be an ad-hoc thing		
			maxKeyLen = valueNVC.GetLongestKeyLength();		
		
		template.innerHTML = TemplateRenderer.FillString( template.innerHTML, valueNVC, maxKeyLen ); //TODO: might want to loop over all variables in the template element itself
		return template; //horribly inneficient, but will be dealing with small amounts of data on the client side, so, let them eat cake
	};
		
	this.GetNextTemplate = function() {
		var nextTemplate = this.GetTemplate( this.currentTemplate )
		this.currentTemplate++;
		return nextTemplate;
	}
		
	this.GetTemplate = function( i ) //a function that returns the appropriate template with respect to the index of the destination child
	{
		if( !i )
			i = 0;
			
		var template = this.templates[i % this.templates.length];
		template = template.cloneNode(true); //deleted line that replaced "Template" in className with nothing
		return template; //now it is ready to be used.
	};
	
	//the below method is private, well it isn't really, but it should not be called after more children have been added
	this.GetTemplates = function() //get any templates that this HTML Element contains, Tested in FireFox2 and IE7, Works!
	{
		var el			= this.templatedEl; //alias the needed var, less typing
		var els			= el.getElementsByTagName( "*" ); //get all child elements
		var templates	= new Array(); //<HTMLElements>, an array of HTMLElements that correspond to templates
		for( var i = 0; i < els.length; i++ )
		{	//foreach element in the resulting list, get the classname value of this element
			var className = els[i].className;
			if( (!className) || className == "" ) //check to see if null, undefined, or empty string
				continue; //move on to the next element		
			else if( className.indexOf( "Template" ) > -1 ) //check for all elements with a class of template
				templates.push( els[i] ); //add the element 
		}
		return templates;
	};		
	
	this.RemoveTemplates = function()
	{
		for( var i = 0; i < this.templates.length; i++ )
			this.templatedEl.removeChild( this.templates[i] );
		return;
	};
	this.templates	= this.GetTemplates( this.templatedEl ); //assign to our templates	
	var tempDiv		= document.createElement( "DIV" );
	for( var i = 0; i < this.templates.length; i++ )
		tempDiv.appendChild( this.templates[i] );
	//now we are good to go!
}

TemplateRenderer.FillString = function( text, valueNVC ) //Works in Firefox2, and IE7
{
	for( var i = 0; i < valueNVC.Keys.length; i++ )	
		while( text.indexOf( '{' + valueNVC.Keys[i] + '}' ) > -1 ) //if the {string} still exists in the HTML
			text = text.replace( "{" + valueNVC.Keys[i] + "}", valueNVC.Get( valueNVC.Keys[i] ) ); //get by index );
	return text; //horribly inneficient, but will be dealing with small amounts of data on the client side, so, let them eat cake
};

TemplateRenderer.RegexFillString = function( text, valueNVC, maxKeyLen )
{

}


/**
 * @author Cory Dambach
 */

 	/* Functions Relating to Web Page User Interfaces */
	/*
		Show			( Url, width, height );	//Displays a popup in the center of the screen.
		ShowDialog	( Url, width, height );	//Shows a Modal Dialog
	*/
	
	var winModalWindow	
	function HandleFocus()
	{
		if ( winModalWindow )
		{
			if (!winModalWindow.closed)
			{
				winModalWindow.focus();
			}
			else
			{
				window.top.releaseEvents ( Event.CLICK | Event.FOCUS );
				window.top.onclick = "";
			}
		}
		return false;
	}
	function IgnoreEvents(e)
	{
		return false;
	}
	function Show( vURL, w, h) //Recommended.  Is the most compatible
	{
		var LeftPos	=	( screen.availWidth		/ 2	) - ( w / 2 );
		var TopPos	=	( screen.availHeight	/ 2	) - ( h / 2 );
		return window.open( vURL, "_blank", "scrollbars=yes,height=" + h + ",width=" + w + ",statusbar=no,resizable=yes,left=" + LeftPos + ",top=" + TopPos );
	}
	//Recieved from James Wilson, Creator Unknown
	//Changed by Cory Dambach June 30, 2005: call to __firefox_ShowDependant() added
	function ShowDialog( vURL, w, h)
	{
		if ( window.showModalDialog )
		{
			var LeftPos	=	( screen.availWidth		/ 2	) - ( w / 2 );
			var TopPos	=	( screen.availHeight	/ 2	) - ( h / 2 );
			return window.showModalDialog( vURL, null, "scroll:no;dialogLeft:" + LeftPos + "px;dialogTop:" + TopPos + "px;dialogHeight:" + h + "px;dialogWidth:" + w + "px;status:no;" );
		}
		else
		{
			return __firefox_ShowDependant( vURL, w, h);
		}
	}
	/* FireFox */
	function __firefox_ShowDependant( vURL, w, h)
	{	
		//Gives a sorta modal window in FireFox however gives a modeless dialog in IE though... 
		//Property "_blank" works in both for the sName of the window strange thought it may be...
		var LeftPos	=	( screen.availWidth		/ 2	) - ( w / 2 );
		var TopPos	=	( screen.availHeight	/ 2	) - ( h / 2 );
		return window.open( vURL, "_blank", "dependent=yes,scrollbars=yes,height=" + h + ",width=" + w + ",statusbar=no,resizable=yes,left=" + LeftPos + ",top=" + TopPos );
	}
	/* FireFox */
	function __firefox_ShowDialog( vURL, w, h )
	{
		//TODO: Implement __firefox_ShowDialog( vURL, w, h )
	}
	/* FireFox */
	function FakeOpenModal( vURL, w, h )
	 {
		window.top.captureEvents( Event.CLICK | Event.FOCUS );
		window.top.onclick	= IgnoreEvents;
		window.top.onfocus	= HandleFocus;
		var LeftPos	=	( screen.availWidth		/ 2	) - ( w / 2 );
		var TopPos	=	( screen.availHeight	/ 2	) - ( h / 2 );
		winModalWindow = __firefox_ShowDialog( vURL, w, h );
		winModalWindow.focus();
		return winModalWindow.returnVal;
	}
	
	/* CSS Methods, Layers and Such */
	function SetPosition( MyElement, x, y )
	{
		MyElement.style.left	= x;
		MyElement.style.top		= y;		
		return;
	}
	/* End CSS Methods */
	
	function HourGlass( bWantItOn ) //Last Modified by James Wilson, Date Unknown
	{
		document.body.style.cursor = ( bWantItOn ) ? "wait" : "default";
	}

	function DateClick( selObj )
	{
		window.dateField = selObj;
		calendar = window.open('/scripts/calendar.htm','cal','WIDTH=200,HEIGHT=250');
	}
	
	function DateClickModal( selObj ) //firefox and ie
	{	
		NewWindowObj = Show( Environment.LibraryPath + 'calendarBlue.htm', 250, 250 ); //YAY, finally got to use Environment!	
		NewWindowObj.dialogArguments = selObj;
		return NewWindowObj;
	}
		
	function DatePopUp( sender, e ) //Creator Unknown, Date Unknown, widely established should be lowercase U in Up, popup is a single word
	{
		if( sender == null && e == null ) //extra measures to ensure that it will still work on IE even if improperly called
		{
			sender = event.srcElement;
			e = event;
		}
		var NameToUpdate = sender.id.replace( /_cal/i, "" );
		return DateClickModal( Find(NameToUpdate) );
	}
		
	function DatePopUp2( SpanName ) //Creator Unknown, Date Unknown, Not in use
	{
		var EventSource = this;
		var SpanElement = document.all[SpanName];
		var NameToUpdate = EventSource.name.replace( /_cal/i, "" );
		DateClickModal(SpanElement.all.item(NameToUpdate));
	}
