//////////////////////////////////////////////////////////////
// Form Validation Functions & Objects
// v1.18 (Budd Wright)
//
// Description:
//////////////////////////////////////////////////////////////
// Contains form validation functions and objects
//////////////////////////////////////////////////////////////
//
// Usage:
//////////////////////////////////////////////////////////////
// <script language="javascript" src="js/form.js"></script>
//////////////////////////////////////////////////////////////
//
// v1.18 Notes
// -----------
// Added acceptedFileTypes validation type ( array of accepted file extensions )
//
// v1.17 Notes
// -----------
// Added isRadio validation type ( requires one selection be made, handles values appropriately )
// Added focus and scrolling to bad field elements on validation errors
// Added requiredForOption validation type, forces a field's validation only when the supplied element (via its ID) is checked
//
// v1.16 Notes
// -----------
// Added GetFormFieldByName method to FormElement
//
// v1.15 Notes
// -----------
// Added isEmailAddress & isPhoneNumber validation types
//
// v1.10 Notes
// -----------
// Added "Add()" function to FormElement for easy insert
//
// v1.00 Notes
// -----------
// Requires CheckDate function from date.js
//
//
// Known Issues
// ------------
// 
//
// Examples
// --------
//	testForm = new FormElement("testItem");
//	[formItem testForm.formItems[0] = new FormField("memberName", "Username");
//	testForm.formItems[0].Resize(5,10);]
//	- or -
//	[var formItem = testForm.Add("memberName", "Username");
//	formItem.Resize(5,10);]
//
// "testForm" is the name of the form element
// "memberName" is the ID of the form item element in the code,
// whereas "Username" is the label used in error messages
// The Resize method call sets a minimum and maximum value range
// and resets the error message for values outside the bounds
//////////////////////////////////////////////////////////////


/* Global settings */
errorColor = "beige";					// Color of form field background after error





/* ValidateForm() Function */
// Validates fields in supplied form are valid
function ValidateForm(label)
{
	var validForm = eval(label);

	for(var x = 0; x < validForm.formItems.length; x++)
	{
		// Reference to current FormField object
		var tempField = validForm.formItems[x];


		// Reference to current form field object & value
		if(! tempField.isRadioButton )
		{
			var tempElement = document.getElementById(tempField.fieldName);
			var tempValue = tempElement.value;
		}
		else
		{
			// Get radio button fields' selected option's value
			var tempElements = document.getElementsByName(tempField.fieldName);
			var tempElement = document.getElementsByName(tempField.fieldName)[0];
			var tempValue = "";

			for(var y = 0; y < tempElements.length; y++)
			{
				if( tempElements[y].checked )
				{
					// Found selection, use its value and object reference
					tempElement = tempElements[y];
					tempValue = tempElement.value;
					break;
				}
			}
		}



		// Set form field requirement: Field dependant on another radio button selection?
		if(tempField.requiredForOption != null && document.getElementById( tempField.requiredForOption ).checked )
			// Parent field is selected, require dependant field
			tempField.isNullable = false;

		else if(tempField.requiredForOption != null)
			// Parent field not selected, do not require dependant field
			tempField.isNullable = true;



		// Test form field: Allow nulls?
		if(!tempField.isNullable && tempValue.length < 1)
		{
			// Field is not nullable, but has been left blank
			ShowError(tempField.msgRequired, tempElement);
			return false;
		}
		else if(tempField.isNullable && tempValue.length == 0)
		{
			// Field is nullable, and has been left blank -- no futher testing required
			continue;
		}


		// Test form field: Is it a file field and are there accepted file type limitations?
		if( tempField.isFile && tempField.acceptedFileTypes.length > 0 )
		{
			var isAcceptedFileType = false;
			var filename = tempValue.substring( tempValue.lastIndexOf("\\") + 1, tempValue.length );
			var extension = filename.substring( filename.lastIndexOf(".") + 1, filename.length );

			for( var y = 0; y < tempField.acceptedFileTypes.length; y++ )
			{
				if( tempField.acceptedFileTypes[ y ].toLowerCase() == extension.toLowerCase() )
				{
					isAcceptedFileType = true;
					break;
				}
			}


			// Was the file type an acceptable type?
			if( ! isAcceptedFileType )
			{
				ShowError( tempField.msgFileType, tempElement );
				return false;
			}		
		}


		// Test form field: Is it a number?
		if(tempField.isNumber && isNaN(tempValue))
		{
			// Number field has non-numeric value
			ShowError(tempField.msgNumber, tempElement);
			return false;
		}

		// Test form field: Is it a date?
		if(tempField.isDate)
		{
			if(!CheckDate(tempValue))
			{
				// Date field has non-date value or wrong date format
				ShowError(tempField.msgDate, tempElement);
				return false;
			}
		}

		// Test form field: Is it an email address?
		if(tempField.isEmailAddress)
		{
			if(! IsEmail(tempValue) )
			{
				// Invalid Email Address
				ShowError(tempField.msgEmail, tempElement);
				return false;
			}
		}

		// Test form field: Is it a phone number?
		if(tempField.isPhoneNumber)
		{
			if(! IsPhoneNumber(tempValue) )
			{
				// Invalid Phone Number
				ShowError(tempField.msgPhone, tempElement);
				return false;
			}
		}

		// Test form field: Does its length meet the minimum?
		if(tempField.lengthMinimum != 0 && tempValue.length < tempField.lengthMinimum)
		{
			// Field does not have enough characters
			ShowError(tempField.msgLength, tempElement);
			return false;
		}

		// Test form field: Does its length fall under the maximum?
		if(tempField.lengthMaximum && tempValue.length > tempField.lengthMaximum)
		{
			// Field has too many characters
			ShowError(tempField.msgLength, tempElement);
			return false;
		}

		// Test form field: Does it contain any illegal characters?
		if(tempField.illegalCharacters.length > 0)
		{
			for(var y = 0; y < tempField.illegalCharacters.length; y++)
			{
				var character = tempField.illegalCharacters.substring(y, y + 1);

				if(tempValue.indexOf(character) != -1)
				{
					// Field has an illegal character
					ShowError(tempField.msgIllegalCharacters, tempElement);
					return false;
				}
			}
		}

	}


	// Form fields validated, submit form
	document.getElementById(validForm.label).submit();
}


/* ShowError() Function */
// Notifies user that validation failed and highlights the bad field
function ShowError(msg, element)
{
	// Highlight text fields only
	if(element.type.toLowerCase() == "text" || element.type.toLowerCase() == "textarea")
	{
		element.style.backgroundColor = errorColor;
		element.onclick = function() { element.style.backgroundColor = "white"; element.onclick = null; };
	}

	// Scroll and focus to the bad field
	var positionX = 0;
	var positionY = 0;
	var tempElement = element;

	element.focus();

	while(tempElement != null)
	{
		positionX += tempElement.offsetLeft;
		positionY += tempElement.offsetTop;
		tempElement = tempElement.offsetParent;
	}

	window.scrollTo(positionX, positionY);


	// Display error message
	alert(msg);
}



/* IsEmail function */
// Checks an email address for validity
function IsEmail(address)
{
	// Must have only 1 "@"
	var atIndex = address.indexOf("@");

	if(atIndex == -1)							return false;
	if(address.indexOf("@", atIndex + 1) != -1)	return false;


	// Must have "." after "@", and at least two characters between
	var dotIndex = address.indexOf(".", atIndex);

	if(dotIndex == -1)				return false;
	if((dotIndex - atIndex) <= 2)	return false;


	// Last two characters must be alphabetic
	for(x = 0; x <= 1; x++)
	{
		var charASC = address.toLowerCase();
			charASC = charASC.substring(charASC.length - x - 1, charASC.length - x);
			charASC = charASC.charCodeAt(0);

		if(charASC < 97 || charASC > 122)	return false;
	}


	// First character must be alphanumeric
	// Cannot be ()[]#.@
	var illegalChars = "()[]#.@";

	charASC = address.toLowerCase();
	charASC = charASC.substring(0, 1);

	if(illegalChars.indexOf(charASC) != -1)	return false;


	// Valid email address
	return true;
}


/* IsPhoneNumber */
// Validates a phone number value
function IsPhoneNumber( number )
{
	// At least 10 digits
	if( number.length < 10 )
		return false;

	// 10 digits: Only the numbers, e.g. 6169752500
	if( number.length == 10 && isNaN( number ) )
		return false;

	// 12 digits: Numbers plus punctuation, e.g. 616.975.2500 or 616-975-2500
	if( number.length == 12 )
	{
		if( number.charAt(3) != "." && number.charAt(3) != "-" )
			return false;
		if( number.charAt(7) != "." && number.charAt(7) != "-" )
			return false;

		var numericalValue = number.replace(/\./g, "");
		var numericalValue = numericalValue.replace(/\-/g, "");

		if( isNaN(numericalValue) || numericalValue.length != 10 )
			return false;
	}

	// All other value lengths are not valid
	if( number.length != 10 && number.length != 12 )
		return false;


	// Valid phone number
	return true;
}



/////////////////////////////////////
// Object Constructors
/////////////////////////////////////


/* FormElement object constructor */
// Constructor for creating FormElement objects
function FormElement(label)
{
	this.id = "validator";
	this.label = label;
	this.formItems = new Array();
	this.cancelHref;

	this.Add = FormElementAdd;
	this.Cancel = FormElementCancel;
	this.SetCancel = FormElementSetCancel;


	// Validate method
	this.Validate = function()
	{
		ValidateForm( this.id );
	}
}


/* FormField object constructor */
// Constructor for creating FormField objects
function FormField(fieldName, label)
{
	this.fieldName = fieldName;
	this.label = label;

	this.acceptedFileTypes = new Array();
	this.illegalCharacters = "";
	this.isDate = 0;
	this.isEmailAddress = false;
	this.isFile = false;
	this.isNullable = 0;
	this.isNumber = 0;
	this.isPhoneNumber = false;
	this.isRadioButton = false;
	this.lengthMinimum = 0;
	this.lengthMaximum = null;
	this.requiredForOption = null;			// ID to a radio button field that when selected, requires this field, too

	this.msgDate = "The " + label + " field must be a valid date. Please enter a date in the format of (mm/dd/yyyy) and try again.";
	this.msgEmail = "The " + label + " field is an email field. Please enter your correct email address and try again.";
	this.msgFileType = "The " + label + " field does not permit the file type you chose.  Please try again.";
	this.msgIllegalCharacters = "The " + label + " field value contains illegal characters. Please remove these characters and try again.";
	this.msgLength = "The " + label + " field value length is out of bounds. Please try again.";
	this.msgNumber = "The " + label + " field must be a numerical value. Please enter a numerical value and try again.";
	this.msgPhone = "The " + label + " field is a phone number field. Please type the number with its area code in this format: 111.222.3333";
	this.msgRequired = "The " + label + " field is required. Please enter a value and try again.";


	this.Resize = FormFieldResize;
	this.Focus = FormFieldFocus;
}


/* FormFieldResize method */
// Resize method for FormField objects
function FormFieldResize(minimum, maximum)
{
	this.lengthMinimum = minimum;
	this.lengthMaximum = maximum;

	this.msgLength = "The " + this.label + " field value must be between " + this.lengthMinimum + " and " + this.lengthMaximum + " characters. Please try again.";
}


/* FormFieldFocus method */
// Focus method for FormField objects
// Focuses on FormField object when page is loaded
function FormFieldFocus()
{
	var fieldName = this.fieldName;
	document.body.onload = function() { document.getElementById(fieldName).focus(); }
}




/* FormElement.GetFormFieldByName() */
// Returns a reference to a FormField in the FormElement formItems collection
FormElement.prototype.GetFormFieldByName = function ( name )
{
	var reference = null;

	// Loop through formItems array and find the requested FormField
	for( var x = 0; x < this.formItems.length; x++)
	{
		if( this.formItems[x].fieldName.toLowerCase() == name.toLowerCase() )
		{
			reference = this.formItems[x];
			break;
		}
	}

	return reference;
};


/* FormElementAdd() */
// Adds a form field item to the form element's formItems array
function FormElementAdd(fieldName, label)
{
	var formItemReference = new FormField(fieldName, label);

	// Add the new form field to the form element's array
	this.formItems[ this.formItems.length ] = formItemReference;

	// Return
	return formItemReference;
}


/* FormElementCancel() */
// Cancels a page view and returns to the form's specified view
function FormElementCancel()
{
	location.href = this.cancelHref;
}


/* FormElementSetCancel method */
// Sets the page a form cancels to
function FormElementSetCancel(pageHref)
{
	this.cancelHref = pageHref;
}

