

/****************************** SCRIPT-WIDE VARIABLES **********************************/


var fieldInfo = new Array();


/****************************** MAIN FUNCTIONS ***************************************/


function ValidateSubmission(form) {
	if(IsValidSubmission(form)) {
		form.submit();
	}
	return false;
}



function IsValidSubmission(form) {
	if (ValidateFields(form,fieldInfo) != false)
	{
		return true;
	}
	return false;
}




/*--------------------------------------------------------------------------------------------*\
Function: ValidateFields
Purpose:
	This is the javascript counterpart to the ColdFusion validateFields function.
	It behaves in a very similar fashion, taking the same arguments for the fieldInfo
	array in the same ColdFusion "List" structure.
Parameters:
	form		- the name of the form to validate
	fieldInfo	- an array. Each index is a string of the form:
		"requirement,fieldname[,fieldname2 [,fieldname3, date_flag]],error message to be alerted".

		fieldName2 is optional, and only used for comparing one field to 
		another (like password fields).

		Valid "requirement" strings are: 
			"required"	- field must be filled in
			"digits_only"	- field must contain digits only
			"length=X" 	- field has to be X characters long
			"length=X-Y" 	- field has to be between X and Y (inclusive) characters long
			"valid_email"	- field has to be valid email address
			"valid_email_list" - field has to contain zero or more valid email addresses
			"valid_date"	- field has to be a valid date
			"url	"	- field has to be a URL 
			"zipcode"	- field has to be a valid zipcode or postal code
								fieldname:	MONTH 
								fieldname2:	DAY 
								fieldname3:	YEAR
								date_flag: 	"later_date" / "any_date"
			"same_as" 	- fieldname is the same as fieldname2 (for password comparison)
			"range=X-Y"	- field must be a number between the range of X and Y inclusive.
			"currency"	- field has to be valid US/Canadian currency
		One last option [can anyone say "feature creep"?]:
		if:FIELDNAME=VALUE	- This can come at the very beginning of the line before 
				"requirement". This allows us to only validate a field 
				IF a fieldname FIELDNAME has a value VALUE.

Notes: With both digits_only and valid_email options, if the empty string is passed in it 
won't generate an error (thus allowing validation of non-required fields).
\*--------------------------------------------------------------------------------------------*/
function ValidateFields(form, fieldInfo)
{

	var errorMessages = new Array()  // all error messages get queued up here
	
	// loop through fieldInfo
	for (var i=0; i<fieldInfo.length; i++)
	{		

//	alert("fieldInfo's length is "+ fieldInfo.length)
//	alert("fieldInfo["+ i +"] is "+ fieldInfo[i])
		// split fieldInfo into component parts 
		var test_params = fieldInfo[i].split(",");


		// while the first test parameter begins with "if:..." test the condition. If true, strip
		// the if:..., part and continue evaluating the rest of the line. Keep repeating this while
		// the line begins with an if-condition. If it fails any of the conditions, it doesn't
		// bother validating the rest of the line.
		var continueValidatingThisLine = true;
		while (test_params[0].match("^if:"))
		{
//	alert("conditional found")
			var condition = test_params.shift();
			continueValidatingThisLine = SatisfiesIfConditions(form, condition);
			if (! continueValidatingThisLine) { break; }
		}
		if (! continueValidatingThisLine ) { continue; }
		// "continue" in Javascript means "go to the next iteration of the loop",
		// not "continue doing this iteration of the loop".

//	alert("test_params are "+ test_params)
//	alert("test_params has "+ test_params.length +" elements")
		// Pull apart the test parameters
   		var errorMessage = test_params.pop()   // error message is at the end
		var requirement  = test_params.shift() // requirement is at the beginning
   		var fieldNames   = new Array()
		fieldNames       = test_params         // field names are everything else
		
//	alert("error Message: "+ errorMessage +"\nrequirement: "+ requirement)
    	
		// Send the data in and run the appropriate test.  If it fails, display
		// an error message.
		if (! PassesAppropriateTest(form, requirement, fieldNames) ) {
		// 3637 - queue up all failures
			//alertMessage(form, fieldNames, errorMessage)
			HighlightFields(form, fieldNames)
			errorMessages.push(errorMessage)
			//return false
		}

	} // end loop through fieldInfo
	
	if ( errorMessages.length > 0 ) {
		DisplayErrors(errorMessages)
		return false;
	}
	else {
		return true
	}
	// 3637

} // end ValidateFields



function SatisfiesIfConditions(form, condition) {
	condition = condition.replace("if:", "");
	var parts = condition.split("=");
	var fieldToCheck = parts[0];
	var valueToCheck = parts[1];

	// find value of FIELDNAME for conditional check
	var fieldnameValue = "";			
	if (form[fieldToCheck].type == undefined)	// RADIO
	{
		for (var j=0; j<form[fieldToCheck].length; j++)
		{
			if (form[fieldToCheck][j].checked)
				fieldnameValue = form[fieldToCheck][j].value;
		}
	}
	else 										// ALL OTHER FIELD TYPES
		fieldnameValue = form[parts[0]].value;
		// if the VALUE is NOT the same, we don't need to validate this field. Return.
	if (fieldnameValue != valueToCheck) { return false; }
	else { return true; }

} // end SatisfiesIfConditions




/*---------------------------------------------------------------------*\
 This function chooses the appropriate test based on the requirement
 field, calls the test, and returns the result:
    passed (true)
    failed (false)
\*---------------------------------------------------------------------*/
function PassesAppropriateTest(form, requirement, fieldNames)
{ 
	// 4000
	// some tests below require access to the input object
	// while others only need its value
	var field1 = form[fieldNames[0]]
	var value1 = field1.value
	var field2
	var value2
	var field3
	var value3
	var date_flag
	if ( form[fieldNames[1]] ) {
		field2    = form[fieldNames[1]]
		value2    = field2.value
	}
	if ( form[fieldNames[2]] ) {
		field3    = form[fieldNames[2]]
		value3    = field3.value
	}
	if ( fieldNames[3] ) {
		date_flag = fieldNames[3]
	}


	// if the requirement is "length=...", rename requirement to "length" for switch statement
	if (requirement.match("^length="))
	{
		var lengthRequirements = requirement;
		requirement = "length";
	}

	// if the requirement is "range=...", rename requirement to "range" for switch statement
	if (requirement.match("^range="))
	{
		var rangeRequirements = requirement;
		requirement = "range";
	}


	// now, validate whatever is required of the field
	switch (requirement)
	{
		case "required":
			if ( IsProvided(field1) ) { return true }
			break;

		case "digits_only":				
			if ( IsDigitsOnly(value1) ) { return true }
			break;

		case "length":
			if ( IsCorrectLength(value1, lengthRequirements) ) { return true }
			break;

		case "valid_email":
			// 3080 - blank emails should pass.  If an email address is required,
			//	  use the "required" test and this one together.
			//if (!form[fieldName].value || !isValidEmail(form[fieldName].value))
			if ( IsValidEmail(value1) ) { return true }
			// 3080
			break;

		// 3080 - this is for validating fields containing multiple email addresses
		case "valid_email_list":
			if ( IsValidEmailList(value1) ) { return true }
			break;
		// 3080

		case "valid_date":
			if ( IsValidDate(value1, value2, value3, date_flag) ) {
				return true
			}
			break;
		
		case "same_as":
			if ( AreSame(value1, value2) ) { return true }
			break;

		case "range":
			if ( IsInRange(value1, rangeRequirements) ) { return true }
			break;

		//added by ACD Nov 9
		// 2861
		case "checked":
			if ( IsChecked(field1) ) { return true }
			break;

		case "url":
			if ( IsValidUrl(value1) ) { return true }
			break;

		case "valid_zipcode":
			if ( IsValidZipcode(value1) ) { return true }
			break;
		// 3122
		case "currency":
			if ( IsValidCurrency(value1) ) { return true }
			break;
		// 3122
		// 3938 - credit card date case
		case "credit_card_date":
			if ( IsValidCreditCardDate(value1, value2) ) { return true }
			break;
		// 3938
		// 5910
		// checkbox_group - a group of checkboxes with the same name,
		// one of which must be checked.
		case "checkbox_group":
			if ( OneIsChecked(fieldNames[0]) ){ return true }
			break;
		default:
			alert("Unknown requirement flag in validateFields(): " + requirement);
			return false;
	}
	return false;

} // end PassesAppropriateTest


/***************************** INDIVIDUAL TESTS ***************************/


// 3359
function IsValidEmail(inputValue)
{
	// 3080 - blank emails should pass.  If an email address is required,
	//        use the "required" test together with "valid_email".
	//alert("str is =" + str + "=");  // useful for debugging
	if (inputValue.match(/^\s*$/)) {
		return true;
	}
	// 3080
	// 3544
	if ( inputValue.match(/noemail/) ) {
		return true;
	}
	// 3544
	// trim starting / ending whitespace
	inputValue = inputValue.replace(/^\s*/, "");
	inputValue = inputValue.replace(/\s*$/, "");

	// check that it's a valid email
	// This regex courtesy of http://www.quirksmode.org/js/mailcheck.html
	//var regex = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/
	// 3372 - Unfortunately, it's too restrictive.  New one below.
	// It turns out the "local part" of an email address can be quite flexible.
	// The very long list of characters below are the ones that (according to the RFC
	// spec) don't require escaping/quoting.
	var regex = /^([a-zA-Z0-9\_\.\-\!\#\$\%\&\'\*\+\/\=\?\^\`\{\|\}\~])+\@(([a-zA-Z0-9\-\_])+\.)+([a-zA-Z0-9]{2,4})+$/
	// 3372
	if ( inputValue.match(regex) ) {
		return true;
	}
	return false;
} // end IsValidEmail



// 2762 
function IsValidUrl(inputValue) {
	var REGEX = /^(http[s]?:\/\/)?([\w-]+\.)+[\w-]+/
	if(!inputValue) {
		return true;
	}
	if(inputValue.match(REGEX)) {
		return true;
	}
	return false;
} // end IsValidUrl



function IsValidZipcode(inputValue) {
	if(!inputValue) {
		return true;
	}

	if (inputValue.match(/^\d{5}$/)) {                                   // US 5 digit zipcode
		return true;
	} else if (inputValue.match(/^\d{5}-\d{4}$/)) {                      // US 5+4 digit zipcode
		return true;
	} else if(inputValue.match(/^[a-zA-Z]\d[a-zA-Z]\s*\d[a-zA-Z]\d$/)) { // Canadian zipcode
		return true;
	}

	// Other zip/postal codes?
	return false;
}
// 2762 



// 3122
function IsValidCurrency(inputValue) {
	if(!inputValue) {
		return true;
	}
	// 3677 - make this more flexible
	//if (inputValue.match(/^\d*(\.\d\d)?$/)) {
	if (inputValue.match(/^\d*(\.(\d(\d)?)?)?$/)) {
	// 3677
		return true;
	}
	return false;
}
// 3122




function IsEmpty(str) {
/*------------------------------------------------------*\
 helper function to check to see if a string is empty
\*------------------------------------------------------*/
	return ((str == null) || (str.length == 0));
}



function IsWhitespace(str) {
/*--------------------------------------------------------------------------------------------*\
	Function: 	IsWhitespace()
	Purpose:	Returns true if string parameter is empty or whitespace characters only.
	Note:       This seems like a confusing way to do this.  Refactor using regexes
	            when possible.
\*--------------------------------------------------------------------------------------------*/
	var whitespace = " \t\n\r"; // viable whitespace characters

    // Is the string empty?
    if (IsEmpty(str)) return true;

    for (var i=0; i<str.length; i++)
    {   
        var c = str.charAt(i);
        if (whitespace.indexOf(c) == -1)
			return false;
    }

    return true;
}




/*--------------------------------------------------------------------------------------------*\
	Function:	IsValidDate()
	Purpose:
		to check an incoming date is valid. If any of the date parameters fail, it 
		returns	a string message denoting the problem.
	Parameters:
		month     - should be an integer between 1 and 12
		day       - should be an integer between 1 and 31 (depending on month)
		year      - should be a 4-digit integer value (but we can handle 2 digits, too)
		date_flag - specifies additional restrictions on the date:
			- any_date   - any valid date is acceptable
			- later_date - the date cannot be in the past
\*--------------------------------------------------------------------------------------------*/
function IsValidDate(month, day, year, date_flag)
{
	// 3923 - Let's be more flexible in terms of the length of the date.  NOTE:
	//        this will need to be updated in the year 2100, but hopefully everyone
	//        will have learned to use 4 digit years by then.
	if ( year.length == 2 ) { year = "20"+year }
	// 3923

	// 3816 - Something in here is failing to verify future dates,
	//        so this a good opportunity to make this less confusing.
	/*
	// this is written for future extensibility of isValidDate function to allow 
	// checking for dates BEFORE today, AFTER today, IS today and ANY day.
	var isLaterDate = false;
	// 3637 - make date_flag checking more bulletproof
	//if (date_flag.match("later_date") ) { isLaterDate = true }
	if ( date_flag && date_flag.match("later_date") ) { isLaterDate = true }
	else                                              { isLaterDate = false }
	// 3637
	*/

//alert(month +" "+ day +" "+ year +" "+ date_flag)
	if(!month || !day || !year) {
		return false;
	}
	if(month.match(/[^\d]/)) {
		return false;
	}
	if(day.match(/[^\d]/)) {
		return false;
	}
	if(year.match(/[^\d]/)) {
		return false;
	}
	// depending on the year, calculate the number of days in the month
	if (year % 4 == 0)			// LEAP YEAR 
		var daysInMonth = new Array(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
	else
		var daysInMonth = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);


	// first, check the incoming month and year are valid. 
	if (!month || !day || !year)			return false;
	if (1 > month || month > 12)			return false;
	// 2725 - We'll allow two year digits (see 3923 above) by tacking "20" on the
	//        front.  (We need this for some online payment forms.)  However, once
	//        that's done, let's be pretty strict in terms of what years we'll 
	//        accept, so that we are in sync with the CGI validation.  (email_form.cgi,
	//        for example, won't accept anything before 1900.)
	//if (year < 0)					return false;
	if (year < 1900 || year > 9999 ) return false;
	// 2725
	if (1 > day || day > daysInMonth[month-1])	return false;


	// 3938 - Verify that the incoming date is not in the past.
	// 3816 - simplify, simplify
	//if (isLaterDate)
	if ( date_flag && date_flag.match("later_date") )
	// 3816
	{
		/*
		// get current date
		var today = new Date();
		var currMonth = today.getMonth() + 1; // since returns 0-11
		var currDay   = today.getDate();
		var currYear  = today.getFullYear();

		// zero-pad today's month & day
		if (String(currMonth).length == 1) 	currMonth = "0" + currMonth;
		if (String(currDay).length == 1) 	currDay   = "0" + currDay;		
		currDate = String(currYear) + String(currMonth) + String(currDay);
		
		// zero-pad incoming month & day
		if (String(month).length == 1) 	month = "0" + month;
		if (String(day).length == 1) 	day   = "0" + day;
		incomingDate = String(year) + String(month) + String(day);

		if (Number(currDate) > Number(incomingDate))
			return false;
		*/

		if ( IsPastDate(year, month, day) )
		{
			return false
		}
	}
	return true;
} // end IsValidDate


// 3938 - new functions
function IsPastDate(year, month, day)
// IsPastDate date only requires the year.  It will compare as much
// as you give it.
{
	var today = new Date()

	
	// process the year (required)
	if ( IsWhitespace(year) ) { return false }
	
	var currYear = today.getFullYear()
	if ( String(year).length == 2 ) { year = "20"+year }
	
	var currDate    = String(currYear)
	var dateToCheck = String(year)


	// process the month
	if ( ! IsWhitespace(month) )
	{
		var currMonth = today.getMonth() + 1  // since returns 0-11
		
		if (String(currMonth).length == 1) { currMonth = "0" + currMonth }
		if (String(month).length == 1)     { month = "0" + month }
		
		currDate    += String(currMonth)
		dateToCheck += String(month)
		
		
		// process the day only if there's a month, too
		if (! IsWhitespace(day) )
		{
			var currDay   = today.getDate()
			
			if (String(currDay).length == 1) { currDay = "0" + currDay }
			if (String(day).length == 1)     { day = "0" + day }  // 3816 minor bug fix here
			
			currDate    += String(currDay)
			dateToCheck += String(day)
		}
	}

	
	// compare the dates
	if ( Number(dateToCheck) < Number(currDate) )
	{
		return true
	}
	return false
}

function IsValidCreditCardDate(year, month)
{
	if ( IsWhitespace(year) || IsWhitespace(month) )
	{
		return false
	}
	if ( IsPastDate(year, month) )
	{
		return false
	}
	return true
}
// 3938


function IsProvided(inputObj) {

//alert("inputObj's type is "+ inputObj.type)
//alert("inputObj's value is "+ inputObj.value)

	// if radio buttons, do separate check:
	// 4284 - These checks need to be more robust.  I'll move them into
	//        functions, which can be more robust, not to mention making
	//        the code more readable.
	//if (inputObj.type == undefined)
	if ( IsRadioButton(inputObj) )
	// 4284
	{
//alert("It's a radio button!")
		var oneIsChecked = false;
		for (var j=0; j<inputObj.length; j++)
		{
			if (inputObj[j].checked)
				oneIsChecked = true;
		}
		if (!oneIsChecked) { return false }
	}
	// 2969
	// if it's a select input, find the value of the selected
	// index.
	// 4284 - ditto above.
	//else if (inputObj.type && inputObj.type.match("select"))
	else if ( IsSelectInput(inputObj) )
	// 4284
	{
	
//alert("It's a select input!")

		var list = inputObj;
		// 4074 - We should use value instead of text, because
		//        sometimes you have "select one" as the text,
		//        which shouldn't pass.
		//var selection = list.options[list.selectedIndex].text;
		//if (selection == "") { return false }
		var selection = list.options[list.selectedIndex].value;
		if ( IsWhitespace(selection) ) { return false }
		// 4074
	}
	// 2969

	// otherwise, just perform ordinary "required" check.
	// 3923 - a space will pass this test... not what we want
	//else if (!inputObj.value) { return false }
	else if ( IsWhitespace(inputObj.value) )
	{
		return false
	}
	return true
} // end IsProvided



function IsDigitsOnly(inputValue) {
	if (inputValue && inputValue.match(/\D/))
	{
		return false;
	}
	else  { return true }
} // end IsDigitsOnly



function IsCorrectLength(inputValue, lengthRequirements) {
	var result  = lengthRequirements.replace("length=", "");
	var range_or_exact_number = result.match(/[^_]+/);
	var fieldCount = range_or_exact_number[0].split("-");

	// if the user supplied two length fields, make sure the field is within that range
	if (fieldCount.length == 2)
	{
		if (inputValue.length < fieldCount[0] || inputValue.length > fieldCount[1])
		{
			return false;
		}
	}

	// otherwise, check it's EXACTLY the size the user specified 
	else
	{
		if (inputValue.length != fieldCount[0])
		{
			return false;
		}
	}
	return true
} // end IsCorrectLength



function IsValidEmailList(inputValue) {
	// 3543 - more tolerant of list delimiters
	//var regex = /,\s*/;  // non JS validation requires a comma
	var regex = /[,\s\t\n\r]+/  // comma or any kind of whitespace
	// 3543
	var emailArray = inputValue.split(regex);
	for (var index in emailArray) {
		if (!IsValidEmail(emailArray[index])) {
			return false;
		}
	}
	return true
} // end IsValidEmailList



function AreSame(inputValue, inputValue2) {
	if (inputValue != inputValue2)
	{
		return false;
	}				
	else  { return true }
} // end AreSame



function IsInRange(inputValue, rangeRequirements) {
	var result  = rangeRequirements.replace("range=", "");
	var rangeValues = result.split("-");
	
	// if the user supplied two length fields, make sure the field is within that range
	if ((inputValue < Number(rangeValues[0])) || (inputValue > Number(rangeValues[1])))
	{
		return false;
	}
	return true
} // end IsInRange



function IsChecked(inputObj) {
	if (!inputObj.checked)
	{
		return false;
	}
	return true
} // end IsChecked

// 5910
/* OneIsChecked(input_name)
 * Takes the name of an input and checks all inputs of that
 * name to see if any of them are checked.
 */
function OneIsChecked(input_name) {
	var inputs = document.getElementsByName(input_name)
	for ( var index = 0; index < inputs.length; index++ )
	{
		var input = inputs.item(index)
		if ( input.checked ) { return true }
	}
	return false
}

/**************************** MISCELLANEOUS FUNCTIONS ********************************/


// 3637 - queue up all failures
// function alertMessage(form, fieldNames, message)
function HighlightFields(form, fieldNames)
{
	//alert(message)

	// 3923 - put the backgroundColor var declaration
	//        where it is actually used.
	// DAN - It'd probably be better to change the 'offending form field'
	// to a class instead of just a background colour
	var backgroundColor = "#ffffcc"; // the colour of offending form fields 
	// 3923

	for (var i in fieldNames) {
		var field = form[fieldNames[i]]
		if (i == 3) { continue }  // skip the date_flag, which is not a form element
		// if field is an array, it's a radio button. Focus on the first element.
		if (field.type == undefined)  // this gives an error
			// 3923 - focusing can cause problems, and is non-
			//        critical, so let's put in a "try".
			try { field[0].focus(); }
			catch(err) {}
		else
		{
			field.style.background = backgroundColor;
			try { field.focus(); }
			catch(err) {}
		}
	}
	//return false;
}

function DisplayErrors(errorMessages)
{
	if ( errorMessages.length < 1 ) {
		alert("No error messages found.")
	}
	else {
		var output = ""
		var count = 1
		for (var i in errorMessages) {
			
			output += count+". "+errorMessages[i]
			output += "\n"
			count++ 
		}
		alert(output)
	}
}



function checkUncheckAll(form, field)
{
	var numfields = field.length;
	if (form.checkAll.checked)
	{
		for (i = 0; i < numfields; i++)
			field[i].checked = true;
	}
	else
		uncheckAll(field);
}



function uncheckAll(field)
{
	for (i = 0; i < field.length; i++)
		field[i].checked = false ;
}



// 4284 - new functions
/************************ INPUT TYPE CHECKERS ******************************
 * These routines are for checking the type of an input object
 */
function IsRadioButton(inputObj)
{
	if (inputObj.type == "radio") { return true }
	else if (inputObj[0] && inputObj[0].type == "radio") { return true }
	return false
}
function IsSelectInput(inputObj)
{
	if (inputObj.type && inputObj.type.match("select")) { return true }
	if (inputObj.options) { return true }
	return false
}
// 4284
