/*
FormValidator.js
FormValidator class plus generic functions
author: Pedro Moraes <pedro@omniconsultoria.com.br>
last update: Pedro Moraes 11/07/01
use and distribute only if maintaining credits.
*/
var arrValidators=new Array();

//<<<<START of the constructor
function FormValidator() {
	//Properties
	var name = arguments[0]; //The name of the form
	var errors = new Array(); //Array with all errors found
	var rules = new Array(); //This array contains all the rules to apply
	this.fieldToFocus = null; //If an error is found, its field will be focused
	var debugMode = (arguments[1]) ? arguments[1] : 0;
	//Methods
	this.showErrorMessage = function(sMsg) {
		alert(sMsg);
	};
	var handleFormValidatorError=function() { //Used to handle errors in the code - only for debug
		if (this.debugMode==1) {
			alert("FormValidator error: " + arguments[0]);
			return(false);
		}
		else return(false); //For debug mode 0 (standard), don't show error messages
	};
	this.handleError=function() {
		if (!this.fieldToFocus && objField)	this.fieldToFocus=arguments[0];
		errors[errors.length]=new error(arguments[0],arguments[1]);
	};
	this.initialize=function() {
		if (document.forms[name]) {
			document.forms[name].onsubmit=function() {
				return(eval("self.objVld_" + name + ".execute()"));
			}
		}
		else
			return(handleFormValidatorError("The form '" + name + "' isn't present or it has not been loaded yet"));
	};
	this.addRule=function() { //Feeds rules array
		NewRule = new rule(arguments[0],arguments[1],(arguments[2]) ? arguments[2] : null,(arguments[3]) ? arguments[3] : null,(arguments[4]) ? arguments[4] : null)
		rules[rules.length] = NewRule;
		return(NewRule);
	};
	this.execute=function() {
		objForm=document.forms[name];
		for (intRule=0;intRule<rules.length;intRule++) {
			objRule=rules[intRule];
			if (objRule.detRule) {
				if (objRule.detRule.status) break;
			}
			if (objForm[objRule.fieldName] || objRule.ruleTypes[0] == "externalFunction") {
				objField=objForm[objRule.fieldName];
				for (iR = 0; iR < objRule.ruleTypes.length; iR ++) {
					switch (objRule.ruleTypes[iR]) {
						case "externalFunction": //Evaluates an external function
							extFunction=eval(objRule.fieldName);
							if (typeof(extFunction) == "function" || typeof(extFunction) == "object") {
								if (!extFunction(objForm)) objRule.error = iR;
							}
							break;
						case "date": //Pattern: dd/mm/yyyy
							if (!isEmpty(getFieldValue(objField)) && !verifyDate(getFieldValue(objField))) {
								objRule.error = iR;
							}
							break;
						case "numeric": //Validates if value in accordance with given regular expression
							if (!isEmpty(getFieldValue(objField)) && !isNumeric(getFieldValue(objField))) {
								objRule.error = iR;
							}
							break;
						case "cep": //Pattern: 99999-999 / 99999999
							if (!isEmpty(getFieldValue(objField)) && !verifyCEP(getFieldValue(objField))) {
								objRule.error = iR;
							}
							break;
						case "email": //a@b.c, c'est claire
							if (!isEmpty(getFieldValue(objField)) && !verifyMail(getFieldValue(objField))) {
								objRule.error = iR;
							}
							break;
						case "required": //The field cannot be empty, that's all
							if (isEmpty(getFieldValue(objField))) {
								objRule.error = iR;
							}
							break;
					}
				}
			}
		}
		for (intRule=0;intRule<rules.length;intRule++) {
			objRule=rules[intRule];
			if (objRule.error != null) {
				if (objRule.detRule) {
					if (objRule.detRule.error != null) this.handleError(objForm[objRule.fieldName] ? objForm[objRule.fieldName] : null, objRule.errorMessage[objRule.error]);
				} else {
					this.handleError(objForm[objRule.fieldName] ? objForm[objRule.fieldName] : null, objRule.errorMessage[objRule.error]);
				}
			}
		}
		for (intRule=0;intRule<rules.length;intRule++) rules[intRule].error = null;
		if (errors.length) { //Show errors and stop the process
			strErrorMessage="Os seguintes erros foram encontrados no preenchimento do formulário:\n\n";
			arrUsedFields=new Array(); //A list of the fields with errors, used to avoid more than one message related to the same field
			for (intError=0;intError<errors.length;intError++) {
				if (!inArray(errors[intError].field,arrUsedFields)) {
					arrUsedFields[arrUsedFields.length]=errors[intError].field;
					strErrorMessage+="- " + errors[intError].message + "\n";
				}
			}
			this.showErrorMessage(strErrorMessage);
			if (this.fieldToFocus) {
				if (this.fieldToFocus.type != "hidden") {
					window.fieldToFocus = this.fieldToFocus;
					window.delayedFocus = function() {
						window.fieldToFocus.focus();
					}
					setTimeout("window.delayedFocus()", 10);
				}
			}
			errors=new Array(); //Cleaning...
			this.fieldToFocus=null; //Cleaning...
			return(false);
		}
		else return(true);
	};
	//Add the object to arrValidators Array
	arrValidators[arrValidators.length]=this;
}
//>>>>END of the constructor

function error(objField,strErrorMessage) { //error class
	this.field=objField;
	this.message=strErrorMessage;
}

function rule(strFieldName,ruleType,strCustomFieldName,errorMessage,detRule) { //rule class
	this.fieldName=strFieldName;
	this.ruleTypes=isArray(ruleType) ? ruleType : [ruleType];
	this.customFieldName=strCustomFieldName;
	this.errorMessage = new Array(this.ruleTypes.length);
	if (isArray(errorMessage)) this.errorMessage = errorMessage;
	else this.errorMessage[0] = errorMessage;
	for (iR = 0; iR < this.errorMessage.length; iR ++) {
		if (this.errorMessage[iR] == null) {
			switch (this.ruleTypes[iR]) {
				case "date":
					this.errorMessage[iR] = "Data inválida";
					break;
				case "cep":
					this.errorMessage[iR] = "CEP inválido";
					break;
				case "email":
					this.errorMessage[iR] = "E-mail inválido";
					break;
				case "numeric":
					this.errorMessage[iR] = "O campo " + ((this.customFieldName) ? this.customFieldName : this.fieldName) + " só pode conter números";
					break;
				case "required":
					this.errorMessage[iR] = "O campo " + ((this.customFieldName) ? this.customFieldName : this.fieldName) + " deve ser preenchido";
					break;
			}
		}
	}
	this.detRule = detRule;
	this.error = null;
}

onload=function() {
	for (intValidator=0;intValidator<arrValidators.length;intValidator++) {
		arrValidators[intValidator].initialize();
	}
}

function getValidator(strFormName) {
	eval('self.objVld_' + strFormName + '=new FormValidator(\'' + strFormName + '\');');
	return(eval('self.objVld_' + strFormName));
}

// Generic functions
function getFieldValue(objField) { //Get the value of a form field - lacks checkbox, but I've made it elsewhere already
	strReturnValue='';
	if (objField[0]) {
		if (objField[0].type=="radio") {
			for (intRadio=0;intRadio<objField.length;intRadio++) {
				if (objField[intRadio].checked) strReturnValue = objField[intRadio].value;
			}
		} else if (objField.type=="select-one") {
			strReturnValue = objField.options[objField.selectedIndex].value;
		} else if (objField.type=="select-multiple") {
			sVal = "";
			for (iOption = 0; iOption < objField.options.length; iOption ++) {
				if (objField.options[iOption].selected) sVal += objField.options[iOption].value + ", ";
			}
			strReturnValue = sVal.substring(0, sVal.length - 2);
		}
	}
	else
		strReturnValue=objField.value;
	return(strReturnValue);
}

function isArray(obj) {
	if (obj != null && typeof(obj) == "object") {
		return(obj.constructor.toString().indexOf("Array") != -1);
	}
	else return(false);
}

function inArray(value, arrAnyArray) { //returns a boolean indicating whether the first argument is present in the second, which must be an array
	for (intPos=0;intPos<arrAnyArray.length;intPos++)
		if (arrAnyArray[intPos]==value) return true;
	return(false);
}

function explode(arrAnyArray,strSeparator) {
	var strReturnValue="";
	for (intPos=0;intPos<arrAnyArray.length;intPos++)
		strReturnValue+=arrAnyArray[intPos] + strSeparator;
	strReturnValue=strReturnValue.substring(0,strReturnValue.length-strSeparator.length);
	return(strReturnValue);
}

function isEmpty(value) {
	if (isArray(value)) return(value.length==0);
	else return(value==null || trim(value)=="");
}

function trim(str) {
	var intChar,strChar,intStartIndex,intEndIndex;
	for (intChar=0;intChar<str.length;intChar++) {
		strChar=str.substr(intChar,1);
		if (strChar!=" " && strChar!="\n" && strChar!="\r" && strChar!="\t") {
			intStartIndex=intChar;
			break;
		}
	}
	for (intChar=str.length-1;intChar>=0;intChar--) {
		strChar=str.substr(intChar,1);
		if (strChar!=" " && strChar!="\n" && strChar!="\r" && strChar!="\t") {
			intEndIndex=intChar;
			break;
		}
	}
	return(str.substring(intStartIndex,intEndIndex+1));
}

function isNumeric(str) {
	for (i = 0; i < str.length; i ++) {
		if (isNaN(str.substr(i, 1))) return(false);
	}
	return(true);
}

function verifyCEP(strCEP) {
	return(!(strCEP.search("[0-9]{8}")==-1 && strCEP.search("[0-9]{5}-[0-9]{3}")==-1));
}

function verifyMail(strEmail) {
	strValidChars="@abcdefghijklmnopqrstuvxywzABCDEFGHIJKLMNOPQRSTUVXYWZ0123456789-_."; //Characters that an email may contain. (i'm not completely sure about it, but I didn't find any specification that could help)
	arrBoundaryChars=new Array();
	for (intPos=0;intPos<strEmail.length;intPos++) { //Check if all characters are valid
		strThisChar=strEmail.substr(intPos,1);
		if (strValidChars.indexOf(strThisChar)==-1) {
			return(false);
		}
		if (strThisChar=="@" || strThisChar==".") { //Feed the BoundaryChars array
			arrBoundaryChars[arrBoundaryChars.length]=parseInt(intPos);
		}
	}
	for (intPos=0;intPos<arrBoundaryChars.length;intPos++) { //Check if dots and ats are not in conflict
		intThisItem=arrBoundaryChars[intPos]
		intNextItem=(intPos==arrBoundaryChars.length) ? 0 : arrBoundaryChars[intPos+1]
		if (intThisItem==0 || intThisItem==strEmail.length-1) {
			return(false);
		}
		if (intThisItem+1==intNextItem) {
			return(false);
		}
	}

	intFirstAtIndex=strEmail.indexOf("@");
	intLastAtIndex=strEmail.lastIndexOf("@");
	intFirstDotIndex=strEmail.indexOf(".");
	intLastDotIndex=strEmail.lastIndexOf(".");

	if (intFirstAtIndex!=intLastAtIndex || intFirstAtIndex==-1 || intFirstDotIndex==-1 || intLastDotIndex<intFirstDotIndex) {
		return(false);
	}
	return(true); //If it got here, it's all ok (I hope)
}

function verifyDate(strDate) {
	strDate=trim(strDate);
	if (strDate.search("[0-9]{2}\/[0-9]{2}\/[0-9]{4}")==-1) //Out of pattern dd/mm/yyyy
		return(false);
	else {
		intDay=strDate.split("\/")[0];
		intMonth=strDate.split("\/")[1];
		intYear=strDate.split("\/")[2];
		if (intDay<1 || intMonth<1 || intMonth>12 || intYear<1900 || intYear>2100) return(false);
		arrLastMonthDay=[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
		if (((intYear % 4 == 0) && intYear % 100 != 0) || intYear % 400 == 0) //Year with 366 days
			arrLastMonthDay[1]=29;
		if (intDay>arrLastMonthDay[-1]) return(false);
	}
	return(true);
}

function convertPlainToHTML(sPlain) {
	var sOutput = "";
	for (i = 0; i < sPlain.length; i ++) {
		sChar = sPlain.substring(i, i + 1);
		switch (sChar) {
			case "\n":
				sOutput += "<br>";
				break;
			case "\t":
				sOutput += "<dd>";
				break;
			default:
				sOutput += sChar;
		}
	}
	return(sOutput);
}
