/*
filename: validate.js
author: mjv

Steps through validateFields array and validates according to options

-------------
Usage Notes:
-------------
1) Create the validateFields array and fill with validation options
	- validateFields.push(new validator("FIELD NAME", "FIELD TYPE", "CONSTRAINT"));
2) Set the form's onSubmit property to "return validate(this")

Valid Field Types:
	- password: a password field
	- text : a text box or textarea
	- select-one : a select box that is a required field (e.g., validator("selectName", "select-one", "required"))
	- radio
	- checkbox
	- hidden

Valid constraints:
	-required : Indicates a required field
	-date : Valid date entry
	-mail : Valid email address
    -maillist : a list of valid email addresses seperated by "," or empty (for cc)
	-dateplus : Valid date greater than or equal to today
	-num : Number
	-eitheror: fields passed as "field1,field2" are checked to make sure at least one of them has a value
	-equality : fields passed as "field1,field2" are checked to make sure they are equal

-------------
Example:
-------------
	<html>
	<head>
	<script language="javascript" src="validate.js"></script>
	<script language="javascript" src="date.js"></script>
	<script language="javascript">
		var validateFields = new Array();
		validateFields.push(new validator("firstrundate", "text", "required"));
		validateFields.push(new validator("firstrundate", "text", "dateplus"));
	</script>
	</head>
	.
	.
	.
	<form onSubmit="return validate(this)">
		<input type="text" name="firstrundate">
		<input type="submit" name="submit">
	</form>
	</body>
	</html>
-------------
*/

sCheckedElements = '';
errColor = "red";
stdColor = "";

function validator(name, type, constraint)
{
	this.name = name;
	this.type = type;
	this.constraint = constraint;
	this.value = "";
}

function validator(name, type, constraint, msg)
{
	this.name = name;
	this.type = type;
	this.constraint = constraint;
	this.value = "";
	if(msg!=null && msg!="") this.message = msg;
}

function doError(item, msg)
{
	if(item.message!=null && item.message!="")
		msg = item.message;
	alert(msg);
}

function changeColor(fldname, frmname, color)
{
	efldname = "err_" + fldname;
	errmarker = document.getElementById(efldname);
	if(errmarker) errmarker.style.color = color;
}

function checkCheckbox(item, form)
{
	// also used for radio buttons, since they behave the same way, and have the same properties

	/* Radio Buttons and Check boxes behave in an unusual manner.	When looping through the form
		 element by element like this, each entry is its own entity with no relation to any others,
		 even with the same name. Because we want to check the complete array of elements with the
		 same name, we have to actually check the form by element name, rather than element number.
		 To prevent duplicate checks, we will keep the names of all checked elements in a global
		 string, and add each new name checked to the string.	If an item name is already in that
		 string, it will not be checked again. */

	// set the local variables
	bLocValid = false;
	sCheckedElements='';
	if (sCheckedElements.indexOf(item.name+',') < 0)
	{
		// hasn't been previously checked.	Add the name to the list and perform checks.
		sCheckedElements = sCheckedElements + item.name + ',';
		// because a checkbox could be one item, or many we first have to see if there is only one item and it is checked
		// if not, then we check to see if there are any items in the array that are checked
		if (eval('form.'+item.name+'.length'))
		{
			// loop through the individual memembers of the checkbox, looking for one that is checked (stop as soon as one is
			//found)
			for (j=0; j < eval('form.'+item.name+'.length'); j++)
			{
				if (eval('form.'+item.name+'['+j+'].checked'))
				{
					bLocValid = true;
					break;
				}
			}
		}
		else
		{
			// only one item
			if (eval('form.'+item.name+'.checked'))
			{
				bLocValid = true;
			}
		}
	}
	else
	{
		//alert("checked already");
		// has already been checked - return true.
		//Any false entries will have already been caught, and we don't want to give duplicate warnings.
		bLocValid = true;
	}
	return bLocValid;
}

function getValue(fld)
{
	var rVal = "";
	switch (fld.type)
	{
		case 'text':
			rVal = fld.value;
			break;

		case 'password':
			rVal = fld.value;
			break;

		case 'textarea':
			rVal = fld.value;
			break;

		case 'hidden':
			rVal = fld.value;
			break;

		case 'select-one':
			rVal = fld[fld.selectedIndex].value;
			break;

		default:
			rVal = "Can't determine field";
			break;
	}
	if(rVal) return rVal;
	return "";
}

function validate(form)
{
	bErr=false;
	bReturn=true;
	var formname = form.name;

	for (i=0; i < validateFields.length; i++)
	{
		bReq = false;
		// store the form element in a new object to make life easier
		var item = validateFields[i];
		var sField = item.name;
		var fld = form[sField];
		if(!fld && item.constraint!='eitheror' && item.constraint!='equality')
		{
			//alert("Validation Error!!!\n\nThere is no such field ("+i+"):\n\n " +sField);
			continue;
		}
		changeColor(sField, formname, stdColor);
		if(item.constraint!='eitheror' && item.constraint!='equality' && fld)
			item.value = getValue(fld);
		if (item.constraint == "required") // ITEM IS REQUIRED
		{
			// find out what type of entry it is
			switch (item.type)
			{
				case 'text':
					if (item.value=="")
					{
						//alert(sField + " is required.");
						doError(item, sField + " is required.");
						changeColor(sField, formname, errColor);
						bReturn = false;
						bErr = true;
						fld.focus();
						return false;
					}
					break;

				case 'password':
					if (item.value=="")
					{
						//alert(sField + " is required.");
						doError(item, sField + " is required.");
						changeColor(sField, formname, errColor);
						bReturn = false;
						bErr = true;
						fld.focus();
						return false;
					}
					break;

				case 'hidden':
					if (item.value=="")
					{
						//alert(sField + " is required.");
						doError(item, sField + " is required.");
						changeColor(sField, formname, errColor);
						bReturn = false;
						bErr = true;
						return false;
					}
					break;

				case 'radio':
					if (!checkCheckbox(item, form))
					{
						// found a bad entry, add the error message, swap the image, set the over-all form value to bad
						changeColor(sField, formname, errColor);
						//alert("At least one " + sField + " is required.");
						doError(item, "At least one " + sField + " is required.");
						bReturn = false;
						bErr = true;
						return false;
					}
					break;

				case 'checkbox':
					if (!checkCheckbox(item, form))
					{
						// found a bad entry, add the error message, swap the image, set the over-all form value to bad
						changeColor(sField, formname, errColor);
						//alert("At least one " + sField + " is required.");
						doError(item, "At least one " + sField + " is required.");
						bReturn = false;
						bErr = true;
						return false;
					}
					break;

				case 'select-one':
					if (fld.selectedIndex == 0)
					{
						changeColor(sField, formname, errColor);
						//alert('You must make a selection in ' + sField + '!');
						doError(item, 'You must make a selection in ' + sField + '!');
						bReturn = false;
						bErr = true;
						return false;
					}
					break;

				default:
					// no error checking - should never get here
					break;
			}
		}
		// Now check other constraints if no errors yet been found
		if (!bErr && item.constraint)
		{
			switch (item.constraint)
			{
				case 'eitheror':
					var hasAtLeastOne = false;
					var aFields = sField.split(",");
					for (var loop=0; loop < aFields.length; loop++)
					{
						var sFld = aFields[loop];
						fld = form[sFld];
						if(getValue(fld)!='')
						{
							hasAtLeastOne = true;
						}
					}
					if(!hasAtLeastOne)
					{
						//alert("You must fill in one of the required fields: " +sField);
						doError(item, "You must fill in one of the required fields: " +sField);
						changeColor('sourceid', formname, errColor);
						bReturn = false;
						bErr = true;
						if (item.type != 'hidden') fld.focus();
						return false;
					}
				break;

				case 'equality':
					var aFields = sField.split(",");
					var lastField;
					var thisField;
					var equals = true;
					if(aFields.length>=2)
					{
						lastField = aFields[0];
						for (var loop=1; loop < aFields.length; loop++)
						{
							thisField = aFields[loop];
							var checkField = form[thisField];
							if(checkField)
							{
								if(getValue(form[thisField])!=getValue(form[lastField]))
								{
									doError(item, "Fields must be equal: "+sField);
									changeColor(lastField, formname, errColor);
									return false;
								}
								else
								{
									lastField = thisField;
								}
							}
						}
					}
				break;

				case 'mail':
                    if(!/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(item.value))
					{
                        //alert("invalid: " + item.value);
						//alert("Email address is improperly formatted.");
						doError(item, "Email address is improperly formatted.");
						changeColor(sField, formname, errColor);
						bReturn = false;
						bErr = true;
						if (item.type != 'hidden') fld.focus();
						return false;
					}
                    else
                    {
                        //alert("valid: " + item.value);
                    }
				break;

                case 'maillist':
                    //var re = new RegExp("(^[^\\s;:\"@,<>]+@[^\\s;:@,<>]+\\.[A-Za-z]+(?:,\\s*)?)+|^$");
                    var re = new RegExp("(^(?:(?:[^\\s;:\"@,<>]+@[^\\s;:@,<>]+\\.[A-Za-z]+,\\s*)+)?[^\\s;:\"@,<>]+@[^\\s;:@,<>]+\\.[A-Za-z]+$)|^$");
                    if (!Trim(item.value).match(re))
					{
                        //alert("invalid: " + item.value);
						doError(item, "Some email addresses in the list are improperly formatted.");
						changeColor(sField, formname, errColor);
						bReturn = false;
						bErr = true;
						if (item.type != 'hidden') fld.focus();
						return false;
					}
                    else
                    {
                        //alert("valid: " + item.value);
                    }
				break;

				case 'num':
					if( isNaN( parseFloat(item.value)))
					{
						//alert("You must enter a valid number for " +sField);
						doError(item, "You must enter a valid number for " +sField);
						bReturn = false;
						bErr = true;
						if (item.type != 'hidden') fld.focus();
						return false;
					}
					break;

				case 'dateplus':
					//alert("checking date " + item.value);
				var err=0;
				// First check validity of entered date
				if (!isDate(item.value, "M/d/y"))
				{
					err=1;
					if (item.type != 'hidden') fld.focus();
					bErr = true;
					bReturn = false;
					changeColor(sField, formname, errColor);
					//alert('Bad date format!\n\r\n\rPlease enter a date for ' + sField + ' in the format \'MM/DD/YYYY\'');
					doError(item, 'Bad date format!\n\r\n\rPlease enter a date for ' + sField + ' in the format \'MM/DD/YYYY\'');

					return false;
					break;

				// If date is valid, compare to today
				}
				else
				{
					var now = new Date();
					var today = (now.getMonth() +1) + "/" + now.getDate() + "/" + now.getFullYear();
					var nogood = compareDates(today, "M/d/y", item.value,"M/d/y", true, false, 1);
					if (nogood<1)
					{
						//alert("Date in " + sField + " must be today or later!");
						doError(item, "Date in " + sField + " must be today or later!");
						bErr = true;
						bReturn = false;
						changeColor(sField, formname, errColor);
						return false;
					}
					break;
				}

				case 'date':
				var err=0;
			if (item.value != "" && !isDate(item.value, "M/d/y"))
			{
				err=1;
				if (item.type != 'hidden') fld.focus();
				bErr = true;
				changeColor(sField, formname, errColor);
				bReturn = false;
				//alert('Bad date format!\n\r\n\rPlease enter a date for ' + sField + ' in the format \'MM/DD/YYYY\'');
				doError(item, 'Bad date format!\n\r\n\rPlease enter a date for ' + sField + ' in the format \'MM/DD/YYYY\'');
				return false;
			}
			break;

			default:
				break;
			}
		}
	}//for i=0 to validateFields.length
return bReturn;
}

function Trim(s) 
{
  // Remove leading spaces and carriage returns
  
  while ((s.substring(0,1) == ' ') || (s.substring(0,1) == '\n') || (s.substring(0,1) == '\r'))
  {
    s = s.substring(1,s.length);
  }

  // Remove trailing spaces and carriage returns

  while ((s.substring(s.length-1,s.length) == ' ') || (s.substring(s.length-1,s.length) == '\n') || (s.substring(s.length-1,s.length) == '\r'))
  {
    s = s.substring(0,s.length-1);
  }
  return s;
}
