/********************
 Form Validator v0.2
 1 January 2007
 
 Jeremy Fee
 jeremyfee@gmail.com
*********************/

//addEvent method from John Resig
// http://ejohn.org/projects/flexible-javascript-events/
function addEvent( obj, type, fn )
{
	if (obj.addEventListener)
		obj.addEventListener( type, fn, false );
	else if (obj.attachEvent)
	{
		obj["e"+type+fn] = fn;
		obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
		obj.attachEvent( "on"+type, obj[type+fn] );
	}
}

//merge two arrays
array_merge = function (a1, a2) {
	var r = new Array();
	
	for (var i = 0; i < a1.length; i++) { r[r.length] = a1[i]; }
	for (var i = 0; i < a2.length; i++) { r[r.length] = a2[i]; }
	
	return (r);
}

//remove leading and trailing whitespace
trim = function (s) { return (s.replace(/^\s+|\s+$/, '')); }

//cross browser hasAttribute() function, added by SAH 02/07/07
function containsAttribute(elem, attr) {
  if (elem.hasAttribute) { //DOM-compliant browsers
    return elem.hasAttribute(attr);
  } else { //MSIE sucks
    var lc_attr = attr.toLowerCase(); //must be lowercase for IE
    return typeof elem.attributes[lc_attr] != "undefined";
  }
}

//get all form elements that need validation
get_form_validate_elements = function (f) {
	var labels = f.getElementsByTagName('LABEL');
	var r = Array();
	
	//only labels that have a rel attribute beginning with 'validate'
	for (i = 0; i < labels.length; i++) {
		if ( containsAttribute(labels[i], 'REL') &&
			  labels[i].getAttribute('REL').match(/^validate/) ) {
			r[r.length] = labels[i];
		}
	}
	
	return (r);
}

//parse the rel attribute of form elements
//rel="validate;message=This element is required;required=yes;pattern=regex"
get_element_parameters = function (el) {
	var rel = el.getAttribute('REL');
	var args = rel.split(';');
	
	//default values
	var message = el.getAttribute('for') + " is invalid\n";
	var required = false;
	var pattern = "";
	var patternmessage = "";
	
	for(var i = 0; i < args.length; i++) {
		var arg = args[i];
		if (/=/.exec(arg)) {
			arg = arg.split('=');
			arg[0] = trim(arg[0]);
			arg[1] = trim(arg[1]);
			
			if (arg[0] == 'message') { message = arg[1] + "\n"; }
			else if (arg[0] == 'required') { required = (arg[1] == 'yes'); }
			else if (arg[0] == 'pattern') { pattern = arg[1]; }
			else if (arg[0] == 'patternmessage') { patternmessage = arg[1] + "\n"; }
		}
	}
	
	return ( {	'message'		:	message, 
			    'required'		:	required, 
				'pattern'		:	pattern,
				'patternmessage':	patternmessage	});
}

//find the corresponding form element, has to check name attribute in case it is a radio/checkbox group...
get_label_field = function (label) {
	var f = label;
	while (f && f.nodeName != 'FORM') { f = f.parentNode; }
	
	if (f) {
		var name = label.getAttribute('FOR');
		if (!name) {
		  var name = label.attributes["for"].value; //added by SAH for IE compatibility
		}
		return(f[name]);
	} else { return (null); }
}

//creates function to validate, and sets or clears label's class
create_element_validate_function = function (label) {
	var args = get_element_parameters(label);
	var el = get_label_field(label);
	
	var r = function () {
		error = '';
		
		if (el) {	
			if (args.required) {
				var hasValue = false;
				//radio groups and checkboxes
				if (typeof(el.length) != 'undefined' && el.nodeName != 'SELECT') {
					for (i = 0; !hasValue && i < el.length; i++) {
						if (el[i].checked) { hasValue = true; }
					}
				} else if (el.value.length != 0) { 
					hasValue = true;

					//watch for single checkbox
					if (typeof(el.getAttribute) != 'undefined' && el.getAttribute('type') == 'checkbox' && !el.checked) {
						hasValue = false;
					}
				}
				
				if (!hasValue) { error = args.message; }
			}
			
			//only validate regex if there is a value, should have req'd attribute if can't be empty
			if (typeof(el.length) == 'undefined' &&
				args.pattern != '' &&
				el.value.length > 0) {
				
				var regexp = new RegExp(args.pattern);
				if (!regexp.exec(el.value)) {
					if (args.patternmessage != '') 
						error = args.patternmessage; 
					else 
						error = args.message; 
				}
			}
		}
		
		if (error != '') { label.className = 'error'; }
		else { label.className = 'ok'; } //added by SAH, 01/26/07
		
		return (error);
	}
	
	label.validate = r;
}

//sets up validation function for each element that has a rel="validate... 
create_form_validate_function = function (f) {
	var labels = get_form_validate_elements(f);
	for (var i = 0; i < labels.length; i++) {
		create_element_validate_function(labels[i]);
	}
	
	//if there are errors, alert the user and cancel form submission
	var r = function (e) {
		//check for errors
		var error = '';
		for (var i = 0; i < labels.length; i++) {
			if (labels[i].validate) { error += labels[i].validate(); }
		}
		
		//if there are errors, keep the form from submitting
		if (error != '') {

			//added 02-07-07, SAH: scroll user to top of form on error
			var startOfForm = document.getElementById('startOfForm');
			if (startOfForm) {
			  location.hash = "#startOfForm";
			}

			alert(error);
			if (e && e.preventDefault) { e.preventDefault(); }
			if (!e) var e = window.event;
			e.returnValue = false; //added by SAH for IE compatibility
			return (false);
		}
		return (true);
	}
	
	return (r);
}

//goes through all forms and assigns validation functions
setup_form_validation = function () {
	if (!document.getElementsByTagName) return;
	
	var forms = document.getElementsByTagName('FORM');
	for (var i = 0; i < forms.length; i++) {
		addEvent(forms[i],
				 "submit",
				 create_form_validate_function(forms[i]));
	}
}

//run script automatically
addEvent(window, "load", setup_form_validation);
