(function(hasClass, addClass, removeClass) {

var extSetupMethods = {};
var extValidationMethods = {};
var validationMethods = {};

var handleExtendedSetup = function(element) {
	var classes = (element.className || '').split(' ');
	for (var i = 0; i < classes.length; i++) {
		var method = (classes[i].indexOf(':') > 0) ? classes[i].substr(0, classes[i].indexOf(':')) : classes[i];
		var f = extSetupMethods[method];
		if (f)
			f.apply(element, extendedClassArguments(classes[i]));
	}
}
var markRequired = function(inputObj) {
	if (!inputObj.className || (!hasClass(inputObj, 'required') && !hasClass(inputObj, 'halfRequired')))
		return;
	var n = getLabelNode(inputObj);
	var a = n && n.getElementsByTagName('span');
	if (a && a.length <= 0) {
		var span = document.createElement('span');
		span.className = 'required';
		span.appendChild(document.createTextNode('*'));
		n.appendChild(span);
	}
}

var disableAutoComplete = function(inputObj) {
	if (!inputObj.getAttribute('autocomplete'))
		inputObj.setAttribute('autocomplete', hasClass(inputObj, 'commonstring') ? 'on' : 'off');
}

var useCal = function(inputObj, anchorName) {
	var calObj = new CalendarPopup("formCalendar");
	// Set css prefix
	calObj.setCssPrefix("STD");
	// Disable appropriate dates
	if (hasClass(inputObj, 'futuredate')) {
		calObj.addDisabledDates(null, new Date());
	} else if (hasClass(inputObj, 'pastdate')) {
		calObj.addDisabledDates(new Date(), null);
	} else if (hasClass(inputObj, 'birthdate')) {
		calObj.addDisabledDates(new Date(), null);
		calObj.showYearNavigationInput();
	}
	// Make monday the first day in each row
	calObj.setWeekStartDay(1);
	// Navigation by year
	calObj.showYearNavigation();
	// Align calendar div below inputObj :)
	calObj.exactX = calculateOffsetLeft(inputObj) - 50;
	calObj.exactY = calculateOffsetBottom(inputObj) + 5;

//	calObj.select(inputObj,anchorName,'MM/dd/yyyy');
	calObj.select(inputObj,anchorName,'d NNN yyyy');
}

var addTimeSelector = function(inputObj) {
	if (!hasClass(inputObj, 'time'))
		return;

	var currentHour = -1;
	var currentMinute = -1
	var currentAMPM = '';

	if (inputObj.value && inputObj.value.match(/^([0-9][0-9]?):([0-9][0-9]?) ([AaPp][Mm])$/)) {
		currentHour = $1;
		currentMinute = $2;
		currentAMPM = ($3 || '').toUpperCase();
	}

	var cell = inputObj.parentNode;

	var newContainer = document.createElement('span');
	newContainer.className = 'timeSelector';
	cell.replaceChild(newContainer, inputObj);
	inputObj.style.display = 'none';
	newContainer.appendChild(inputObj);

	var hourDropdown = document.createElement('select');
	hourDropdown.className = 'hourSelector enum';
	hourDropdown.realInput = inputObj;
	var defaultHour = document.createElement('option');
	defaultHour.appendChild(document.createTextNode('hour'));
	defaultHour.value = -1;
	hourDropdown.appendChild(defaultHour);
	for (var i = 1; i <= 12; i++) {
		var opt = document.createElement('option');
		opt.appendChild(document.createTextNode(i));
		if (i == currentHour) opt.selected = true;
		hourDropdown.appendChild(opt);
	}
	addEvent (hourDropdown, 'change', timeChangeHandler);

	var minuteDropdown = document.createElement('select');
	minuteDropdown.className = 'minuteSelector enum';
	minuteDropdown.realInput = inputObj;
	var defaultMinute = document.createElement('option');
	defaultMinute.appendChild(document.createTextNode('min'));
	defaultMinute.value = -1;
	minuteDropdown.appendChild(defaultMinute);
	for (var i = 0; i < 60; i += (hasClass(inputObj, 'fine') ? 5 : 15)) {
		var opt = document.createElement('option');
		opt.appendChild(document.createTextNode((i < 10 ? '0' : '') + i));
		if (i == currentMinute) opt.selected = true;
		minuteDropdown.appendChild(opt);
	}
	addEvent (minuteDropdown, 'change', timeChangeHandler);

	var amLabel = document.createElement('label');

	var amOption = document.createElement('input');
	amOption.type = 'radio';
	amOption.name = inputObj.name + 'autoAMPM';
	amOption.value = 'AM';
	amOption.className = 'radio';
	if (currentAMPM == 'AM') amOption.checked = true;
	amOption.realInput = inputObj;
	// addEvent (amOption, 'click', timeChangeHandler);
	// addEvent (amOption, 'blur', timeChangeHandler);
	amLabel.appendChild(amOption);
	amLabel.appendChild(document.createTextNode('am'));

	var pmLabel = document.createElement('label');

	var pmOption = document.createElement('input');
	pmOption.type = 'radio';
	pmOption.name = inputObj.name + 'autoAMPM';
	pmOption.value = 'PM';
	pmOption.className = 'radio';
	if (currentAMPM == 'PM') pmOption.checked = true;
	pmOption.realInput = inputObj;
	// addEvent (pmOption, 'click', timeChangeHandler);
	// addEvent (pmOption, 'blur', timeChangeHandler);
	pmLabel.appendChild(pmOption);
	pmLabel.appendChild(document.createTextNode('pm'));

	newContainer.appendChild(hourDropdown);
	newContainer.appendChild(document.createTextNode(' '));
	newContainer.appendChild(minuteDropdown);
	newContainer.appendChild(document.createTextNode('  '));
	newContainer.appendChild(amLabel);
	newContainer.appendChild(document.createTextNode('  '));
	newContainer.appendChild(pmLabel);

	inputObj.hourSelector = hourDropdown;
	inputObj.minuteSelector = minuteDropdown;
	inputObj.amOption = amOption;
	inputObj.pmOption = pmOption;

	// As we've just hidden an input that may very well have stuff try to focus
	// it (validation, for example), we attempt to come up with a suitable
	// replacement for the default focus method, which would just die horribly.
	inputObj.focus = function() {
		if (fieldIsEmpty(hourDropdown))
			return hourDropdown.focus();
		if (fieldIsEmpty(minuteDropdown))
			return minuteDropdown.focus();
		if (!amOption.checked && !pmOption.checked)
			return amOption.focus();
		return hourDropdown.focus();
	}
}

var timeChangeHandler = function(e) {
	var realInput = e.target.realInput || e.target;
	if (realInput.hourSelector.selectedIndex == 0 || realInput.minuteSelector.selectedIndex == 0 || !(realInput.amOption.checked || realInput.pmOption.checked))
		realInput.value = '';
	else
		realInput.value = realInput.hourSelector.value + ':' + realInput.minuteSelector.value + ' ' + (realInput.amOption.checked ? 'AM' : 'PM');
	return true;
}

var addCalendar = function(inputObj) {
	if (!hasClass(inputObj, 'date'))
		return;

	var cell = inputObj.parentNode;			// td

	var inputDiv = document.createElement('span');
	inputDiv.className = 'dateSelector';
	cell.replaceChild(inputDiv, inputObj);
	inputDiv.appendChild(inputObj);

	var a = document.createElement('a');	// anchor

	// setup anchor
	a.href = "javascript:useCal(document.forms[0]."+ inputObj.name +", 'a_"+ inputObj.name +"');";
	//a = cell.insertBefore(a,inputObj);
	inputDiv.appendChild(a);

	a.name = 'a_'+ inputObj.name;
	a.id = 'a_'+ inputObj.name;
	a.title = 'Calendar Tool';
	a.className = "input_calendar";

	// drop in a fake image, to make everything line up right :(
	var img = document.createElement('img');
	img.src = '/images/icons/calendar.png';
	img.alt = '';
	inputDiv.appendChild(img);

	// drop in calendar image
	img = document.createElement('img');
	img.src = '/images/icons/calendar.png';
	img.alt = ' [Calendar] ';
	a.appendChild(img);

	// drop in div
	var divObj = document.getElementById('formCalendar');
	if (divObj == null) {
		divObj = document.createElement('div');
		divObj.id = "formCalendar";
		divObj.position = "absolute";
		divObj.visibility = "hidden";
		divObj.backgroundColor = "white";
	//	document.body.appendChild(divObj);
		document.body.insertBefore(divObj, document.body.childNodes[0]);
		//document.write("<div id='formCalendar' style='position:absolute;visibility:hidden;background-color:white;layer-background-color:white;'></div>");
	}

	renderBug(inputDiv);
}

var registerBlurHandler = function(inputObj) {
	if (!inputObj.className || !hasClass(inputObj, 'date'))
		return;
	if (!chkdate)
		return;
	addEvent(inputObj, 'blur', function(e) {
		if (!e) e = window.event;
		var t = e.target ? e.target : e.srcElement;
		chkdate(t);
	});
}
var registerClickHandler = function(inputObj) {
	if (!(inputObj.type && (inputObj.type.toLowerCase() == 'submit' || (inputObj.type.toLowerCase() == 'image' && inputObj.tagName.toLowerCase() == 'input'))))
		return;

	if (!inputObj.form.submitButtons)
		inputObj.form.submitButtons = [];
	inputObj.form.submitButtons[inputObj.form.submitButtons.length] = inputObj;

	addEvent(inputObj, 'click', function(e) {
		if (!e) e = window.event;
		var t = e.target ? e.target : e.srcElement;
		// Disable all the submit buttons on the form
		//for (var i = 0; i < t.form.submitButtons.length; i++)
		//t.form.submitButtons[i].disabled = true;
	});

	if (inputObj.name == 'CANCEL')
		addEvent(inputObj, 'click', function(e) {
			var isModified = isFormModified(e.target.form);
			var yesPlease = !isModified || confirm('Are you sure you want to discard your changes to this record?');

			if (e.preventDefault)
				if (!yesPlease)
					e.preventDefault();
			if (e.stopPropagation)
				e.stopPropagation();

			return yesPlease;
		});

	if (inputObj.name == 'DELETE')
		addEvent(inputObj, 'click', function(e) {
			var yesPlease = confirm('Are you sure you want to delete this record?');

			if (e.preventDefault)
				if (!yesPlease)
					e.preventDefault();
			if (e.stopPropagation)
				e.stopPropagation();

			return yesPlease;
		});

	if (hasClass(inputObj, 'write'))
		addEvent(inputObj, 'click', function(e) {
			// Under some conditions, if the user has clicked, for example,
			// 'Delete', then decided not to delete it, the form can still be set
			// to skip validation. Just to be sure, we explicitely tell it to
			// validate.
			e.target.form.skipValidation = false;

			if (e.target.form.richTexts)
				for (var i = 0; i < e.target.form.richTexts.length; i++)
					updateRTE(e.target.form.richTexts[i]);
		});

	if (hasClass(inputObj, 'abort'))
		addEvent(inputObj, 'click', function(e) {
			e.target.form.skipValidation = true;
		});
}

// "Touch" each form element... only once though, so flag each one as we do
// so.
var touchFormElement = function(element) {
	if (element.elementTouched) return;
	element.elementTouched = true;

	// Remember the initial value of this element, so we can determine
	// whether it's changed in the cancel button handler
	element.initialValue = element.value;

	// Add an asterisk to the label if this element is required
	markRequired(element);

	// Ensure the label node is linked to the element
	if (!element.title)
		element.title = getLabel(element);

	// Convert to a date selection control if this is a date input
	addCalendar(element);

	// Convert to a time selection control if this is a time input
	addTimeSelector(element);

	// Disable the browser autocomplete functionality, unless we've
	// specifically enabled it
	disableAutoComplete(element);

	// Add a handler to correct the formatting upon blur if this is a
	// date input
	registerBlurHandler(element);

	// Add appropriate button click handlers for submit, cancel, and
	// delete buttons
	registerClickHandler(element);

	handleExtendedSetup(element);
}

var validationOnLoad = function() {
	var formList = document.getElementsByTagName('form');
	for (var i = 0; i < formList.length; i++) {
		for (var j = 0; j < formList[i].elements.length; j++)
			if (formList[i].elements[j].tagName != 'FIELDSET')
				touchFormElement(formList[i].elements[j]);

		// Apparently, form.elements doesn't contain inputs of type "image"...
		// and possibly others. Just to be sure, spin through looking by tag
		// name.
		var inputList = formList[i].getElementsByTagName('input');
		for (var j = 0; j < inputList.length; j++)
			touchFormElement(inputList[j]);

		var buttonList = formList[i].getElementsByTagName('button');
		for (var j = 0; j < buttonList.length; j++)
			touchFormElement(buttonList[j]);

		var selectList = formList[i].getElementsByTagName('select');
		for (var j = 0; j < selectList.length; j++)
			touchFormElement(selectList[j]);

		addEvent(formList[i], 'submit', validateForm);
	}
}

// If addEvent isn't defined, we won't actually do anything at all.
if (addEvent)
	addEvent(window, 'load', validationOnLoad);


// Utility function to get the caption from a select list at the given
// index. If not provided, the currently selected index is assumed.
var selectCaption = this.selectCaption = function(listBox, desiredIndex) {
	listBox = getField(listBox);
	if (desiredIndex == null) desiredIndex = listBox.selectedIndex;
	return listBox.options[desiredIndex].innerHTML;
}

var registerPreValidation = this.registerPreValidation = function(inputObj, handlerFunction) {
	inputObj = getField(inputObj);
	if (!inputObj.preValidation)
		inputObj.preValidation = [];
	inputObj.preValidation[inputObj.preValidation.length] = handlerFunction;
}
var registerExtraValidation = this.registerExtraValidation = function(inputObj, handlerFunction) {
	inputObj = getField(inputObj);
	if (!inputObj.extraValidation)
		inputObj.extraValidation = [];
	inputObj.extraValidation[inputObj.extraValidation.length] = handlerFunction;
}
var registerPostValidation = this.registerPostValidation = function(inputObj, handlerFunction) {
	inputObj = getField(inputObj);
	if (!inputObj.postValidation)
		inputObj.postValidation = [];
	inputObj.postValidation[inputObj.postValidation.length] = handlerFunction;
}

var isFormModified = function(formObj) {
	for (var i = 0; i < formObj.elements.length; i++)
		if (formObj.elements[i].initialValue != formObj.elements[i].value && formObj.elements[i].elementTouched)
			return true;
}


// HACK: This is a nasty method of handling the onsubmit richtext-to-hidden
// translation. It'll work for now, though.
var registerRichText = this.registerRichText = function(elementName) {
	var f = document.getElementById('hdn'+elementName).form;
	if (!f.richTexts) f.richTexts = [];
	f.richTexts[f.richTexts.length] = elementName;
}

// Clear the list of errors for the given form. Usually called immediately
// prior to looping through all the inputs and revalidating them, such as
// following a form submit.
var clearErrors = function(formObj) {
	formObj.errorList = null;
}

// Note an error against the provided input
var addError = this.addError = function(formObj, inputObj, errorMsg) {
	if (!formObj.errorList)
		formObj.errorList = new Array();

	formObj.errorList[formObj.errorList.length] = { form: formObj, input: inputObj, error: errorMsg };
}

// Appropriately identify all current errors. Return value indicates whether
// any errors have been noted.
var finalizeErrors = function(formObj) {
	var formContainer = findFormContainer(formObj);
	var errorDiv = document.getElementById('formErrors');
	if (errorDiv)
		errorDiv.parentNode.removeChild(errorDiv);
	if (!formObj.errorList)
		return true;
	var ul = null;
	if (!errorDiv) {
		errorDiv = document.createElement('div');
		errorDiv.id = 'formErrors';
		var img = document.createElement('img');
		img.src = '/images/icons/error.gif';
		var p = document.createElement('p');
		p.appendChild(document.createTextNode('The following problems were identified with your request:'));
		var p2 = document.createElement('p');
		p2.appendChild(document.createTextNode('Please rectify these issues, and resubmit the form.'));
		ul = document.createElement('ul');
		var but = document.createElement('button')
		but.appendChild(document.createTextNode('OK'));
		addEvent(but, 'click', function() {
			var o = document.getElementById('formErrors');
			o.parentNode.removeChild(o);
			restoreBrokenObjects(formContainer, 'validation');
			if (window.activateContainingTab)
				activateContainingTab(formObj.errorList[0].input);
         if (formObj.errorList[0].input.style.display != 'none')
            formObj.errorList[0].input.focus();
		});

		errorDiv.appendChild(img);
		errorDiv.appendChild(p);
		errorDiv.appendChild(ul);
		errorDiv.appendChild(p2);
		errorDiv.appendChild(but);
	} else {
		ul = errorDiv.getElementsByTagName('ul')[0];
		while(ul.childNodes.length)
			ul.removeChild(ul.firstChild);
	}

	var lastInput = '';
	var previousErrors = [];
	for (var i = 0; i < formObj.errorList.length; i++) {
      if(formObj.errorList[i].input.type == 'radio' || formObj.errorList[i].input.type == 'checkbox') {
         addClass(formObj.errorList[i].input, 'fieldCheckError');
      } else {
         addClass(formObj.errorList[i].input, 'fieldError');
      }
		if (formObj.errorList[i].input.name != lastInput || formObj.errorList[i].unique) {
			lastInput = formObj.errorList[i].input.name;
      
			var labelText = getLabel(formObj.errorList[i].input) || '!!!';
			var errorText = formObj.errorList[i].error.replace(/%LABEL%/, labelText);
			for (var j = 0; j < previousErrors.length; j++) {
				if (errorText == previousErrors[j]) errorText = '';
			}
			if (errorText && errorText != '') {
				previousErrors[previousErrors.length] = errorText;
				addListElement(ul, errorText);
			}
		}
	}
	if (previousErrors.length <= 0) addListElement(ul, 'At least one unlabelled field had a validation error');
	formContainer.parentNode.insertBefore(errorDiv, formContainer);

	// Scroll to the top of the page.
	document.body.scrollTop = 0;

	var okayButton = errorDiv.getElementsByTagName('button')[0];
	if (okayButton && okayButton.focus && okayButton.style.display != '') okayButton.focus();

	clearAreaOfBrokenObjects(formContainer, errorDiv, 'validation');

	/*
	if (formObj.errorList[0].input.focus)
		formObj.errorList[0].input.focus();
		*/
	return false;
}

var getFirstNamed = function(inputObj) {
	return inputObj.name ? document.getElementsByName(inputObj.name)[0] : inputObj;
}
var findLabelForNode = function(targetNode) {
	if(targetNode) {//hack placed here as next line was returning id null or not an object in IE
		if (targetNode.id) {
			var labels = document.getElementsByTagName('label');
			for (var i = 0; i < labels.length; i++) {
				if (labels[i].getAttribute('for') == targetNode.id)
					return labels[i];
			}
		}
	}
}
var getLabelNode = function(inputObj) {
	var labelSearch = /^legend$|^th$|^dt$/;

	var labelForNode = findLabelForNode(inputObj) || findContainer(inputObj, 'label', false);

	if(inputObj){ //hack placed here as next line was returning id null or not an object in IE
		if ((!inputObj.name && !inputObj.id) || document.getElementsByName(inputObj.name || inputObj.id).length <= 1) {
			if (labelForNode)
				return labelForNode;
			labelSearch = /^label$|^legend$|^th$|^dt$/;
		}
	}

	var labelNode = precedingNodeByTagName(inputObj, labelSearch);

	if (labelNode && !labelForNode && labelNode.getElementsByTagName('label').length == 0 && inputObj.type != 'hidden') {
		if (!inputObj.id) inputObj.id = uniqueNodeIdentifier(inputObj);
		var labelObj = document.createElement('label');
		labelObj.setAttribute('for', inputObj.id);
		while (labelNode.firstChild)
			labelObj.appendChild(labelNode.removeChild(labelNode.firstChild));
		labelNode.appendChild(labelObj);
		labelNode = labelObj;
	}

	return labelNode;
}

// Generates an identifier for the given node that will be at least
// reasonably unique within the current document. Realistically, it
// won't be particularly unique if you start messing with the DOM... but
// it should be good enough for most purposes. The calculation process
// is volatile; it will create a different value each time it's given
// the same node. To counter this, it automatically stores the created
// identifier as the node's ID (and returns the node's ID if it has one,
// instead of doing any calculation).
var uniqueNodeIdentifier = function(identifiedNode) {
	if (!identifiedNode.id) {
		var nearbyIdentifiers = [];

		var parentNodes = 0;
		var o = identifiedNode;
		while (o = o.parentNode) {
			if (o.id) nearbyIdentifiers.push(o.id);
			if (o.name) nearbyIdentifiers.push(o.name);
			parentNodes++;
		}

		var previousSiblings = 0;
		o = identifiedNode;
		while (o = o.previousSibling) {
			if (o.id) nearbyIdentifiers.push(o.id);
			if (o.name) nearbyIdentifiers.push(o.name);
			previousSiblings++;
		}

		var childNodes = identifiedNode.getElementsByTagName('*').length;

		var randomNumber = Math.abs(Math.floor(Math.random() * 10000));

		var timeNumber = Math.abs(Date.UTC());

		var idComponents = [];
		idComponents.push('NODE');
		idComponents.push(parentNodes);
		idComponents.push(previousSiblings);
		while (nearbyIdentifiers.length)
			idComponents.push(nearbyIdentifiers.pop());
		idComponents.push(childNodes);
		idComponents.push(randomNumber);
		idComponents.push(timeNumber);

		identifiedNode.id = idComponents.join('_');
	}

	return identifiedNode.id;
}

var precedingNodeByTagName = function(o, tagName) {
	if (typeof tagName == 'string') tagName = new RegExp(tagName);
	while (o = precedingNode(o))
		if (o.tagName && tagName.test(o.tagName.toLowerCase()))
			return o;
}
var precedingNode = function(o) {
	if(o) //hack placed here as next line was returning id null or not an object in IE
		return previousRealSibling(o) || o.parentNode;
}
var previousSiblingByTagName = function(o, tagNames) {
	if (typeof tagNames == 'string') tagNames = [tagNames];
	o = previousRealSibling(o);
	while (o && o.tagName) {
		for (var i = 0; i < tagNames.length; i++)
			if (lc(o.tagName) == lc(tagNames[i]))
				return o;
		o = previousRealSibling(o);
	}
	return null;
}
var previousRealSibling = function(o) {
	if(o){//hack placed here as next line was returning id null or not an object in IE
		o = o.previousSibling;
		while (o && o.nodeType != 1 && o.nodeType != 9)
			o = o.previousSibling;
		return o;
	}
}
var getLabel = this.getLabel = function(inputObj) {
	if (inputObj.title) return inputObj.title;
	var n = getLabelNode(inputObj) || getLabelNode(getFirstNamed(inputObj));
	if (!n || !n.firstChild) return null;
	var value = n.textContent || n.innerText || '';
	value = value.replace(/ *:? *\*? *$/, '');
	if (value.match(/[.!?]$/)) value = '"' + value + '"';
	return value;
}
var addListElement = function(listObj, textString) {
	var li = document.createElement('li');
	li.appendChild(document.createTextNode(textString));
	listObj.appendChild(li);
}

// The 'Form Container' is the block that contains the useful part of
// the form. It is used as the insertion target for the validation error
// popup. Also, only contents of the Form Container are considered when
// attempting to clear the validation error popup's area of broken
// objects.
var findFormContainer = function(formObj) {
	if (formObj.formContainer) return formObj.formContainer;

	var a = document.getElementsByTagName('div');
	for (var i = 0; i < a.length; i++)
		if (hasClass(a[i], 'formBody'))
			return formObj.formContainer = a[i];

	return formObj.formContainer = formObj;
}

// Form submit handler... identifies the targetted form object, and then
// validates each field as appropriate.
var validateForm = this.validateForm = function(e) {
	if (!e) e = window.event;
	var f = e.target ? e.target : e.srcElement;

	if (f.form) f = f.form;

	if (f.skipValidation)
		return true;

	var callPreValidation = function(parentObj, tagName) {
		var elementList = parentObj.getElementsByTagName(tagName);
		for (var i = 0; i < elementList.length; i++)
			if (elementList[i].preValidation)
				for (var j = 0; j < elementList[i].preValidation.length; j++)
					elementList[i].preValidation[j](elementList[i]);
	};
	callPreValidation(f, 'input');
	callPreValidation(f, 'select');
	callPreValidation(f, 'textarea');
	callPreValidation(f, 'button');

	clearErrors(f);
	for (var i = 0; i < f.elements.length; i++) {
		validateField(f.elements[i]);
		if (f.elements[i].extraValidation)
			for (var j = 0; j < f.elements[i].extraValidation.length; j++)
				f.elements[i].extraValidation[j](f.elements[i]);
	}
   
	var allOkay = finalizeErrors(f);

	var callPostValidation = function(parentObj, tagName) {
		var elementList = parentObj.getElementsByTagName(tagName);
		for (var i = 0; i < elementList.length; i++)
			if (elementList[i].postValidation)
				for (var j = 0; j < elementList[i].postValidation.length; j++)
					elementList[i].postValidation[j](elementList[i], allOkay);
	};
	callPostValidation(f, 'input');
	callPostValidation(f, 'select');
	callPostValidation(f, 'textarea');
	callPostValidation(f, 'button');

	if (e.preventDefault) {
		if (!allOkay)
			e.preventDefault();
	} else {
		e.returnValue = allOkay;
	}
	if (!allOkay) {
      Element.scrollTo('formErrors');
		if (e.stopPropagation)
			e.stopPropagation();
		else
			e.cancelBubble = true;
	}
   
   if(allOkay && f.getAttribute('onvalidated'))
      eval(f.getAttribute('onvalidated'));
   
	return allOkay;
}

var validateField = function(fieldObj) {
	fieldObj = getField(fieldObj);
	if (!fieldObj) return;

   removeClass(fieldObj, 'fieldError');
	if (fieldIsEmpty(fieldObj)) {
		var required = fieldIsRequired(fieldObj);
		if (required) {
			if (typeof required != 'string') required = '%LABEL% is required';
			return addError(fieldObj.form, fieldObj, required);
		}
	} else {
		validateFieldUsingClassString(fieldObj, fieldObj.className);
		if (fieldObj.conditionalValidations)
			for (var i = 0; i < fieldObj.conditionalValidations.length; i++)
				if (fieldObj.conditionalValidations[i].f(fieldObj))
					validateFieldUsingClassString(fieldObj, fieldObj.conditionalValidations[i].s);
	}
}
var validateFieldUsingClassString = function(fieldObj, classString) {
	var classes = (classString || '').split(' ');
	for (var i = 0; i < classes.length; i++) {
		var func = validationMethods[classes[i]];
		if (func) {
			func(fieldObj, classes[i]);
		} else {
			var method = classes[i].substr(0, classes[i].indexOf(':'));
			var f2 = extValidationMethods[method];
			if (f2)
				f2.apply(fieldObj, extendedClassArguments(classes[i]));
		}
	}
}
var extendedClassArguments = function(extendedClassString) {
	if (extendedClassString.indexOf(':') <= 0) return [];
	var args = extendedClassString.substr(extendedClassString.indexOf(':') + 1).split('/');
	for (var j = 0; j < args.length; j++)
		args[j] = decodeURI(args[j]);
	return args;
}

// Registers a validation method
var registerValidationMethod = this.registerValidationMethod = function(methodName, validationFunction) {
	validationMethods[methodName] = validationFunction;
}
var registerExtendedSetup = this.registerExtendedSetup = function(methodName, setupFunction) {
	extSetupMethods[methodName] = setupFunction;
}
var registerExtendedValidation = this.registerExtendedValidation = function(methodName, validationFunction) {
	extValidationMethods[methodName] = validationFunction;
}

var customRequiredFunction = 'customRequiredFunction';
var fieldIsRequired = function(fieldObj) {
	fieldObj = getField(fieldObj);
	if (!fieldObj)
		return false;
	// If it depends on something that isn't present, it definitely isn't
	// required.
	if (!dependencyFree(fieldObj))
		return false;
	if (hasClass(fieldObj, 'required'))
		return true;
	return callRegisteredFunctionAny(fieldObj, customRequiredFunction, null, [fieldObj]);
}
var notifyValueWatchersOfChange = this.notifyValueWatchersOfChange = function(o, reason) {
	callRegisteredFunction(o, 'valueChange', o, [reason || false]);
	if (window.Event && Event.observers) {
		var l = Event.observers.length;
		for (var i = 0; i < l; i++) {
			var obs = Event.observers[i];
			if (obs && obs[0] == o && obs[1] == 'change')
				obs[2]({target:o});
		}
	}
}
var notifyValueWatchersOfChange_event = function(e) {
	callRegisteredFunction(e.target, 'valueChange', e.target, [true]);
}
// We compensate for IE's stupidity in getElementsByName (specifically,
// it searches elements by their names at the time they were added to
// the document, not their current names), by checking that a
// getElementsByName with our name includes us; if it doesn't, we'll
// just assume we're the only element with our name.
var getLinkedElements = function(fieldObj) {
	if (typeof fieldObj != 'object')
		return document.getElementsByName(fieldObj);
	var ls = document.getElementsByName(fieldObj.name);
	for (var i = 0; i < ls.length; i++)
		if (ls[i] === fieldObj)
			return ls;
	return [fieldObj];
}
var ensureValueWatched = function(fieldObj) {
	var fieldList = getLinkedElements(fieldObj);
	for (var i = 0; i < fieldList.length; i++) {
		if (fieldList[i].valueWatched)
			return;
		//addEvent(fieldList[i], 'blur', notifyValueWatchersOfChange_event);
		addEvent(fieldList[i], 'change', notifyValueWatchersOfChange_event);
		addEvent(fieldList[i], 'click', notifyValueWatchersOfChange_event);
		addEvent(fieldList[i], 'keyup', notifyValueWatchersOfChange_event);
	}
}
var registerValueWatcher = this.registerValueWatcher = function(fieldObj, valueChangeCallback) {
	ensureValueWatched(fieldObj);

	var fieldList = getLinkedElements(fieldObj);
	for (var i = 0; i < fieldList.length; i++) {
		registerFunction(fieldList[i], 'valueChange', valueChangeCallback);
	}
}
var dependencyFree = function(inputObj) {
	if (!inputObj.dependsOn) return true;

	if (inputObj.handlingDependencies) return true;
	inputObj.handlingDependencies = true;
	for (var m in inputObj.dependsOn) {
		var f = getField(m);
		if (f && !f.handlingDependencies) {
			if (!dependencyFree(getField(m))) {
				inputObj.handlingDependencies = false;
				return false;
			}
			if (inputObj.dependsOn[m] ? (!fieldHasValue(m, inputObj.dependsOn[m])) : fieldIsEmpty(m)) {
				inputObj.handlingDependencies = false;
				return false;
			}
		}
	}

	inputObj.handlingDependencies = false;
	return true;
}
var updateInputObj = this.updateInputObj = function(inputObj) {
	if (inputObj.handlingValueChange) return;
	inputObj.disabled = !dependencyFree(inputObj);
	inputObj.handlingValueChange = true;
	callRegisteredFunction(inputObj, 'valueChange', inputObj, []);
	inputObj.handlingValueChange = false;
}
var registerConditionalValidation = this.registerConditionalValidation = function(fieldObj, validationString, conditionFunction) {
	fieldObj = getField(fieldObj);
	if (!fieldObj) return;
	fieldObj.conditionalValidations = fieldObj.conditionalValidations || [];
	fieldObj.conditionalValidations[fieldObj.conditionalValidations.length] = { s: validationString, f: conditionFunction };
}
var registerRequirement = this.registerRequirement = function(fieldObj, conditionFunction) {
	fieldObj = getField(fieldObj);
	if (!fieldObj) return;
	registerFunction(fieldObj, customRequiredFunction, conditionFunction);
}
var registerFieldDependency = this.registerFieldDependency = function(fieldObj, dependedFields) {
	if (typeof fieldObj == 'string') {
		var fieldList = document.getElementsByName(fieldObj);
		for (var i = 0; i < fieldList.length; i++)
			registerFieldDependency(fieldList[i], dependedFields);
		return;
	}

	fieldObj = getField(fieldObj);
	if (!dependedFields || !fieldObj) return;

	fieldObj.dependsOn = fieldObj.dependsOn || {};
	for (var fieldName in dependedFields) {
		fieldObj.dependsOn[fieldName] = dependedFields[fieldName];
		registerValueWatcher(getField(fieldName), function() { updateInputObj(fieldObj); });
		updateInputObj(fieldObj);
	}
}
var setField = this.setField = function(fieldObj, newValue) {
	fieldObj = getField(fieldObj);
	if (!fieldObj) return;

	switch (fieldObj.type && fieldObj.type.toLowerCase()) {
		case 'radio':
			var fields = document.getElementsByName(fieldObj.name);
			for (var i = 0; i < fields.length; i++)
				fields[i].checked = fields[i].value == newValue;
			return;
		case 'checkbox':
			fieldObj.checked = newValue;
			return;
		case 'select':
			var options = fieldObj.options;
			for (var i = 0; i < options.length; i++)
				if (options[i].selected = options[i].value == newValue)
					return;
			for (var i = 0; i < options.length; i++)
				if (options[i].selected = options[i].innerHTML == newValue)
					return;
			return;
		default:
			fieldObj.value = newValue;
			return;
	}
}
var fieldValue = this.fieldValue = function(fieldObj) {
	fieldObj = getField(fieldObj);
	if (!fieldObj) return;

	switch (fieldObj.type && fieldObj.type.toLowerCase()) {
		case 'radio':
			var fields = document.getElementsByName(fieldObj.name);
			for (var i = 0; i < fields.length; i++)
				if (fields[i].checked) return fields[i].value;
			return;
		case 'checkbox':
			return fieldObj.checked;
		default:
			if (fieldIsEmpty(fieldObj)) return;
			return fieldObj.value;
	}
}
var fieldHasValue = this.fieldHasValue = function(fieldObj, matchValue) {
	var isEmpty = fieldIsEmpty(fieldObj);
	if (valueIsEmpty(fieldObj, matchValue))
		return isEmpty;
	var value = fieldValue(fieldObj);
	if (matchValue && typeof matchValue == 'object')
		for (var m in matchValue) {
			if (valueIsEmpty(fieldObj, matchValue[m])) {
				if (isEmpty)
					return true;
			} else if (value == matchValue[m]) {
				return true;
			}
		}
	return value == matchValue;
}

var countFieldValues = function(fieldArray) {
	var count = 0;
	for (var m in fieldArray)
		if (!fieldIsEmpty(fieldArray[m]))
			count++;
	return count;
}
var getField = this.getField = function(fieldObj, nameIndex) {
	if (typeof nameIndex != 'number')
		nameIndex = 0;
	if (typeof fieldObj == 'string')
		fieldObj = document.getElementsByName(fieldObj)[nameIndex];
	return fieldObj;
}
var valueIsEmpty = function(fieldObj, fieldValue) {
	return (fieldValue+'').match(/^ *$/) || (hasClass(getField(fieldObj), 'enum') && fieldValue == '-1');
}
var fieldIsEmpty = this.fieldIsEmpty = function(fieldObj) {
	fieldObj = getField(fieldObj);
	if (!fieldObj) return;

	if(valueIsEmpty(fieldObj, fieldObj.value))
		return true;

	if(fieldObj.type && fieldObj.type.toLowerCase() == 'radio') {
		// Only complain if this is the first in the set of radio buttons.
		var fields = getElementsByName2(fieldObj.name, 'input');
		if (fields[0].value == fieldObj.value) {
			for (var i = 0; i < fields.length; i++)
				if (fields[i].checked)
					return false;
			return true;
		}
	}

	if(fieldObj.type && fieldObj.type.toLowerCase() == 'checkbox') {
		// Checkboxes, like radio buttons, always have a value; the question is
		// will they use it.
		return !fieldObj.checked;
	}
}
var valueText = this.valueText = function(fieldName, valueTransform) {
	var f = getField(fieldName);
	if (!f) return fieldName;
	var v = fieldValue(f);
	if (valueTransform) v = valueTransform(v);
	var t = getLabel(f);
	if (v) t = t + ' (' + v + ')';
	return t;
}

// NOTE: As implemented at the moment, 'pastdate' will accept today's
// date, but 'futuredate' will not. This is probably a good thing, but
// will require some more thought if we encounter a situation where we
// need different behaviour.
registerValidationMethod('date', function(fieldObj) {
	if(fieldObj.value != '' && chkdate && !chkdate(fieldObj))
		addError(fieldObj.form, fieldObj, '%LABEL% must be a valid date (e.g., \'17 Mar 2005\')');
	else if(hasClass(fieldObj, 'pastdate') && new Date(fieldObj.value) > new Date())
		addError(fieldObj.form, fieldObj, '%LABEL% must be a date that has already occurred');
	else if(hasClass(fieldObj, 'futuredate') && new Date(fieldObj.value) < new Date())
		addError(fieldObj.form, fieldObj, '%LABEL% must be a date in the future');
});
registerValidationMethod('datetime', function(fieldObj) {
	if(!fieldObj.value.match(/^([012]?[0-9]|3[01])[- ](Jan|Feb|Ma[ry]|Apr|Ju[ln]|Aug|Sep|Oct|Nov|Dec)[- ]\d{4} (([01]?[0-9]|2[0-3]):[0-5][0-9]|(0?[1-9]|1[0-2]):[0-5][0-9] [AP]M)$/))
		addError(fieldObj.form, fieldObj, '%LABEL% must be a valid date and time (e.g., \'17-Mar-2005 13:30\')');
});
// FIXME: Aren't domains unicode nowdays?
registerValidationMethod('email', function(fieldObj) {
	if(!fieldObj.value.match(/^[a-z0-9_.+-]+@([a-z0-9-]+\.)+[a-z]{2,}$/i))
		addError(fieldObj.form, fieldObj, '%LABEL% must be a valid email address');
});
registerValidationMethod('integer', function(fieldObj) {
	if(!fieldObj.value.match(/^-?[0-9]+$/))
		addError(fieldObj.form, fieldObj, '%LABEL% must be a number');
});
registerValidationMethod('number', function(fieldObj) {
	if(!fieldObj.value.match(/^-?[0-9]+\.?[0-9]*$/))
		addError(fieldObj.form, fieldObj, '%LABEL% must be a number');
});
registerValidationMethod('money', function(fieldObj) {
	if(!fieldObj.value.match(/^\$?(\d{1,3}(\, *\d{3})*|(\d+))(\.\d{0,2})?$/))
		addError(fieldObj.form, fieldObj, '%LABEL% must be a money amount (only 2 decimal places)');
});
registerValidationMethod('phone', function(fieldObj) {
	if(!fieldObj.value.match(/^\+[0-9]{11}$/))
		addError(fieldObj.form, fieldObj, '%LABEL% must be a full international phone number (e.g., \'+61882129544\')');
});
registerValidationMethod('phonemobile', function(fieldObj) {
	if(!fieldObj.value.match(/^\+[0-9]{11}$/))
		addError(fieldObj.form, fieldObj, '%LABEL% must be a full international phone number (e.g., \'+61882129544\')');
});
registerValidationMethod('shortphone', function(fieldObj) {
	if(!fieldObj.value.match(/^[ -]*([0-9][ -]*){8}$/))
		addError(fieldObj.form, fieldObj, '%LABEL% must be an eight digit phone number');
});
registerValidationMethod('ozphone', function(fieldObj) {
	var num = fieldObj.value.replace(/[ -()]/g, '');
	if(!num.match(/^0([0-9]){9}$|^13[0-9]{4}$|^1[389]00[0-9]{6}$/))
		addError(fieldObj.form, fieldObj, '%LABEL% must be a complete Australian phone number, with area code (e.g., \'08 8212 9544\')');
});
registerValidationMethod('shortphonemobile', function(fieldObj) {
	if(!fieldObj.value.match(/^[ -]*0[ -]*([0-9][ -]*){9}$/))
		addError(fieldObj.form, fieldObj, '%LABEL% must be a ten digit mobile phone number, starting with a zero');
});
registerValidationMethod('callback', function(fieldObj) {
	// If this is a required value, and there isn't anything in here, we can
	// just ignore it; the standard 'required' check will deal with it; if
	// it isn't required, and it's empty, we're all good.
	if (fieldObj.value == '') return;

	// If we have a value node, and it has a value, something's been selected
	// from the list, so we're happy with that.
	if (fieldObj.valueNode && fieldObj.valueNode.value != '') return;

	// If we require a selection from the list, and we haven't returned from
	// this function yet, they've failed validation.
	if (hasClass(fieldObj, 'fixedlist'))
		addError(fieldObj.form, fieldObj, '%LABEL% must be selected from the available list');
});
registerValidationMethod('locality', function(fieldObj) {
	// If they've entered a value, we should check that it will be able to be
	// parsed by the form processor.
	//
	// FIXME: As this item does not yet exist, we really should prompt the user
	// to confirm they want to create it.
	if (fieldObj.value != '' && !fieldObj.value.match(/^[A-Za-z0-9' -]+, (WA|NT|QLD|NSW|ACT|VIC|TAS|SA) [0-9]+$/i))
		addError(fieldObj.form, fieldObj, '%LABEL% must be of the form \'Suburb, State Postcode\'');
});
// gmtoffset
registerValidationMethod('csslength', function(fieldObj) {
	if (!fieldObj.value.match(/^[0-9]+(\.[0-9]+)?(%|px|mm|em)$/))
		addError(fieldObj.form, fieldObj, "%LABEL% must be a valid CSS length specification ('%', 'em', 'mm', 'px', etc.)");
});

registerExtendedValidation('minimum', function(minimumField, errorMessage) {
	var min = parseFloat(fieldValue(minimumField) || minimumField);
	msg = (errorMessage || '').replace(/%MINIMUM%/, min);
	if (msg == '') msg = '%LABEL% cannot be less than %MINIMUM%'.replace(/%MINIMUM%/, valueText(minimumField, parseFloat));
	if (min > parseFloat(this.value))
		addError(this.form, this, msg);
});
registerExtendedValidation('maximum', function(maximumField, errorMessage) {
	var max = parseFloat(fieldValue(maximumField) || maximumField);
	msg = (errorMessage || '').replace(/%MAXIMUM%/, max);
	if (msg == '') msg = '%LABEL% cannot be greater than %MAXIMUM%'.replace(/%MAXIMUM%/, valueText(maximumField, parseFloat));
	if (max < parseFloat(this.value))
		addError(this.form, this, msg);
});
registerExtendedValidation('regexp', function(expression, flags, errorMessage) {
	var re = new RegExp(expression, flags);
	if (!re.test(this.value))
		addError(this.form, this, (errorMessage || '%LABEL% must be correctly formed'));
});

registerExtendedSetup('filterGroup', function(filterSource) {
	var src = getField(filterSource);
	if (src) {
		var handler = function() { filterSelectList(this, src.options[src.selectedIndex].innerHTML, 'caption', '  ', true); };
		addEvent(src, 'change', handler);
		handler();
	}
});
registerExtendedSetup('dependsOn', function() {
	var deps = {};
	for (var i = 0; i < arguments.length; i++) {
		var parts = arguments[i].split('=');
		var fieldName = parts[0];
		var acceptedValues = parts[1].split('|');
		for (var j = 0; j < acceptedValues.length; j++)
			acceptedValues[j] = decodeURI(acceptedValues[j]);
		deps[fieldName] = acceptedValues;
	}
	registerFieldDependency(this, deps);
});

})(hasClass, addClass, removeClass);


