//<script language="JavaScript">
/*
	Author:			Robert Graves
	Created:		11/22/2000
	Last Modified:	11/22/2000

	Description:
		Objects for validating forms.

	Modification log:
*/
usat.validator = clsValidator;

function clsDatatype(){
	/*
		Comments
		Class holding constants defining datatypes and returning
		their associated regular expressions.
	*/
	this.add = fxAdd;
	this.getRegExp = fxGetRegExp;

	this.arrDatatypes = new Array();

	this.add("integer", "^-?\\d+$");
	this.add("decimal", "^-?\\d+(\\.\\d+)?$");
	this.add("time", "^(0?[1-9]|1[0-2]):[0-5]\\d(:[0-5]\\d)?(\\s(am|pm))$", "i");
	this.add("militaryTime", "^(((1\\d|0[1-9]|2[0-3]):[0-5]\\d)|(24:00)|((00:0[1-9])|(00:[1-5]\\d)))(:[0-5]\\d)?$");
	this.add("date", "^((((0?(1|3|5|7|8))|10|12)\\/(0?[1-9]|[1-2][0-9]|3[0-1]))|(((0?(4|6|9))|11)\\/(0?[1-9]|[1-2][0-9]|30))|(0?2\\/(0?[1-9]|[1-2][0-9])))\\/\\d{4}$");
	this.add("USCurrency", "^\\$\\d+.\\d{2}$");
	this.add("email", "^\\w+((-\\w+)|(\\.\\w+))*\\@[a-z0-9]+((\\.|-)[a-z0-9]+)*\\.[a-z0-9]+$", "i");
	this.add("url", "^([a-z])+:\\/\\/\\S+", "i");
	this.add("phoneNumber", "^((\\(\\d{3}\\))|(\\d{3}\\s?-))\\s?\\d{3}\\s?[-]\\s?\\d{4}$|^\\d{3}\\.\\d{3}\\.\\d{4}$");
	this.add("zipcode", "^\\d{5}(((\\s?-\\s?)|(\\s))\\d{4})?$");

	/*
		Members
	*/
	function fxAdd(strName, strRegExp, strSwitch){
		/*
			Comments
			Add a datatype to this object
		*/
		var intIndex = this.arrDatatypes.length;
		var dataNumber = 1;
		if (intIndex > 0){
			dataNumber = this.arrDatatypes[intIndex-1][0] * 2;
		}   // if
		eval("this." + strName + " = " + dataNumber);
		this.arrDatatypes[intIndex] = new Array(3);
		this.arrDatatypes[intIndex][0] = dataNumber;
		this.arrDatatypes[intIndex][1] = strRegExp;
		this.arrDatatypes[intIndex][2] = strSwitch;
	}

	function fxGetRegExp(datatype){   // member of clsDatatype
		/*
			Comments
			Do a binary search looking for the datatype, and return it's 
			regular expression if a regular expression hasn't been used
			yet, convert it from the string representation to a
			RegularExpression object.  Store it back in the array incase
			it is needed again.
		*/
		var intMin = 0;
		var intMax = this.arrDatatypes.length - 1;
		var intCurrent = Math.round(this.arrDatatypes.length/2);
		var regExp = null;

		while ((intMin <= intMax) && (regExp == null)){
			if (this.arrDatatypes[intCurrent][0] == datatype){
				regExp = this.arrDatatypes[intCurrent][1];
				if (typeof regExp == "string"){
					if (this.arrDatatypes[intCurrent][2]){
						regExp = new RegExp(regExp, this.arrDatatypes[intCurrent][2]);
					}   // if
					else{
						regExp = new RegExp(regExp);
					}   // else
				}   // if
			}   // if
			else{
				if (datatype > this.arrDatatypes[intCurrent][0]){
					intMin = intCurrent + 1;
				}   // if
				else{
					intMax = intCurrent - 1;
				}   // else
				intCurrent = Math.round(intMin + (intMax-intMin)/2);
			}   // else
		}   // while
		return regExp;
	}   // fxGetRegExp
}   // datatype

function clsValidator (objForm) {
	/*
		Comments
		Class for validating forms. Create one instance of this 
		class for each form to be validated.
		Limitations:
			- Select boxes must have explicit values declared if required.
			- Only one radio button within a group should be set as required.
				Setting it multiple times will just slow client performance.
	*/
	this.objForm = objForm;
	objForm.validator = this;
	
	this.strSpace = " ";
	this.errorHeader = "The following errors have occured:\n";
	this.errorPrefix = this.strSpace + this.strSpace + this.strSpace 
						+ "-" + this.strSpace;
	this.errorSuffix = "\n";
	this.errorList = null;
	this.rules = new Array();
	this.datatype = new clsDatatype;

	this.validate = fxValidate;
	this.getFieldValue = fxGetFieldValue;
	this.getFieldData = fxGetFieldData;
	this.checkValidData = fxCheckValidData;
	this.addRule = fxAddRule;
	this.checkRules = fxCheckRules;
	this.addError = fxAddError;
	this.reportErrors = fxReportErrors;
	/*
		Members
	*/
	function fxValidate(){   // member of clsValidator
		/*
			Comments
			Runs all validation scripts and reports any errors
			Returns true if no errors occured, otherwise false
		*/
		var blnNoErrors, objFormElement, strValue, regExp;
		// loop through all the elements in the form
		for (var intIndex=0; intIndex<this.objForm.elements.length; intIndex++){
			objFormElement = this.objForm.elements[intIndex];
			strValue = this.getFieldValue(objFormElement);
			if (strValue){
				strValue = new String(strValue);
				strValue = strValue.trim();
			}
			// Check required fields are filled
			if ((objFormElement.required) &&
					((strValue == null) || (strValue == ""))) {
				objFormElement.blnValid = false;
				this.addError(objFormElement.requiredError);
			}   // if
			else{
				objFormElement.blnValid = true;
			}   // id
			// If the field has a datatype specified, check if the data
			// supplied is of the correct datatype.
			if ((objFormElement.datatype != null) && (strValue != null)
													&& (strValue != "")){
				this.checkValidData(objFormElement, strValue);
			}   // if
		}   // for
		this.checkRules();
		blnNoErrors = ((this.errorList == null) || (this.errorList.length == 0));
		if (!blnNoErrors){
			this.reportErrors();
		}   // if
		this.errorList = null;   // reset the error list for next validation request
		return blnNoErrors;
	}   // fxValidate

	function fxGetFieldValue (objField) {   // member of clsValidator
		/*
			Comments
			Returns the value of the object.  Has to determine the type of
			the object to figure out how to grab the value.  Assumes that each 
			option of a select box has an explicit value assigned.
		*/
		var value = null;
		switch (objField.type){
			case "select-one":
				// this must be a select box
				value = objField.options[objField.selectedIndex].value;
				break;
			case "radio":
				// this is a radio button.  loop through and make sure at least 
				// one is selected
				var arrRadios = eval("objField.form." + objField.name);
				for (var intIndex=0;((intIndex<arrRadios.length) 
									&& (value == null));intIndex++){
					if (arrRadios[intIndex].checked){
						value = arrRadios[intIndex].value;
					}   // if
				}   // for
				break;
			default:
				// this must be a form field that has a value (input box or something)
				value = objField.value;
		}
		return value;
	}   // fxGetFieldValue

	function fxGetFieldData (objField) {   // member of clsValidator
		/*
			Comments
			Gets the value of the field, and if appropriate converts it to the
			correct data type and returns it.  Otherwise returns it as a string.
		*/
		var value = this.getFieldValue(objField);
		if (objField.datatype){
			if (objField.datatype == this.datatype.integer) {
				value = parseInt(value, 10);
			}   // if
			else if (objField.datatype == this.datatype.decimal) {
				value = parseFloat(value);
			}   // else if
			else if (objField.datatype == this.datatype.date) {
				value = new Date(value);
			}   // else if
		}   // if
		return value;
	}   // fxGetFieldData

	function fxCheckValidData(objField, strInput){   // member of clsValidator
		/*
			Comments
			Checks to make sure the data fits the datatype of the object.
			If not add the error to the error list and return whether valid or not.
		*/
		// get the regular expression describing the format of the datatype
		var regExp = this.datatype.getRegExp(objField.datatype);
		if ((regExp) && (!regExp.test(strInput))){
			this.addError(objField.datatypeError);
			objField.blnValid = false;
		}   // if
		else{
			objField.blnValid = true;
		}   // else
		return objField.blnValid;
	}   // fxCheckValidData

	function fxAddRule (objField1, objField2, strOperator, strError) {   // member of clsValidator
		/*
			Comments
			Adds a new rule to the rules array
		*/
		this.rules[this.rules.length] = (new clsRule(objField1, objField2, strOperator, strError, this));
	}   // fxAddRule

	function fxCheckRules () {   // member of clsValidator
		/*
			Comments
			Loops through the rules array and checks if they are obeyed.  Adds
			any errors to the error array if encountered.  Only checks a rule
			if both it's fields have already been passed as valid for their data
			types.
		*/
		var rule;

		for (var intIndex=0;intIndex<this.rules.length;intIndex++){
			rule = this.rules[intIndex];
			// this condition is a bit complex, but essentailly
			// attempt to validate only if each field either is
			// a form object and has valid data or is some primitive
			// datatype
			if ((((typeof rule.objField1 == "object") &&
					(rule.objField1.blnValid)) ||
					(typeof rule.objField1 != "undefined")) &&
					(((typeof rule.objField2 == "object") &&
					(rule.objField2.blnValid)) ||
					(typeof rule.objField2 != "undefined"))){
				if (!rule.validate()){
					this.addError(rule.strError);
				}   // if
			}   // if
		}   // for
	}   // fxAddRule

	// Error Handling Functions
	function fxAddError(strError){   // member of clsValidator
		/*
			Comments
			Adds an error to the error list.  If the errorList is 
			null, create it.  Can be called by custom validation
			scripts before a call to vlaidate.
		*/
		if (this.errorList == null){
			this.errorList = new Array();
		}
		this.errorList[this.errorList.length] = strError;
	}   // fxAddError

	function fxReportErrors () {   // member of clsValidator
		/*
			Comments
			Alerts any errors that have occured
		*/
		var strErrors = this.errorHeader;
		for (var intIndex=0;intIndex<this.errorList.length;intIndex++){
			strErrors += this.errorPrefix + this.errorList[intIndex] + this.errorSuffix;
		}   // for
		alert(strErrors);
	}   // fxReportErrors

	function clsRule (objField1, objField2, strOperator, strError, objParent) {   // member of clsValidator
		/*
			Comments
			Class representing a rule for a form.  Contains the two objects
			that are related: objField1 and objField2, the relationship 
			rule: strOperator, and the error message: strError.  Can only
			be created by clsValidator.addRule().
		*/
		this.parent = objParent;
		this.objField1 = objField1;
		this.objField2 = objField2;
		this.strOperator = strOperator;
		this.strError = strError;

		this.validate = fxValidate;
		/*
			Members
		*/
		function fxValidate () {   // member of clsRule
			/*
				Comments
				Checks weather the two fields obey the supplied rule.  If a data type
				is specified for the fields, it tries to grab the fields as their
				data type, not as a string.
			*/
			var blnResult = false;
			var strVal1 = this.objField1;
			var strVal2 = this.objField2;
			if (typeof this.objField1 == "object")
				strVal1 = this.parent.getFieldData(this.objField1);
			if (typeof this.objField2 == "object")
				var strVal2 = this.parent.getFieldData(this.objField2);
			blnResult = eval(strVal1 + this.strOperator + strVal2);
			return blnResult;
		}   // fxValidate
	}   // clsRule
}   // clsValidator

