/*********************************************************************
JavaScript 1.2 Validation Script (IE and Netscape)
	version 1.02
	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:

		nn-validation.js   - this file
		nn-validation.htm  - example form
		nn-readme.htm      - directions on using this script
		(nn-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: Validation
Purpose:  Custom object constructor.
Inputs:   None
Returns:  undefined
====================================================================*/
function Validation()
{
	/*====================================================================
	Function: Err
	Purpose:  Custom object constructor
	Inputs:   None
	Returns:  undefined
	====================================================================*/
	var Err=function()
	{
		var oSource  = new Object;
		var sMessage = new String;
		/*********************************************************************
		Method:   Err.clear
		Purpose:  Clear values from Error object
		Inputs:   none
		Returns:  undefined
		*********************************************************************/
		this.clear=function ()
		{
			oSource  = new Object;
			sMessage = new String;
		}
		/*********************************************************************
		Method:   Validation.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 (oSrc,sMsg)
		{
			oSource  = oSrc;
			sMessage = sMsg;
		}
		/*********************************************************************
		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 sName = oSource.NAME;
			var sMsg  = oSource.MSG;

			// Alert appropriate error message
			sMsg = sMsg ? sMsg : sMessage + (sName ? " in the "+sName+" field" : "");
			alert(sMsg);

			// Give invalid field focus
			if(oSource.focus)
				oSource.focus();
			if(oSource.select)
				oSource.select();
			// Clear the Err object
			this.clear();
		}
	}
	/*********************************************************************
	Function: isDate
	Purpose:  Check that value is a date of the correct format
	Inputs:   sDate   - date as string
	          sFormat - string format
	Returns:  boolean
	*********************************************************************/
	var isDate=function (sDate,sFormat)
	{
		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);

		// Assign day, month, year based on format
		var iMonth,iDay,iYear;
		iMonth = aValueMDY[0];
		iDay   = aValueMDY[1];
		iYear  = aValueMDY[2];

		// Validate that all pieces of the date are numbers
		if (  !isNum( iMonth )
			||!isNum( iDay   )
			||!isNum( iYear  ) )
			return false;

		// Require 4 digit year
		if(iYear.length!=4)
			return false;

		// 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);
	}
	/********************************************
	Function: isNum
	Purpose:  Check that parameter is a number
	Inputs:   v - string value
	Returns:  boolean
	********************************************/
	var isNum=function (v)
	{
		return (typeof v!="undefined" && v.toString() && !/\D/.test(v));
	}
	/********************************************
	Function: formatNumber
	Purpose:  Format an integer for display
	Inputs:   i - integer value (as string)
	Returns:  string
	********************************************/
	var formatNumber=function (i)
	{
		var sEnd="$";
		i = i.toString();
		if (/\./.test(i))
			sEnd="\\.";
		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:   oElement - form field
	Returns:  boolean
	********************************************/
	var getValueOf=function (oElement)
	{
		var sReturnValue = null;
		switch (oElement.type)
		{
			case "text" : case "textarea" : case "file" : case "password" : case "hidden" :
				sReturnValue = oElement.value;
				break;
			case "select-one" :
				if(oElement.selectedIndex>-1)
					sReturnValue = oElement.options[oElement.selectedIndex].value;
				break;
			case "select-multiple" :
				var i, iOptions = oElement.options.length;
				for(i=0; i<iOptions; i++)
					if(oElement.options[i].selected && oElement.options[i].value.toString().trim())
					{
						sReturnValue = true;
						break;
					}
				break;
			case "radio" : case "checkbox" :
				if(oElement.checked)
					sReturnValue = oElement.value ? oElement.value : true;
				break;
		}
		return sReturnValue;
	}
	/*********************************************************************
	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,oForm,iForms=document.forms.length;
		for(i=0; i<iForms; i++){
			oForm=document.forms[i];
			if(!oForm.bProcessed)
			{
				// Capture and replace onsubmit event handler
				var fnSubmit=oForm.onsubmit;

				// Create new event handlers
				oForm.onsubmit=function ()
				{
					// Execute onbeforevalidate processing
					if(typeof this.onbeforevalidate=="function" && this.onbeforevalidate()==false)
						return false;
					var i, oElement, iElements=this.elements.length;
					// Validate individual elements
					for(i=0;i<iElements;i++)
					{
						oElement=this.elements[i];
						// Perform default validation for element
						if (!oElement.valid())
						{
							Validation.Err.raise();
							return false;
						}
					}
					// Execute onaftervalidate processing
					if(typeof this.onaftervalidate=="function" && this.onaftervalidate()==false)
						return false;

					// Perform original onsubmit event handler
					if (fnSubmit && fnSubmit()==false)
						return 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)
				{
					/**********************************************
					Method:   Input.valid
					Purpose:  Validate an element based on the
					          attributes provided in the HTML text
					Inputs:   none
					Returns:  boolean
					**********************************************/
					oElement.valid=function ()
					{
						// Trim leading and trailing spaces
						if(this.value)
							this.value = this.value.trim();
						var sValue = getValueOf(this);
						// REQUIRED
						if(this.REQUIRED && !sValue)
						{
							Validation.Err.add(this, "Please enter a value");
					 		return false;
						}
						// FLOAT
						var bSigned=this.SIGNED;
						if (this.FLOAT && sValue)
						{
							var re=new RegExp("^"+((bSigned)?"-?":"")+"(\\d*(,?\\d{3})*\\.?\\d+|\\d+(,?\\d{3})*\\.?\\d*)$");
							if (!re.test(sValue))
							{
								Validation.Err.add(this, "Please enter a number");
								return false;
							}
							// MIN
							var iMin = this.MIN;
							if(iMin==parseFloat(iMin) && sValue.replace(/,/,"")<iMin)
							{
								Validation.Err.add(this, "Please enter a value greater than or equal to "+formatNumber(iMin));
								return false;
							}
							// MAX
							var iMax = this.MAX;
							if(iMax==parseFloat(iMax) && sValue.replace(/,/,"")>iMax)
							{
								Validation.Err.add(this, "Please enter a value less than or equal to "+formatNumber(iMax));
								return false;
							}
						}
						// AMOUNT
						if (this.AMOUNT && sValue)
						{
							var sSigned = "(\\$?-?|-?\\$?)";
							var re = new RegExp("^"+((bSigned)?sSigned:"\\$?")+"((\\d{1,3})*(,?\\d{3})*\\.?\\d{2}|\\d{1,3}(,?\\d{3})*\\.?(\\d{2})?)$");
							if(!re.test(sValue))
							{
								Validation.Err.add(this, "Please enter a dollar amount");
								return false;
							}
							// MIN
							var iMin = this.MIN;
							if(iMin==parseFloat(iMin) && sValue.replace(/[\$,]/,"")<iMin)
							{
								Validation.Err.add(this, "Please enter a value greater than or equal to $"+formatNumber(iMin));
								return false;
							}
							// MAX
							var iMax = this.MAX;
							if(iMax==parseFloat(iMax) && sValue.replace(/[\$,]/,"")>iMax)
							{
								Validation.Err.add(this, "Please enter a value less than or equal to $"+formatNumber(iMax));
								return false;
							}
						}
						// INTEGER
						if (this.INTEGER && sValue)
						{
							var re=new RegExp("^"+((bSigned)?"-?":"")+"\\d{1,3}(,?\\d{3})*$");
							if (!re.test(sValue))
							{
								Validation.Err.add(this, "Please enter an integer");
								return false;
							}
							// MIN
							var iMin = this.MIN;
							if(iMin==parseInt(iMin) && sValue.replace(/,/,"")<iMin)
							{
								Validation.Err.add(this, "Please enter a value greater than or equal to "+formatNumber(iMin));
								return false;
							}
							// MAX
							var iMax = this.MAX;
							if(iMax==parseInt(iMax) && sValue.replace(/,/,"")>iMax)
							{
								Validation.Err.add(this, "Please enter a value less than or equal to "+formatNumber(iMax));
								return false;
							}
						}
						// DATE
						if (this.DATE && sValue)
						{
							// Set default date format
							var sFormat = "MM/DD/YYYY";
							if (!isDate(sValue,sFormat))
							{
								Validation.Err.add(this, "Please enter a date as MM/DD/YYYY");
								return false;
							}
						}
						// REGEXP
						var oRegexp=this.REGEXP;
						if(oRegexp && oRegexp.constructor==RegExp && sValue)
						{
							if(!oRegexp.test(sValue))
							{
								Validation.Err.add(this, "Please enter a valid value");
								return false;
							}
						}
						// PHONE
						if(this.PHONE && sValue)
						{
							var sPhone  = sValue.replace(/\D/g,"");
							var iDigits = sPhone.length;
							if( iDigits<7 )
							{
								Validation.Err.add(this, "Please enter a valid phone number");
								return false;
							}
						}
						// LENGTH
						if(sValue && this.LENGTH && isNum(this.LENGTH) && sValue.length>this.LENGTH)
						{
							Validation.Err.add(this, "Please enter a value with fewer than " + formatNumber(this.LENGTH) + " characters");
							return false;
						}
						// EMAIL
						if(this.EMAIL && sValue)
						{
							if(!/^[\w_-]+(\.[\w_-]+)*@[\w_-]+(\.[\w_-]+)*\.\w{2,3}$/i.test(sValue))
							{
								Validation.Err.add(this, "Please enter a valid email address");
								return false;
							}
						}
						// ZIP Code
						if(this.ZIP && sValue)
						{
							if(!/^\d{5}(-?\d{4})?$/.test(sValue))
							{
								Validation.Err.add(this, "Please enter a valid ZIP code");
								return false;
							}
						}
						// AND
						var vAnd=this.AND;
						if(vAnd && sValue)
						{
							// If not an array, create one
							if(vAnd.constructor!=Array)
								vAnd = vAnd.toString().split(/,/);
							// Require each element in the list
							var i, iFields=vAnd.length;
							for(i=0; i<iFields; i++)
							{
								var oNewElement =
									(vAnd[i].form)
									? vAnd[i]
									: this.form.elements[vAnd[i].trim()];
								if(oNewElement)
								{
									if(!getValueOf(oNewElement))
									{
										Validation.Err.add(oNewElement, "Please enter a value");
										return false;
									}
								}
							}
						}
						// OR
						var vOr=typeof this.OR=="object";
						if(vOr && !sValue)
						{
							vOr = this.OR["fields"];
							if(vOr)
							{
								// If not an array, create one
								if(vOr.constructor!=Array)
									vOr = vOr.toString().split(/,/);
								// Require each element in the list
								var i, iFields=vOr.length;
								var bValue=false;
								for(i=0; i<iFields; i++)
								{
									var oNewElement =
										(vOr[i].form)
										? vOr[i]
										: this.form.elements[vOr[i].trim()];
									if(oNewElement)
										bValue |= !!getValueOf(oNewElement);
								}
								if(!bValue)
								{
									Validation.Err.add(this, this.OR["msg"]?this.OR["msg"]:"Please enter a value");
									return false;
								}
							}
						}
						// NOSPACE
						if (this.NOSPACE && this.value)
							this.value=this.value.replace(/\s/g,"");
						// UPPERCASE
						if (this.UPPERCASE && this.value)
							this.value=this.value.toUpperCase();
						// LOWERCASE
						if (this.LOWERCASE && this.value)
							this.value=this.value.toLowerCase();

						// Perform onvalidate event handler
						if(typeof this.onvalidate=="function" && this.onvalidate()==false)
							return false;

						return true;
					}
					oElement.bProcessed=true;
				}
			}
		}
	}
	// Limit use of script to valid environments
	if("".replace && window.RegExp)
	{
		/*********************************************************************
		Method:   String.trim
		Purpose:  Removing leading and trailing spaces
		Inputs:   none
		Returns:  string
		*********************************************************************/
		String.prototype.trim=function ()
		{
			return this.replace(/^\s+|\s+$/g,"");
		}

		// Form setup
		if(document.forms)
		{
			this.setup();
			this.Err=new Err;
		}
	}
}

Validation=new Validation;