//***************************************************************
//  validform.js - validates the text and textarea input 
//  elements for an HTML form. 

//***************************************************************
//	Originally adapted from O'Reilly's 'Javascript: The Definitive 
//	Guide' and maintained from there.  Pass a form object to the 
//	validate() function and it will iterate the elements list and 
//	validate any text / hidden / password fields on the form.  
//	Fields are assigned specific attributes beform the call to 
//	perform specific edits.  Default attribute field definition
//	is a required text field.  See list of supported attributes
//	below for defaults and overrides.
//
//	David Connell   -  10/27/99
//
//***************************************************************
//	Maintenance Logs
//***************************************************************
//  (dkc) 10/27/1999
//			- Modified to report first error and return false.
//
//			- Added properties for invalid (not allowed) fields
//			  and positive numeric fields.		
//
//  (dkc) 03/23/2000
//			- Incorporated the date validation routines
//			  (source adapted from DateValidation.js)
//
//			- Added optional range checking for date fields
//
//  (dkc) 11/03/2003
//			- Added utility functions to retrieve select values
//			- Added time format checking
//
//	(dkc) 07/02/2004
//			- removed seconds from time format for SSI app...
//
//----------------------------------------------------------------
//	Supported Field Attributes  
//
//		optional	Field may be empty ( boolean )
//		invalid		Field must be empty	( boolean )
//		numeric		Field must be a number ( boolean )	
//		positive	Numeric field must be >= 0 ( boolean )
//		min			Numeric field lower range boundry ( numeric )
//		max			Numeric field upper range boundry ( numeric )
//		minDate		Date field lower range boundry ( MDYDate )
//		maxDate		Date field upper range boundry ( MDYDate )

//		-- Format Edits  --
//		isMDYDate	Field must be a date in m/d/yyyy format	
//    	isTime		Field must be valid time element in hh:mm
//    	isMilTime	Field must be valid time element  in hh:mm[:ss]  (24 hour clock)
//		isTimeAMPM	Field must be valid time element (including am/pm text) in hh:mm am
//		isPhone		Requires 10 numerics and allows phone format chars
//		isEMail		Proper format for internet email
//		isZip		Checks for standard or Zip+4 formats
//		isState 	Validated against static variable of 2 char 
//						state abbreviations 
//		isSSN		Checks for Social Sec # format
//
//		Note: using a range and a type is redundant, if a range is 
//			  specified that range type is assumed.  I.e.  if you 
//			  specify a minDate you don't have to specify isMDYDate
//			  as true also.
//				
//----------------------------------------------------------------
var unitedStatesAbbreviations = "AL|AK|AS|AZ|AR|CA|CD|CN|CO|CT|DE|DC|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|PR|RI|SC|SD|TN|TX|VI|UT|VT|VA|WA|WV|WI|WY";
var mailStates = unitedStatesAbbreviations.split("|"); 

//----------------------------------------------------------------
//  getSelectValue()
//  retrieve the value of the selected option from
//  a select tag.
function getSelectValue(form,fldName)
{
		// get the field from the form
		var fld = form.elements[fldName];
		var rv = -1;
		
		// if the field was valid and it was a selection,
		if (fld && fld.options)
		{
			// get the value of the selected option
			var ndx = fld.selectedIndex;
			rv = fld.options[ndx].value;
		}
		
		// return results
		return rv;
}
	
//----------------------------------------------------------------
// isBlank()
//	 A utility function that returns true if a string contains only 
//   whitespace characters.function isblank(s)
function isBlank(s){
    for(var i = 0; i < s.length; i++) {
        var c = s.charAt(i);
        if ((c != ' ') && (c != '\n') && (c != '\t')) return false;
    }
    return true;
}

//----------------------------------------------------------------------
// parseDate ( )
//   extract the date string from the value member
//   and parses it into it's 'm,d & y' components.  Can be 
//	 used directly or as a helper function for validDate.
//
// Return:
//		True if successfully parsed, false if not.
//
function parseDate( dtObj )
{
	// date parsers
	var slashpos1, slashpos2, lastpos;

	// the date string
	var date = dtObj . value;		
	
	// parse month
	if (date.substring(1,2) != "/")
	{	if (date.substring(0,1) < "0" || date.substring(0,1) > "9")
		{	return false;
		}
		if (date.substring(1,2) < "0" || date.substring(1,2) > "9")
		{	return false;
		}
		if (date.substring(0,2) <= "0" || date.substring(0,2) > 12)
		{	return false;
		}
		if (date.substring(2,3) != "/")
		{	return false;
		}

		// set month member
		dtObj . month = date.substring(0,2);
		slashpos1 = 3;
	}
	else
	{	if (date.substring(0,1) < "1" || date.substring(0,1) > "9")
		{	return false;
		}
		
		// set month member
		dtObj . month = date.substring(0,1);
		slashpos1 = 2;
	} 
	
	// parse day
	if (date.substring(slashpos1+1,slashpos1+2) != "/")
	{	if (date.substring(slashpos1,slashpos1+1) < "0" || date.substring(slashpos1,slashpos1+1) > "9")
		{	return false;
		}
		if (date.substring(slashpos1+1,slashpos1+2) < "0" || date.substring(slashpos1+1,slashpos1+2) > "9")
		{	return false;
		}
			if (date.substring(slashpos1+2,slashpos1+3) != "/")
		{	return false;
		}
		
		// set day member
		dtObj . day = date.substring(slashpos1, slashpos1+2);
		slashpos2 = slashpos1 + 3;
	}
	else
	{	if (date.substring(slashpos1,slashpos1+1) < "1" || date.substring(slashpos1,slashpos1+1) > "9")
		{	return false;
		}

		// set day member
		dtObj . day = date.substring(slashpos1, slashpos1+1);
		slashpos2 = slashpos1 + 2;
	} 
	
	// parse year ( 4 digits for year representation )
	lastpos = slashpos2+3;
	if ((date.substring(slashpos2,date.length)).length > 4)
	{	return false;
	}
	for (i=(slashpos2); i<=(lastpos);i++)
	{	var temp = date.substring(i,i+1);
		if (temp < "0" || temp > "9")
		{	return false;
		}
	}

	// set year member
	dtObj . year = date.substring(slashpos2,lastpos+1)	
	
	// test for valid day in month
	if (dtObj . month == 1 || dtObj . month == 3 || 
	    dtObj . month == 5 || dtObj . month == 7 || 
	    dtObj . month == 8 || dtObj . month == 10 || 
	    dtObj . month == 12)
	    {	if ( dtObj . day > 31 )
			{	return false;
			}
		}
		else
		{	if ( dtObj . month != 2 )
			{	if ( dtObj . day > 30 )
				{	return false;
				}
			}
			else //month = 2
			{	if ((dtObj . year % 4) != 0)	//NOT LEAP YEAR
				{	if (dtObj . day > 28)
					{	return false;
					}	
				}
				else //LEAP YEAR
				{	if (dtObj . day > 29)
					{	return false;
					}
				}
			}
		}

	// build hash value for comparisons
	dtObj . nVal = ( dtObj . year * 10000 ) + ( dtObj . month * 100 ) + dtObj . day

	// all done
	return true;
}

//----------------------------------------------------------------------
// validTimeAMPM()
//	evaluates a time (using validTime())
//		plus ensures an AM/PM follows with a space.
//
function validTimeAMPM( e ) {
 	vals = e.value.slice().split(" ");
 	
 	if (vals.length != 2)
 		return "Invalid Time Format (HR:MI AM)";
 	
 	msg = validTime( vals[0] );
 	
 	if (msg.length < 1) {
 		if (vals[1].toUpperCase() != "AM" && vals[1].toUpperCase() != "PM")
 			msg = "Invalid Time Format (HR:MI AM)";
 	}
 	
 	return msg;
}

//----------------------------------------------------------------------
// validTime()
//   parses value for 2-3 elements within proper ranges
//	 
function validTime( e )
{
	var hr = -1;
	var min = -1;
	var msg = "";
	err = 0;
	
	var mxHour = (e.isMilTime ? 23 : 12);
	var  mnHour = (e.isMilTime ? 0 : 1);
	var vals = null;
	
	if (e.value)
		vals = e.value.split(":");
	else
		vals = e.split(":");
	
	// checks by length
	if (vals.length == 2)
	{
		if (vals[0].length < 1 || vals[1].length != 2)
			err = 1
		else {
			
			try {
				hr = vals[0];
				min = vals[1];
				
				if (isNaN(hr) || isNaN(min))
					return "Invalid Time Format (hh:mm)";
						
				// check agains constraints
				if ( (hr > mxHour ) ||
				     (hr < mnHour) ||
			    	 (min < 0) ||
			     	(min > 59) )
			     		
			     		err = 1;	
			} catch (e) {
				
			}
			
		}
	}
	else
		err = 1;
		
	if (err == 1)     
	     msg = "Invalid Time Format (hh:mm)";
	     
	return msg;	     
	
}
//----------------------------------------------------------------------
// validDate ( )
//   calls parseDate() to extract and parse the string dateand then 
//	 does any specified range checks.
//
// Return:
//	 Error message suitable for validform() or null if all OK.
//
function validDate ( dtObj )
{
	// attempt to parse the date
	if ( !parseDate( dtObj ) )
		return ( "Invalid date format for " + dtObj . name + " ( Use 'mm/dd/yyyy' ) ." );
	
	// range testing (min)	
	if ( dtObj . minDate )
	{	
		// we need another date obj
		var dtMin = new Object();
		
		// extract it from original
		dtMin . value = dtObj . minDate
		
		// is it a good date
		if ( !parseDate( dtMin ) )
			return ( "Invalid Minimum Date Format ( " + dtObj . name + " )." );

		// do the test
		if ( dtMin	. nVal > dtObj . nVal )
			return ( dtObj . name + " must be greater than " + dtObj . minDate + "." );
	}		

	// range testing ( max )	
	if ( dtObj . maxDate )
	{	
		// we need another date obj
		var dtMax = new Object();
		
		// extract it from original
		dtMax . value = dtObj . maxDate
		
		// is it a good date
		if ( !parseDate( dtMax ) )
			return ( "Invalid Minimum Date Format ( " + dtObj . name + " )." );

		// do the test
		if ( dtMax	. nVal < dtObj . nVal )
			return ( dtObj . name + " must be less than " + dtObj . maxDate + "." );
	}		
	
	// if we get here, return null as OK
	return;
}	
//--------------------------------------------------------------------------	
// This is the function that performs form verification. It should be invoked
// (directly or otherwise) from the onSubmit() event handler of the form.
// The handler should return whatever value this function returns.
function validate(f)
{   
	var msg;    
	var errFlg = 0;

	// Loop through the elements of the form, looking for all 
	// text and textarea elements.  If found, begin validations.
    	// If an error is found, a message will be displayed and the
    	// function will exit with a false return code.    
	for(var i = 0; i < f.elements.length; i++)     
	{
		var e = f.elements[i];        
		var mtFlg = 0;        
		var numFlg = 0;
		
        	// first determine if it is a text based input         
		if ((e.type == "text") || (e.type == "textarea") || e.type == "password")
		{
			var name = e.id.replace(/_/g," ");			
			
			// does the field contain any data
			mtFlg = ((e.value == null) || (e.value == "") || isBlank(e.value)) ? true : false;

			// empty field			
			if (mtFlg)			
			{
				
				// required field??				
				if ((!e.optional && !e.optional == "1") && !e.invalid) 
				{
					msg = "Please enter a value for " + name + ".";
				}			
			}				
			
			// non empty field
			else
			{
				// dissallowed field?? (conditioned before call)
				if ( e.invalid ) 
				{
					msg = "Entry for " + name + " is not allowed with current options.";
				}
				
				// Now check numeric validations.
				if (e.numeric || e.min || e.max || e.positive)
				{

					// valid number edit
					if (isNaN(e.value)) 
					{
						msg = "The field " + name + " must be a number" ;
						// show error message
						validationErrorMessage(msg);

						// set focus back to the field
						e.focus();

						// and return failure flag
						return false;

					}
					var v = parseFloat(e.value);
				    
					// lower range check 						
					if ((e.min != null) && (v < e.min))
						msg = "The field " + name + " must be greater than " + e.min;
					// upper range check
					if ((e.max != null) && (v > e.max))
						msg = "The field " + name + " must be less than " + e.max;
					// non-negative edit
					if ( e.positive  &&  ( v < 0.0 ) )
						msg = "The field " + name + " cannot be negative.";
				}
				 	
				// location oriented edits
				// test for zip / zip+4
				if (e.isZip)
				{
					// start with length
					x = e.value.length;
					
					// check for zip+4 format
					if (x == 10) 					
					{
						// split zip  / +4
						a = e.value.split('-');
						
						// check it out
						if ((a[0].len != 5) || isNaN(a[1]) || isNaN(a[2]) )
							msg = "Invalid Zip Code Format in " + name + ". [ xxxxx or xxxxx-xxxx ]";
					}
					// test traditional zip format
					else if ((x != 5) || isNaN(e.value))
					{
						msg = "Invalid Zip Code Format in " + name + ". [ xxxxx or xxxxx-xxxx ]";
					}
				}
				
				if (e.isState)
				{
					// format to fit the variable pattern
					found = 0;
					for(ab=0;ab<mailStates.length;ab++)
						if(e.value.toUpperCase() == mailStates[ab])
							found ++;
							
					if (found == 0)
						msg = "Invalid State Abbreviation: " + name ;
				}				
				
				// test phone numbers for 10 numerics and 
				// allowed punctuation				
				if (e.isPhone)
				{				
					// we're looking for 10
					cnt = 0;
					
					// get entry data
					txt = e.value;
					
					// test each character
					for(x=0;x<txt.length;x++)
					{
						// extract char
						tst = txt.substr(x,1);
						
						// and test it form numerics
						if ( (tst >= '0') && (tst <= '9') )
							// bump counter
							cnt++;							
						else
							// test from allowed punctuation
							if ( '()-. '.indexOf(tst) == -1)
							{
								// set flags and exit condition
								cnt = -1;
								x = txt.length;
							}
													
					}		
					
					// check for any right numerics count
					if (cnt != 10)
						// oops...
						msg = "Invalid Phone Number Format in " + name + ". [ (xxx) xxx-xxxx ]";
						
				}
				
				// test email formats
				if (e.isEmail)
				{
					// assume good result
					rv = 1;
					
					// split domain from user name
					p1 = e.value.split("@");
					
					// and check it
					if (p1.length == 2)
					{
						// split the domain from top domain
						p2 = p1[1].split('.');
					
						// and check it
						if (p2.length < 2)
							// invalid domain
							rv = 0;
					}		
					else
						// invalid format
					  	rv = 0;
					
					if (rv == 0)
						msg = "Invalid Email Format. [ xxxx@xxxxx.xxx ]";
				}
				
				if (e.isSSN)
				{
					pts = e.value.split('-');
					if ((pts.length != 3) || (pts[0].length != 3) || 
					    (pts[1].length != 2) || (pts[2].length != 4))
						msg = "Invalid SSN Format. [ xxx-xx-xxxx ]";
			
				}			
				
				// Date checks (All dates expected in mm/dd/yyyy format)
				if ( e.isMDYDate || e.minDate || e.maxDate )
					msg = validDate ( e );
				
				// Time checks 					
				if ( e.isTime || e.isMilTime ) 
					msg = validTime( e );
				if ( e.isTimeAMPM)
					msg = validTimeAMPM( e );
			}
			
			//---------------------------
			// evaluate the situation
			if ((msg != null) && (msg.length > 0))
			{
				// show error message
				validationErrorMessage(msg);

				// set focus back to the field
				e.focus();

				// and return failure flag
				return false;
			}	
		}
	}
    
    	// all well that ends well    
	return true;
	
}

function validationErrorMessage ( msg)
{
	err_msg  = "______________________________________________________\n\n";
	err_msg += "The form was not submitted for the following reason.\n";
	err_msg += "Please correct the error and re-submit the form.\n";
	err_msg += "______________________________________________________\n\n";
	err_msg += msg;	

	alert(err_msg);

	return false;
}
    
