/*********************************************************************
JavaScript 1.2 Validation Script (IE and Netscape)
	version 3.0.0 BETA 1
	by matthew frank

There are no warranties expressed or implied.  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_3_0_0b1.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 Yahoo! Groups discussion forum dedicated to validation:

	http://groups.yahoo.com/group/validation

*********************************************************************/
/*====================================================================
Function: Validation
Purpose:  Custom object constructor.
Inputs:   None
Returns:  undefined
====================================================================*/
function Validation(){
	/*====================================================================
	Function: Err
	Purpose:  Custom object constructor
	Inputs:   None
	Returns:  undefined
	====================================================================*/
	var Err=function(){
		/*********************************************************************
		Method:  Err.raise
		Purpose: Gives visual warning to user about all errors contained in
		         the Error object
		Inputs:  source   - element object
		         message  - error message to user
		         override - flag to override MSG value with message
		Returns: undefined
		*********************************************************************/
		this.raise=function(source,message,override){
// 			var sName=getProperty(source,"NAME");
			var sName=source.name;
			var sMsg=getProperty(source,"MSG");
			alert((override?message:sMsg?sMsg:message).replace(/\.?$/,sName?"\nin the <"+sName+"> field.":"."));
			if(source.focus) source.focus();
			if(source.select) source.select();
		}
	}
	/*********************************************************************
	Function: getProperty
	Purpose:  Retrieve property value of element
	Inputs:   object   - object reference
	          property - string
	Returns:  variant
	*********************************************************************/
	var getProperty=function(object, property){
		var returnValue;
		if(object.getAttribute){
			// attempt case-sensitive lookup first
			returnValue=object.getAttribute(property,1);
			if(returnValue==null||typeof returnValue=="undefined")
				returnValue=object.getAttribute(property,0);
			return returnValue;
		}else return object[property];
	}
	/*********************************************************************
	Function: propertyOn
	Purpose:  Check that property is turned on (defined, non-null, non-false)
	Inputs:   attribute - value of attribute (result of getProperty call)
	Returns:  boolean
	*********************************************************************/
	var propertyOn=function(attribute){
		return typeof attribute!="undefined" && attribute!=null && attribute!=false;
	}
	/*********************************************************************
	Function: trim
	Purpose:  Remove leading and trailing spaces
	Inputs:   string
	Returns:  string
	*********************************************************************/
	var trim=function(string){
		return string.replace(/^\s+|\s+$/g,"");
	}
	/*********************************************************************
	Function: pad
	Purpose:  Pad a number with zeros to a given width
	Inputs:   value, width (defaults to 2)
	Returns:  numeric string of width characters
	*********************************************************************/
	var pad=function(value, width){
		if(width==null) width=2;
		var returnValue=value.toString();
		for(var i=width-value.length;i>0;i--)
			returnValue="0"+returnValue
		return returnValue;
	}	
	/*********************************************************************
	Function: minMaxRange
	Purpose:  Helps to build an appropriate error message
	Inputs:   min, max
	Returns:  string
	*********************************************************************/
	var minMaxRange=function(min,max){
		if(!!min&&!!max)
			return " between "+min+" and "+max;
		else if(!!min)
			return " greater than or equal to "+min;
		else if(!!max)
			return " less than or equal to "+max;
		else return "";
	}
	/*********************************************************************
	Function: dateOrTime
	Purpose:  Determines whether a data type is date, time or datetime
	          Accepted tokens: d dd m mm yyyy hh hh24 nn ss ap
	Inputs:   format
	Returns:  string
	*********************************************************************/
	var dateOrTime=function(format){
		var date=false,time=false;
		with(format){
			if(search(/mm?/i)>-1||search(/dd?/i)>-1||search(/yyyy/i)>-1)
				date=true;
			if(search(/hh?/i)>-1||search(/nn/i)>-1||search(/ss/i)>-1||search(/ap/i)>-1)
				time=true;
		}
		return (date||time?" ":"")+(date?"date":"")+(time?"time":"");
	}
	/*********************************************************************
	Function: toDate
	Purpose:  Converts string to date based on format
	          Accepted tokens: d dd m mm yyyy hh hh24 nn ss ap
	Inputs:   date, format
	Returns:  string (YYYYMMDDHH24NNSS), undefined indicates failure to match format
	*********************************************************************/
	var toDate=function(date,format){
		var i, regex, index=new Array;
		var day, month, year, hour, minute, second, ampm;
		// Determine order of datetime tokens
		with(format){
			index[search(/dd?/i)]="day";
			index[search(/mm?/i)]="month";
			index[search(/yyyy/i)]="year";
			index[search(/hh/i)]="hour";
			index[search(/nn/i)]="minute";
			index[search(/ss/i)]="second";
			index[search(/ap/i)]="ampm";
			
			// timing of replaces is quite important!
			regex=format.replace(/(\$|\^|\*|\(|\)|\+|\.|\?|\\|\{|\}|\||\[|\])/g,"\\$1");
			// only allow one pass for day and month
			if(search(/dd/i)>-1)
				regex=regex.replace(/dd/i,"(0[1-9]|[1-2]\\d|3[0-1])");
			else
				regex=regex.replace(/d/i,"(0?[1-9]|[1-2]\\d|3[0-1])");
			if(search(/mm/i)>-1)
				regex=regex.replace(/mm/i,"(0[1-9]|1[0-2])");
			else
				regex=regex.replace(/m/i,"(0?[1-9]|1[0-2])");
			regex=regex.replace(/nn/i,"([0-5]\\d)")
				.replace(/ss/i,"([0-5]\\d)")
				.replace(/yyyy/i,"(\\d{4})")
				.replace(/\s+/g,"\\s*");
			if(search(/hh24/i)>-1)
				regex=regex.replace(/hh24/i,"([0-1]\\d|2[0-3])");
			else
				regex=regex.replace(/hh/i,"(0\\d|1[0-2])").replace(/ap/i,"([ap]m?)");
		}
		// test date against format
		if(!new RegExp("^"+regex+"$","i").test(date))
			return;
		// set values from user input
		year=month=day=hour=minute=second=0,ampm="";
		for(var key=0,i=0;key<index.length;key++)
			if(index[key]) eval(index[key]+"=RegExp.$"+ ++i);
		// set default values
		if(hour<12&&/^pm?$/i.test(ampm))
			hour+=12;
		else if(hour==12&&/^am?$/i.test(ampm))
			hour=0;
		if(year==0) year=1;
		if(month==0) month=1;
		if(day==0) day=1;
		// check days in month
		if(month==2 && day>((year%4==0&&year%100!=0||year%400==0)?29:28) || day>((month-1)%7+1)%2+30)
			return;
		// build result value
		return ""+pad(year,4)+pad(month)+pad(day)+pad(hour)+pad(minute)+pad(second);
	}
	/*********************************************************************
	Function: formatNumber
	Purpose:  Format an integer for display
	Inputs:   i - integer value as string
	Returns:  string
	*********************************************************************/
	var formatNumber=function(i){
		if(typeof i=="undefined"||i==null) return null;
		var sEnd=(/\./.test(i=i.toString()))?"\\.":"$";
		var re=new RegExp("(\\d)(\\d{3})(,|"+sEnd+")");
		if(re.test(i)) i=formatNumber(i.replace(re, "$1,$2$3"));
		return i;
	}
	/*********************************************************************
	Function: getValueOf
	Purpose:  Return the value of a form field as seen at server
	Inputs:   element - form field
	Returns:  variant
	*********************************************************************/
	var getValueOf=function(element){
		var returnValue=null;
		switch (element.type){
			case "text" : case "textarea" : case "file" : case "password" : case "hidden" :
				returnValue=element.value;
				break;
			case "select-one" :
				returnValue=element.options[element.selectedIndex].value;
				break;
			case "select-multiple" :
				for(var i=0,iOptions=element.options.length; i<iOptions; i++)
					if(element.options[i].selected && trim(element.options[i].value.toString())){
						returnValue=true;
						break;
					}
				break;
			case "radio" : case "checkbox" :
				if(element.checked)
					returnValue=element.value?element.value:true;
				break;
		}
		return returnValue;
	}
	/******************************************************************************
	Function: valid
	Purpose:  Validate an element based on the attributes provided in the HTML text
	Inputs:   element - form field
	Returns:  boolean
	******************************************************************************/
	var valid=function(element){
		var iLength,vAnd,or,oRegexp,sValue,iMin,iMax,bSigned,format,sMask,date,minDate,maxDate;
		var pass=true;
		sValue=getValueOf(element);
		bSigned=getProperty(element,"SIGNED");
		// Do not validate disabled/readonly fields or those already validated
		if(element.__||propertyOn(getProperty(element,"disabled"))||propertyOn(getProperty(element,"readOnly")))
			return true;
		element.__=true;
		// Execute onbeforevalidate processing
		if(element.onbeforevalidate && (typeof element.onbeforevalidate=="function" || (element.onbeforevalidate=new Function(element.onbeforevalidate))) && element.onbeforevalidate()==false)
			return false;
		// Trim leading and trailing spaces
		if(element.value) element.value = trim(element.value);
		// REQUIRED
		if(propertyOn(getProperty(element,"REQUIRED")) && !sValue){
			Validation.Err.raise(element, "Please enter a value");
	 		return false;
		}
		// FLOAT, NUMBER
		if((propertyOn(getProperty(element,"FLOAT"))||propertyOn(getProperty(element,"NUMBER"))) && sValue){
			iMin=getProperty(element,"MIN");
			iMax=getProperty(element,"MAX");
			if(!new RegExp("^"+(propertyOn(bSigned)?"-?":"")+"(\\d*(,?\\d{3})*\\.?\\d+|\\d+(,?\\d{3})*\\.?\\d*)$").test(sValue))
				pass=false;
			else if(iMin==parseFloat(iMin) && sValue.replace(/,/,"")<iMin)
				pass=false;
			else if(iMax==parseFloat(iMax) && sValue.replace(/,/,"")>iMax)
				pass=false;
			if(!pass){
				Validation.Err.raise(element, "Please enter a "+(bSigned?"":"positive ")+"number"+minMaxRange(formatNumber(iMin),formatNumber(iMax)));
				return false;
			}
		}
		// AMOUNT, CURRENCY
		else if ((propertyOn(getProperty(element,"AMOUNT"))||propertyOn(getProperty(element,"CURRENCY"))) && sValue){
			iMin=getProperty(element,"MIN");
			iMax=getProperty(element,"MAX");
			if(!new RegExp("^"+((bSigned)?"(\\$?-?|-?\\$?)":"\\$?")+"((\\d{1,3})*(,?\\d{3})*\\.?\\d{2}|\\d{1,3}(,?\\d{3})*\\.?(\\d{2})?)$").test(sValue))
				pass=false;
			else if(iMin==parseFloat(iMin) && sValue.replace(/[\$,]/,"")<iMin)
				pass=false;
			else if(iMax==parseFloat(iMax) && sValue.replace(/[\$,]/,"")>iMax)
				pass=false;
			if(!pass){
				Validation.Err.raise(element, "Please enter a "+(bSigned?"":"positive ")+"dollar amount"+minMaxRange(formatNumber(iMin),formatNumber(iMax)));
				return false;
			}
		}
		// INTEGER
		else if (propertyOn(getProperty(element,"INTEGER")) && sValue){
			iMin=getProperty(element,"MIN");
			iMax=getProperty(element,"MAX");
			if (!new RegExp("^"+((bSigned)?"-?":"")+"\\d{1,3}(,?\\d{3})*$").test(sValue))
				pass=false;
			else if(iMin==parseInt(iMin) && sValue.replace(/,/,"")<iMin)
				pass=false;
			else if(iMax==parseInt(iMax) && sValue.replace(/,/,"")>iMax)
				pass=false;
			if(!pass){
				Validation.Err.raise(element, "Please enter a"+(bSigned?"n":" positive")+" integer"+minMaxRange(formatNumber(iMin),formatNumber(iMax)));
				return false;
			}
		}
		// DATE, DATETIME
		else if(propertyOn((format=getProperty(element,"DATE")))||propertyOn((format=getProperty(element,"DATETIME")))&&sValue){
			// Set default date format
			if(format==""||typeof format!="string") format="M/D/YYYY";
			iMin=getProperty(element,"MIN");
			iMax=getProperty(element,"MAX");
			minDate=toDate(iMin,format);
			maxDate=toDate(iMax,format);
			if(typeof (date=toDate(sValue,format))=="undefined")
				{pass=false; alert("format");}
			else if(propertyOn(iMin)&&typeof minDate!="undefined"&&date<minDate)
				{pass=false; alert("min");}
			else if(propertyOn(iMax)&&typeof maxDate!="undefined"&&date>maxDate)
				{pass=false; alert("max");}
			if(!pass){
				Validation.Err.raise(element, "Please enter a"+dateOrTime(format)+" value"+minMaxRange(iMin,iMax)+" in the proper format: "+format.replace(/ap/i,"AM/PM").toUpperCase());
				return false;
			}
		}
		// PHONE
		else if(propertyOn(getProperty(element,"PHONE"))&&sValue){
			var sPhone=sValue.replace(/\D/g,"");
			var iDigits=sPhone.length;
			if(!(iDigits==10||iDigits==11&&/^1/.test(sPhone))){
				Validation.Err.raise(element, "Please enter a valid phone number");
				return false;
			}
		}
		// EMAIL
		else if(propertyOn(getProperty(element,"EMAIL"))&&sValue){
			if(!/^[\w_-]+(\.[\w_-]+)*@[\w_-]+(\.[\w_-]+)*\.[a-z]{2,3}$/i.test(sValue)){
				Validation.Err.raise(element, "Please enter a valid email address");
				return false;
			}
		}
		// ZIP Code
		else if(propertyOn(getProperty(element,"ZIP"))&&sValue){
			if(!/^\d{5}(-?\d{4})?$/.test(sValue)){
				Validation.Err.raise(element, "Please enter a valid ZIP code");
				return false;
			}
		}
		// MASK
		if(propertyOn(sMask=getProperty(element,"MASK"))&&sValue){
			if(!new RegExp("^"+sMask
				.replace(/(\$|\^|\*|\(|\)|\+|\.|\?|\\|\{|\}|\||\[|\])/ig,"\\$1")
				.replace(/9/g,"\\d")
				.replace(/x/ig,".")
				.replace(/z/ig,"\\d?")
				.replace(/a/ig,"[A-Za-z]")+"$")
				.test(sValue)){
				Validation.Err.raise(element, "Please enter a value in the proper format: "+sMask);
				return false;
			}
		}
		// REGEXP
		else if(propertyOn(oRegexp=getProperty(element,"REGEXP"))&&sValue){
			if(oRegexp.constructor!=RegExp)
				oRegexp = new RegExp(oRegexp, "i");
			if(!oRegexp.test(sValue)){
				Validation.Err.raise(element, "Please enter a valid value");
				return false;
			}
		}
		// LENGTH
		if(sValue&&(iLength=getProperty(element,"LENGTH"))&&!/\D/.test(iLength)&&sValue.length>iLength){
			Validation.Err.raise(element,"Please enter a value with fewer than " + formatNumber(iLength) + " characters");
			return false;
		}
		// AND
		if(propertyOn(vAnd=getProperty(element,"AND"))&&sValue){
			// If not an array, create one
			if(vAnd.constructor!=Array) vAnd = vAnd.toString().split(/,/);
			// Require each element in the list
			for(var oNewElement,i=0,iFields=vAnd.length; i<iFields; i++){
				if(oNewElement=(vAnd[i].form)?vAnd[i]:element.form.elements[trim(vAnd[i])]){
					if(!getValueOf(oNewElement)){
						Validation.Err.raise(oNewElement, "Please enter a value");
						return false;
					}else if(!valid(oNewElement))
						return false;
				}
			}
		}
		// OR
		if((or=getProperty(element,"OR"))&&!sValue){
			var fields,message;
			if(or.constructor==Array||or.constructor==String){
				fields=or;
				message=getProperty(element,"OR_MSG");
			}else{
				fields=or["fields"];
				message=or["msg"];
			}
			if(fields){
				// If not an array, create one
				if(fields.constructor!=Array)
					fields=fields.toString().split(/,/);
				// Require each element in the list
				for(var oNewElement,i=0,iFields=fields.length,bValue; !bValue&&i<iFields; i++){
					oNewElement=(fields[i].form)?fields[i]:element.form.elements[trim(fields[i])];
					if(oNewElement){
						bValue = !!getValueOf(oNewElement);
						if(!valid(oNewElement)) return false;
					}
				}
				if(!bValue){
					Validation.Err.raise(element, message?message:"Please enter a value");
					return false;
				}
			}
		}
		// Perform onvalidate event handler
		if(element.onvalidate && (typeof element.onvalidate=="function" || (element.onvalidate=new Function(element.onvalidate))) && element.onvalidate()==false)
			return false;
		// Execute onaftervalidate processing
		if(element.onaftervalidate && (typeof element.onaftervalidate=="function" || (element.onaftervalidate=new Function(element.onaftervalidate))) && element.onaftervalidate()==false)
			return false;
		// element is valid
		return true;
	}
	/*********************************************************************
	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
		for(var i=0,oForm,iForms=document.forms.length; i<iForms; i++){
			oForm=document.forms[i];
			if(!oForm._){
				// Capture and replace onsubmit event handler
				var fnSubmit=oForm.onsubmit;
				oForm.onsubmit=function(){
					// Reset field markers
					for(var iElements=this.elements.length,i=0;i<iElements;i++)
						this.elements[i].__=null;
					// Execute onbeforevalidate processing
					if(this.onbeforevalidate && (typeof this.onbeforevalidate=="function" || (this.onbeforevalidate=new Function(this.onbeforevalidate))) && this.onbeforevalidate()==false)
						return false;
					// Validate individual elements
					for(i=0;i<iElements;i++)
						if (!valid(this.elements[i])) return false;
					// Execute onaftervalidate processing
					if(this.onaftervalidate && (typeof this.onaftervalidate=="function" || (this.onaftervalidate=new Function(this.onaftervalidate))) && this.onaftervalidate()==false)
						return false;
					// Perform original onsubmit event handler
					if (fnSubmit && fnSubmit()==false)
						return false;
					return true;
				}
				oForm._=true;
			}
			for(var j=0,element,iElements=oForm.elements.length;j<iElements;j++){
				element=oForm.elements[i];
				if(!element._){
					element.validate=function(){
						return valid(this);
					}
				}
				element._=true;
			}
		}
	}
	this.Err=new Err;
	this.setup();
}
// Limit use of script to valid environments
if(window.RegExp && "".replace && "ab".replace(/a/,"")=="b" && document.forms)
	Validation=new Validation;
