
function validationOnLoad() {
	var formList = document.getElementsByTagName('form');
	for (var i = 0; i < formList.length; i++) {
		for (var j = 0; j < formList[i].elements.length; j++) {
			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);
	}
}
// "Touch" each form element... only once though, so flag each one as we do
// so.
function touchFormElement(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;

	// Ensure the label node is linked to the element
	getLabelNode(element);

	// Add an asterisk to the label if this element is required
	markRequired(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);
}
function handleExtendedSetup(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 = window.extSetupMethods[method];
		if (f)
			f.apply(element, extendedClassArguments(classes[i]));
	}
}

// If addEvent isn't defined, we won't actually do anything at all.
if (addEvent)
	addEvent(window, 'load', validationOnLoad);

function disableAutoComplete(inputObj) {
	if (!inputObj.getAttribute('autocomplete'))
		inputObj.setAttribute('autocomplete', hasClass(inputObj, 'commonstring') ? 'on' : 'off');
}
function markRequired(inputObj) {
	if (!inputObj.className || !hasClass(inputObj, 'required'))
		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);
	}
}

function useCal(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');
}

function addTimeSelector(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;
		currentMinutes = $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();
	}
}

function timeChangeHandler(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;
}

function addCalendar(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);
}

function registerBlurHandler(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);
	});
}
function registerClickHandler(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;
		});
}

// Utility function to get the caption from a select list at the given
// index. If not provided, the currently selected index is assumed.
function selectCaption(listBox, desiredIndex) {
	listBox = getField(listBox);
	if (desiredIndex == null) desiredIndex = listBox.selectedIndex;
	return listBox.options[desiredIndex].innerHTML;
}

function registerPreValidation(inputObj, handlerFunction) {
	inputObj = getField(inputObj);
	if (!inputObj.preValidation)
		inputObj.preValidation = [];
	inputObj.preValidation[inputObj.preValidation.length] = handlerFunction;
}
function registerExtraValidation(inputObj, handlerFunction) {
	inputObj = getField(inputObj);
	if (!inputObj.extraValidation)
		inputObj.extraValidation = [];
	inputObj.extraValidation[inputObj.extraValidation.length] = handlerFunction;
}
function registerPostValidation(inputObj, handlerFunction) {
	inputObj = getField(inputObj);
	if (!inputObj.postValidation)
		inputObj.postValidation = [];
	inputObj.postValidation[inputObj.postValidation.length] = handlerFunction;
}

function isFormModified(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.
function registerRichText(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.
function clearErrors(formObj) {
	formObj.errorList = null;
}

// Note an error against the provided input
function addError(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.
function finalizeErrors(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);
			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.name != lastInput) {
			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.focus();

	clearAreaOfBrokenObjects(formContainer, errorDiv, 'validation');

	/*
	if (formObj.errorList[0].input.focus)
		formObj.errorList[0].input.focus();
		*/
	return false;
}

function getFirstNamed(inputObj) {
	return document.getElementsByName(inputObj.name)[0];
}
function findLabelForNode(targetNode) {
	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];
		}
	}
}
function getLabelNode(inputObj) {
	var labelSearch = /^legend$|^th$|^dt$|^h[654321]$/;

	var labelForNode = findLabelForNode(inputObj) || findContainer(inputObj, 'label', false);

	if ((!inputObj.name && !inputObj.id) || document.getElementsByName(inputObj.name || inputObj.id).length <= 1) {
		if (labelForNode)
			return labelForNode;
		labelSearch = /^label$|^legend$|^th$|^dt$|^h[654321]$/;
	}

	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).
function uniqueNodeIdentifier(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;
}

function precedingNodeByTagName(o, tagName) {
	if (typeof tagName == 'string') tagName = new RegExp(tagName);
	while (o = precedingNode(o))
		if (tagName.test(lc(o.tagName)))
			return o;
}
function precedingNode(o) {
	return previousRealSibling(o) || o.parentNode;
}
function previousSiblingByTagName(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;
}
function previousRealSibling(o) {
	o = o.previousSibling;
	while (o && o.nodeType != 1 && o.nodeType != 9)
		o = o.previousSibling;
	return o;
}
function getLabel(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;
}
function addListElement(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.
function findFormContainer(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.
function validateForm(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 (e.stopPropagation)
		e.stopPropagation();
	else
		e.cancelBubble = true;
   
   if (allOkay && window.afterValidation)
      window.afterValidation(f);
	return allOkay;
}

function validateField(fieldObj) {
	fieldObj = getField(fieldObj);
	if (!fieldObj) return;

	if (fieldIsEmpty(fieldObj)) {
		var required = fieldIsRequired(fieldObj);
		if (required) {
			if (typeof required != 'string') required = '%LABEL% is required';
			return addError(fieldObj.form, fieldObj, required);
		}
	} else {
		var cN = fieldObj.className || '';
		var classes = cN.split(' ');
		for (var i = 0; i < classes.length; i++) {
			var func = getValidationMethod(classes[i]);
			if (func) {
				func(fieldObj, classes[i]);
			} else {
				var method = classes[i].substr(0, classes[i].indexOf(':'));
				var f2 = window.extValidationMethods[method];
				if (f2)
					f2.apply(fieldObj, extendedClassArguments(classes[i]));
			}
		}
	}
}
function extendedClassArguments(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;
}

function getValidationMethod(fieldType) {
	return window.validationMethods[fieldType];
}

// Registers a validation method
function registerValidationMethod(methodName, validationFunction) {
	if (!window.validationMethods)
		window.validationMethods = {};
	window.validationMethods[methodName] = validationFunction;
}
function registerExtendedSetup(methodName, setupFunction) {
	if (!window.extSetupMethods)
		window.extSetupMethods = {};
	window.extSetupMethods[methodName] = setupFunction;
}
function registerExtendedValidation(methodName, validationFunction) {
	if (!window.extValidationMethods)
		window.extValidationMethods = {};
	window.extValidationMethods[methodName] = validationFunction;
}

var customRequiredFunction = 'customRequiredFunction';
function fieldIsRequired(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]);
}
function notifyValueWatchersOfChange(e) {
	callRegisteredFunction(e.target, 'valueChange', e.target, []);
}
function ensureValueWatched(fieldObj) {
	if (typeof fieldObj == 'object')
		fieldObj = fieldObj.name;

	var fieldList = document.getElementsByName(fieldObj);
	for (var i = 0; i < fieldList.length; i++) {
		if (fieldList[i].valueWatched)
			return;
		//addEvent(fieldList[i], 'blur', notifyValueWatchersOfChange);
		addEvent(fieldList[i], 'change', notifyValueWatchersOfChange);
		addEvent(fieldList[i], 'click', notifyValueWatchersOfChange);
		//addEvent(fieldList[i], 'keypress', notifyValueWatchersOfChange);
	}
}
function registerValueWatcher(fieldObj, valueChangeCallback) {
	ensureValueWatched(fieldObj);

	if (typeof fieldObj == 'object')
		fieldObj = fieldObj.name;

	var fieldList = document.getElementsByName(fieldObj);
	for (var i = 0; i < fieldList.length; i++) {
		registerFunction(fieldList[i], 'valueChange', valueChangeCallback);
	}
}
function dependencyFree(inputObj) {
	if (!inputObj.dependsOn) return true;
	if (inputObj.dependsOn.length <= 0) return true;

	for (var m in inputObj.dependsOn) {
		if (!dependencyFree(getField(m)))
			return false;
		if (inputObj.dependsOn[m] ? (!fieldHasValue(m, inputObj.dependsOn[m])) : fieldIsEmpty(m))
			return false;
	}
	return true;
}
function updateInputObj(inputObj) {
	inputObj.disabled = !dependencyFree(inputObj);
	callRegisteredFunction(inputObj, 'valueChange', inputObj, []);
}
function registerRequirement(fieldObj, conditionFunction) {
	fieldObj = getField(fieldObj);
	if (!fieldObj) return;
	registerFunction(fieldObj, customRequiredFunction, conditionFunction);
}
function registerFieldDependency(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);
	}
}
function setField(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;
	}
}
function fieldValue(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;
	}
}
function fieldHasValue(fieldObj, matchValue) {
	var value = fieldValue(fieldObj);
	if (matchValue && typeof matchValue == 'object')
		for (var m in matchValue)
			if (value == matchValue[m])
				return true;
	return value == matchValue;
}

function countFieldValues(fieldArray) {
	var count = 0;
	for (var m in fieldArray)
		if (!fieldIsEmpty(fieldArray[m]))
			count++;
	return count;
}
function getField(fieldObj, nameIndex) {
	if (typeof nameIndex != 'number')
		nameIndex = 0;
	if (typeof fieldObj == 'string')
		fieldObj = document.getElementsByName(fieldObj)[nameIndex];
	return fieldObj;
}
function fieldIsEmpty(fieldObj) {
	fieldObj = getField(fieldObj);
	if (!fieldObj) return;

	if(fieldObj.value.match(/^ *$/) || (fieldObj.value == '-1' && hasClass(fieldObj, 'enum')))
		return true;

	if(fieldObj.type && fieldObj.type.toLowerCase() == 'radio') {
		// Only complain if this is the first in the set of radio buttons.
		var fields = document.getElementsByName(fieldObj.name);
		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;
	}
}
function valueText(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) {
	if(!fieldObj.value.match(/^[ -()]*0[ -()]*([0-9][ -()]*){9}$/))
		addError(fieldObj.form, fieldObj, '%LABEL% must be a ten digit Australian phone number, starting with a zero');
});
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);
});

