/**************************************************************************
Script: 	form-validate.js 
Version: 	1 
Date: 		31 July 2003
Contact: 	Tim Snadden snaddent@maf.govt.nz
Purpose:	Performs client-side validation on data from web forms and alerts
					user to any errors.
Usage: 		In <head> of document:
						<script src="/scripts/form-validate.js" type="text/javascript">
						</script>
					
					In <body> of document:
						<form ... onsubmit="return verify(this)">...
					
					Alternatively, if page specific javascript is needed as well - 
					
					In <head> of document:
						<script src="/scripts/form-validate.js" type="text/javascript">
						</script>
						<script type="text/javascript">
						<!--
							function checkForm(f) {
								//page specific javascript goes here
								//(before verify function)
								return verify(f);
							}
						//-->
						</script>
					
					In <body> of document:
						<form ... onsubmit="return checkForm(this)">...

***************************************************************************/

/**************************************************************************
Function:	verify
Purpose:	Goes through form inputs and checks contents. If data is missing
					or in the wrong format returns 'false' and alerts user. Otherwise
					returns true.
Argument:	Name of form object to be checked
Comment:	-	fields beginning with 'r_' must have a value (or be checked)
					-	fields without a value are sent to findLabelTextFor() to get a
						more user friendly name
					- fields with 'email' in name are sent to validEmail for syntax check
***************************************************************************/
function verify(f){
	var msg;
	var empty_fields = '';
	var errors = '';
	for(var i = 0; i < f.length; i++) {
		var e = f.elements[i];
		//only check fields which are required
		//indicated by 'r_' at beginning of name
		if (e.type == 'radio' && (e.name.substring(0,2)=='r_')) {
			if(!radioChecked(e)) {	
				//can't use findLabelTextFor as it will find label
			  //for first button rather than group.	
				empty_fields += "\n          " +  niceName(e);
			}		
			//increment i so as to not do the same test on a group
			//of radio buttons with the same name.
			while(e.name == f.elements[i+1].name) i++;
			continue;
		}

		if ((e.type == 'checkbox') && (e.name.substring(0,2)=='r_') && 
					(!e.checked)) {
			empty_fields += "\n          " +  findLabelTextFor(e);
			continue;
		}	
	
		if ((e.type == 'text' || e.type == 'textarea') && 
						(e.name.substring(0,2)=='r_')) {			
			if ((e.value == null) || (e.value == "") || isBlank(e.value)) {
				empty_fields += "\n          " + findLabelTextFor(e);
				continue;
			}			
		}

		if(e.type == 'text' && e.name.indexOf('email') != -1){		  
			if(!isBlank(e.value) && !validEmail(e.value)) {
				errors += "- '" + e.value + "'" + " is not a valid email address.\n";
			}
		}
		
		if((e.type == 'select-one') && (e.name.substring(0,2)=='r_')) {
			if ((e.options[e.selectedIndex].value == null) || 
						(e.options[e.selectedIndex].value == "") || 
						isBlank(e.options[e.selectedIndex].value)) {
				empty_fields += "\n          " + findLabelTextFor(e);
				continue;
			}
		}
	}
		
	// If there weren't any errors or empty fields, return true. Otherwise 
	// display the messages, and return false to prevent the form from being 
	// submitted.
	if (!empty_fields && !errors) return true;
	
	msg  = "______________________________________________________\n\n"
	msg += "The form was not submitted because of the following error(s).\n";
	msg += "Please correct these error(s) and re-submit.\n";
	msg += "______________________________________________________\n\n"
	
	if (empty_fields) {
		msg += "- The following required field(s) are empty:" 
		+ empty_fields + "\n";
		if (errors) msg += "\n";
	}
	msg += errors;
	alert(msg);
	return false;
}

/**************************************************************************
Function: radioChecked
Purpose:	Checks if one of a group of radio buttons (same name) has been 
					selected and returns true or false.
Argument: ID of one of the radio buttons (NOT name).
Comment:	Browsers that don't support getElementsByName get 'true' returned
					no matter what.
***************************************************************************/

function radioChecked(r) {
	if(!document.getElementsByName)	return true;
	var choices = document.getElementsByName(r.name);
	for(var i = 0; i < choices.length; i++){
		if(choices[i].checked) return true;
	}
	return false;
}


/**************************************************************************
Function:	findLabelFor
Purpose:	Takes an element and returns it's related label. e.g.
					<label for="example">text</label><input id="example" ....>
					findLabelFor(formObject.example) returns the label above.					
Usage:		Called by findLabelTextFor. 
Comment:	Uses w3c DOM methods so not compatible with older browsers.					
***************************************************************************/
function findLabelFor (elOrId) {
	var el = typeof elOrId == 'string' ? document.getElementById(elOrId) : elOrId;	
	var labels = document.getElementsByTagName('label');
	var found = false;
	for (var l = 0; l < labels.length; l++)
		if (found = el.id == labels[l].htmlFor)
			break;
		if (found)
			return labels[l];
		else
			return null;
}

/**************************************************************************
Function:	findLabelTextFor
Purpose:	Takes an element and returns it's related label text. e.g.
					<label for="example">text</label><input id="example" ....>
					findLabelTextfor(formObject.example) returns "text".
Usage:		Called by verify function to find name to refer to required fields
					that haven't been filled out.
Comment:	Uses w3c DOM methods. Older browsers instead use niceName function.
					Also uses niceName if no related label exists.
					Chops off '*' (required indicator) from returned text.
					Currently can't handle labels containing child html elements.
***************************************************************************/
function findLabelTextFor (elOrId) {
	if(document.getElementById){
		if(findLabelFor(elOrId)) {
			var returnText = findLabelFor(elOrId).firstChild.nodeValue;	
			returnText = stringReplace(returnText, '*', '');
			return returnText;
		} else {
		return niceName(elOrId);
		}
	} else {
		return niceName(elOrId);
	}
}

/**************************************************************************
Function: niceName
Purpose: 	Make element names more human readable.
					Takes element name drops 'r_' if there (flag for required
					fields) or '_', capitalises it and replaces underscores and hyphens 
					with spaces.
Usage:		Called by findLabelTextFor if browser can't handle w3c DOM methods.
					Called by verify function for radio buttons as each button has its 
					own label so findLabelTextFor is not appropriate.
***************************************************************************/
function niceName(n) {
	var tmp = n.name;
	if(tmp.substring(0,1)=='_') tmp = tmp.substring(1,tmp.length);
	if(tmp.substring(0,2)=='r_') tmp = tmp.substring(2,tmp.length);
	tmp = stringReplace(tmp, '_', ' ');
	tmp = stringReplace(tmp, '-', ' ');
	var first = tmp.substring(0,1);
	var rest = tmp.substring(1,tmp.length);
	tmp = first.toUpperCase() + rest;
	return tmp;
}

/**************************************************************************
Function:		stringReplace
Purpose:		Generic search and replace function.
Usage:			Called by niceName.
Arguments:	originalString - string to work on
						findText - what to look for in the string
						replaceText - what to replace it with
Comment:		Doesn't use regexps in order to remain backwards compatible.
***************************************************************************/
function stringReplace(originalString, findText, replaceText) {
	var pos = 0;
	var len = findText.length;
	pos = originalString.indexOf(findText);
	while(pos != -1) {
		preString = originalString.substring(0, pos);
		postString = originalString.substring(pos + len, originalString.length);
		originalString = preString + replaceText + postString;
		pos = originalString.indexOf(findText);		
	}
	return originalString;
}

/**************************************************************************
Function:	validEmail
Purpose:	Check a string to see it it conforms to email address syntax and
					returns 'true' or 'false'.
Usage:		Called by verify function if input name contains 'email'.
Comment:	Not intended to be exhaustive. Picks up most basic errors, including -
					- must have an '@' and must be at least 2nd character
					- can't have more than one '@'
					- must have at least one period after '@' but not directly after
					- can't end with a period
					- must be at least two characters after final period.
***************************************************************************/
function validEmail(e) { 
	if ((e == null) || (e == '') || isBlank(e)) return false;
	// remove leading whitespace
	var i = 0;
	while (e.charAt(i) == ' ') i++;
	e = e.substr(i,e.length - i);
	// remove trailing  whitespace
	i = e.length-1;
	while (e.charAt(i) == ' ') i--;
	e = e.substr(0,i+1);
	//check for invalid characters
	// var invalidChars = ' ()<>,;:\/"'; 
	// for (var i=0;i<invalidChars.length;i++) { 
		// var badChar = invalidChars.charAt(i);
		// if (e.indexOf(badChar,0) != -1) return false;
	// } 
	var atPos = e.indexOf('@',1);
	if (atPos == -1) return false;
	if (e.indexOf('@',atPos+1) != -1) return false;
	var periodPos = e.indexOf('.',atPos);
	if (periodPos == -1) return false;
	if (periodPos == atPos + 1) return false;	
	var lastPeriodPos = e.lastIndexOf('.');
	if (lastPeriodPos == e.length - 1) return false;
	if (lastPeriodPos+2 == e.length) return false; 
	return true;
}
/**************************************************************************
Function:	confirmEmail
Purpose:	confirm email address to see whether user type a wrong email address and
					returns 'true' or 'false'.
Arguments:		email address and confirmation of email address
Usage:	confirm(formObject.email, formObject.email1)
		Not integrated into verify function. Could be called from within HTML page
***************************************************************************/
function confirmEmail(e,e1){
if (e.value!=e1.value){
alert("Please ensure the email addresses are the same");
e.focus();
e1.value="";
return false;
}
return true;
}

/**************************************************************************
Function:		atLeastOne
Purpose:		Returns true if at least one of a list of form inputs has been 
						filled	out. 
Arguments:	Any number of form element IDs.
Usage:			atLeastOne(formObject.inputId_1,formObject.inputId_2 ... etc)
						Not integrated into verify function. Could be called from within
						HTML page.
***************************************************************************/
function atLeastOne() {
	var tmp = 0;
	for(var i = 0; i < arguments.length; i++) {
		var arg = arguments[i];
		var j = arg.value;
		if (arg.type == 'select-one') j = arg.options[arg.selectedIndex].value;
		if (arg.type == 'checkbox' && !arg.checked) j = null;
		if (arg.type == 'radio' && !radioChecked(arg)) j = null;
		// increment tmp if input has no value or is unchecked
		if ((j == null) || (j == "") || isBlank(j)) tmp++;
	}
	// if tmp hasn't been incremented for each argument at least one of the 
	// inputs must have a value or have been checked. 	
	if(tmp < arguments.length) return true;
	return false;
}

/**************************************************************************
Function:	isBlank
Purpose:	A utility function that returns true if a string contains only 
					whitespace characters or is null.
***************************************************************************/
function isBlank(s){
	for(var i = 0; i < s.length; i++) {
		var c = s.charAt(i);
		if ((c != ' ') && (c != '\n') && (c != '\t')) return false;
	}
	return true;
}
