/* Form Validation. Version 1.1.4 */

var warning_class = "validation_error";
var warning_element_id = "validation_error_message";
var error_msg_element;
var error_msg;
var error_msg_intro = "";
var error_count;
var error_message_position = "appears_before_form";
var cancelled = false;
var form_element;

/* Parses an array of form fields that require validation.
 * Validates them, highlights the invalid ones (if any), 
 * outputs some helpful messages regarding the errors onto the 
 * HTML page calling the function (above the first form field) 
 * if any errors exist.  
 * 
 * @param	array_of_params		a 2D array containing a collection
 * of form fields that require validation, each entry includes an 
 * internal array containing 3 values.  
 * 1st field is the value of the form field attribute name. 
 * 2nd field type of data the form field must contain. 
 * 3rd field indicates if the input field MUST be completed 
 * ("required") or can be left empty "optional".
 *
 * @param	form_element	the form element.
*/
function validate_form(array_of_params, form_element){
	// if not cancelled
	if (cancelled == false){
		this.form_element = form_element;
		error_msg = "";
		error_count = 0;
		//loop the array of function calls 
		for (count=1;count < array_of_params.length; count++){
		    // split the string into its 3 variables
			var input_array = array_of_params[count].split(",",3);
			// format the input variables
			var form_field = Trim(input_array[0]).toLowerCase();
			var type = Trim(input_array[1]).toLowerCase();
			var requirement = Trim(input_array[2]).toLowerCase();
			if (type.match("radio") == "radio"){
				is_radio_selected(form_field);
			}
			else if (type.match("checkbox")){
				is_checked(form_field);
			}
			// if a filled form field should contain a string
			else if (type.match("string") == "string"){
				is_string(form_field);
			}
			// if a filled form field should contain a number
			else if (type.match == "number"){
				is_number(form_field, requirement);
			}
			// if a select form field should contain a postive integer or non-empty string value
			else if (type.match("select") == "select"){
				is_selected(form_field, requirement);
			}
			else if (type.match("email") == "email"){
				is_email(form_field, requirement);
			}	
		}
		
		if (error_count > 0){ 
			if (error_msg_intro != ""){
				error_msg = error_msg_intro + error_msg;
			}
			else if (error_count == 1){
				error_msg = "A mistake was found in the form.  Please correct it then re-submit.<ul>" + error_msg;
			}
			else{
				error_msg = "Some mistakes were found in the form.  Please correct them then re-submit.<ul>" + error_msg;
			}
			create_error_message(form_element);
			return false;
		}
	}
	return true;
}

/* Customise the introductory error message text.
*/
function set_error_message_intro(intro){
	error_msg_intro = intro + "<ul>";
}

/* Used to turn off client side validation when user click 'cancel' submit
 * button. 
 */
function set_submit_cancelled(flag){
	cancelled = flag;
}

function set_error_position(position){
	error_message_position = position;
}

/*	Creates a div with a class attribute set to the value of
 *  warning_element_id, adds an error message if there is one.
 *
 *	@param	form_element	the form to add the div to as the first
 * 							child.
*/
function create_error_message(form_element){
	if (document.getElementById(warning_element_id) == null){
		// Create error message element and class attribute
		error_msg_element = window.document.createElement("div");
		var error_msg_class = window.document.createAttribute("class");
		// IF Make the error message element the first child of the form element
		// ELSE the error message element appear after the form
		if (error_message_position == "appears_before_form"){
			form_element.insertBefore(error_msg_element, form_element.firstChild);
		}
		else{
			insertAfter(error_msg_element, form_element);
		}
		// add the class attribute to the error message element
		error_msg_element.setAttributeNode(error_msg_class);
		// set the name of the element
		error_msg_element.id = warning_element_id;
	}
	else{
		error_msg_element = document.getElementById(warning_element_id);
	}
	// set the error message's value
	error_msg_element.innerHTML = error_msg + "</ul>";	
}

/* Get the innerHTML of the 'for' attribute for the form field's label */
function get_field_name(form_field){
	var label = get_label(form_field);
	if (label != null){
		return label.innerHTML;
	}
	else{
		// IF a label is not found for that is for this field then use the id of the field
		return form_field;
	}
}


/* Returns the label element that has a 'for' attribute that matches the
 * 'name' attribute of the input (form) element. 
*/
function get_label(form_field){
	 // get all the labels for the form
	 var labels = form_element.getElementsByTagName("label");
	 // loop labels in form
	 for (var count=0 ; count<labels.length; count = count+1){
	  	/* The XMLDOM getAttribute() function works with the 'for' attribute
	  	in Firefox as it should, but it doesn't work in IE.  In IE it will
	  	return null whatever the value of the 'for' attribute.*/
	  	
	  	/* This works with FF.  IF the getAttribute is not null and the
	  	value is equal that of the form_field we're searching for then return
	  	its innerHTML*/
	  	if (labels[count].getAttribute('for') != null && labels[count].getAttribute('for').indexOf(form_field) != -1){
			return labels[count];
		}
		/* This works with IE 6.  IF the 'for' attrbute is null and the
	  	value of the 79th attribute (which is 'for' in IE) of the 'label' 
	  	element matches the form_field we're searching for then return
	  	its innerHTML*/
	  	else if (labels[count].getAttribute('for') == null && labels[count].attributes.item(79).nodeName == 'for'){
	  		if (labels[count].attributes.item(79).nodeValue.indexOf(form_field) != -1){
				return labels[count];
			}
		}
		/* Same as the previous conditional but will work if Microsoft change
		the value of the 79th attribute to an attribute that isn't 'for'*/
		else if (labels[count].getAttribute('for') == null){
			for (var attribute_count=0; attribute_count < labels[count].attributes.length; attribute_count++) {
				if (labels[count].attributes.item(attribute_count).nodeName == 'for') {
					if (labels[count].attributes.item(attribute_count).nodeValue.indexOf(form_field) != -1){
						return labels[count];
					}
				}
			}
		}
	}
	return null;
}


/*	Establishes if the user has selected a radio button out of the group of
 *  radio buttons.  The button group is uniquely identified by the field_name
 *  and form_element combination.
*/
function is_radio_selected(field_name){
	var selected = false;
	
	var inputs = form_element.getElementsByTagName("input");
	// loop the radio buttons whose "attribute" matches the field_name
	for(var count=0; count < inputs.length && selected == false; count = count + 1){
		// if we find an input element with the field_name then it is a radio button
		// that we are looking for
		if (inputs[count].getAttribute('name').indexOf(field_name) != -1){
			// true if button is selected (and thus ends loop)
			selected = evaluate_radio_button(field_name, count);
		}
	}
	
	// if no buttons were selected in the group
	if(selected == false) {
		set_validation_error(field_name);
		error_msg = error_msg + "<li>Please select a radio button answer in the field called: " + get_field_name(field_name) + "</li>";
	}
	else{
		set_validated(field_name);	
	}
}

/*	Return true if the radio button is selected, else false
*/
function evaluate_radio_button(field_name, button_number){
	if (form_element.elements[field_name][button_number].checked == true){
		set_validated(field_name);
		return true;
	}
	else{
		return false;
	}
}

/* Sets the error message using the text from the form element's alt 
 * attribute if it exists and isn't an empty string, otherwise it will look
 * to use the title attribute.  If neither exist then it will produce a generic message 
 * using the elements label innerHTML
 *	
 * @param	@field_name 	name of the of the form element
 */
function set_error_message(field_name){
	if (form_element.elements[field_name].alt != null && form_element.elements[field_name].alt != ""){
		error_msg = error_msg + "<li>" + form_element.elements[field_name].alt + "</li>";
	}
	else if(form_element.elements[field_name].title != null && form_element.elements[field_name].title != ""){
		error_msg = error_msg + "<li>" + form_element.elements[field_name].title + "</li>";
	}
	else{
		error_msg = error_msg + "<li>Please complete the field called " + get_field_name(field_name) + ".</li>";
	}
}


/**  Evaluate whether a form field contains a non-empty value. 
 *	@param	field_name	the input field to validate for a string.
 *
*/
function is_string(field_name){
	// if it is empty then highlight in form
	if (form_element.elements[field_name].value == ""){
		set_validation_error(field_name);
		set_error_message(field_name);
	}
	else {
		set_validated(field_name);
	}
}

/**  Evaluate whether a checkbox has been checked. 
 *	@param	field_name	the name of the input checkbox element.
 *
*/
function is_checked(field_name){
	if(form_element.elements[field_name].checked != true){
		set_validation_error(field_name);
		set_error_message(field_name);
	}
	else{
		set_validated(field_name);
	}
}

/*  Evaluate if a form field contains a number.
 *  @param  field_name	 	name of the input field to valid for a number.
 *  @param	requirement	 	indicates if the the input field can
 * 						 	be left empty by the user or not by the user.
 */
function is_number(field_name, requirement){
	//establish if the field contains a number 
	var is_number = !isNaN(form_element.elements[field_name].value);
	if (form_element.elements[field_name].value == ""){
		is_number = false;
	}

	// IF the field must contain a number (but doesn't)
	if (requirement == "required" && is_number == false && form_element.elements[field_name].value == ""){
		 set_validation_error(field_name);
		 error_msg = error_msg + "<li>Please enter a numeric value into the box called " + get_field_name(field_name) + ".</li>";
	}
	// ELSE IF it is not a number and can be blank, but isn't either
	else if (requirement == "optional" && !isNaN(form_element.elements[field_name].value)){
		set_validation_error(field_name);
		error_msg = error_msg + "<li>Please enter a numeric value or leave the field called " + get_field_name(field_name) + " empty.</li>";
	}
	else {
		if (requirement == "required"){
			set_validated(field_name);
		}
	}
}

/*  Evaluate if a option is selected in a select box. An option with value of non-positive integer or [emptystring] means that nothing is selected.
 *  @param  field_name	 	name of the input field to valid for a number.
 */
function is_selected(field_name){
	var selected_option_value = form_element.elements[field_name].options[form_element.elements[field_name].selectedIndex].value;
	//establish if the field a postive int or a string value
	var is_answered = (isInt(selected_option_value) && selected_option_value > 0) && !form_element.elements[field_name].value == "";

	// IF the field must contain a number (but doesn't)
	if (!is_answered){
		 set_validation_error(field_name);
		 error_msg = error_msg + "<li>Please choose an option from the select box called " + get_field_name(field_name) + ".</li>";
	}
	else {
		set_validated(field_name);
	}
}

/*  Evaluate if a form field contains a valid email address.
 *  @param	field_name	 name of the input field to valid for an email address.
 *  @param	requirement	 indicates if the the input field can be left empty 
 *						 by the user or not by the user.
 */
function is_email(field_name, requirement){
	var is_email;
	
	if (form_element.elements[field_name].value.indexOf("@") == -1)
	is_email = false;
	
	var emailFilter = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i;
	is_email = emailFilter.test(Trim(form_element.elements[field_name].value));
	
	//IF the field is empty
	if(form_element.elements[field_name].value == ""){
		set_validation_error(field_name);
		set_error_message(field_name);
	}
	//ELSE IF it doesn't contain an email address and it cannot be blank, 
	//but it is
	else if(is_email == false && requirement == "required"){
		set_validation_error(field_name);
		error_msg = error_msg + "<li>Please enter a valid email address into the field called " + get_field_name(field_name) + ".</li>";
	}
	// ELSE IF it can be blank or contain an email address, but it contains something
	// that isn't an email address
	else if (requirement == "optional" && form_element.elements[field_name].value != "" && is_email == false){
		set_validation_error(field_name);
		error_msg = error_msg + "<li>Please enter a valid email address or leave the field called " + get_field_name(field_name) + " empty.</li>" ;
	}
	else{
		set_validated(field_name);
	}
}



/*  Switches the class of the input field (field_name) so CSS 
 *  can be applied to its label to highlight that the field in invalid.
 *  @param	field_name	 the form input whose class is to change.
 */
function set_validation_error(field_name){
	var label = get_label(field_name);
	error_count = error_count + 1;
	
	if (label != null){
		// IF the warning class doesn't already exist in the class attribute then
		// add it
		if (label.className.match(warning_class) == null){
			label.className = warning_class + " " + label.className;
		}
		
		if (error_count == 1){
			// if the form field is a radio or checkbox then focus
			// on its first element
			if (form_element.elements[field_name][1] != null){
				form_element.elements[field_name][0].focus();
			}
			// else focus on the element (as the id is the input parameter)
			else {
				form_element.elements[field_name].focus();
			}
		}
	}
	// else there is no label for the form element
}

/*  Set the class of the form field to indicate that it's content is valid.
 */
function set_validated(field_name){
	var label = get_label(field_name);
	if (label != null){
		label.className = label.className.replace(warning_class,"");
	}
	// else there is no label for the form element
}


function insertAfter(newElement,targetElement) {
	var parent = targetElement.parentNode;
	//if the parents lastchild is the targetElement...
	if(parent.lastchild == targetElement) {
		//add the newElement after the target element.
		parent.appendChild(newElement);
	} 
	else {
		// else the target has siblings, insert the new element between the target and it's next sibling.
	parent.insertBefore(newElement, targetElement.nextSibling);
	}
}

/* Determines if a given parameter is an integer */
function isInt(x) {
   var y=parseInt(x);
   if (isNaN(y)) return false;
   return x==y && x.toString()==y.toString();
 } 
