function numericField(el) {
	el.baddata = false;
	if(el.value) {
		humantitle = el.getAttribute("humantitle");	
		min 		= el.getAttribute("minimum");
		max 		= el.getAttribute("maximum");
		maxdp 	= el.getAttribute("maxdp");
		
		el.value = el.value.replace(/[^0-9.\-]/g	,'');
		testVal = parseFloat(el.value);
		if(isNaN(testVal)) {
			alert(humantitle + " must be a number.");
			el.baddata = true;
			return false;		
		}
		
		if(testVal != 0) {
			if((min != null) && (testVal < min)) {
				alert("I'm sorry, but the " + humantitle + " is too low. It must be at least " + min  + '.');
				el.baddata = true;
				el.value = min;
				return false;
			}
			if((max != null) && (testVal > max)) {
				var msg;
				if(msg = el.getAttribute("tooHighMessage"))
					alert(msg);
				else
					alert("I'm sorry, but the " + humantitle + " is too high. It must be at most " + max + '.');
				el.baddata = true;				
				el.value = max;
				return false;
			}
			
			if(maxdp != null) {								
				// round the value according to maximum number decimal points, 
				// which means if maxdb is 0, then there are no dp allowed.								
				el.value *= Math.pow(10,maxdp);				
				el.value = Math.round(el.value);
				el.value /= Math.pow(10,maxdp);
				return true;
			}
		}
	}
	return true;
}

function currencyField(el, dp) {
	el.baddata = false;
	if(el.value) {
		el.value = el.value.replace(/[^0-9.\-]/g,'');
		testVal = parseInt(el.value);	
		if(isNaN(testVal)) {
			alert("Please enter a number");
			el.baddata = true;
			return false;
		} else {
			el.value = Math.round(testVal);
		}		
	}
	return true;
}
function alphanumeric(el) {
	if(!el.value) return true;
	el.baddata = false;
 	if (el.value.match(/^[a-zA-Z]+[a-zA-Z0-9]*$/)) {
 		return true;
 	} else {
		el.baddata = true;
 		alert("'" + el.value + "' can only contain numbers and letters, and must begin with a letter. e.g. apple123");
 		return false;
 	} 	
}

function alphanumericWithUnderscore(el) {
	if(!el.value) return true;
	el.baddata = false;
 	if (el.value.match(/^[a-zA-Z_]+[a-zA-Z0-9_]*$/)) {
 		return true;
 	} else {
		el.baddata = true;
 		alert("'" + el.value + "' can only contain numbers and letters (or the underscore character) and must begin with a letter. e.g. apple_123");
 		return false;
 	} 	
}

function thousandCommas(strVal) {
	var i = strVal.length, outVal = "";
	while(i > 3) {
		if(outVal) outVal = "," + outVal;
		outVal = strVal.substr(i-3,3) + outVal;
		i-= 3;
	}
		if(outVal) outVal = "," + outVal;
	outVal = strVal.substr(0,i) + outVal;
	return outVal;
}


function emailField(el) {
	if(!el.value) return true;
	el.baddata = false;
 	if(el.value.match(/^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/)) {
 		return true;
 	} else {
		el.baddata = true;
 		alert("'" + el.value + "' is not a valid Email Address. Please check your typing. It should be something like name@company.co.nz");
 		return false;
 	} 	
}

function passwordField(el) {	
	humantitle = el.getAttribute("humantitle");
	el.baddata = false;
	if (! el.value) return true;
	
 	if (! el.value.match(/[a-zA-Z]+/)) {
 		alert(humantitle + ' must contain at least one letter of the alphabet.');
 		el.baddata = true;
 		return false; 		
 	}
 	
 	if (! el.value.match(/[0-9]+/)) {
 		alert(humantitle + ' must contain at least one number.');
 		el.baddata = true;
 		return false; 		
 	}
 	
 	if (el.value && el.value.length < 4) {
 		alert(humantitle + ' must be at least four characters long.');
 		el.baddata = true;
 		return false; 		
 	}
}


function updateRequired(formObj, changedEl) {
	var reqImgBase;
	if(!(formObj = getFormObj(formObj))) return;
	
	checkEventHandlers(formObj);
	
	var i, el, isDisplayed, reqImg;
	var isFilledOut = Array();
	var elReq;
	
	var isOnBlur = changedEl ? true : false;
	
	// If we've just changed 1 field, we don't need to update everything....
	// Additionally, behaviour is sometimes different on a field update, we might show alerts, for example
	if(changedEl) {
		el = changedEl;
		elReq = el.getAttribute('required');

		// If there's no required field here, then just move on
		if(!elReq) {
			return;
		}
				
		// ... as long as there isn't a multi-field requiremenet thang going on
		var isSingleField = false;
		if(elReq && el.type != 'radio') {
			if(elReq == 'required' + el.name) {
				isSingleField = true;
			} else if(el.name.match(/\[([A-Za-z0-9]+)]\[]/)) {
				if(elReq.match(new RegExp('^required' + RegExp.$1 + '[0-9]+$'))) isSingleField = true;
			}
		}
		if(isSingleField) {
			if(el.className)
				el.className = el.className.replace(/ (emptyfield)|(badfield)/,'');
			else el.className = "";

			// __XXXX signifies a control value, eg (Add Item), that should be cleared
			if(typeof el.value == 'string' && el.value.substr(0,2) == "__")
				el.value = "";
			
			reqImg = getRequiredImage(el);
			
			if(reqImg && !reqImgBase) {
				if(reqImg.src.indexOf('required.gif') > -1)
					reqImgBase = reqImg.src.substr(0,reqImg.src.lastIndexOf('/')+1);
				else
					reqImgBase = 'images/';
			}			
			
			if(isntOkay(el, null, formObj, isOnBlur)) {
				if(el.className != "") el.className += " emptyfield";
				else el.className = 'emptyfield';
	
				if(reqImg != null) reqImg.src = reqImgBase + "required.gif";
			} else {
				if(reqImg != null) reqImg.src = reqImgBase + "notrequired.gif";
			}
			return;
		}
	} 
	// Check which groups are filled out	
	for(i=0;i<formObj.elements.length;i++) {
		el = formObj.elements[i];
		if(el.type == 'hidden') continue;
		if(el.name){
			//if el.name doesn't exist, then dont compare.
			if(el.name && el.name.substr(0,13) == '_subdropdown_') checkSubdropdown(el, formObj);
		}
		elReq = el.getAttribute('required');
		

		if(elReq && isFilledOut[elReq] == null) isFilledOut[elReq] = false;
		
		// __XXXX signifies a control value, eg (Add Item), that should be cleared
		if(typeof el.value == 'string' && el.value.substr(0,2) == "__")
			el.value = "";

		if(elReq && hasValue(el)) {
			isFilledOut[elReq] = true;			
		}
	}
	
	// Set the empty / not empty information
	for(i=0;i<formObj.elements.length;i++) {
		el = formObj.elements[i];
		elReq = el.getAttribute('required');

		if(el.className)
			el.className = el.className.replace(/ (emptyfield)|(badfield)/,'');
		else el.className = "";
		
		if(isVisible(el)) {
			reqImg = getRequiredImage(el);
			if(reqImg && !reqImgBase) {
				if(reqImg.src.indexOf('required.gif') > -1)
					reqImgBase = reqImg.src.substr(0,reqImg.src.lastIndexOf('/')+1);
				else
					reqImgBase = 'images/';
			}			

			if(isntOkay(el, isFilledOut, formObj, isOnBlur)) {
//				alert(el.name);
				if(el.className != "") el.className += " emptyfield";
				else el.className = 'emptyfield';
				
				if(reqImg != null) reqImg.src = reqImgBase + "required.gif";
			} else {
				if(reqImg != null) reqImg.src = reqImgBase + "notrequired.gif";
			}
		}
	}
}

/*
 * Return true if the given field has a value
 */
function hasValue(el) {
	return (el.value || el.value == "0") && ((el.type != 'checkbox' && el.type != 'radio') || el.checked)
}

function getRequiredImage(el) {
	var img;
	if (img = document.getElementById(el.name + "_requiredimg")) return img;

	var allImages = el.parentNode.getElementsByTagName('img');
	
	if(allImages && allImages.length > 0) {
		for(var i=0;i<allImages.length;i++) {
			if(allImages[i].tagName && allImages[i].tagName.toLowerCase() == "img" && allImages[i].className && allImages[i].className.indexOf('requiredimg') > -1) {
				return allImages[i];
			}
		}
	}
}

function checkRequired(formObj) {
	if(!(formObj = getFormObj(formObj))) return;
	
	var i, el, elReq, elTitle, isDisplayed, reqImg, isMissing;
	var isFilledOut = Array();
	var isValidated = Array();
	var missingFields = "";
	var alreadyListed = Array();
	var msg;

	// Check which groups are filled out	
	for(i=0;i<formObj.elements.length;i++) {
		el = formObj.elements[i];
		elReq = el.getAttribute('required');
		
		if(elReq && (el.value || el.value == "0") && ((el.type != 'checkbox' && el.type != 'radio') || el.checked)) {
			isFilledOut[elReq] = true;
		}
	}

	// Set the empty / not empty information
	for(i=0;i<formObj.elements.length;i++) {
		el = formObj.elements[i];
		elTitle = el.getAttribute('humantitle');
		
		if(isntOkay(el, isFilledOut, formObj)) {
			// Focus on the first element
			if(!isMissing) isMissing = el;
			
			if(!alreadyListed[elTitle]) {
				if(elTitle != null) missingFields += '\n * ' + elTitle;
				else missingFields += '\n * ' + el.name;
				if(msg = el.getAttribute('emptywarning')) missingFields += ": "+ msg;
				
				alreadyListed[elTitle] = true;
			}	
		}
	}
	
	if(isMissing) {
		var message = formObj.getAttribute('validationErrorMessage');

		if(!message) message = 'Sorry, but you have to fill out all the marked fields before I can continue:';
		
		message += '\n' + missingFields;
		alert(message);		
		isMissing.focus();
		return false;
	}

	removeSelects();
	
	return true;
}

/*
 * Use this instead of checkRequired on a search form with subdropdowns
 */
function removeSelects() {
	// Don't send the hidden subdropdown fields
	var i;
	var allSelects = document.getElementsByTagName('select');
	for(i=0;i<allSelects.length;i++)
		if(allSelects[i].style.display == 'none') allSelects[i].removeAttribute('name');
	return true;	
}

function ccExpiryValidate(el) {
	// checks the input to ensure its a valid date.
	// Because the user types in a two digit year, 
	// it expects the current year to be between 2000 and 2100.
	
	el.baddata = false;
	if(el.value == "") return;
	el.value = el.value.replace(/[^0-9]/g,'');
	
	if (el.value.length != 4) {
		alert("The expiry date field must be entered in as 12/09 or 1209.");
	}
	
	month = el.value.substr(0,2);
	year = 2000 + parseInt(el.value.substr(2,2),10); // convert string to numeric (base10 :P )
	
	if (month > 12 || month == 0) {
		alert("The first two digits of the expiry date are supposed to be the month (01 to 12), however you entered " + month + ".");
		el.baddata = true;		
		return false;
	}
		
	var currentdate = new Date();	
	if (currentdate.getFullYear() > year) {
		alert("The expiry date you entered is prior to the current year.");
		el.baddata = true;		
		return false;
	}
		
	if  (currentdate.getFullYear() == year && currentdate.getMonth() +1 > month) {
		alert("You entered an expiry date which has recently expired.");
		el.baddata = true;		
		return false;
	}
	
	if (year - currentdate.getFullYear() > 10) {
		alert("You entered an expiry date more than ten years away.");
		el.baddata = true;		
		return false;
	}	
	
	return true;
}

function ccValidate(el) {
	// overhaul done by SVM, 4 June 2004, to provide issuer check, and a Luhn check which works on AmEx cards.
	el.baddata = false;
	if(el.value == "") return false;

	el.value = el.value.replace(/[^0-9]/g,'');
	
	if(el.value.substr(0,4) == "1234") {
		alert("Test CC# used, the server will not be contacted.");
		return;
	}
	
	// Verify issuer	
	var length = el.value.length;	
	var Issuer = '', lenmsg = '';
	var OneDigit = parseInt( el.value.substr(i,1) );
	var TwoDigit = parseInt( el.value.substr(i,2) );	
	var FourDigit= parseInt( el.value.substr(i,4) );
	
	if (OneDigit == '4') {
		Issuer = "Visa";
		if (! (length == 13 || length == 16)) lenmsg = "13 or 16";			
	
	} else if (TwoDigit >= 51 && TwoDigit <= 55) {
		Issuer = "Mastercard";
		if (length != 16) lenmsg = 16;			
	
	} else if (TwoDigit == 34 || TwoDigit == 37) {
		Issuer = "American Express";
		if (length != 15) lenmsg = 15;			
		
	} else if (FourDigit == 3088 || FourDigit == 3096 || FourDigit == 3112 || FourDigit == 3158 || FourDigit == 3337 || FourDigit == 3528) {
		Issuer = "JCBCard";
		if (length != 15) lenmsg = 15;			
		
	} else if (TwoDigit == 30 || TwoDigit == 36 || TwoDigit == 38) {
		Issuer = "Diners Club";
		if (length != 14) lenmsg = 14;			
	
	} else if (FourDigit == 6011) {
		Issuer = "DiscoverCard";
		if (length != 16) lenmsg = 16;			
	
	} else if (FourDigit == 2014 || FourDigit == 2149) {
		Issuer = "enRouteCard";
		if (length != 15) lenmsg = 15;			
	} else {		
		lenmsg = "The start of your card number prefix (" + FourDigit + ") does not appear to be a valid Visa, Mastercard, Amex, DinersClub, Discover, enRoutecard, or JCBCard.";		
		if (length < 13) lenmsg += "\n\nIn addition, a credit card is expected to have atleast 13 digits, but you typed in " + length + " digits.";
		alert(lenmsg);
				
		el.baddata = true;
		el.focus();
		return false;
	}
	
	if (lenmsg) {
		alert(Issuer + " credit cards are expected to be " + lenmsg + " digits long, but you typed in " + length + " digits.");
		el.focus();
		el.baddata = true;
		return false;
	}
	
	if(el.value.length != 13 && el.value.length != 15 && el.value.length != 16) {
		el.baddata = true;		
		alert("That number does not appear to be a valid length for a card");
		el.focus();
		return;
	}
	
	// Luhn check. Verifies the sum of digits in the number (with every second doubled, to a max of 9),
	// equal a multiple of ten.
	var i,product=0,factor=1,sum=0;		
	for(i=el.value.length-1;i >= 0;i--) {				
		product = factor * parseInt(el.value.substr(i,1));
		if (product > 9) product -= 9				
		sum += product						
		factor = 3 - factor; // alternate between 1 and 2.		
	}
		
	if(sum % 10 != 0) {
		el.baddata = true;		
		el.focus();
		alert("The credit card number you typed does not appear to be a valid " + Issuer + " number.");			
	}
}

/*
 * Returns true if the given tag is visible
 */
function isVisible(el) {
	var i;
	
	if(el.type == 'hidden') return false;
	
	// Detect a stretchy table
	if(el.name && el.name.indexOf('[]') > -1) stretchyTable = true;
	
	while(el.tagName != "BODY") {
		if(el.style.display == "none") return false;
		
		// Found a record in a table - blankrow is not required, provided that we have the minimum number of rows
		if(el.tagName.toLowerCase() == 'tr') {
			var minRows = el.parentNode.getAttribute('minrows');
			
			// No minimum row count
			if(!minRows) return false;

			// Get the row num
			var i, rowNum = 0;
			for(i=0;i<el.parentNode.childNodes.length;i++) {
				if(el.parentNode.childNodes[i].tagName == "tr" || el.parentNode.childNodes[i].tagName == "TR") rowNum++;
				if(el == el.parentNode.childNodes[i]) {
					break;
				}
			}
			if(rowNum > minRows) return false;
		}
		
		if(el.parentNode.tagName.toLowerCase() == 'tr' && el.innerHTML.indexOf('[4]') != -1) {
			el = el.parentNode;
			for(i=0;i < el.parentNode.childNodes.length;i++) {
				if(el == el.parentNode.childNodes[i]) break;
			}
		} else {
			el = el.parentNode;
		}
	}
	return true;	
}

// Returns true if a visible field is invalid or illegally empty
function isntOkay(el, isFilledOut, formObj, isOnBlur) {
	if(!isVisible(el)) return false;
	
	var elReq = el.getAttribute('required');
	var isEmptyField = false;
	if(isFilledOut != null) isEmptyField = (elReq && !isFilledOut[elReq]);
	else isEmptyField = elReq && !hasValue(el);
	if(isEmptyField) {
		var msg;
		if(isOnBlur && (msg = el.getAttribute('emptywarning'))) alert(el.getAttribute('humantitle') + ' is required: ' + msg);
		return true;
	}
	
	return isInvalid(el, formObj);
}


// Legacy function
function isEmpty(el, isFilledOut) {
	if(isFilledOut != null) return (elReq && !isFilledOut[elReq] && (isVisible(el) || el.getAttribute('requiredWhenHidden')));
	else return elReq && !hasValue(el) && (isVisible(el) || el.getAttribute('requiredWhenHidden'));
}

function isInvalid(el, formObj) {
	elValidation = el.getAttribute('validation');
	var elTitle = el.getAttribute('humantitle');
	if(!elTitle) elTitle = el.name;

	if (elValidation != null) switch (elValidation) {
		case 'password':
			if (passwordField(el) == false) return true;
			break;								
		
		case 'confirmpassword':
				var passwordValue 		 = formObj.Password.value;
				var confirmPasswordValue = el.value;
				
				if (passwordValue != confirmPasswordValue) {
					alert("The 'Password' and 'Confirm Password' fields are not the same. Please retype them identically.");							
					return true;
				}	
				break;

		case 'ccexpiry':
			if (ccExpiryValidate(el) == false) {
				return true;										
			}
			break;
			
		case 'cc':
			if (ccValidate(el) == false) {
				return true;										
			}
			break;
									
		case 'email':
			return !emailField(el);
			break;					
			
		case 'numeric':				
			if (!numericField(el)) return true;										
			break;
	
						
		case 'alphanumeric':
			if (alphanumeric(el) == false) {
				return true;										
			}
			break;
		
		case 'alphanumericwithunderscore':
			if (alphanumericWithUnderscore(el) == false) {
				return true;										
			}
			break;

		case 'file':
			if(!el.value) return false;
			var exts = el.getAttribute('allowedextensions'), extsText, i;
			if(exts) {
				exts = exts.split(',');
				extsText = '.' + exts.join(', .');
				extsText = extsText.replace(', .' + exts[exts.length-1], ' or .' + exts[exts.length-1]);

				if(el.value.match(/\.([^.]+)$/)) {
					var isOkay = false;
					var ext = RegExp.$1.toLowerCase();
					for(i=0;i<exts.length;i++)
						if(ext == exts[i]) { isOkay = true; break; }
						
					if(!isOkay) {
						alert('You are trying to upload a file of the wrong type.  You must upload a ' + extsText + ' file');
					}
						
				} else {
					alert('The file you have selected appears not to have an extension.  The filename must end in ' + extsText);
					return true;
				}
			}
			break;

		case 'currency':				
			if (!currencyField(el)) return true;										
			break;
														
		case '': alert("Incorrect FormSpec: Blank 'validation' provided for "+elTitle); break;
		default: alert('Sorry, support for validating fields as "'+elValidation+'" is not supported for "'+elTitle+'" in formops.js');
	}
}

/*
 * Show and hide fields depending on the status of checkboxes
 * Expects 
 */
function FieldList(formObj) {
	if(!(formObj = getFormObj(formObj))) return;
	
	var i, el, displayMe;
	for(i=0;i<formObj.elements.length;i++) {
		el = formObj.elements[i];
		if(el.parentNode != null) {
			el.parentNode.parentNode.style.display = isIncluded(el, formObj) ? "block" : "none";
		}
	}
}

function getFormObj(formObj) {
	if(formObj == ''){ formObj = null;}
	else if( formObj == null){ alert("You need to set a form name in the getFormObj function. For example getFormObj($FormName)"); }
		
	orig = formObj;
	try {
		if(typeof formObj == 'string'){
			if(document.getElementById(formObj)) {
				formObj = document.getElementById(formObj);
			} else {
				formObj = eval('document.' + formObj);
			}
		}
	} catch(er){		
		 formObj = null; 
	}
	
	
	// Detect mutiple forms using the same name, which means this function doesn't know what to return!
	if ( formObj.tagName != 'FORM' && formObj.length != null) {
		alert ( "SilverStripe has found " + formObj.length + " forms named " + orig + ", so form validation will not work.");	
		return null;
	}	
	
	if(formObj == null) formObj = document.GenericForm;
	if(formObj == null) {
		alert("SilverStripe cannot find a form named '" + (orig ? orig : 'GenericForm') + "' in the template used to create a form on this page. As a result, field-validation will not work. Ensure your form template contains <form ... name='$FormName' ... >.");
		return false;
	}

	if(formObj.elements == null || formObj.elements.length == null) formObj = null;
				
	return formObj;
}

/*
 * Returns true if the given element is included in the 'active' set of elements
 */
function isIncluded(el, formObj) {
	if(el.requiredin != null) {
		return eval('formObj.' + el.requiredin + '.checked');
	} else {
		return true;
	}
}



/*
 * Date entry field
 */
function date_keyDown(el, evt) {
	if(typeof event == "undefined") event = evt;
	event.returnValue = false;

	// Ignore Shift, Ctrl, Alt until pressed *with* something
	if(event.keyCode >= 16 && event.keyCode <= 18) return;

	var tr = document.selection.createRange();

	if(el.template.length > el.value.length) {
		var extraPart = el.template.substr(el.value.length);
		el.value += extraPart;
		tr.moveStart('character', -extraPart.length);
		tr.moveEnd('character', -extraPart.length);
	}

	// if we've not selected a character, select it
	if(tr.text.length == 0) tr.moveEnd('character',1)

	// if we have more than one character selected, select only the first one	
	// this doesn't apply to deleting text
	if(tr.text.length > 1 && event.keyCode != 46) tr.moveEnd('character', -tr.text.length + 1);

	scanCodes = Array();
	scanCodes[191] = "/?";
	scanCodes[220] = "\\|";
	scanCodes[186] = ";:";
	scanCodes[189] = "-_";
	
	var keyPressed;
	if(scanCodes[event.keyCode]) {
		keyPressed = event.shiftKey ? scanCodes[event.keyCode].substr(1,1) : scanCodes[event.keyCode].substr(0,1);
	} else if(event.keyCode == 96) {
		keyPressed = "0";
	} else if(event.keyCode > 96 && event.keyCode <= 105) {
		keyPressed = "" + (event.keyCode - 96);
	} else {
		keyPressed = String.fromCharCode(event.keyCode);
	}

	
	// Let tab,enter,arrowkeys,home,end and ctrl-**, ctrl-del, shift-del
	if(event.keyCode == 13 || event.keyCode == 9 || (event.keyCode >= 35 && event.keyCode <= 40)  
		|| event.ctrlKey || (event.shiftKey && (event.keyCode == 46 || event.keyCode == 45) ) ) {
		event.returnValue = true;

	// Backspace key 
	} else if(event.keyCode == 8) {
		tr.moveStart('character',-1);

		if(tr.text.length > 1) tr.moveEnd('character',-1);
		
		while(String("0123456789_").indexOf(tr.text) == -1) {
			tr.moveStart('character',-1);
			tr.moveEnd('character',-1);
		}
		tr.text = "_";
		tr.moveStart('character',-1);
		tr.moveEnd('character',-1);
		tr.select();

	// Delete key
	} else if(event.keyCode == 46) {
		tr.text = tr.text.replace(/[0-9]/g,'_');

	} else {
		date_insertCharacter(el, keyPressed, tr) 
	}
}

/*
 * Common to paste and keydown, insert a non-control character
 */
function date_insertCharacter(el, keyPressed, tr) {
	if(tr == null) {
		tr = document.selection.createRange();
		if(tr.text.length == 0) tr.moveEnd('character',1)
		if(tr.text.length > 1 && event.keyCode != 46) tr.moveEnd('character', -tr.text.length + 1);
		tr.select();
	}

	while(String("0123456789_").indexOf(tr.text) == -1) {
		tr.moveStart('character',1); tr.moveEnd('character',1);
	}

	// Numeric char
	if(String("0123456789").indexOf(keyPressed) != -1) {				
		if(tr.text.length > 0) tr.text = keyPressed;
	
		tr.moveEnd('character',1);
		while(String("0123456789_").indexOf(tr.text) == -1) {
			tr.moveStart('character',1); tr.moveEnd('character',1);
		}
		tr.select();

	// Separator char						
	} else if(String("-:/\\").indexOf(keyPressed) != -1) {		
		var tr_prev = tr.duplicate();
		tr_prev.moveStart('character',-1);
		tr_prev.moveEnd('character',-1);	

		// If the previous char wasn't a separator, move to after the next char
		var first = true;
		while(String("-:/\\").indexOf(tr_prev.text) == -1) {
			// Overwrite all the characters getting skipped with a _
			if(!first) {
				tr_prev.text = '_';
				tr_prev.moveStart('character',-1);
				tr_prev.select();
			}
			tr_prev.moveEnd('character',1);	
			tr_prev.moveStart('character',1);
			first = false;
		}
		
		// Select the character after that
		tr_prev.moveEnd('character',1);	
		tr_prev.moveStart('character',1);
		tr_prev.select();
	}
}
 
function date_paste(el, evt) {
	if(typeof event == "undefined") event = evt;
	event.returnValue = false;

	if(el == null) el = this;
	
	var tr = document.selection.createRange();

	if(el.template.length > el.value.length) {
		var extraPart = el.template.substr(el.value.length);
		el.value += extraPart;
		tr.moveStart('character', -extraPart.length);
		tr.moveEnd('character', -extraPart.length);
	}
	//if(tr.text.length == 0) tr = el.createTextRange();
	tr.select();
	
	var pasted = clipboardData.getData('text');
	var nextChar;
	while(pasted.length > 0) {
		nextChar = pasted.substr(0,1);		
		pasted = pasted.substr(1);
		date_insertCharacter(el, nextChar);
	}
	
	event.returnValue = false;
}

/***** COMBO BOX *****/

//----------------------------------------------------------------------
// global variables
//----------------------------------------------------------------------
var buffer, menu;

// -- Determine browser
var IE  = (document.all)? true: false;

//----------------------------------------------------------------------
// toggle/relocate menu on screen
//----------------------------------------------------------------------
function comboBox_toggle(ref, m) {
	menu = document.getElementById(m);
	if (menu.style.display == 'block') {
		menu.style.display = 'none';
		return;
	}

   // -- if we're here, relocate the menu object and make it visible
	var r = getRect(ref);

	/*
	if(menu.parentNode != document.body) {
		menu.parentNode.removeChild(menu);
		document.body.appendChild(menu);
	}	

	with (menu.style) {
   	left    = r.left + "px";
      top     = r.bottom + "px";
   }
  */

/*	menu.parentNode.removeChild(menu);
	ref.parentNode.appendChild(menu);
	ref.style.display = 'relative'; */

	with (menu.style) {
	   	left    ="0px";
      width   = ref.offsetWidth + "px";
      top	   = (ref.offsetHeight - 3) + "px";
      display = 'block';
	}
	

	// -- wouldn't hurt to save a reference to the active combo element
	menu.caller = ref;
}

function comboBox_blur(menu) {
	if(typeof menu == "string") menu = document.getElementById(menu);
	menu.timer = setTimeout('comboBox_close("' + menu.id + '")', 50);
}
function comboBox_focus(menu) {
	if(menu.timer) {
		clearTimeout(menu.timer);
		menu.timer = null;
	}
}

function comboBox_close(m) {
	menu = document.getElementById(m);
	if(menu.timer) {
		clearTimeout(menu.timer);
		menu.timer = null;
	}
	
	menu.style.display = 'none';
	return;
}


//----------------------------------------------------------------------
// search the menu based on the contents of input field t
//----------------------------------------------------------------------
function comboBox_searchList(ref, m) {
	var i;
   var menu = document.getElementById(m);


	// -- decloak if not already visible
   if (menu.style.display == 'none')
   	comboBox_toggle(ref, m);

	// -- loop over all options until we get a match
	for(i=0; i<menu.length; i++) {
		var opt = menu.options[i];
		if (ref.value != opt.text.substr(0, ref.value.length))
			continue;
		opt.selected = true;
		break;
	}
}


//----------------------------------------------------------------------
// Make menu invisible and take action on the selection
//----------------------------------------------------------------------
function comboBox_searchDone(menu) {
	menu.caller.value = menu.value; //menu.options[menu.selectedIndex].text;
	menu.style.display = 'none';
	if(menu.caller.onchange) menu.caller.onchange();
}


//----------------------------------------------------------------------
// borrowed from a future strange code
//----------------------------------------------------------------------
function getRect(obj) {
	rect = getPos(obj);
	// alert(rect.top);
	rect.bottom = rect.top  + obj.offsetHeight;
	rect.right  = rect.left + obj.offsetWidth;
	return rect;
}

var isIE = (navigator.userAgent.indexOf("MSIE") != -1);
var isNetscape = (navigator.userAgent.indexOf("Netscape") != -1);
function getPos(el) {
	if(isNetscape) return { left: el.offsetLeft, top: el.offsetTop };

/*	
	if(isIE && el.style.overflow == "auto") {
		return { left: el.scrollLeft, top: };
	}
*/	
	
	switch(el.tagName.toUpperCase()) {
		case "BODY": 
			return { left: 0, top: 0,  debug: "" };
		case "TR": case "TBODY": case "SCRIPT":
			return getPos(el.parentNode);
		default: 
			if(true || navigator.userAgent.indexOf('MSIE') > -1) {
				prnt = getPos(el.parentNode);			
				if(!el.className || el.className.indexOf('getPos_ignore') == -1) {
					// alert(el.tagName + ': ' + el.offsetTop);
					prnt.left += el.offsetLeft - el.scrollLeft; //  + el.clientLeft  ;
					prnt.top += el.offsetTop - el.scrollTop; // + el.clientTop ;
					prnt.debug += el.tagName + (el.className ? ("." + el.className) : "") + (el.id ? ("#" + el.id) : "") + ": " + el.offsetTop + ";  ";
				}
				return prnt;
			} else {
				prnt = Object();
				prnt.left = el.offsetLeft - el.scrollLeft;
				prnt.top = el.offsetTop - el.scrollTop;
				prnt.debug += el.tagName + (el.className ? ("." + el.className) : "") + (el.id ? ("#" + el.id) : "") + ": " + el.offsetTop + ";  ";
				return prnt;
			}
	}
}

function goForm(formName, code) {
	formObj = getFormObj(formName);
	if(formObj.action == "") formObj.action = "index.php";
	formObj.action += "?" + code + "=1";
	formObj.submit();
}

var eventHandlersChecked = false;
function checkEventHandlers(formObj) {
	if(eventHandlersChecked) return;	
	eventHandlersChecked = true;
	
	var i;
	for(i=0;i<formObj.elements.length;i++) {
		if(formObj.elements[i].tagName) {			
			if(! (formObj.elements[i].type == "submit" || formObj.elements[i].type == "button" || formObj.elements[i].tagName.toLowerCase() == "fieldset" || 
						formObj.elements[i].type == "textarea" || formObj.elements[i].tagName.toLowerCase() == "textarea")) {
				formObj.elements[i].onkeypress = enterToTab;
			}
		}
	}
}

function enterToTab(evt) {
	if(!evt) evt = event;

	if(evt.keyCode == 13) {
		// our very own 'tab' code
		evt.returnValue = false;
		var nextField = getNextElement(evt.srcElement ? evt.srcElement : evt.originalTarget);
		if(nextField != null) {
			if(nextField.type == 'submit') nextField.click();
				else nextField.focus();
		}			
		return false;
	}
}

function getNextElement(el) {
	if(el == null) return null;
	
	var formObj = el;
	while(formObj.tagName.toUpperCase() != "FORM") {		
		formObj = formObj.parentNode;
		if(formObj.tagName.toUpperCase() == "BODY") return null;
	}
	var good = false;
	
	for(var i=0;i<formObj.elements.length;i++) {
		// if good, only return a visible form element
		if(good) {
			if(formObj.elements[i].type != "hidden" && isVisible(formObj.elements[i]))
				return formObj.elements[i];
			
		// set the good flag, wait for the next visible form element
		} else if(formObj.elements[i] == el) {
			good = true;
		}
	}
	return null;
}

/*
 * Submit the given form, passing the given actioncode as a $_GET in the action URL
 */
function submitFormWithAction(formObj, code) {
	formObj.action = setGetVar(formObj.action, 'action', code);
	formObj.submit();
}
	
/*
 * Set the given get variable in the given URL, returning the updated URL
 * Both varName and varValue must have been previously urlencoded
 */
function setGetVar(formURL, varName, varValue) {
	formURL = formURL.replace(new RegExp("&" + varName + "=[^&]*"),'');
	formURL = formURL.replace(new RegExp("\\?" + varName + "=[^&]&*"),'?');
	formURL = formURL.replace(new RegExp("\\?" + varName + "=[^&]*"), '');
	
	formURL += (formURL.indexOf('?') != -1) ? '&' : '?';
	formURL += varName + "=" + varValue;
	
	return formURL;
}
 
/*
 * Allows a pair of dropdowns to be paired; the first one alters the options found in
 * the second, e.g. the first dropdown may list countries, the second one a number of
 * cities within a country. This function is called when the parent dropdown changes,
 * to update the child dropdown.
 */
var last_selectSubdropdown = Array();
function selectSubdropdown(el, subdropdownName ) {	
	getSubdropdownMap.cached = Object();
	
	if(el.name.match(/_subdropdown_[^_]+_([^_]+)_/)) var parentName = RegExp.$1;
	else var parentName = el.name;
	var formElName 	= '_subdropdown_' + parentName + "_" + subdropdownName + '_' + el.value.replace(' ','');
	var formEl 			= document.getElementById(formElName);		

	if (formEl) {
		formEl.style.display = 'block';			
		
		// When used in the SilverStripe CMS, we must perform the following check
		// TODO: Bypass this entirely within the CMS

		if(formEl.selectedIndex == -1 || typeof _RUNNING_CHANGERS_FROM_TAB == 'undefined' || !_RUNNING_CHANGERS_FROM_TAB) {
			formEl.selectedIndex = 0;
		}
	
		if(formEl.onchange) formEl.onchange();
	
	} else {
		formEl = document.getElementById('_subdropdown_' + parentName + '_' + subdropdownName + '_blankval');
		if(formEl) formEl.style.display = 'block';			
	}
	
	if (last_selectSubdropdown[parentName] && (!formEl || last_selectSubdropdown[parentName].name != formEl.name)) {
		last_selectSubdropdown[parentName].style.display = 'none';
		last_selectSubdropdown[parentName].value = '';
	}
		
	last_selectSubdropdown[parentName] = formEl;
}

/*
 * Generate (and cache) a map of all subdropdowns and their current parent fields
 */
function getSubdropdownMap(formObj) {
	if(!getSubdropdownMap.cached[formObj.name ? formObj.name : formObj.id]) {
		var generatedMap = Object();
		var parts, testName;
		
		var items = formObj.getElementsByTagName('select');
		for(i in items) {
			if(i != 'attributes' && items[i] && (testName = items[i].name) && (testName.substr(0,13) == '_subdropdown_') && items[testName]) {
				if(formObj.elements[testName].style && formObj.elements[testName].style.display != 'none') {
					parts = testName.substr(13).split('_');
					generatedMap[parts[1]] = testName;
				}
			}
		}
		
		getSubdropdownMap.cached[formObj.name ? formObj.name : formObj.id] = generatedMap;
	}
	return getSubdropdownMap.cached[formObj.name ? formObj.name : formObj.id];
}
getSubdropdownMap.cached = Object();

function enumerateObject(x) {
	var i,s="";
	for(i in x) s+= i + ": " + x[i] + "\n";
	return s;
}

/* 
 * Check whether the given subdropdown field should be visible or hidden
 */
function checkSubdropdown(el, formObj) {
	el.name.substr(13).match(/^([^_]+)_([^_]+)_(.*)$/);
	var parts = Array(RegExp.$1, RegExp.$2, RegExp.$3);
	
	var fieldName = parts[0], parentName = fieldName;
	
	if(!formObj.elements[fieldName])
		fieldName = getSubdropdownMap(formObj)[parts[0]];
//		alert(parts[2] + ' , ' + fieldName + ' , ' + formObj.elements[fieldName].value);

	// if(fieldName) Alert(el.name + ', ' + fieldName + ', ' + parentName);
	
	if(fieldName && formObj.elements[fieldName].value == parts[2]) {
		el.style.display = '';
		last_selectSubdropdown[parentName] = el;
	} else {
		el.style.display = 'none';
		
		// Try and find a blank subdropdown
		if(typeof last_selectSubdropdown[parentName] == 'undefined') {
			el.name.match(/(.*)_[^_]+$/);
			if((blankSubdropdownEl = document.getElementById(RegExp.$1 + '_blankval')) && blankSubdropdownEl.style.display == 'block') {
				last_selectSubdropdown[parentName] = blankSubdropdownEl;
			}
		}
	}
}



// Text Counter for SS interface text area's

function createTextCounter(el){
	var maxLength = parseInt(el.getAttribute('MAXLENGTH'));
	
	if(maxLength) {
		//create an input box to show the max characters
		if(!el.charBox) {
			el.charBox = document.createElement("DIV");
			el.charBox.style.padding = '2px';
			el.charBox.style.border = '1px #CCC solid';
			el.charBox.style.backgroundColor = '#FFFFA4';
	
			el.parentNode.appendChild(el.charBox)
		}
		
		textCounter(el)
	}

}

function removeTextCounter(el){
	var maxLength = parseInt(el.getAttribute('MAXLENGTH'));
	
	if(maxLength > 0 && maxLength < el.value.length)
		alert("You've written too much text.  I'm going to have to trim it to the following:\n\n" + el.value.substring(0, parseInt(maxLength)));
	
	//if((maxLength < el.value.length) && !confirm("You've written too much text.  I'm going to have to trim it to the following:\n\n" + el.value.substring(0, parseInt(maxLength)))) {
		
	//} else {
		// remove counter
		if( el.charBox != null ){
			el.parentNode.removeChild(el.charBox)
			el.charBox = null
		}
		
		// truncate it on blur
		el.value = el.value.substring(0, parseInt(maxLength))
	//}
}

var dontUpdateUntil = 0;
function textCounter(el) {
	if(new Date() < dontUpdateUntil) {
		// alert(dontUpdateUntil);
		return;
	}
	
	var maxLength = parseInt(el.getAttribute('MAXLENGTH'));
	if(maxLength > 0) {
 
		if(! el.charBox ){ createTextCounter(el) }
		if(! el.charBox ) alert('ra');

		charLeft = maxLength - el.value.length

		if( parseInt(charLeft) < 0 ){
			el.charBox.style.color = "red"
			if(el.charBox.innerHTML) el.charBox.innerHTML = '';
			el.charBox.appendChild(document.createTextNode(-charLeft + " characters too many! (Max. length: " + maxLength + ")"));
		} else {
			el.charBox.style.color = ""
			if(el.charBox.innerHTML) el.charBox.innerHTML = '';
			el.charBox.appendChild(document.createTextNode(charLeft + " characters to go (Max. length: " + maxLength  + ")"));
		}
	}

	dontUpdateUntil = new Date();
	dontUpdateUntil.setMilliseconds( dontUpdateUntil.getMilliseconds() + 500);
}


function inArray(val, arr) {
	var i;
	for(i in arr) {
		if(val == arr[i]) return i;
	}
	return null;
}

function formatAsDate( el ){
	if (el.value == "" ){ return false } // don't check if empty
	var msg = ""

	var days = Array(null, 'mon' , 'tue' , 'wed' , 'thu' , 'fri' , 'sat' , 'sun');
	var months = Array(null, 'jan' , 'feb' , 'mar' , 'apr' , 'may' , 'jun' , 'jul', 'aug' , 'sep' , 'oct' , 'nov' , 'dec' );

	var theDate = new Date()
	var testVal = el.value.toLowerCase().replace(/^ +/,'').replace(/ +$/,'');
	var dayNum;
	
	// Today
	if(testVal == "now" || testVal == "today") {
		
	// Tomorrow
	} else if(testVal == "tomorrow") {
		theDate.setHours(theDate.getHours() + 24);
		
		
	// Days of the week
	} else if(dayNum = inArray(testVal.substr(0,3), days)) {
		daysAway = dayNum - theDate.getDay()
		if( daysAway <= 0 ) daysAway += 7;
		theDate.setHours(daysAway * 24);
		
	// "next " + mon-fri
	} else if((testVal.substr(0,5) == "next ") && (dayNum = inArray(testVal.substr(5,3), days))) {
		daysAway = dayNum - theDate.getDay()
		if( daysAway <= 0 ) daysAway += 7;
		daysAway += 7;
		theDate.setHours(daysAway * 24);

	// X weeks
	} else if(testVal.match(/^([0-9]+) +w($|eek)/)) {
		var numWeeks = parseInt(RegExp.$1);

		theDate.setDate( theDate.getDate() + ( 7 * numWeeks ))
	// X months
	} else if(testVal.match(/^([0-9]+) +m($|onth)/)) {
		var numMonths = parseInt(RegExp.$1);

		theDate.setMonth( theDate.getMonth() + numMonths );

	// Date entered
	} else {
		var parts = testVal.replace(/^0+/,'').split(/[ -\/]+0?/);
		var isImplicitYear = false;
		
		if(parts.length == 2) {
			parts[2] = theDate.getFullYear();
			isImplicitYear = true;
		} else if(parts.length >= 3) parts[2] = parts[2].substr(0,4);
		
		if(parts.length != 3) msg = "You must provide a date as 'day month year', 'day/month/year', 'day month' or 'day/month'";

		if(!msg) {
			var newMonth;
			if(newMonth = inArray(parts[1].substr(0,3), months))
				parts[1] = newMonth;
				
			var i;
			for(i=0;i<parts.length;i++) if(isNaN(parseInt(parts[i]))) msg = "You have entered urecognised text '" + parts[i] + "'";
		}
		
		if(!msg) {
			parts[0] = parseInt(parts[0]);
			parts[1] = parseInt(parts[1]);
			parts[2] = parseInt(parts[2]);
			
			if(parts[0] < 1 || parts[0] > 31) msg = "The day must be between 1 and 31";
			else if(parts[1] < 1 || parts[1] > 12) msg = "The month must be between 1 and 12";
			else if(parts[2] < 1000) parts[2] += Math.floor(theDate.getFullYear() / 100) * 100;
	
			theDate.setFullYear( parseInt(parts[2]) );
			theDate.setMonth( parseInt(parts[1]) - 1);
			theDate.setDate( parseInt(parts[0]) );
			
			// If we've guessed the year, we should always choose the closest date
			if(isImplicitYear) {
				var diff1 = Math.abs(theDate - new Date());
				theDate.setFullYear(parseInt(parts[2]) + 1);
				var diff2 = Math.abs(theDate - new Date());
				if(diff1 < diff2) theDate.setFullYear(parseInt(parts[2]));
			}
			
		}
	}

	if( msg != "" ){
		alert(msg);
		el.focus();
		return false;

	} else {
		// Render theDate as a string
		var day = theDate.getDate();
		if(day < 10) day = "0" + day;
		var month = parseInt(theDate.getMonth()) + 1;
		if(month < 10) month = "0" + month;
		var year = theDate.getFullYear();

		el.value = day + "/" + month + "/" + year
		return true;
	}
}

// we must have a few of these floating around now..
function jsTrim(s) {
	
	while (s.substring(0,1) == ' ') {
		s = s.substring(1,s.length);
	}
	
	while (s.substring(s.length-1,s.length) == ' ') {
		s = s.substring(0,s.length-1);
	}
	return s;
}
