/*********************************************************************
multi-language validation script
	version 2.21
	by matthew frank

There are no warranties expressed or implied.  I will not be held
responsible for any loss of data or sanity through the use or
implementation of this script.  This script may be re-used and
distrubted freely provided this header remains intact and all
supporting files are included (unaltered) in the distribution:

	validation.js   - this file
	validation.htm  - example form
	readme.htm      - directions on using this script
	(validation.zip contains all files)

If you are interested in keeping up with the latest releases of this
script or asking questions about its implementation, think about joining
the eGroups discussion forum dedicated to data validation:

	http://www.egroups.com/group/validation

*********************************************************************/


/*====================================================================
Function: Err
Purpose:  Custom object constructor
Inputs:   None
Returns:  undefined
====================================================================*/
function Err(){
	/*********************************************************************
	Method:   Err.clear
	Purpose:  Clear values from Error object
	Inputs:   none
	Returns:  undefined
	*********************************************************************/
	this.clear=function (){
		this.source=new Object;
		this.type=new Object;
		this.format=new String;
	}
	/*********************************************************************
	Method:   Err.add
	Purpose:  Adds error to Error object
	Inputs:   oSource - source element object
	          vType   - integer value of error type (or custom string)
	          sFormat - optional date format
	Returns:  undefined
	*********************************************************************/
	this.add=function (oSource,vType,sFormat){
		this.source=oSource;
		this.type=vType;
		this.format=sFormat;
	}
	/*********************************************************************
	Method:   Err.raise
	Purpose:  Gives visual warning to user about all errors contained in
	          the Error object
	Inputs:   none
	Returns:  undefined
	*********************************************************************/
	this.raise=function (){
		var oElement=this.source;
		var sLang;
		var sNym=oElement.getAttribute("nym");
		// if type is not a number, it must be a custom error message
		var sMsg=(typeof this.type=="string")?this.type:oElement.getAttribute("msg");

		oElement.paint();
		if(oElement.select)
			oElement.select();
		if(sMsg)
			alert(sMsg);
		else{
			// Walk through object hierarchy to find applicable language
			var oParent=oElement;
			sLang=oParent.getAttribute("lang").substring(0,2).toLowerCase();
			while(!sLang || !_validation.messages[sLang]){
				oParent=oParent.parentElement;
				if(oParent)
					sLang=oParent.getAttribute("lang").substring(0,2).toLowerCase();
				else
					// Default language is English
					sLang="en";
			}
			sMsg=_validation.messages[sLang][this.type];
			alert(((sNym)?sNym+": ":"")+sMsg+((this.format)
				?" "+this.format.reformat(sLang,this.type):""));
		}

		// Perform onvalidatefocus event handler for invalid field
		if(oElement.onvalidatefocus)
			oElement.onvalidatefocus();

		// Give invalid field focus
		oElement.focus();
		// Clear the Err object
		this.clear();
	}

	// Define the working object model
	this.clear();
}

/*====================================================================
Function: Validation
Purpose:  Custom object constructor.
Inputs:   None
Returns:  undefined
====================================================================*/
function Validation(){
	// Define global constants for calls to error message arrays
	this.REQUIRED = 0;
	this.INTEGER  = 1;
	this.FLOAT    = 2;
	this.DATE     = 3;
	this.AMOUNT   = 4;
	this.MASK     = 5;

	// Create error message dictionary
	this.messages = new Array;

	// Prototype the date tokens for each language
	Array.prototype.MM = new String;
	Array.prototype.DD = new String;
	Array.prototype.YYYY = new String;

	//English
	this.messages["en"]=new Array(
		"Please enter a value",
		"Please enter a valid integer",
		"Please enter a valid floating point",
		"Please enter a valid date",
		"Please enter a valid monetary amount",
		"Please enter a value in the form of ");
		with(this.messages["en"]){
			MM="MM";
			DD="DD";
			YYYY="YYYY";
		}

	//Portuguese
	this.messages["pt"]=new Array(
		"Por favor digite um valor válido",
		"Por favor digite um valor numérico inteiro",
		"Por favor digite o valor ponto flutuante",
		"Por favor digite uma data válida",
		"Por favor digite o valor monetário",
		"Por favor digite um valor no formulario ");
		with(this.messages["pt"]){
			MM="MM";
			DD="DD";
			YYYY="AAAA";
		}

	//French
	this.messages["fr"]=new Array(
		"Entrer une valeur, SVP",
		"Entrer un entier correct, SVP",
		"Entrer un point flottant correct, SVP",
		"Entrer une date correcte, SVP",
		"Entrer une valeur monetaire correcte, SVP",
		"Entrer, SVP, une valeur suivant le format suivant: ");
		with(this.messages["fr"]){
			MM="MM";
			DD="JJ";
			YYYY="AAAA";
		}

	//German
	this.messages["de"]=new Array(
		"Tragen Sie bitte einen Wert ein",
		"Tragen Sie bitte eine gültige Ganze Zahl ein",
		"Tragen Sie bitte ein Fließkomma ein",
		"Tragen Sie bitte ein gültiges Datum ein",
		"Tragen Sie bitte eine gültigen Geldbetrag ein",
		"Tragen Sie bitte einen Wert ein in Form von ");
		with(this.messages["de"]){
			MM="MM";
			DD="TT";
			YYYY="JJJJ";
		}

	//Italian
	this.messages["it"]=new Array(
		"Si prega di inserire un valore",
		"Si prega di inserire un valido numero intero",
		"Si prega di inserire un valido numero in virgola mobile",
		"Si prega di inserire una data valida",
		"Si prega di inserire una quantitá di valuta valida",
		"Si prega di inserire un valore nel formato ");
		with(this.messages["it"]){
			MM="MM";
			DD="GG";
			YYYY="AAAA";
		}

	//Spanish
	this.messages["es"]=new Array(
		"Por favor introduzca un valor",
		"Por favor introduzca un numero entero válido",
		"Por favor introduzca un punto flotante válido",
		"Por favor introduzca una fecha válida",
		"Por favor introduzca una cantidad monetaria válida",
		"Por favor introduzca un valor con el siguiente formato ");
		with(this.messages["es"]){
			MM="MM";
			DD="DD";
			YYYY="AAAA";
		}

	//Dutch
	this.messages["nl"]=new Array(
		"Vul s.v.p. een waarde in",
		"Vul s.v.p. een geldig geheel getal in",
		"Vul s.v.p. een geldig zwevend decimaal teken in",
		"Vul s.v.p. een geldige datum in",
		"Vul s.v.p. een geldig geldbedrag in",
		"Vul s.v.p. een waarde in, in de vorm van ");
		with(this.messages["nl"]){
			MM="MM";
			DD="DD";
			YYYY="JJJJ";
		}

	//Swedish
	this.messages["sv"]=new Array(
		"Var vänlig fyll i ett värde",
		"Var vänlig fyll i ett giltigt heltal",
		"Var vänlig fyll i ett giltigt tal",
		"Var vänlig fyll i ett giltigt datum",
		"Var vänlig fyll i ett giltigt belopp",
		"Var vänlig fyll i ett värde på formen ");
		with(this.messages["sv"]){
			MM="MM";
			DD="DD";
			YYYY="ÅÅÅÅ";
		}

	/*********************************************************************
	Method:   Validation.setDefault
	Purpose:  Set value for variable v if v is zero, empty string or
	          undefined
	Inputs:   v - variable (passed by value)
	          d - default value
	Returns:  v or d
	*********************************************************************/
	this.setDefault=function (v, d){
		return (v)?v:d;
	}
	/*********************************************************************
	Method:   Validation.isDate
	Purpose:  Check that value is a date of the correct format
	Inputs:   oElement - form element
	          sFormat  - string format
	Returns:  boolean
	*********************************************************************/
	this.isDate=function (oElement,sFormat){
		var sDate=oElement.value;
		var aDaysInMonth=new Array(31,28,31,30,31,30,31,31,30,31,30,31);

		// Fetch the date separator from the user's input
		var sSepDate=sDate.charAt(sDate.search(/\D/));
		// Fetch the date separator from the format
		var sSepFormat=sFormat.charAt(sFormat.search(/[^MDY]/i));
		// Compare separators
		if (sSepDate!=sSepFormat)
			return false;

		// Fetch the three pieces of the date from the user's input and the format
		var aValueMDY=sDate.split(sSepDate);
		var aFormatMDY=sFormat.split(sSepFormat);
		var iMonth,iDay,iYear;

		// Validate that all pieces of the date are numbers
		if (  !_validation.isNum(aValueMDY[0])
			||!_validation.isNum(aValueMDY[1])
			||!_validation.isNum(aValueMDY[2]))
			return false;

		// Assign day, month, year based on format
		switch (aFormatMDY[0].toUpperCase()){
			case "YYYY" :
				iYear=aValueMDY[0];
				break;
			case "DD" :
				iDay=aValueMDY[0];
				break;
			case "MM" :
				iMonth=aValueMDY[0];
				break;
			default :
				return false;
		}
		switch (aFormatMDY[1].toUpperCase()){
			case "YYYY" :
				iYear=aValueMDY[1];
				break;
			case "MM" :
				iMonth=aValueMDY[1];
				break;
			case "DD" :
				iDay=aValueMDY[1];
				break;
			default :
				return false;
		}
		switch(aFormatMDY[2].toUpperCase()){
			case "MM" :
				iMonth=aValueMDY[2];
				break;
			case "DD" :
				iDay=aValueMDY[2];
				break;
			case "YYYY" :
				iYear=aValueMDY[2];
				break;
			default :
				return false;
		}

		// Require 4 digit year
		if(oElement.form.getAttribute("year4")!=null && iYear.length!=4)
			return false;
		// Process pivot date and update field
		var iPivot=_validation.setDefault(oElement.getAttribute("pivot"),
			oElement.form.getAttribute("pivot"));
		if(iPivot && iPivot.length==2 && iYear.length==2){
			iYear=((iYear>iPivot)?19:20).toString()+iYear;
			var sValue=aFormatMDY.join(sSepFormat).replace(/MM/i,iMonth);
			sValue=sValue.replace(/DD/i,iDay).replace(/YYYY/i,iYear);
			oElement.value=sValue;
		}

		// Check for leap year
		var iDaysInMonth=(iMonth!=2)?aDaysInMonth[iMonth-1]:
			((iYear%4==0 && iYear%100!=0 || iYear % 400==0)?29:28);

		return (iDay!=null && iMonth!=null && iYear!=null
				&& iMonth<13 && iMonth>0 && iDay>0 && iDay<=iDaysInMonth);
	}
	/********************************************
	Method:   Validation.isNum
	Purpose:  Check that parameter is a number
	Inputs:   v - string value
	Returns:  boolean
	********************************************/
	this.isNum=function (v){
		return (typeof v!="undefined" && v.toString() && !/\D/.test(v));
	}
	/*********************************************************************
	Method:   Validation.setup
	Purpose:  Set up methods and event handlers for all forms and elements
	Inputs:   none
	Returns:  undefined
	*********************************************************************/
	this.setup=function (){
		// Fan through forms on page to perform initializations
		var i,iForms=document.forms.length;
		for(i=0; i<iForms; i++){
			var oForm=document.forms[i];
			if(!oForm.bProcessed){
				/*********************************************
				Method:   Form.markRequired
				Purpose:  Mark all required fields for a form
				Inputs:   none
				Returns:  undefined
				*********************************************/
				oForm.markRequired=function (){
					var i, iElements=this.elements.length;
					var sMarkHTML, sMarkWhere;
					for(i=0; i<iElements; i++){
						var oElement=this.elements[i];
						// Perform onmark event handler
						if(oElement.onmark && oElement.onmark()==false)
							continue;
						if(oElement.getAttribute("required")!=null){
							sMarkHTML=this.getAttribute("insert");
							sMarkWhere=this.getAttribute("mark");
							if(sMarkHTML){
								switch(sMarkWhere.toLowerCase()){
									case "before" :
										sMarkWhere="beforeBegin";
										break;
									default :
										sMarkWhere="afterEnd";
								}
								oElement.insertAdjacentHTML(sMarkWhere,sMarkHTML);
							}else{
								var sClassName=oElement.className;
								if(sClassName!="required"){
									oElement.setAttribute("nonreqClass",oElement.className);
									oElement.className="required";
								}else{
									oElement.className=_validation.setDefault(oElement.getAttribute("nonreqClass"),oElement.className);
									oElement.removeAttribute("nonreqClass");
								}
							}
						}
					}
				}
				var sValidateWhen=oForm.getAttribute("validate");
				if (sValidateWhen!=null){
					//
					// Capture and replace onreset and onsubmit event handlers
					//
					oForm.fSubmit=oForm.onsubmit;
					oForm.fReset=oForm.onreset;

					// Create new event handlers
					oForm.onsubmit=function (){
						var i, oElement, iElements=this.elements.length;
						// Restore all elements to original style
						for (i=0; i<iElements; i++)
							this.elements[i].restore();
						// Validate individual elements
						for(i=0;i<iElements;i++){
							oElement=this.elements[i];
							// Perform default validation for element
							if (!oElement.valid()){
								_err.raise();
								event.returnValue=false;
								return;
							}
						}

						// Perform original onsubmit event handler
						if (this.fSubmit && this.fSubmit()==false){
							event.returnValue=false;
							return;
						}

						// Insert default values just before submit
						var vDefault;
						for(i=0;i<iElements;i++){
							oElement=this.elements[i];
							vDefault=oElement.getAttribute("default");
							if(vDefault && !oElement.value)
								oElement.value=vDefault;
						}
					}
					oForm.onreset=function (){
						var i, iElements=this.elements.length;
						for (i=0; i<iElements; i++)
							this.elements[i].restore();
						// Perform original event handler if present
						if (this.fReset && this.fReset()==false)
								event.returnValue=false;
					}
				}
				oForm.bProcessed=true;
			}
			// Create Input methods
			var j, iElements=oForm.elements.length;
			for(j=0; j<iElements; j++){
				var oElement=oForm.elements[j];
				if(!oElement.bProcessed) {

					// All event handlers are presumed to be strings/functions
					// at parse-time and assigned only as functions at run-time.

					// Create custom onvalidate event handlers
					var vOnValidate=oElement.getAttribute("onvalidate");
					if(vOnValidate){
						if(typeof vOnValidate!="function")
							oElement.onvalidate=new Function(vOnValidate);
						else
							oElement.onvalidate=vOnValidate;
					}
					// Create custom handler for onvalidatefocus event
					var vOnValidateFocus=oElement.getAttribute("onvalidatefocus");
					if(vOnValidateFocus){
						if(typeof vOnValidateFocus!="function")
							oElement.onvalidatefocus=new Function(vOnValidateFocus);
						else
							oElement.onvalidatefocus=vOnValidateFocus;
					}
					// Custom onmark event handler
					var vOnMark=oElement.getAttribute("onmark");
					if(vOnMark){
						if(typeof vOnMark!="function")
							oElement.onmark=new Function(vOnMark);
						else
							oElement.onmark=vOnMark;
					}
					// Custom onkeypress filtering for text fields
					if(oElement.onkeypress)
						oElement.fKeypress=oElement.onkeypress;
					oElement.onkeypress=function (){
						if(this.fKeypress && this.fKeypress()==false)
							event.returnValue=false;
						var sFilter=this.getAttribute("filter");
						if(sFilter){
							var sKey=String.fromCharCode(event.keyCode);
							var re=new RegExp(sFilter);
							// Do not filter out ENTER!
							if(sKey!="\r" && !re.test(sKey))
								event.returnValue=false;
							event.keyCode=sKey.charCodeAt(0);
						}
					}
					// Custom onchange validation
					if(sValidateWhen=="onchange") {
						// Capture and replace onchange event handlers
						if(oElement.onchange)
							oElement.fChange=oElement.onchange;
						oElement.onchange=function (){
							this.restore();
							if(!this.valid()){
								_err.raise();
								event.returnValue=false;
							}
							if(this.fChange && this.fChange()==false){
								event.returnValue=false;
							}
						}
					}
					/***********************************
					Method:   Input.paint
					Purpose:  Change style of element
					Inputs:   none
					Returns:  undefined
					***********************************/
					oElement.paint=function(){
						var sColor = _validation.setDefault(
							this.getAttribute("invalidColor"),
							this.form.getAttribute("invalidColor") );
						if (!sColor){
							// Paint element by changing class
							this.setAttribute("oldClass", this.className);
							this.className = "invalid";
						}else{
							// Paint element by changing color directly
							this.setAttribute("bg", this.style.backgroundColor);
							this.style.backgroundColor = sColor;
						}
					}
					/********************************************
					Method:   Input.restore
					Purpose:  Restore element to original style
					Inputs:   none
					Returns:  undefined
					********************************************/
					oElement.restore=function () {
						var sBG=this.getAttribute("bg");
						if (sBG!=null) {
							// Revert to previous background color
							this.style.backgroundColor = sBG;
							this.removeAttribute("bg");
						}else{
							var sOldClass=this.getAttribute("oldClass");
							if (sOldClass!=null){
								// Revert to previous class
								this.className=sOldClass;
								this.removeAttribute("oldClass");
							}
						}
					}
					/**********************************************
					Method:   Input.valid
					Purpose:  Validate an element based on the
					          attributes provided in the HTML text
					Inputs:   none
					Returns:  boolean
					**********************************************/
					oElement.valid=function (){
						var sType=this.type;
						if(sType=="text" || sType=="textarea" || sType=="file"){
							// Trim leading and trailing spaces
							if(this.form.getAttribute("notrim")==null)
								this.value = this.value.trim();
							// Remove any Server Side Include text
							if(this.form.getAttribute("ssi")==null){
								while (this.value.search("<!-"+"-#") > -1)
									this.value = this.value.replace("<"+"!--#", "<"+"!--");
							}
						}
						// REQUIRED
						if(this.getAttribute("required")!=null && !this.value){
							_err.add(this, _validation.REQUIRED, null);
					 		return false;
						}
						// FLOAT
						var sFloatDelimiter=this.getAttribute("float");
						var bSigned=this.getAttribute("signed")!=null;
						if (sFloatDelimiter!=null && this.value){
							// Assign default value to delimiter
							sFloatDelimiter=(sFloatDelimiter==",")?",":"\\.";
							var re=new RegExp("^("+((bSigned)?"[\\-\\+]?":"")+"(\\d*"+sFloatDelimiter+"?\\d+)|(\\d+"+sFloatDelimiter+"?\\d*))$");
							if (!re.test(this.value)){
								_err.add(this, _validation.FLOAT, null);
								return false;
							}
						}
						// AMOUNT
						var sAmtDelimiter = this.getAttribute("amount");
						if (sAmtDelimiter!=null && this.value){
							// Assign default value to delimiter
							sAmtDelimiter=(sAmtDelimiter==",")?",":"\\.";
							var re = new RegExp("^"+((bSigned)?"[\\-\\+]?":"")+"\\d+("+sAmtDelimiter+"\\d{2})?$");
							if(!re.test(this.value)){
								_err.add(this, _validation.AMOUNT, null);
								return false;
							}
						}
						// INTEGER
						if (this.getAttribute("integer")!=null && this.value){
							var re=new RegExp("^"+((bSigned)?"[\\-\\+]?":"")+"\\d+$");
							if (!re.test(this.value)){
								_err.add(this, _validation.INTEGER, null);
								return false;
							}
						}
						// DATE
						var sFormat=this.getAttribute("date");
						if (sFormat!=null && this.value) {
							// Set default date format
							sFormat = _validation.setDefault(sFormat, "MM/DD/YYYY");
							if (!_validation.isDate(this,sFormat)){
								_err.add(this, _validation.DATE, sFormat.toUpperCase());
								return false;
							}
						}
						// MASK
						var sMask=this.getAttribute("mask");
						if(sMask && this.value){
							var sPattern=sMask.replace(
								/(\$|\^|\*|\(|\)|\+|\.|\?|\\|\{|\}|\||\[|\])/g,"\\$1");
							sPattern=sPattern.replace(/9/g ,"\\d");
							sPattern=sPattern.replace(/x/ig,".");
							sPattern=sPattern.replace(/z/ig,"\\d?");
							sPattern=sPattern.replace(/a/ig,"[A-Za-z]");
							var re=new RegExp("^"+sPattern+"$");
							if(!re.test(this.value)){
								_err.add(this, _validation.MASK, sMask);
								return false;
							}
						}
						// REGEXP
						var sRegexp=this.getAttribute("regexp");
						if(sRegexp && this.value){
							var re=new RegExp(sRegexp);
							if(!re.test(this.value)){
								_err.add(this, _validation.MASK, sRegexp);
								return false;
							}
						}
						// AND
						var sAnd=this.getAttribute("and");
						if(sAnd && this.value){
							var aAnd = sAnd.split(/,/);
							var i, iFields=aAnd.length;
							// Require each element in the list if this element is valued
							for(i=0; i<iFields; i++){
								var oNewElement=this.form.elements[aAnd[i]];
								if(oNewElement && oNewElement.value.trim()==""){
									_err.add(oNewElement, _validation.REQUIRED, null);
									return false;
								}
							}
						}
						// OR
						var sOr=this.getAttribute("or");
						if(sOr && this.value==""){
							var aOr=sOr.split(/,/);
							var i, iFields=aOr.length;
							var oNewElement, bAccum=false;
							for(i=0; i<iFields; i++){
								oNewElement=this.form.elements[aOr[i]];
								if(oNewElement)
									bAccum |= !!oNewElement.value.trim();
							}
							if(!bAccum){
								_err.add(this, _validation.REQUIRED, null);
								return false;
							}
						}
						// NOSPACE
						if (this.getAttribute("nospace")!=null)
							this.value=this.value.replace(/\s/g,"");
						// UPPERCASE
						if (this.getAttribute("uppercase")!=null)
							this.value=this.value.toUpperCase();
						// LOWERCASE
						if (this.getAttribute("lowercase")!=null)
							this.value=this.value.toLowerCase();
						// Perform onvalidate event handler
						if(this.onvalidate && this.onvalidate()==false)
							return false;

						return true;
					}
					oElement.bProcessed=true;
				}
			}
		}
	}
	// Limit use of script to valid environments
	if("".replace && document.body && document.body.getAttribute){
		/*********************************************************************
		Method:   String.trim
		Purpose:  Removing leading and trailing spaces
		Inputs:   none
		Returns:  string
		*********************************************************************/
		String.prototype.trim=function (){
			return this.replace(/^\s+|\s+$/g,"");
		}
		/*********************************************************************
		Method:   String.reformat
		Purpose:  Translate the date format into the correct language
		Inputs:   sLang - language of error message to display
		          iType - type of failed validation
		Returns:  string
		*********************************************************************/
		String.prototype.reformat=function (sLang,iType){
			var sString=this.valueOf();
			if (iType==_validation.DATE && _validation.messages[sLang]) {
				sString=sString.replace(/MM/,_validation.messages[sLang].MM);
				sString=sString.replace(/DD/,_validation.messages[sLang].DD);
				sString=sString.replace(/YYYY/,_validation.messages[sLang].YYYY);
			}
			return sString;
		}
		// Form setup
		if(document.forms){
			// Process forms and elements
			this.setup();

			var i, iForms=document.forms.length;
			for(i=0; i<iForms; i++){
				var oForm=document.forms[i];
				if(oForm.getAttribute("mark")!=null)
					oForm.markRequired();
			}
		}
	}
}
_validation=new Validation;
_err=new Err;

