/**
 * @(#)validateForm.js
 *
 * This function validates all elements on the given form.  Elements with
 * the attribute 'optional="true"' and disabled elements are ignored.
 * Text boxes can have a 'subtype="name"' attribute to do further
 * well-formedness testing (not true validation).
 * The title attribute is used as the descriptive text to alert the user
 * that item needs attention.
 *
 * @param thisForm The form to validate
 * @returns true if all non-optional/enabled items have information
 *
 * @author Tom Blough
 * @version 1.5
 * @since 17.10.2001
 *
 * @history 03.04.2002 Tom Blough
 *          Added number subType for text elements.
 * @history 25.10.2001 Tom Blough
 *          Added subTypes for text elements to perform crude validation
 *			by checking for well-formedness.
 * @history 25.10.2001 Tom Blough
 *          Added test to ignore disabled form elements.
 * @history 18.10.2001 Tom Blough
 *          Added explicit test for <code>true</code> to optional element test
 *          in <code>validateForm</code>.
 * @history 19.10.2001 Tom Blough
 *          Re-coded <code>isBlank</code> for faster execution by
 *          not using <code>String.substr</code> calls.
 */
function validateForm( thisForm)
{
	var incompleteItems = "";	//list of items needing attention
	var attentionItem = null;	//first item needing attention
	var groupedItems = ";";		//'collection' to keep track of radio and checkbox groups
	
	//iterate through all of the form elements
	for( var i = 0; i < thisForm.elements.length; i++)
	{
        var thisElement = thisForm.elements[i];

		//Check maxLength on all elements
		var max = thisElement.getAttribute('maxLength');
		if (max != null && thisElement.value.length > max){
			incompleteItems += thisElement.title + " exceeds the max length of " + max + "\n";
			if( attentionItem == null) attentionItem = thisElement;
		}

		//skip disabled and optional elements
		if( (! thisElement.disabled) && (! (String(thisElement.optional).toLowerCase() == "true")))
		{
			if( thisElement.type == 'radio' || thisElement.type == 'checkbox')
			{
				//have we already checked this group?  This is a kludgy way of keeping
				//track, but should work ok as long as the form does not contain a lot of
				//grouped elements or uses really long form element names.
				if( groupedItems.indexOf( ";" + thisElement.name + ";") == -1)
				{
					var group = thisForm[thisElement.name];	//the group object
					var groupLength = group.length;
					var checked = false;

					//we want to make sure at least one item in each group is selected.
					if( groupLength == null)	//single button and not a group
						checked = thisElement.checked;
					else
					{
						//new group.  add it to our list
						groupedItems += thisElement.name + ";";

						//now check all of the groups elements
						for( var r = 0; r < groupLength; r++)
							if( (checked = group[r].checked)) break;
					}
					if( ! checked)
					{
						//add it to our list of items needing attention
						incompleteItems += thisElement.title + " is not selected\n";
						//if this is the first item needing attention, save a reference to it
						if( attentionItem == null) attentionItem = thisElement;
					}
				}
			}
		
			if( thisElement.type == 'text')
			{
				//perform further validation based on the subType attribute.
				//we use regular expressions to test well-formedness but we
				//do not actually validate the values.
				var rExp = new RegExp();
				var problem = "";
				
				switch( String(thisElement.subType).toLowerCase())
				{
					case "email":
						rExp = /^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/
						if( ! rExp.test( thisElement.value)) problem = " is not a valid e-mail address\n";
						break;
					case "zip":
						rExp = /^\d{5}$|^\d{5}[\-\s]?\d{4}$/
						if( ! rExp.test( thisElement.value)) problem = " is not a valid zip code\n";
						break;
					case "phone":
						rExp = /^\D?(\d{3})\D?\D?(\d{3})\D?(\d{4})$/
						if( ! rExp.test( thisElement.value)) problem = " is not a valid phone number [(nnn)nnn-nnnn]\n";
						break;
					case "ssn":
						rExp = /^\d{3}-\d{2}-\d{4}$/
						if( ! rExp.test( thisElement.value)) problem = " is not a valid SSN [nnn-nn-nnnn]\n";
						break;
					case "url":
						rExp = /((mailto\:|(news|(ht|f)tp(s?))\:\/\/){1}\S+)/
						if( ! rExp.test( thisElement.value)) problem = " is not a valid Internet URL\n";
						break;
					case "ipaddr":
						rExp = /\b((25[0-5]|2[0-4]\d|[01]\d\d|\d?\d)\.){3}(25[0-5]|2[0-4]\d|[01]\d\d|\d?\d)\b/
						if( ! rExp.test( thisElement.value)) problem = " is not a valid IP address\n";
						break;
					case "name":
						rExp = /^([a-zA-Z]+\s?)+$/ 
						if( ! rExp.test( thisElement.value)) problem = " is not a valid name\n";
						break;
					case "time":
						rExp = /^([1-9]|1[0-2]):[0-5]\d$/ 
						if( ! rExp.test( thisElement.value)) problem = " is not a valid time [nn:nn]\n";
						break;
					case "date":
						rExp = /^\d{1,2}(\-|\/|\.)\d{1,2}\1\d{4}$/ 
						if( ! rExp.test( thisElement.value)) problem = " is not a valid date [mm/dd/yyyy]\n";
						break;
					case "currency":
						rExp = /\$\d{1,3}(,\d{3})*\.\d{2}/ 
						if( ! rExp.test( thisElement.value)) problem = " is not a valid dollar amount\n";
						break;
					case "number":
						// allows optional sign, variable number of digits, optional decimal point, 
						// and another group of digits
						rExp = /^[-+]?\d*\.?\d*$/
						// the above rexp matches empty strings as well so we must check for an empty response
						if( rExp.test( thisElement.value) && (! isEmpty( thisElement.value)))
						{
							value = parseFloat( thisElement.value);
							// handle the minimum and maximum combinations
							if( thisElement.minVal && thisElement.maxVal)
							{
								var minval = parseFloat( thisElement.minVal);
								var maxval = parseFloat( thisElement.maxVal);
								if( (value < minval) || (value > maxval))
									problem = " must be between " + minval + " and " + maxval + " inclusive\n";
							}
							else if( thisElement.minVal)
							{
								var minval = parseFloat( thisElement.minVal);
								if( value < minval)
									problem = " must be greater than or equal to " + minval + "\n";
							}
							else if( thisElement.maxVal)
							{
								var maxval = parseFloat( thisElement.maxVal);
								if( value > maxval)
									problem = " must be less than or equal to " + maxval + "\n";
							}
						}
						else	// failed rexp or was empty
							problem = " is not a valid number\n";
						break;
					default:	//unknown subtype or none specified
						//did they type something other than whitespace?
						if( isEmpty( thisElement.value)) problem = " is empty\n";
						break;
				}
				
				//did we find a problem?
				if( problem.length > 0)
				{
					incompleteItems += thisElement.title + problem;
					if( attentionItem == null) attentionItem = thisElement;
				}
			}
			
			if( thisElement.type == 'password' || thisElement.type == 'textarea')
			{
				if( isEmpty( thisElement.value))	//did they type something other than spaces?
				{
					incompleteItems += thisElement.title + " is empty\n";
					if( attentionItem == null) attentionItem = thisElement;
				}
			}
			
			if( thisElement.type == 'select-one' || thisElement.type == 'select-multiple')
			{
				//this works only if the first list item is a 'dummy' i.e. something
				//like 'Select one'
				if( thisElement.selectedIndex == 0)
				{
					incompleteItems += thisElement.title + " is not selected\n";
					if( attentionItem == null) attentionItem = thisElement;
				}
			}
		}
	}
	
	//did we find any items needing attention?
	if( attentionItem != null)
	{
		attentionItem.focus();
		try //not all of our form items have a select method
		{
			attentionItem.select(); //highlight the problem entry
		}
		catch( err)
		{
			//do nothing
		}
		alert( "Please complete the following items and resubmit:\n\n" + incompleteItems);
		return false;
	}
	else	//all items had some kind of entry
	{
		return true;
	}
}

/**
 * Function to check if string contains something other than whitespace
 *
 * @returns true if string contains only spaces, carriage returns
 *          linefeeds, and horizontal tabs
 */
function isEmpty( str)
{
	var i = 0;
	var c;
	
	//we could have used a regular expression to replace all of the
	//whitespace and then check if the length was > 0, but this iterator
	//should be faster than the object creation overhead since it stops at
	//the first non-whitespace character.
	while( (c = str.charAt( i++)))
		if( (c != ' ') && (c != '\r') && (c != '\n') && (c != '\t')) return false;
	return true;
}

/*
 * Function to check if string is greater than maxlength specified
 * @returns true if string is less than or equal to the maxlength, false otherwise
 *
*/

function checkLength(thisElement)
{
 var max = thisElement.getAttribute('maxLength');
 if (thisElement.value.length > max)
	return false;
 return true;
}

