/* Make life easier by adding a push method to IE/5 */
if( !(new Array()).push ) {
	/**
	 * Add an item to an array.
	 * <p>This method is part of the JavaScript 1.2 spec but was not implemented in IE 5.0 (Win) or 5.2 (Mac)
	 * @ignore
	 **/
	Array.prototype.push = function() {
		var rtn = new Array();
		for( var i=0; i<arguments.length; i++ ) {
			this[this.length] = rtn[rtn.length] = arguments[i];
			
		}
		return rtn;
	}
}

if( !("test").trim ) {
	// use two separate RegExps because IE 5 doesn't support non-greedy quantifiers
	/**
	 * @private
	 **/
	String.prototype.trimRE = /^\s+|\s+$/g;

	/**
	 * Retrieve the String value less any trailing whitespace.
	 * @return <dfn>String</dfn> the trimmed string
	 **/
	String.prototype.trim = function() {
		return this.replace( this.trimRE, "" );
	}

}


/**
 * Create a new FieldValidator
 * @class Base Class for field validation.
 * @constructor
 **/
function FieldValidator() {}

/**
 * Unique FieldValidator type name. This must be unique across the FieldValidators
 * @type String
 * @private
 **/
FieldValidator.prototype.typeName = "-";

/**
 * Regular expression to check for empty (whitespace-only) field values.
 * @type Object
 * @private
 **/
FieldValidator.prototype.whitespace = {whitespaceRE:/^\s*$/, test:function( str ) {
		// this is a safari bug workaround. The RegExp does not properly evaluate against empty fields
		if( str == null || str.length == 0 ) return true;
		else return this.whitespaceRE.test( str );
	} };



/**
 * Retrieve the type of the field from a field element.
 * <p>Return values are:</p>
 * <ul>
 * 	<li>radio</li>
 * 	<li>checkbox</li>
 * 	<li>select-one</li>
 * 	<li>select-multiple</li>
 * 	<li>button</li>
 * 	<li>file</li>
 * 	<li>hidden</li>
 * 	<li>image</li>
 * 	<li>password</li>
 * 	<li>reset</li>
 * 	<li>submit</li>
 * 	<li>text</li>
 * 	<li>textarea</li>
 * </ul>
 * <p>Example: <code>fieldValidator.getFieldType( document.formName.fieldName );</code></p>
 *
 * @param field The field whose type needs to be determined.
 * @return The field type
 * @type String
 **/
FieldValidator.prototype.getFieldType = function( field ) {
	var type = null;

	if( field.length ) if( field[0].type == "radio" ) type = "radio";
	if( type == null ) type = field.type;

	return type;
}

/**
 * Retrieve the value of any form field.
 *
 * @param field The field whose value is to be retrieved
 * @return The field's value
 * @type String
 **/
FieldValidator.prototype.getFieldValue = function( field ) {
	switch( this.getFieldType( field ) ) {
		case "radio":
			for( var i=0; i<field.length; i++ ) {
				if( field[i].checked ) return field[i].value;
			}
			return null;
			break;
		case "checkbox":
			return ( field.checked ? field.value : null );
			break;
		case "select-one":
			return field[field.selectedIndex].value
			break;
		case "select-multiple":
			var val = new Array();
			for( var i=0; i<field.options.length; i++ ) {
				if( field.options[i].selected ) val.push( field.options[i].value );
			}
			break;
		case "button":
		case "file":
		case "hidden":
		case "image":
		case "password":
		case "reset":
		case "submit":
		case "text":
		case "textarea":
		default:
			return field.value;
			break;
	}
}

/**
 * Validate a vield. This must be overridden by subclasses
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
FieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	return true;
}

/**
 * Create a validation failure message.
 * <p>This is used to alert a user of a problem with a specific field. The field in question receives focus and an alert is displayed.</p>
 * 
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param message <dfn>String</dfn> The message returned to the user
 **/
FieldValidator.prototype.createMessage = function( err ) {
	var field = document[err.form][err.fieldID];

	if( this.getFieldType( field ) == "radio" )
		field[0].focus();
	else field.focus();

	alert( ( this.customMessage != null && this.customMessage != "" ) ? this.customMessage : err.message );
}

/**
 * This field can be used to override the default message for a FieldValidator instance.
 * @type String
 **/
FieldValidator.prototype.customMessage = null;

/**
 * Access the typeName of this FieldValidator.
 **/
FieldValidator.prototype.toString = function() { return this.typeName; }

/**
 *
 **/
FieldValidator.prototype.onFieldError = function( err ) {
	this.createMessage( err );
}


/*===============================================================================================*/

function FieldValidationFailedError( message, validator, formName, fieldID, fieldFriendlyName ) {
	this.message = ( validator.customMessage != null && validator.customMessage != "" ) ? validator.customMessage : message;
	this.name = validator.typeName + "FieldValidationFailedError";
	this.form = formName;
	this.fieldID = fieldID;
	this.fieldFriendlyName = fieldFriendlyName;
}

FieldValidationFailedError.prototype = new Error;


/*===============================================================================================*/


/**
 * Create an EmptyFieldValidator instance
 * @class Validate fields for non-emptiness.
 * @constructor
 **/
function EmptyFieldValidator() {}

// extend the base class
EmptyFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is always "Empty" for all EmptyFieldValidator instances.
 * @type String
 * @private
 **/
EmptyFieldValidator.prototype.typeName = "Empty";

/**
 * Validate a field.
 * <p>Will succeed if the value is set and is non-empty.</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
EmptyFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	if( !this.whitespace.test( document[formName][fieldID].value ) ) return true;

	this.onFieldError( new FieldValidationFailedError(friendlyName + " is a required field.", this, formName, fieldID, friendlyName ) );

	return false;
}


/*===============================================================================================*/


/**
 * Create an MinLengthFieldValidator instance
 * @param minLength <dfn>Number</dfn> minimum length
 * @class Validate fields for non-emptiness.
 * @constructor
 **/
function MinLengthFieldValidator( minLength ) {
	this.minLength = minLength;
	this.typeName += ":" + minLength;
}

// extend the base class
MinLengthFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is always "Empty" for all MinLengthFieldValidator instances.
 * @type String
 * @private
 **/
MinLengthFieldValidator.prototype.typeName = "MinLength";

/**
 * Validate a field.
 * <p>Will succeed if the value is set and is at least minLength chars long.</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
MinLengthFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	var val = document[formName][fieldID].value;
	val = val.trim();
	document[formName][fieldID].value = val;
	
	if( val.length == 0 ) return true;

	if( val.length == 0 || val.length >= this.minLength ) return true;

	this.onFieldError( new FieldValidationFailedError(friendlyName + " must contain at least "+this.minLength+" characters.", this, formName, fieldID, friendlyName ) );

	return false;
}


/*===============================================================================================*/


/**
 * Create an EmailFieldValidator instance
 * @class Validate email addresses.
 * @constructor
 **/
function EmailFieldValidator() {}

// extend the base class
EmailFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is always "Email" for all EmailFieldValidator instances.
 * @type String
 * @private
 **/
EmailFieldValidator.prototype.typeName = "Email";

/**
 * Regular Expression used for basic email address verification.
 * <p>There is a lot of room for improvement in this RegExp</p>
 * @private
 * @type RegExp
 **/
EmailFieldValidator.prototype.emailRE = /^\s*(([\w\.\-\_]+)@([\w\-\_]+\.)+\w{2,})\s*$/;

/**
 * Validate a field.
 * <p>Will succeed if the value is determined to be an email address.</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
EmailFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	// don't validate empty fields
	if( this.whitespace.test( document[formName][fieldID].value ) ) return true;

	if( this.emailRE.test( document[formName][fieldID].value ) ) {
		// do a bit of house cleaning
		document[formName][fieldID].value = RegExp.$1;
		return true;
	}

	this.onFieldError( new FieldValidationFailedError( friendlyName + " does not look like a valid email address.", this, formName, fieldID, friendlyName ) );

	return false;
}

/*===============================================================================================*/

/**
 * Create an USAPhoneNumberFieldValidator instance
 * @class Validate phone numbers within the United States
 * @constructor
 **/
function USAPhoneNumberFieldValidator() {}

// extend the base class
USAPhoneNumberFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is always "Email" for all EmailFieldValidator instances.
 * @type String
 * @private
 **/
USAPhoneNumberFieldValidator.prototype.typeName = "USAPhoneNumber";

// Regular Expressions are static fields. If used more than once, this vastly improves performance.
// Genneral phone number validation: area codes and exchanges cannot start with 0 or 1
// since there is no punctuation character class in JS, we can't use this....
// USAPhoneNumberFieldValidator.prototype.phoneRE = /^[\p{P}\s]*(?:\+?1)?[\p{P}\s]*([2-9]\d{2})[\p{P}\s]*([2-9]\d{2})[\p{P}\s]*(\d{4})(?:[\p{P}\s]*(?:x|ext|extension)[\p{P}\s]*(\d{1,7}))?[\p{P}\s]*$/i;

RegExp.punct = "\\!\\'\\#\\%\\&\\'\\(\\)\\*\\+,\\-\\./:;\\<=\\>\\?\\@\\[\\/\\]\\^_\\{\\|\\}\\~";
USAPhoneNumberFieldValidator.prototype.phoneRE = new RegExp("^["+RegExp.punct+"\\s]*(?:\\+?1)?["+RegExp.punct+"\\s]*([2-9]\\d{2})["+RegExp.punct+"\\s]*([2-9]\\d{2})["+RegExp.punct+"\\s]*(\\d{4})(?:["+RegExp.punct+"\\s]*(?:x|ext|extension)["+RegExp.punct+"\\s]*(\\d{1,7}))?["+RegExp.punct+"\\s]*$","i");

// area codes and exchanges cannot be #11 or 555, area codes can't be 900 or 976
USAPhoneNumberFieldValidator.prototype.phoneInvalidExchangeRE = /^(\d11|555)$/;
USAPhoneNumberFieldValidator.prototype.phoneInvalidAreaCodeRE = /^(\d11|555|900|976)$/;

USAPhoneNumberFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	var phone = document[formName][fieldID].value;

	// don't validate empty fields
	if( this.whitespace.test( phone ) ) return true;

	phone = phone.trim();
	var msg = "";

	if( this.phoneRE.test(phone) ) {
		var areaCode  = RegExp.$1;
		var exchange  = RegExp.$2;
		var number    = RegExp.$3;
		var extension = RegExp.$4;

		if( !( this.phoneInvalidAreaCodeRE.test(areaCode) || this.phoneInvalidExchangeRE.test(exchange) ) ) {
			phone = "(" + areaCode + ") " + exchange + "-" + number;
			if( extension != null && extension.length > 0 ) {
				phone += " x" + extension;
			}
			document[formName][fieldID].value = phone;
			return true;
		}
	}

	this.onFieldError( new FieldValidationFailedError( friendlyName + " does not look like a valid phone number. Please us the format: (555) 555-5555 or (555) 555-5555 x555.", this, formName, fieldID, friendlyName ) );

	return false;
}


/*===============================================================================================*/

/**
 * Create an Zip Code Validator instance
 * @class Validate a Zip Code
 * @constructor
 **/
function ZipCodeFieldValidator( zipValidationType ) {
	this.validationType = zipValidationType;
	if( zipValidationType != null ) {
		this.typeName += ":" + zipValidationType;
	}
}

// extend the base class
ZipCodeFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is always "ZipCode" for all ZipCodeFieldValidator instances.
 * @type String
 * @private
 **/
ZipCodeFieldValidator.prototype.typeName = "ZipCode";

ZipCodeFieldValidator.BASIC_ZIP = 1;
ZipCodeFieldValidator.EXTENDED_ZIP = 2;
ZipCodeFieldValidator.BASIC_OR_EXTENDED_ZIP = 3;
ZipCodeFieldValidator.ZIP_EXTENSION_ONLY = 4;

/**
 * Type of validation to do
 * @type Number
 * @private
 **/
ZipCodeFieldValidator.prototype.validationType = ZipCodeFieldValidator.BASIC_OR_EXTENDED_ZIP;

/**
 * Regular Expression used for basic Zip Code verification.
 * <p>This RegExp looks for 5 consecutive digits</p>
 * @private
 * @type RegExp
 **/
ZipCodeFieldValidator.prototype.basicZipRE = /^\s*(\d{5})\s*$/;

/**
 * Regular Expression used for Zip Code +4 verification.
 * <p>This RegExp looks for 5 consecutive digits followed by - and 4 more digits</p>
 * @private
 * @type RegExp
 **/
ZipCodeFieldValidator.prototype.extendedZipRE = /^\s*(\d{5})-(\d{4})\s*$/;

/**
 * Regular Expression used for +4 verification.
 * <p>This RegExp looks for 4 consecutive digits</p>
 * @private
 * @type RegExp
 **/
ZipCodeFieldValidator.prototype.extensionZipRE = /^\s*(\d{4})\s*$/;

ZipCodeFieldValidator.prototype.allZerosRE = /^\s*0+\s*$/;

/**
 * Validate a field.
 * <p>Will succeed if the value is determined to be an appropriate zip and or extension</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
ZipCodeFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	// don't validate empty fields
	var field = document[formName][fieldID];
	if( this.whitespace.test( field.value ) ) return true;

	var autofail = this.allZerosRE.test( field.value );

	switch( this.validationType ) {
		case ZipCodeFieldValidator.BASIC_ZIP:
			if( !autofail && this.basicZipRE.test( field.value ) ) {
				field.value = RegExp.$1;
				return true;
			} else {
				this.onFieldError( new FieldValidationFailedError(friendlyName + " is not a valid zip code. Please us the format #####.", this, formName, fieldID, friendlyName ) );
				return false;
			}
			break;
		case ZipCodeFieldValidator.EXTENDED_ZIP:
			if( !autofail && this.extendedZipRE.test( field.value ) ) {
				field.value = RegExp.$1;
				return true;
			} else {
				this.onFieldError( new FieldValidationFailedError(friendlyName + " is not a valid zip code + 4. Plase use the format #####-####", this, formName, fieldID, friendlyName ) );
				return false;
			}
			break;
		case ZipCodeFieldValidator.ZIP_EXTENSION_ONLY:
			if( !autofail && this.extensionZipRE.test( field.value ) ) {
				field.value = RegExp.$1;
				return true;
			} else {
				this.onFieldError( new FieldValidationFailedError(friendlyName + " is not a valid zip code extension. Extensions must be four digits long.", this, formName, fieldID, friendlyName ) );
				return false;
			}
			break;
		case ZipCodeFieldValidator.BASIC_OR_EXTENDED_ZIP:
		default:
			if( !autofail && ( this.basicZipRE.test( field.value ) || this.extendedZipRE.test( field.value ) ) ) {
				field.value = RegExp.$1;
				return true;
			} else {
				this.onFieldError( new FieldValidationFailedError(friendlyName + " is not a valid zip code. Please us the format ##### or #####-####.", this, formName, fieldID, friendlyName ) );
				return false;
			}
			break;
	}
}

/*===============================================================================================*/

/**
 * Create an SSNFieldValidator instance
 * @class Validate a Social Security Number
 * @constructor
 **/
function SSNFieldValidator() {}

// extend the base class
SSNFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is always "SSN" for all SSNFieldValidator instances.
 * @type String
 * @private
 **/
SSNFieldValidator.prototype.typeName = "SSN";

/**
 * Regular Expression used for basic SSN verification.
 * <p>This RegExp looks for digit groupings (3-2-4) delimited by any non-alphanumeric characters or nine consecutive digits</p>
 * @private
 * @type RegExp
 **/
SSNFieldValidator.prototype.ssnRE = /^\s*(\d{3})\W*(\d{2})\W*(\d{4})\s*$/;

/**
 * Validate a field.
 * <p>Will succeed if the value is determined to be an social security number.</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
SSNFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	// don't validate empty fields
	if( this.whitespace.test( document[formName][fieldID].value ) ) return true;

	if( this.ssnRE.test( document[formName][fieldID].value ) ) {
		// do a bit of house cleaning
		document[formName][fieldID].value = RegExp.$1 + "-" + RegExp.$2 + "-" + RegExp.$3;
		return true;
	}

	this.onFieldError( new FieldValidationFailedError("The social security number you entered does not look valid.", this, formName, fieldID, friendlyName ) );

	return false;
}


/*===============================================================================================*/


/**
 * Create an NumberFieldValidator instance
 * @class Validate a Number
 * @constructor
 **/
function NumberFieldValidator() {}

// extend the base class
NumberFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is always "Number" for all NumberFieldValidator instances.
 * @type String
 * @private
 **/
NumberFieldValidator.prototype.typeName = "Number";

/**
 * Regular Expression used for basic Number verification.
 * <p>This RegExp looks for 1 or more digits containing no more than one "." with an optional leading "+" or "-"</p>
 * @private
 * @type RegExp
 **/
NumberFieldValidator.prototype.numberRE = /^\s*((\+|-)?(\d*\.\d+|\d+\.?\d*))\s*$/;

/**
 * Validate a field.
 * <p>Will succeed if the value is determined to be a number.</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
NumberFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	// don't validate empty fields
	if( this.whitespace.test( document[formName][fieldID].value ) ) return true;

	if( this.numberRE.test( document[formName][fieldID].value ) ) {
		// do a bit of house cleaning
		document[formName][fieldID].value = RegExp.$1;
		return true;
	}

	this.onFieldError( new FieldValidationFailedError(friendlyName + " does not look like a valid number.", this, formName, fieldID, friendlyName ) );

	return false;
}


/*===============================================================================================*/


/**
 * Create an NumberRangeFieldValidator instance
 * @class Validate a Numbuer within a given range.
 *  <p>Open-ended ranges can be achieved by setting either min or max to <code>null</code></p>
 * @constructor
 * @param min <dfn>Number</dfn> the minimum (inclusive) value allowed for the field.
 * @param max <dfn>Number</dfn> the maximum (inclusive) value allowed for the field.
 **/
function NumberRangeFieldValidator( min, max ) {
	this.min = min;
	this.max = max;
	this.typeName += ":" + min + ":" + max
}

// extend the base class
NumberRangeFieldValidator.prototype = new NumberFieldValidator;

/**
 * The minimum value allowed.
 * <p>Do not set this directly</p>
 * @type Number
 * @private
 **/
NumberRangeFieldValidator.prototype.min = null;

/**
 * The maximum value allowed.
 * <p>Do not set this directly</p>
 * @type Number
 * @private
 **/
NumberRangeFieldValidator.prototype.max = null;

/**
 * Unique FieldValidator type name.
 * <p>The value is set at instantiation and is of the form "NumberRange:" + min + ":" + max
 * @type String
 * @private
 **/
NumberRangeFieldValidator.prototype.typeName = "NumberRange";

/**
 * Validate a field.
 * <p>Will succeed if the value is within the given range.</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
NumberRangeFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	// don't validate empty fields
	if( this.whitespace.test( document[formName][fieldID].value ) ) return true;

	if( this.numberRE.test( document[formName][fieldID].value ) ) {
		// do a bit of house cleaning
		document[formName][fieldID].value = RegExp.$1;
		var val = parseFloat( RegExp.$1 );

		if( this.min != null && this.min > val ) {
			this.onFieldError( new FieldValidationFailedError(friendlyName + " must be at least " + this.min + ".", this, formName, fieldID, friendlyName ) );

			return false;
		} else if( this.max != null && this.max < val ) {
			this.onFieldError( new FieldValidationFailedError(friendlyName + " must be no more than " + this.max + ".", this, formName, fieldID, friendlyName ) );

			return false;
		}

		return true;
	}

	this.onFieldError( new FieldValidationFailedError(friendlyName + " does not look like a valid number.", this, formName, fieldID, friendlyName ) );

	return false;
}


/*===============================================================================================*/


/**
 * Create an IntegerFieldValidator instance
 * @class Validate that a field contains only an integer
 *  <p>Note: this allows for numbers with values <= 0. Use with a NumberRange validator to disallow negatives.</p>
 * @constructor
 **/
function IntegerFieldValidator( ) {}

// extend the base class
IntegerFieldValidator.prototype = new NumberFieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is always "Integer" for all IntegerFieldValidator instances.
 * @type String
 * @private
 **/
IntegerFieldValidator.prototype.typeName = "Integer";

/**
 * Regular Expression used for basic Number verification.
 * <p>This RegExp looks for 1 or more digits with an optional leading "+" or "-"</p>
 * @private
 * @type RegExp
 **/
IntegerFieldValidator.prototype.integerRE = /^\s*((\+|-)?\d+)\s*$/;

/**
 * Validate a field.
 * <p>Will succeed if the value is determined to be an integer.</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
IntegerFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	// don't validate empty fields
	if( this.whitespace.test( document[formName][fieldID].value ) ) return true;

	if( this.numberRE.test( document[formName][fieldID].value ) ) {
		// do a bit of house cleaning
		document[formName][fieldID].value = RegExp.$1;
		return true;
	}

	this.onFieldError( new FieldValidationFailedError(friendlyName + " does not look like a valid integer.", this, formName, fieldID, friendlyName ) );

	return false;
}


/*===============================================================================================*/


/**
 * Create an RadioFieldValidator instance
 * @class Validate that a radio field set has a selected value
 * @constructor
 **/
function RadioFieldValidator() {}

// extend the base class
RadioFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is always "Radio" for all RadioFieldValidator instances.
 * @type String
 * @private
 **/
RadioFieldValidator.prototype.typeName = "Radio";

/**
 * Validate a field.
 * <p>Will succeed if a value has been selected for the given radio set.</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
RadioFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	var radioGrp = document[formName][fieldID];
	var val = this.getFieldValue( radioGrp );
	if( val != null ) return true;

	this.onFieldError( new FieldValidationFailedError("Please select a " + friendlyName, this, formName, fieldID, friendlyName ) );

	return false;
}

/*===============================================================================================*/


/**
 * Create an RequireOneFieldValidator instance
 * @class Validate that at least one given field has a value.
 * <p>Supply more than one parameter for multiple fields. Example:</p>
 * <p><code>new RequireOneFieldValidator( {id:"sat",friendlyName:"SAT Score"}, {id:"act",friendlyName:"ACT Score"} );</code></p>
 * @constructor
 * @param otherFields* objects of form [{id:"field_id",friendlyName:"Friendly Name"}]
 **/
function RequireOneFieldValidator( otherFields ) {
	for( var i=0; i<arguments.length; i++ )
		this.otherFields.push( arguments[i] );

	for( var i=0; i<this.otherFields.length; i++ )
		this.typeName += ":" + this.otherFields.id;
}

// extend the base class
RequireOneFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is of the form "RequireOne:" + fieldID1 + ":" + fieldID2 + ...</p>
 * @type String
 * @private
 **/
RequireOneFieldValidator.prototype.typeName = "RequireOne";

/**
 * Storage for fields to check.
 * @type Array
 * @private
 **/
RequireOneFieldValidator.prototype.otherFields = [];

/**
 * Validate a field.
 * <p>Will succeed if at least one of the given fields is non-empty.</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
RequireOneFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	// check this field first
	var val = this.getFieldValue( document[formName][fieldID] );
	if( val != null && !this.whitespace.test( val ) ) return true;

	// check other fields
	var friendlyNames = [friendlyName];
	for( var i=0; i<this.otherFields.length; i++ ) {
		if( this.otherFields[i].id == fieldID ) continue;
		val = this.getFieldValue( document[formName][this.otherFields[i].id] );

		if( val != null && !this.whitespace.test( val ) ) return true;

		friendlyNames.push( this.otherFields[i].friendlyName );
	}

	this.onFieldError( new FieldValidationFailedError("Please enter a value for at least one of the following fields: " + friendlyNames.join(', ') + ".", this, formName, fieldID, friendlyName ) );

	return false;
}


/*===============================================================================================*/

/**
 * Create an MatchingFieldValidator instance
 * @class Validate that the the values of two fields match
 * @constructor
 * @param otherFieldID <dfn>String</dfn> the other field to check the value of
 * @param otherFriendlyName <dfn>String</dfn> the friendly name of the other field
 **/
function MatchingFieldValidator( otherFieldID, otherFriendlyName ) {
	this.otherFieldID = otherFieldID;
	this.otherFriendlyName = otherFriendlyName;

	this.typeName += ":" + this.otherFieldID;
}

// extend the base class
MatchingFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is of the form "IfFieldValueMatches:" + otherFieldID</p>
 * @type String
 * @private
 **/
MatchingFieldValidator.prototype.typeName = "IfFieldValueMatches";

/**
 * The other field to check the value of
 * @type String
 * @private
 **/
MatchingFieldValidator.prototype.otherFieldID = null;

/**
 * The friendly name of the other field
 * @type String
 * @private
 **/
MatchingFieldValidator.prototype.otherFriendlyName = null;

/**
 * Validate a field.
 * <p>Will succeed if the other field has the same value as this field</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
MatchingFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	if( this.getFieldValue( document[formName][this.otherFieldID] ).trim() == this.getFieldValue( document[formName][fieldID] ).trim() ) return true;

	this.onFieldError( new FieldValidationFailedError(friendlyName + " and " + this.otherFriendlyName + " do not match.", this, formName, fieldID, friendlyName ) );

	return false;
}


/*===============================================================================================*/



/**
 * Create an EmptyIfOtherValIsFieldValidator instance
 * @class Validate that the field is non-empty only if a specific value is selected in another field.
 * @constructor
 * @param otherFieldID <dfn>String</dfn> the other field to check the value of
 * @param otherValue <dfn>String</dfn> the value required in other field to for the check of this field to happen
 * @param otherFriendlyName <dfn>String</dfn> the friendly name of the other field
 **/
function EmptyIfOtherValIsFieldValidator( otherFieldID, otherValue, otherFriendlyName ) { /////////// need to integrate radio btns
	this.otherFieldID = otherFieldID;
	this.otherValue = (otherValue==null) ? "" : otherValue.toString().trim();

	this.otherFriendlyName = otherFriendlyName;

	this.typeName += ":" + this.otherFieldID + "=" + this.otherValue;
}

// extend the base class
EmptyIfOtherValIsFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is of the form "IfOtherValueIs:" + otherFieldID + ":" + otherValue</p>
 * @type String
 * @private
 **/
EmptyIfOtherValIsFieldValidator.prototype.typeName = "IfOtherValueIs";

/**
 * The other field to check the value of
 * @type String
 * @private
 **/
EmptyIfOtherValIsFieldValidator.prototype.otherFieldID = null;

/**
 * The value required in other field to for the check of this field to happen
 * @type String
 * @private
 **/
EmptyIfOtherValIsFieldValidator.prototype.otherValue = null;

/**
 * The friendly name of the other field
 * @type String
 * @private
 **/
EmptyIfOtherValIsFieldValidator.prototype.otherFriendlyName = null;

/**
 * Validate a field.
 * <p>Will succeed if the other field does not have the provided value OR this field is non-empty</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
EmptyIfOtherValIsFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	var otherField = document[formName][this.otherFieldID];
	var val = this.getFieldValue( otherField );

	var checkThisField = false;

	// check to see if both the set and required value are null or empty strings
	if( ( val == null || this.whitespace.test( val ) ) && this.whitespace.test( this.otherValue ) ) checkThisField = true;

	// check to see if the values match
	else if( val != null ) {
		val = val.trim();
		if( val == this.otherValue ) checkThisField = true;
	}

	if( !checkThisField ) return true;

	if( !this.whitespace.test( document[formName][fieldID].value ) ) return true;

	this.onFieldError( new FieldValidationFailedError(friendlyName + " is required based on the value entered for " + this.otherFriendlyName + ".", this, formName, fieldID, friendlyName ) );

	return false;
}


/*===============================================================================================*/


/** 
 * Compare two dates.
 * <p>This extension to the Date class compares dates--including the time portion.</p>
 * @param date a <dfn>Date</dfn>, <dfn>Number</dfn>, or <dfn>String</dfn> instance representing a date.
 * @return Positive if the other date is after this instance. Negative if it is before. Zero if the two dates are equal. <code>null</code> if the otherDate can not be converted into a date instance.
 * @type Number
 **/
Date.prototype.compare = function( otherDate ) {
	if( otherDate == null ) return 1;

	switch( typeof otherDate ) {
		case "number":
			return ( this.getTime() - otherDate );
		case "string":
			return ( this.getTime() - (new Date( otherDate )).getTime() );
		case "object":
			return ( this.getTime() - otherDate.getTime() );
		default:
			return null;
	}
}

/**
 * Zeros the time part of the date instance.
 * <p>This following two statements are equivalent:</p>
 * <p><code>newDate = oldDate.toMidnight();</code></p>
 * <p><code>newDate = new Date( oldDate.getFullYear(), oldDate.getMonth(), oldDate.getDate() );</code></p>
 *
 * @return the new Date instance with the same date and a time of 12:00 am
 * @type Date
 **/
Date.prototype.toMidnight = function() {
	return new Date( this.getFullYear(), this.getMonth(), this.getDate() );
}


/*===============================================================================================*/


/**
 * Create a new DateRangeFieldValidator instance.
 * @class Verify that the supplied date is within the given range
 * <p><strong>Important:</strong> this does not validate that the fields contain integers. Use the EmptyFieldValidator and IntegerFieldValidator before this one.</p>
 * @param dayFieldID <dfn>String</dfn> the id of the field containing the day of the month
 * @param monthFieldID <dfn>String</dfn> the id of the field containing the month
 * @param yearFieldID <dfn>String</dfn> the id of the field containing the year
 * @param rangeStart <dfn>Date</dfn> the Date instance representing the start of the range (set to null for no range start)
 * @param rangeEnd <dfn>Date</dfn> the Date instance representing the end of the range (set to null for no range end)
 **/
function DateRangeFieldValidator( dayFieldID, monthFieldID, yearFieldID, rangeStart, rangeEnd ) {
	this.dayFieldID = dayFieldID;
	this.monthFieldID = monthFieldID;
	this.yearFieldID = yearFieldID;

	this.rangeStart = (rangeStart == null ) ? null : rangeStart.toMidnight();
	this.rangeEnd   = (rangeEnd   == null ) ? null : rangeEnd.toMidnight();

	this.typeName += ":" + this.dayFieldID + ":" + this.monthFieldID + ":" + this.yearFieldID + ":" + this.rangeStart + ":" + this.rangeEnd;
}

// extend the base class
DateRangeFieldValidator.prototype = new FieldValidator;

/**
 * Unique FieldValidator type name.
 * <p>The value is of the form "DateRange:" + dayFieldID + ":" + monthFieldID + ":" + yearFieldID + ":" + rangeStart + ":" + rangeEnd</p>
 *
 * @type String
 * @private
 **/
DateRangeFieldValidator.prototype.typeName = "DateRange";

/**
 * The day of the month field to check
 * @private
 * @type String
 **/
DateRangeFieldValidator.prototype.dayFieldID = null;

/**
 * The month field to check
 * @private
 * @type String
 **/
DateRangeFieldValidator.prototype.monthFieldID = null;

/**
 * The year field to check
 * @private
 * @type String
 **/
DateRangeFieldValidator.prototype.yearFieldID = null;

/**
 * Date that starts the validation range
 * @private
 * @type Date
 **/
DateRangeFieldValidator.prototype.rangeStart = null;

/**
 * Date that ends the validation the range
 * @private
 * @type Date
 **/
DateRangeFieldValidator.prototype.rangeEnd = null;

/**
 * Validate a field.
 * <p>Valdates that the date is within the given range.</p>
 *  
 * @param formName <dfn>String</dfn> The name of the form to validate
 * @param fieldID <dfn>String</dfn> The field within the form to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @return <code>true</code> if the field validated, <code>false</code> if validation failed
 **/
DateRangeFieldValidator.prototype.validate = function( formName, fieldID, friendlyName ) {
	// don't vaildate empty ranges
	if( this.rangeStart == null && this.rangeEnd == null ) return true;

	var dayField  = document[formName][this.dayFieldID ];
	var monthField = document[formName][this.monthFieldID];
	var yearField  = document[formName][this.yearFieldID ];

	var day  = parseInt( this.getFieldValue( dayField  ) );
	var month = parseInt( this.getFieldValue( monthField ) );
	var year  = parseInt( this.getFieldValue( yearField  ) );

	var checkDate = new Date( year, month-1, day );

	if( this.rangeEnd != null && checkDate.compare( this.rangeEnd ) > 0 ) {
		this.onFieldError( new FieldValidationFailedError(friendlyName + " must be on or before " + this.rangeEnd.toDateString() + ".", this, formName, fieldID, friendlyName ) );

		return false;
	}

	if( this.rangeStart != null && checkDate.compare( this.rangeStart ) < 0 ) {
		this.onFieldError( new FieldValidationFailedError(friendlyName + " must be on or after " + this.rangeStart.toDateString() + ".", this, formName, fieldID, friendlyName ) );

		return false;
	}

	return true;

}


/*===============================================================================================*/


/**
 * Create a new FormValidator
 * @class Validate forms using FieldValidators
 * <p>Example: <code>formValidator.addValidation( "formName", "fieldName", "Friendly Field Name", ["Empty","Integer",anyYearRangeValidator] );</code></p>
 **/
function FormValidator() {
	// add some default validators
	this.addValidator( FormValidator.EMPTY );
	this.addValidator( FormValidator.EMAIL );
	this.addValidator( FormValidator.NUMBER );
	this.addValidator( FormValidator.INTEGER );
	this.addValidator( FormValidator.RADIO );
	this.addValidator( FormValidator.PHONE );
}

/**
 * The default EmptyFieldValidator instance
 * @type EmptyFieldValidator
 **/
FormValidator.EMPTY = new EmptyFieldValidator();

/**
 * The default EmailFieldValidator instance
 * @type EmailFieldValidator
 **/
FormValidator.EMAIL = new EmailFieldValidator();

/**
 * The default NumberFieldValidator instance
 * @type NumberFieldValidator
 **/
FormValidator.NUMBER = new NumberFieldValidator();

/**
 * The default IntegerFieldValidator instance
 * @type IntegerFieldValidator
 **/
FormValidator.INTEGER = new IntegerFieldValidator();

/**
 * The default RadioFieldValidator instance
 * @type RadioFieldValidator
 **/
FormValidator.RADIO = new RadioFieldValidator();

/**
 * The default USAPhoneNumberFieldValidator instance
 * @type USAPhoneNumberFieldValidator
 **/
FormValidator.PHONE = new USAPhoneNumberFieldValidator();

/**
 * A store of all fields to validate and the FieldValidators to use
 * @private
 * @type Object
 **/
FormValidator.prototype.fields = new Object();

/**
 * Add validation to a form field.
 * <p>For simple, predefined types, pass either the static reference (like FormValidator.INTEGER) or the string type name (like "Integer")</p>
 * 
 * @param formName <dfn>String</dfn> the name of the form containing the field to validate
 * @param fieldID <dfn>String</dfn> the id of the field to validate
 * @param friendlyName <dfn>String</dfn> the label or friendly name of the form field
 * @param validationTypes <dfn>Array</dfn> or <dfn>String</dfn> an Array containing the types of validation to do OR a String if only validating one type.
 **/
FormValidator.prototype.addValidation = function( formName, fieldID, friendlyName, validationTypes ) {
	// create a map for the form
	if( !this.fields[formName] ) this.fields[formName] = new Array();

	// create the validation array
	if( !this.fields[formName][fieldID] ) {
		this.fields[formName].push( fieldID ); // store the field name in the array
		this.fields[formName][fieldID] = new Array();
		this.fields[formName][fieldID].friendlyName = friendlyName;
	}

	var field = this.fields[formName][fieldID];

	// if a string was passed, convert to an array
	if( typeof validationTypes == "string" ) validationTypes = [validationTypes];

	// loop through the validation types and store the info
	for( var i=0; i<validationTypes.length; i++ ) {
		if( !field[validationTypes[i]] ) field.push( validationTypes[i] );
		field[validationTypes[i]] = true;
	}
}

/**
 * Validate the form contents.
 *
 * @param formName <dfn>String</dfn> the name of the form to validate against
 * @return <code>true</code> if validation succeeded, <code>false</code> on failure
 * @type Boolean
 **/
FormValidator.prototype.validate = function( formName ) {
	if( !this.fields[ formName ] ) return true; // no validation set

	var rtn = true;

	// loop through all form fields
	for( var i=0; i<this.fields[formName].length; i++ ) {
		if( !this.validateField( formName, this.fields[formName][i] ) ) {
			rtn = false;
			if( this.stopOnInvalid ) break;
		}
	}

	if( rtn && this.onValid ) this.onValid( formName );
	else if( !rtn && this.onInvalid ) this.onInvalid( formName );

	return rtn;
}

/**
 * Validate the a field in a given form.
 * <p>Before this method is called, the field must have validation associated with it via <code>addValidation</code>
 * @param formName <dfn>String</dfn> the name of the form containing the field
 * @param fieldID <dfn>String</dfn> the id of the field to validate
 * @return <code>true</code> if validation succeeded, <code>false</code> on failure
 * @type Boolean
 * @see #addValidation
 **/
FormValidator.prototype.validateField = function( formName, fieldID ) {
	if( !this.fields[ formName ] ) return true; // no validation set

	var theField = this.fields[formName][fieldID];
	var fieldIsValid = true;

	// loop through all validation types on the field
	for( var j=0; j<theField.length; j++ ) {
		// ignore unknown validation types
		if( !this.validators[theField[j]] ) continue;

		// Validate the field. Stop execution if a field fails to validate
		if( !this.validators[theField[j]].validate( formName, fieldID, theField.friendlyName ) ) {
			fieldIsValid = false;
			break;
		}
	}
	
	if( this.onFieldValid && fieldIsValid ) this.onFieldValid( formName, fieldID, theField.friendlyName );
	else if( this.onFieldInvalid && !fieldIsValid ) this.onFieldInvalid( formName, fieldID, theField.friendlyName );

	return fieldIsValid;
}

/**
 * A store of all available validators
 * @private
 * @type Object
 **/
FormValidator.prototype.validators = new Object();

/**
 * Make a validator instance available to the FormValidator
 * <p>This method is used to enable a given validation type within this FormValidator</p>
 * @param validator <dfn>{@link FieldValidator}</dfn> a FieldValidator instance
 * @see FieldValidator
 **/
FormValidator.prototype.addValidator = function( validator ) {
	this.validators[validator.typeName] = validator;
	return validator;
}

FormValidator.prototype.stopOnInvalid = false;

FormValidator.prototype.onFieldInvalid = null;
FormValidator.prototype.onFieldValid = null;

FormValidator.prototype.onValid = null;
FormValidator.prototype.onInvalid = null;


/*===============================================================================================*/


/***** EXTRA VALIDATORS ******/
var formValidator = new FormValidator();
/*
var dayRangeValidator = formValidator.addValidator( new NumberRangeFieldValidator( 1, 31 ) );

var monthRangeValidator = formValidator.addValidator( new NumberRangeFieldValidator( 1, 12 ) );

var futureYearRangeValidator = formValidator.addValidator( new NumberRangeFieldValidator( (new Date()).getFullYear(), null ) );

var min12YearAgeRangeValidator = formValidator.addValidator( new NumberRangeFieldValidator( 1900, (new Date()).getFullYear()-12 ) );

var min18YearAgeRangeValidator = formValidator.addValidator( new NumberRangeFieldValidator( 1900, (new Date()).getFullYear()-18 ) );

var anyYearRangeValidator = formValidator.addValidator( new NumberRangeFieldValidator( 1900, null ) );

var gpaRangeValidator = formValidator.addValidator( new NumberRangeFieldValidator( 0, 4 ) );

var positiveNumberValidator = formValidator.addValidator( new NumberRangeFieldValidator( 0, null ) );
*/
