function JSFormVal( formElt ) {

	this._formElt = this.convertStringToElement( formElt );
	this._validators = [];

}

// Class constants 

JSFormVal.prototype._VALTYPE_REQUIRED =  1;
JSFormVal.prototype._VALTYPE_EMAIL    =  2;
JSFormVal.prototype._VALTYPE_PHONE    =  3;
JSFormVal.prototype._VALTYPE_URL      =  4;
JSFormVal.prototype._VALTYPE_ZIP      =  5;
JSFormVal.prototype._VALTYPE_NUMERIC  =  6;
JSFormVal.prototype._VALTYPE_REGEX    =  7;
JSFormVal.prototype._VALTYPE_DATE     =  8;
JSFormVal.prototype._VALTYPE_TIME     =  9;
JSFormVal.prototype._VALTYPE_MATCH    = 10;
JSFormVal.prototype._VALTYPE_STATE    = 11;
JSFormVal.prototype._VALTYPE_RADIO    = 12;
JSFormVal.prototype._VALTYPE_CHECKBOXES = 13;

JSFormVal.prototype._DATEFORMAT_SQL    =  1;
JSFormVal.prototype._DATEFORMAT_US     =  2;

// Class properties

JSFormVal.prototype._formElt;
JSFormVal.prototype._validators = [];

// Methods

JSFormVal.prototype.required = function( formControl, errorMsg ) {

	var validator = new Object;

	validator.type = JSFormVal.prototype._VALTYPE_REQUIRED;
	validator.formControl = this.convertStringToElement( formControl );
	validator.errorMsg = errorMsg;

	this._validators.push( validator );

}

JSFormVal.prototype.email = function( formControl, errorMsg ) {
	
	var validator = new Object;
	
	validator.type = JSFormVal.prototype._VALTYPE_EMAIL;
	validator.formControl = this.convertStringToElement( formControl );
	validator.errorMsg = errorMsg;

	this._validators.push( validator );
	
}

JSFormVal.prototype.phone = function( formControl, errorMsg ) {

	var validator = new Object;
	
	validator.type = JSFormVal.prototype._VALTYPE_PHONE;
	validator.formControl = this.convertStringToElement( formControl );
	validator.errorMsg = errorMsg;

	this._validators.push( validator );
	
}

JSFormVal.prototype.url = function( formControl, errorMsg ) {

	var validator = new Object;
	
	validator.type = JSFormVal.prototype._VALTYPE_URL;
	validator.formControl = this.convertStringToElement( formControl );
	validator.errorMsg = errorMsg;

	this._validators.push( validator );
	
}

JSFormVal.prototype.regex = function( formControl, errorMsg, regEx, regExFlags ) {

	if ( typeof regExFlags == "undefined" ) {
		regExFlags = null;
	}

	var validator = new Object;
	
	validator.type = JSFormVal.prototype._VALTYPE_REGEX;
	validator.formControl = this.convertStringToElement( formControl );
	validator.errorMsg = errorMsg;
	
	// if regEx was provided as a string, convert it to a RegExp
	if ( typeof regEx == "string" ) {
		if ( regExFlags != null ) {
			regEx = new RegExp( regEx, regExFlags );
		} else {
			regEx = new RegExp( regEx );
		}
	}
	
	validator.regEx = regEx;

	this._validators.push( validator );

}

JSFormVal.prototype.match = function( formControl, errorMsg, formControl2 ) {

	var validator = new Object;
	
	validator.type = JSFormVal.prototype._VALTYPE_MATCH;
	validator.formControl = this.convertStringToElement( formControl );
	validator.errorMsg = errorMsg;
	validator.formControl2 = this.convertStringToElement( formControl2 );

	this._validators.push( validator );

}

JSFormVal.prototype.zip = function( formControl, errorMsg, allowPlus4 ) {
	
	if ( typeof allowPlus4 == "undefined" ) {
		allowPlus4 = false;
	}
	
	var validator = new Object;
	
	validator.type = JSFormVal.prototype._VALTYPE_ZIP;
	validator.formControl = this.convertStringToElement( formControl );
	validator.errorMsg = errorMsg;
	validator.allowPlus4 = allowPlus4;

	this._validators.push( validator );
	
}

JSFormVal.prototype.state = function( formControl, errorMsg ) {

	var validator = new Object;
	
	validator.type = JSFormVal.prototype._VALTYPE_STATE;
	validator.formControl = this.convertStringToElement( formControl );
	validator.errorMsg = errorMsg;

	this._validators.push( validator );

}

JSFormVal.prototype.numeric = function( formControl, errorMsg, minValue, maxValue, minDecimals, maxDecimals ) {

	if ( typeof minValue == "undefined" ) {
		minValue = null;
	}

	if ( typeof maxValue == "undefined" ) {
		maxValue = null;
	}

	if ( typeof minDecimals == "undefined" ) {
		minDecimals = null;
	}

	if ( typeof maxDecimals == "undefined" ) {
		maxDecimals = null;
	}

	var validator = new Object;
	
	validator.type = JSFormVal.prototype._VALTYPE_NUMERIC;
	validator.formControl = this.convertStringToElement( formControl );
	validator.minValue = minValue;
	validator.maxValue = maxValue;
	validator.minDecimals = minDecimals;
	validator.maxDecimals = maxDecimals;
	validator.errorMsg = errorMsg;

	this._validators.push( validator );

}

JSFormVal.prototype.date = function( formControl, errorMsg, dateFormat ) {

	var validator = new Object;
	
	validator.type = JSFormVal.prototype._VALTYPE_DATE;
	validator.formControl = this.convertStringToElement( formControl );

	if ( dateFormat == "sql" ) {
		validator.dateFormat = JSFormVal.prototype._DATEFORMAT_SQL;
	} else if ( dateFormat == "us" ) {
		validator.dateFormat = JSFormVal.prototype._DATEFORMAT_US;
	}

	validator.errorMsg = errorMsg;

	this._validators.push( validator );

}

JSFormVal.prototype.time = function( formControl, errorMsg, withSecs ) {

	if ( typeof withSecs == "undefined" ) {
		withSecs = false;
	}

	var validator = new Object;
	validator.type = JSFormVal.prototype._VALTYPE_TIME;
	validator.formControl = this.convertStringToElement( formControl );
	validator.errorMsg = errorMsg;
	validator.withSecs = withSecs;

	this._validators.push( validator );

}

JSFormVal.prototype.radio = function( formControl, errorMsg ) {
	
	var validator = new Object;
	validator.type = JSFormVal.prototype._VALTYPE_RADIO;
	validator.formControl = formControl;
	validator.errorMsg = errorMsg;
	
	this._validators.push( validator );
	
}

JSFormVal.prototype.checkboxes = function( formControls, errorMsg, minChecked ) {

	var validator = new Object;
	validator.type = JSFormVal.prototype._VALTYPE_CHECKBOXES;
	validator.formControls = formControls;
	validator.errorMsg = errorMsg;
	validator.minChecked = minChecked;
	
	this._validators.push( validator );

}

JSFormVal.prototype.convertStringToElement = function( theValue ) {
	if ( typeof theValue == "string" ) {
		theValue = document.getElementById( theValue );
	}
	return theValue;
}

JSFormVal.prototype.validate = function() {
	
	var i;
	var numVals = this._validators.length;

	for ( i = 0; i < numVals; i++ ) {
		
		thisVal = this._validators[i];
		
		switch ( thisVal.type ) {
		
			case JSFormVal.prototype._VALTYPE_REQUIRED:
				isValid = ( thisVal.formControl.value != "" );
				break;
		
			case JSFormVal.prototype._VALTYPE_EMAIL:
				isValid = this._isEmail( thisVal.formControl.value );
				break;
				
			case JSFormVal.prototype._VALTYPE_PHONE:
				isValid = this._isPhone( thisVal.formControl.value );
				break;
				
			case JSFormVal.prototype._VALTYPE_URL:
				isValid = this._isURL( thisVal.formControl.value );
				break;
				
			case JSFormVal.prototype._VALTYPE_ZIP:
				isValid = this._isZIP( thisVal.formControl.value, thisVal.allowPlus4 );
				break;
				
			case JSFormVal.prototype._VALTYPE_DATE:
				isValid = this._isDate( thisVal.formControl.value, thisVal.dateFormat );
				break;
				
			case JSFormVal.prototype._VALTYPE_TIME:
				isValid = this._isTime( thisVal.formControl.value, thisVal.withSecs );
				break;
				
			case JSFormVal.prototype._VALTYPE_STATE:
				isValid = this._isState( thisVal.formControl.value );
				break;
				
			case JSFormVal.prototype._VALTYPE_NUMERIC:
				isValid = this._isNumeric( thisVal.formControl.value, thisVal.minValue, thisVal.maxValue, thisVal.minDecimals, thisVal.maxDecimals );
				break;
				
			case JSFormVal.prototype._VALTYPE_REGEX:
				isValid = ( thisVal.formControl.value == "" ) || thisVal.regEx.test( thisVal.formControl.value );
				break;
				
			case JSFormVal.prototype._VALTYPE_MATCH:
				isValid = ( thisVal.formControl.value == thisVal.formControl2.value );
				break;
				
			case JSFormVal.prototype._VALTYPE_RADIO:
				var radioButtons = document.getElementsByName( thisVal.formControl );
				isValid = false;
				var numRadioButtons = radioButtons.length;
				for ( j = 0; j < numRadioButtons; j++ ) {
					if ( radioButtons[j].checked ) {
						isValid = true;
						break;
					}
				}
				break;
				
			case JSFormVal.prototype._VALTYPE_CHECKBOXES:
				var numChecked = 0;
				var numControls = thisVal.formControls.length;
				for ( k = 0; k < numControls; k++ ) {
					if ( thisVal.formControls[k].checked ) {
						numChecked++;
					}
				}
				
				isValid = numChecked >= thisVal.minChecked;
			
				break;
		
		}
		
		if ( !isValid ) {
		
			if ( thisVal.type == JSFormVal.prototype._VALTYPE_RADIO ) {
				radioButtons[0].focus();
			} else if ( thisVal.type == JSFormVal.prototype._VALTYPE_CHECKBOXES ) {
				thisVal.formControls[0].focus();
			} else {
				thisVal.formControl.focus();
			}
			alert( thisVal.errorMsg );
			return false;
			
		}
				
	}
	
	return true;
	
}

JSFormVal.prototype._isEmail = function( theString ) {

	if ( theString == "" ) {
		return true;
	}

	//var emailRegEx = /^.+@.+\..{2,3,4,6}$/;
	var emailRegEx = new RegExp( "^[\\w-_\.]*[\\w-_\.]\@[\\w]\.+[\\w]+[\\w]$" );
	return emailRegEx.test( theString );
}

JSFormVal.prototype._isPhone = function( theString ) {

	if ( theString == "" ) {
		return true;
	}

	var phoneRegEx = new RegExp( "^[2-9][0-9]{2}-[2-9][0-9]{2}-[0-9]{4}$" );
	return phoneRegEx.test( theString );
}

JSFormVal.prototype._isURL = function( theString ) {

	if ( theString == "" ) {
		return true;
	}

	var urlRegEx = new RegExp( "^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$", "i" );
	return urlRegEx.test( theString );
}

JSFormVal.prototype._isZIP = function( theString, allowPlus4 ) {

	if ( theString == "" ) {
		return true;
	}

	if ( allowPlus4 ) {
		var zipRegEx = new RegExp( "^[0-9]{5}(-[0-9]{4}){0,1}$" );
	} else {
		var zipRegEx = new RegExp( "^[0-9]{5}$" );
	}
	
	return zipRegEx.test( theString );
}

JSFormVal.prototype._isState = function( theString ) {

	var isValid = false;
	var states = [
		'AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FL',
		'GA', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME',
		'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH',
		'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI',
		'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI',
		'WY'
	];
	
	theString = theString.toUpperCase();
	
	for ( i = 0; i < states.length; i++ ) {
		if ( theString == states[i] ) {
			isValid = true;
			break;
		}
	}
	
	return isValid;

}

JSFormVal.prototype._isNumeric = function( theString, minValue, maxValue, minDecimals, maxDecimals ) {

	if ( theString == "" ) {
		return true;
	}
	
	// \56 is the escape sequence for a decimal point
	
	if ( minDecimals == null && maxDecimals == null ) {
		regEx = new RegExp( "^-{0,1}[0-9]+$" );
	} else if ( minDecimals == null && maxDecimals != null ) {
		regEx = new RegExp( "^-{0,1}[0-9]+(\56[0-9]{," + maxDecimals + "}){0,1}$" );
	} else if ( minDecimals != null && maxDecimals == null ) {
		regEx = new RegExp( "^-{0,1}[0-9]+\56[0-9]{" + minDecimals + ",}$" );
	} else {
		regEx = new RegExp( "^-{0,1}[0-9]+\56[0-9]{" + minDecimals + "," + maxDecimals + "}$" );
	}

	if ( !regEx.test( theString ) ) {
		return false;
	} else {
		theNumber = parseFloat( theString );
		return ( minValue == null ? true : theNumber >= minValue ) && ( maxValue == null ? true : theNumber <= maxValue );
	}
	
}

JSFormVal.prototype._isDate = function( theString, dateFormat ) {

	var dateRegEx;
	var month;
	var day;
	var year;

	// TODO: add minDate and maxDate params

	// First, determine whether the date is in the correct format

	switch ( dateFormat ) {
		
		case JSFormVal.prototype._DATEFORMAT_SQL:
			dateRegEx = new RegExp( "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$" );
			break;
			
		case JSFormVal.prototype._DATEFORMAT_US:
			dateRegEx = new RegExp( "^(0[1-9]|1[0-2])/(0[1-9]|[1-2][0-9]|3[0-1])/[0-9]{4}$" );
			break;
		
	}
	
	if ( !dateRegEx.test( theString ) ) {
	
		return false;
	
	} else {
	
		// date is in the correct format, but is it a real date?
		// extract the month, day, and year
		
		if ( dateFormat == JSFormVal.prototype._DATEFORMAT_SQL ) {
			month = theString.substr( 5, 2 );
			day = parseInt( theString.substr( 8, 2 ) );
			year = parseInt( theString.substr( 0, 4 ) );
		} else if ( dateFormat == JSFormVal.prototype._DATEFORMAT_US ) {
			month = theString.substr( 0, 2 );
			day = parseInt( theString.substr( 3, 2 ) );
			year = parseInt( theString.substr( 6, 4 ) );
		}
		
		if ( month == "01" || month == "03" || month == "05" || month == "07" || month == "08" || month == "10" || month == "12" ) {
			return day <= 31;
		} else if ( month == "04" || month == "06" || month == "09" || month == "11" ) {
			return day <= 30;
		} else if ( month == "02" ) {
			if ( JSFormVal.prototype._isLeapYear( year ) ) {
				return day <= 29;
			} else {
				return day <= 28;
			}
		} else {
			return false;
		}
	
	}

}

JSFormVal.prototype._isLeapYear = function( year ) {
	return ( year % 4 == 0 ) && ( !( year % 100 == 0 ) || ( year % 400 == 0 ) );
}

JSFormVal.prototype._isTime = function( theString, withSecs ) {
	
	if ( theString == "" ) {
		return true;
	}
	
	if ( withSecs ) {
		var regEx = new RegExp( "^([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$" );
	} else {
		var regEx = new RegExp( "^([0-1][0-9]|2[0-3]):[0-5][0-9]$" );
	}
	
	return regEx.test( theString );
	
}
