function createMethodReference(object, methodName) {
	// From http://www.brockman.se/writing/method-references.html.utf8
	return function () {
		object[methodName](object);
	}
}

function yValidate() {
	// This function creates the JavaScript version of the yValidate.php object
	// This is the list of fields and the validations to apply (see 
	// $validations)
	this.validations = [];
	// This is the list of Name:Regular Expression combinations (see 
	//$regValidations)
	this.regValidations = [];
	// When the page has loaded, attach the yValidate validation hooks
	addLoadEvent(createMethodReference(this,'attachHandlers'));
}

yValidate.prototype.addBaseValidation = function(sName, sExp) {
	// Attach the regular expression for a validation to a name
	this.regValidations[sName] = sExp;
}

yValidate.prototype.addValidation = function(sID, sType, sMsgID, sMessage) {
	// Attach a validation type to an ID in an HTML form.
	// sID is the HTML ID of the element
	// sType is the type of validation to attach
	// sMsgID is the ID of the element to display failure messages
	// sMessage is the text to put as the failure message
	this.validations[sID] = {"Type":sType, "MsgID":sMsgID, "Message":sMessage};
}

yValidate.prototype.attachHandlers = function (me) {
	// This function uses the validations array to attach field validation
	// events to onchange, and attaching full-form validation to submitting.
	if (document.getElementById) {
		var forms = document.forms;
		for (var i=0;i<forms.length;i++) {
			var form = forms[i];
			form.onsubmit = function(){return me.validateForm(me,this);} // attach validate() to the form
		}
		for (var j in me.validations) {
			var x = document.getElementById(j);
			if (x && x.type != 'checkbox' && x.type != 'radio') {
				x.onchange = function(){return me.validate(this,'field');}
			} else if (document.getElementsByName(j)) {
				// checkboxes and radio
				var all = document.getElementsByName(j);
				for (k=0;k<all.length;k++) {
					document.getElementById(all[k].id).onclick = function() {return me.validate(this,'field');}
				}
			}
		}
		return true;
	} else {
		return false;
	}
}

yValidate.prototype.setFeedback = function(ok,feedback,sFeedbackMsg) {
	// Sets the feedback appearance and text depending on validation.
	// If OK, we get the validateOK class, if broken we get the validateBAD
	// class and a message
		if (ok) {
			feedback.className = 'validateOK';
			feedback.innerHTML = '&nbsp;';
		} else {
			feedback.className = 'validateBAD';
			feedback.innerHTML = sFeedbackMsg;
		}
}

yValidate.prototype.validate = function(objInput,from) {
	// This validates the passed field.
	sVal = objInput.value; 
	// The JavaScript version of the validations array is indexed by the
	// field name for quick reference, but doesn't need the name inside the
	// array.
	sTypeCheck = this.validations[objInput.name].Type; //[0];
	sFeedbackLoc = this.validations[objInput.name].MsgID;
	sFeedbackMsg = this.validations[objInput.name].Message;
	myform = objInput.form;
	ok=true;
	feedback = document.getElementById(sFeedbackLoc);

	// See the PHP code for the logic behind these calls.
	if (objInput.type=='checkbox') {
		// Checkboxes can handle required and counts.
		var ccount = 0;
		for (var i=0;i<myform.length;i++) {
			if (myform[i].name == objInput.name && myform[i].checked) {
				ccount++;
			}
		}
		if (sTypeCheck.indexOf('+') == 0 && ccount==0) {
			ok = false;
		} else if (/^\+?\d*-\d*$/.test(sTypeCheck)) {
			var numList = sTypeCheck.split('-');
			if (numList[0]>0) {
				ok = ok && (ccount==0 || ccount>=numList[0]);
			}
			if (numList[1]>0) {
				ok = ok && (ccount==0 || ccount<=numList[1]);
			}
			if (numList[0][0]=='+') {
				ok = ok && (ccount>0);
			}
		} else if (/^\+?\d+$/.test(sTypeCheck)) {
			var number = parseInt(sTypeCheck,10);
			ok = ok && (ccount == number);
		}
		yValidate.prototype.setFeedback(ok,feedback,sFeedbackMsg);	
	} else if (objInput.type=='radio') {
		// Radios can only be required
		if (sTypeCheck == '+') {
			ok = false;
			for (var i=0;i<myform.length;i++) {
				if (myform[i].name == objInput.name && myform[i].checked) {
					ok = true;
				}
			}
		yValidate.prototype.setFeedback(ok,feedback,sFeedbackMsg);	
		}
	} else {
		if (sTypeCheck.substr(0,1)=='+') {
			ok = !(sVal.length==0);
			sTypeCheck = sTypeCheck.substring(1);
		}
		if (ok && this.regValidations[sTypeCheck]>'') {
			var ok = sVal.match(this.regValidations[sTypeCheck]);	
		} else {
			// If you need more than just a regexp to validate, put it here.
			// Just be sure to set 'ok' to whether the field's OK or not.
		}
		yValidate.prototype.setFeedback(ok,feedback,sFeedbackMsg);
	}
	if (from=="submit") {
		return(ok);
	}
	return(true);
}

yValidate.prototype.validateForm = function(me,form) {
	// This validates a submit, working through the form and finding any 
	// element with an onchange function - signifies a validation attachment
	// Radio, checkbox and select get onclick rather than onchange
	var ok = true;
	for (var l=0;l<form.elements.length;l++) {
		var element = form.elements[l];
		if (element.onchange && typeof element.onchange == 'function') {
			ok2 = me.validate(element,'submit');
			ok = (ok && ok2);
		} else if (element.onclick && typeof element.onclick == 'function') {
			ok2 = me.validate(element,'submit');
			ok = (ok && ok2);
		}
	}
	if (ok) 
		return ok;
	alert('Whoops!  There are some submissions that don\'t make sense to us, please go back and change the ones indicated.');
	return false;
}

function addLoadEvent(func) {
	// Standard onLoadEvent logic
	var oldonload = window.onload;
	if (typeof window.onload != 'function') {
		window.onload = func;
	} else {
		window.onload = function() {
			oldonload();
			func();
		}
	}
}