/*

		Last Modified: 9/13/05 by Mike Parrish

		This is a generic validation class for html forms.  By default, it scans all the fields in the form,
		minus hidden and submit fields, and checks for population.  This can be altered to search a predefined
		set of fields, or all fields less a predefined set.  Additionally, regular expressions can be assigned
		to fields to be used instead of simple null/not null verification.  All these customizations are done
		through a set of arrays.

		Special Functions:
			validateForm 				= validates the entire form.  It performs checks for validateMethod as well as the
														contents of both the fieldsToCheck and fieldsToIgnore arrays in order to establish
														what fields to validate and what fields to skip.
			validateField 			= validates a single field.  If the user wants to validate fields individually, this
														is the function to use.  This function is called within a loop in the validateForm
														function.
			addError						= adds a message to the error stack for displaying to the user.
			isValidField  			= checks for validity of field either via regular exp. or simply check for null,
														based on whether the field is in the regExpressions array
			cleanupName					= returns a modified name for displaying in the popup.  If the user has put an entry
														in the fieldDisplayNames for a particular field it will use that, otherwise it will
														delete all underscores and change to title case
			addCheckField				= adds a field to the fieldsToCheck array
			ignoreField					= adds a field to the fieldsToIgnore array
			addDisplayName			= adds a field, along with a name to display on the popup, to the fieldDisplayNames
														array
			addRegExpression		= adds a field, along with a regular expression, for validation
			setValidateMethod		= change method of validating to "include" or "exclude".  "include" method will
														cause the script to validate only those fields in the fieldsToCheck array.
														"exclude" method will cause the script to validate all fields except those in the
														fieldsToIgnore array.

			**** Note: submit and hidden fields are automatically skipped.  Those must be validated manually,
								 through the validateField function.
			**** Note: there are several other functions that are more self-explanatory than those listed above
			
			***************************************************************************************************
			Typical implementation:
			
					<script language="JavaScript" src="/common_scripts/formValidator.js"></script>
					<script language="JavaScript" type="text/javascript">
					<!--
					var fv = new formValidator();
					fv.fieldsToCheck = new Array('first_name','last_name','email');
					var formSubmitted = false;
					
					function checkForm(f){
						if (formSubmitted) return false;
						fv.setForm(f);
						formSubmitted = fv.validateForm();
						return formSubmitted;
					}
					//-->
					</script>
					
					<form action="<?php print $_SERVER["PHP_SELF"]?>" method="post" onSubmit="return checkForm(this)">
			***************************************************************************************************
			
*/

function formValidator(docForm,debug){

	this.form								= "";								// form object to be passed on init or through setForm function
	this.debug             	= debug?true:false;
	this.validateMethod		 	= "include";				// validateMethod uses the following values:
	this.fieldsToCheck     	= new Array();			// 	include	= validate fields in fieldsToCheck array only
	this.fieldsToIgnore    	= new Array();			// 	exclude	= validate all except those in fieldsToIgnore array
	this.regExpressions	   	= new Array();			// array of fields to be matched using regular expressions
	this.radioGroups			 	= new Array();			// used to validate radio groups for a checked value
	this.fieldDisplayNames 	= new Array();			// optional assoc array of names to display in alert
	this.requiredBlocks 		= new Array();			// optional assoc array of span or div blocks to denote req field
	this.focusField				 	= "";								// used to store first field to return focus after validation fails
  this.enableAutoValidate	= true;							// for performing auto email validation based on name of field
  this.totalErrors				= 0;								// running total of errors
  this.errorPopup					= "";								// message to be displayed in popup
  this.reqBlockColor			= "#FF0000";				// color to mark required block, if exists, when validation fails
  this.reqBlockDefColor   = "";								// used for the default color of the required blocks (asterisks)
  this.trace							= "";

	if (this.isObject(docForm)) {
		this.form = docForm;
	} else {
		this.form = "";
		this.addTrace('No valid form passed on instantiation');
	}
}
formValidator.prototype.validateForm = function(){
	if (!this.isObject(this.form)){
		this.addTrace("No form detected.  Validation is terminated!");
		if (this.debug) alert(this.trace);
		return false;
	}
		// lets see if there is a required block and, if so, get the default color
		// of the first field we find (assuming there aren't different colors for each field)
	if(!this.reqBlockDefColor){
		for(var i=0;i<this.form.length;i++){
			oName = this.form.elements[i].name;
			requiredBlk = this.requiredBlocks[oName] ? this.requiredBlocks[oName] : "Required" + oName;
			try {
				s = document.getElementById(requiredBlk);
				if (s != null && s.style) {
					if (s.style.color) this.reqBlockDefColor =	s.style.color;
					break;
				}
			} catch (er) {
				this.addTrace("Error trying to set default color for reqBlockDefColor. \n" + er);
			}
		}
		if(!this.reqBlockDefColor) this.reqBlockDefColor = "#000000";
	}
	
	this.trace = "";
	if (this.fieldsToCheck.length && this.fieldsToIgnore.length){
				// both arrays have been explicity populated, which way do we go - inclusive or exclusive?
		this.addTrace("Ambiguity alert: both arrays populated - could cause unexpected results.\n");
	}
	this.errorPopup = "The following field(s) need to be filled out:\n\n";
	this.totalErrors = 0;
		/*
			The following is checking if the default settings have not been changed, but
			entries have been added to the fieldsToIgnore array.  We'll ASSuME that the
			user wants the validation method to be changed to 'exclude' under this circumstance
		*/
	if (this.validateMethod == "include") {
		if (!this.fieldsToCheck.length) {
			if (this.fieldsToIgnore.length) {
				this.validateMethod = "exclude";
				this.addTrace('Changing validate method to "exclude"');
			} else {
				this.addTrace('Validating all fields in form (no fields explicity tagged).');
				for(var i=0;i<this.form.length;i++){
						// default has not changed, so lets place all fields in the array
					this.fieldsToCheck[this.fieldsToCheck.length] = this.form.elements[i].name;
				}
			}
		}
	}
	var errors=0;
	var objField="";
	var n="";

	for (var i=0;i<this.form.length;i++){
			// make sure we are suppose to check this field if method is inclusive
		if (this.validateMethod == "include" && !this.inArray(this.form.elements[i].name,this.fieldsToCheck)) continue;
			// make sure we are not suppose to ignore this field if method is exclusive
		if (this.validateMethod == "exclude" && this.inArray(this.form.elements[i].name,this.fieldsToIgnore)) continue;
		this.addTrace("Checking field: "+this.form.elements[i].name);
		this.validateField(this.form.elements[i]);
	}
	for (i in this.radioGroups)  {
		if (this.debug) alert('Found radio - '+i);
		if (this.radioGroups[i] != true) this.addError(i);
	}
	if (this.totalErrors>0){
		alert(this.errorPopup);
		if(this.focusField) {
			try {
				this.focusField.focus();
			} catch (er) {
				this.addTrace("Error trying to focus "+er);
			}
		}
		this.focusField = false;
		if (this.debug && this.trace) alert(this.trace);
		return false;
	}
	if (this.debug){
		alert("Validation Succeeded!\nForm not submitted in debug mode\n\nTrace:\n"+this.trace);
		return false;
	} else {
		return true;		// nothing failed, so that means success
	}
}

formValidator.prototype.validateField = function(objField){
	if (this.isString(objField)){
		objField = eval('this.form.'+objField);
	}
	if (!this.isObject(objField)){
		this.addTrace("Field object could not be resolved.");
		return false;
	}
	objName = objField.name;
	objValue = objField.value;
	var retValue = true;

					// For Text Fields
	if (objField.type == "text" || objField.type == "password" || objField.type == "textarea"){
		this.addTrace("Found type - text");
		if (!this.isValidField(objName,objValue)){
			this.addError(objField);
			retValue = false;
		} else if (this.enableAutoValidate){
					// if auto validation is enabled, check for common verifications (email, phone, etc)
			if (objName.toLowerCase().indexOf("email")>=0){
				if (!this.isEmail(objValue)) {
					this.addError(objField,"Valid Email Address");
					retValue = false;
				}
			} else if (objName.toLowerCase().indexOf("phone")>=0){
				var re = new RegExp('[^0-9()/-]','gi');
				var re2 = new RegExp('','gi');
				var tempValue = objValue.replace(re,'');
				var tempValue = tempValue.replace(re2,'');
				if (tempValue != objValue){
					var n = this.cleanupName(objName);
					this.addError(objField,n+" (Valid Format)");
					retValue = false;
				}
			} else if (objName.toLowerCase() == "age"){
				if (isNaN(objValue) || objValue>130){
					var n = this.cleanupName(objName);
					this.addError(objField,n+" (Valid Age)");
					retValue = false;
				}
			}
		}
					// for dropdown menus (netscape specific)
	} else if (objField.type == "select-one"){
		this.addTrace("Found type - drop down");
		if (!this.isValidField(objName,objField.options[objField.selectedIndex].value)) {
			this.addError(objField);
			retValue = false;
		}

					// For file fields (in case we want to do extra stuff for file fields in future)
	} else if (objField.type == "file"){
		this.addTrace("Found type - file");
		if (objValue == "") {
			this.addError(objField);
			retValue = false;
		}

					// For radio fields
	} else if (objField.type == "radio"){										// check to ensure at least one radio button is checked
		this.addTrace("Found type - radio button");
	
		var radiogroup = this.form.elements[objName]; 	// get the whole set of radio buttons.
		var radioChecked = false;
	  for(var j = 0 ; j < radiogroup.length ; ++j) {
			if (this.radioGroups[objName] != true) this.radioGroups[objName] = radiogroup[j].checked;
	  	if(radiogroup[j].checked) {
	  		radioChecked = true;
	  		break;
	  	}
	  }
	  if (!radioChecked) retValue = false;	// no radio button selected, so return false
	}
	if (retValue == false){
		this.flagRequiredField(objName);
	} else {
		this.flagRequiredField(objName,this.reqBlockDefColor);
	}
	return retValue;
}

formValidator.prototype.addError = function(field,msg){
		//alert(field.name);
		var n = "";
		if (this.isObject(field)){
			n = field.name;
		} else {
			n = field;
		}
		msg = msg ? msg : this.cleanupName(n);
		this.errorPopup += msg+"\n";
		if (!this.focusField) {
			if (this.isObject(field)){
				this.focusField = field;
			} else {
				this.focusField = eval('this.form.'+field);
			}
		}
		this.totalErrors++;
}

formValidator.prototype.isValidField = function(field,value){
	if (this.regExpressions[field]){
		this.addTrace("Applying regular expression to "+field+"("+this.regExpressions[field]+" - "+value+")");
		var re = new RegExp(this.regExpressions[field]);
		return re.exec(value)==null?false:true;
	} else {
		this.addTrace("Checking if "+field+" ("+value+") is empty");
		return !(value == "");
	}
}

formValidator.prototype.cleanupName = function(fieldName){
	if (!fieldName && this.debug) alert(this.trace);
	if (this.isObject(fieldName)) {
		try {
			fieldName=fieldName.name; // convert field object to string
		} catch (er) {
			this.addTrace("Error trying to translate field object to name");
			return false;
		}
	}
	if (this.fieldDisplayNames[fieldName]) return this.fieldDisplayNames[fieldName];

	var n=fieldName.charAt(0).toUpperCase();							// store the first character of fieldName into n (uppercase)
	for (var a=1;a<fieldName.length;a++){									// loop thru fieldName, starting at the 2nd character
		if (fieldName.charAt(a).search(/[A-Z]/) != -1 || 		// if the character is uppercase or
		    fieldName.charAt(a).search(/[0-9]/) != -1) {	 	// the character is a number or
			n+=" "+fieldName.charAt(a);												// start a new word by inserting space, then the next character
		} else if (fieldName.charAt(a).search(/_/) != -1){	// the character is an underscore(_):
			n+=" "+fieldName.charAt(a+1).toUpperCase();				// replace the underscore with a space and cap the next letter
			a++;
		} else {						// otherwise
			n+=fieldName.charAt(a);				//   just add the next character
		}
	}

	// Below is specifically for form fields that end in 'id'
	// It just eliminates those letters at the end.  Shouldn't hurt most other uses
	if (n.substring(n.length-2,n.length) == "id"){
		n = n.substring(0,n.length-2);
	}
	return n;
}

formValidator.prototype.inArray = function (value,arr) {
	if (!this.isArray(arr)) {
		if (this.debug) alert('nonArray passed to inArray function');
		return false;
	}
	var i;
	for (i=0; i < arr.length; i++) {
		// Matches identical (===), not just similar (==).
		if (arr[i] === value) {
			return true;
		}
	}
	return false;
}
formValidator.prototype.isAlien = function(a) {
   return this.isObject(a) && typeof a.constructor != 'function';
}
formValidator.prototype.isArray = function(a) {
		return this.isObject(a) && a.constructor == Array;
}
formValidator.prototype.isBoolean = function(a) {
    return typeof a == 'boolean';
}
formValidator.prototype.isEmpty = function(o) {
    var i, v;
    if (this.isObject(o)) {
        for (i in o) {
            v = o[i];
            if (isUndefined(v) && isFunction(v)) {
                return false;
            }
        }
    }
    return true;
}
formValidator.prototype.isFunction = function(a) {
    return typeof a == 'function';
}
formValidator.prototype.isNull = function(a) {
    return typeof a == 'object' && !a;
}
formValidator.prototype.isNumber = function(a) {
    return typeof a == 'number' && isFinite(a);
}
formValidator.prototype.isObject = function(a) {
    return (a && typeof a == 'object') || this.isFunction(a);
}
formValidator.prototype.isString = function(a) {
    return typeof a == 'string';
}
formValidator.prototype.isUndefined = function(a) {
    return typeof a == 'undefined';
}
formValidator.prototype.isEmail = function(str) {
  // are regular expressions supported?
  var supported = 0;
  if (window.RegExp) {
    var tempStr = "a";
    var tempReg = new RegExp(tempStr);
    if (tempReg.test(tempStr)) supported = 1;
  }
  if (!supported)
    return (str.indexOf(".") > 2) && (str.indexOf("@") > 0);
  var r1 = new RegExp("(@.*@)|(\\.\\.)|(@\\.)|(^\\.)");
  var r2 = new RegExp("^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,4})(\\]?)$");
  return (!r1.test(str) && r2.test(str));
}
formValidator.prototype.isUndefined = function(a) {
    return typeof a == 'undefined';
}
formValidator.prototype.addCheckField = function(fieldName,reqBlock) {
		if (this.isObject(fieldName)) {
			try {
				fieldName = fieldName.name; // convert field object to string
			} catch (er) {
				this.addTrace("Error trying to translate field object to name");
			}
		}
		if (reqBlock){
			this.addRequiredBlock(fieldName,reqBlock);
		}
    this.fieldsToCheck[this.fieldsToCheck.length] = fieldName;
}
formValidator.prototype.ignoreField = function(fieldName) {
		if (this.isObject(fieldName)) {
			try {
				fieldName = fieldName.name; // convert field object to string
			} catch (er) {
				this.addTrace("Error trying to translate field object to name");
			}
		}
    this.fieldsToIgnore[this.fieldsToIgnore.length] = fieldName;
}
formValidator.prototype.addDisplayName = function(fieldName,displayName) {
		if (this.isObject(fieldName)) {
			try {
				fieldName=fieldName.name; // convert field object to string
				this.fieldDisplayNames[fieldName] = displayName;
			} catch (er) {
				this.addTrace("Error trying to translate field object to name");
			}
		} else {
			this.fieldDisplayNames[fieldName] = displayName;
		}
}
formValidator.prototype.addRequiredBlock = function(fieldName,reqBlock) {
	  if (this.isObject(reqBlock)){
	  	try {
	  		reqBlock = reqBlock.name;
	  	} catch(er){
				this.addTrace("Error trying to translate required object to name");
				return false;
	  	}
	  }
	  
		if (this.isObject(fieldName)) {
			try {
				fieldName=fieldName.name; // convert field object to string
			} catch (er) {
				this.addTrace("Error trying to translate field object to name");
				return false;
			}
		}
		this.requiredBlocks[fieldName] = reqBlock;
}
formValidator.prototype.addRegExpression = function(fieldName,regex) {
		if (this.isObject(fieldName)) {
			try {
				fieldName=fieldName.name; // convert field object to string
			} catch (er) {
				this.addTrace("Error trying to translate field object to name");
				return false;
			}
		}
    this.regExpressions[fieldName] = regex;
}
formValidator.prototype.addTrace = function(str) {
    this.trace += str+"\n";
}
formValidator.prototype.setValidateMethod = function(str) {
    this.validateMethod = (str == "exclude") ? str : "include";
}
formValidator.prototype.setDebug = function(b) {
    this.debug = b;
}
formValidator.prototype.setForm = function(f) {
    if (this.isObject(f)) {
    	this.form = f;
    } else {
    	// try to create object from string
    	try {
    		this.form = eval('document.'+f);
    	} catch (er) {
    		this.addTrace("Error setting form object. \n" + er);
    	}
    }
}
formValidator.prototype.flagRequiredField = function(field,color) {
		//changes the color of the specified span, if no color provided it defaults to red
		//to be used for indicating missing required fields
	if (!document.getElementById) return;
	span = this.requiredBlocks[field] ? this.requiredBlocks[field] : "Required" + field;
	try {
		s = document.getElementById(span);
		if (s == null) return;
		if (!s.style) return;
		if (!color) color = this.reqBlockColor;
		s.style.color = color;
	} catch (er) {
		this.addTrace("Error trying to flag span object. \n" + er);
	}
}