

// create an instance of the Validator object
var validator = new Validator();

/* -----------------------------------------------------------------------------
 * Returns a label element with the supplied for attribute.
 */
function GetLabel(id) {
	var labels = document.getElementsByTagName("label");

	for(var i = 0; i < labels.length; i++) {
		// 'htmlFor' as 'for' is a JS reserved word
		// there are also some browser incompatibilites when using .getAttribute("htmlFor")
		// in that Firefox returns null
		if(labels[i].htmlFor && labels[i].htmlFor == id) return labels[i];
	}
	return null;
}


/* -----------------------------------------------------------------------------
 * validate data in the form based on the custom required, format, and rule attributes
 *     required - [boolean]  whether the field must be filled in or not
 *     format   - [RegExp]   either the case insensitive key for one of the existing 
 *                           Format regular expressions, or a regular expression 
 *                           (excluding starting and ending /)
 *     rule     - [Function] function body code to use as a rule for the data. This 
 *                           *must* return a boolean value
 */
function ValidateForm(fm) {
	// required features aren't supported so rely on the server
	if(!fm.getAttribute) return false;

	validator.ClearAll();

	// loop through all the elements in the form
	for(var i = 0; i < fm.elements.length; i++) {
		var self = fm.elements[i];

		// ignore all buttons
		if(self.type.toLowerCase() == "submit" || self.type.toLowerCase() == "reset" || self.type.toLowerCase() == "button") continue;

		// *deep breath* - this is actually 2 compound if statements which assign Formats[attribute], a regular expression from the formats attribute or null to the variable
		var format   = (self.getAttribute("format") ? (Formats[self.getAttribute("format").toUpperCase()] ? Formats[self.getAttribute("format").toUpperCase()] : new RegExp(self.getAttribute("format"))) : null);
		var required = (self.getAttribute("required") && self.getAttribute("required").toLowerCase() == "true");
		var func     = null;
		var rule     = self.getAttribute("rule");

		// if we have a rule create the function
		if(rule) {
			// 'this' won't refer to the form field object it will refer to the scope the function is created in, in this case 'this' will refer
			// to the scope of the current function i.e. window. So we replace all occurences of 'this' with 'self' as that refers to the form
			// field we're creating the code from, as created in the scope of the current function
			if(rule.indexOf("this") != -1) rule = rule.replace(/\bthis\b/g, "self");

			// the value argument is supplied by the validator.Validate method and is basically the current value of the form field
			func = function(value) {
				// horrible use of eval but using the new Function syntax we immediately lose the scope we're building the function in.
				// new Function basically just eval's the code anyway...
				return eval(rule);
			};
		}

		var result = validator.Validate(self.value, validator.Format(self.name, required, format, null, func));

		// basically if the grand-pappy node of the form field is a <tr> set its class to "error"
		// failing that, if there's a label set its class to "error"
		// a handy way of doing it as I *never* put classes on <tr>'s or <label>'s
		// realistically it should add a new class to the className list and remove it if necessary
		if(self.parentNode && self.parentNode.parentNode && self.parentNode.parentNode.nodeName.toLowerCase() == "tr") {
			var parent = self.parentNode.parentNode;

			result ? RemoveClassName(parent, "error") : AddClassName(parent, "error");
		}
		else if(self.getAttribute("id")) {
			var label = GetLabel(self.getAttribute("id"));

			if(label) result ? RemoveClassName(label, "error") : AddClassName(label, "error");
		}
	}

	// if there's an error build the error message and alert it
	if(validator.HasError()) {
		var msg = "There has been an error submitting your form. Please review the following information:\n\n";

		for(var i = 1; i <= 8; i = i * 2) {
			if(validator.HasError(i)) msg += "\t" + ErrorCode[i] + "\n";
		}

		alert(msg);
		return false;
	}

	return true;
}


/* -----------------------------------------------------------------------------
 * Clears the values in a form's fields, ignoring buttons, submits, resets and 
 * radio buttons.
 */
function ClearForm(fm) {
	for(var i = 0; i < fm.elements.length; i++) {
		var field = fm.elements[i];

		switch(field.type) {
			case "select-one":
			case "select-multiple": field.selectedIndex = 0;
				break;
			case "checkbox": field.checked = false;
				break;
			case "textarea":
			case "text": field.value = "";
				break;
		}
	}
}


/* -----------------------------------------------------------------------------
 * Adds the supplied css class name to the supplied object, if it doesn't have it.
 */
function AddClassName(o, css) {
	o.className = (o.className.indexOf(css) == -1) ? (o.className == "") ? css : o.className + " " + css : o.className;
}


/* -----------------------------------------------------------------------------
 * Removes the supplied css class name from the supplied object.
 */
function RemoveClassName(o, css) {
	o.className = o.className.replace(css, '');
}
