var BPDATEEDIT_INPUTFORMAT_EU = 1;
var BPDATEEDIT_INPUTFORMAT_UK = 1;
var BPDATEEDIT_INPUTFORMAT_US = 2;
var BPDATEEDIT_INPUTFORMAT_INVALID = 3;
var BPDATEEDIT_INPUTFORMAT_DEFAULT = 1;


/*
** This function gets the text currently selected in a
** text form element. It is based on an adaptation of the
** insertAtCursor() function below. See the commentary for
** that function for attribution and acknowledgement.
*/
function bpLineEdit_selectedText(element) {
	if(document.selection) {
		/* generally ie */
		element.focus();
		sel = document.selection.createRange();
		return sel.text;
	}
	else if(element.selectionStart || element.selectionStart == '0') {
		/* moz/netscape/others that support selectionStart/selectionEnd */
		return element.value.substring(element.selectionStart, element.selectionEnd);
	}
	else
		return '';
}


/*
** This function was sourced and enhanced from
**
** http://www.alexking.org/blog/2003/06/02/inserting-at-the-cursor-using-javascript/
**
** From what I can see on this page, I think it's OK to use this code (and only
** polite to thank Alex and PHYMyAdmin for it - thanks Alex and PHPMyAdmin). If not,
** please let me know (dodgie74@yahoo.co.uk) and I'll attribute/remove/modify it as
** required.
*/
function bpLineEdit_insertAtCursor( idOrObject, insertText ) {
	var object = idOrObjectToObject(idOrObject);
	if(!object) return;

	if(document.selection) {
		/* IE - won't support more than one textarea per form */
		object.focus();
		sel = document.selection.createRange();
		sel.text = insertText;
	}
	else if(object.selectionStart || object.selectionStart == "0") {
		/* moz/netscape/others that support selectionStart/selectionEnd */
		var start = object.value.substring(0, object.selectionStart);
		var end = object.value.substring(object.selectionEnd, object.value.length);
		object.value = start + insertText + end;
	}
	else {
		/* others that don't support cursor position */
		object.value += insertText;
	}
}


/* deprecated - use bpLineEdit_insertAtCursor() instead */
function insertAtCursor(object, insertText) { return bpLineEdit_insertAtCursor(object, insertText); }


/*
** encase the current selection with a pre- and post-text
**
** useful for surrounding the selection in HTML tags, for
** example
*/
function bpLineEdit_encloseSelection(idOrObject, preText, postText) {
	var object = idOrObjectToObject(idOrObject);
	if(object) insertAtCursor(object, preText + bpLineEdit_selectedText(object) + postText);
}


/* deprecated - use bpLineEdit_encloseSelection */
function encloseSelection(idOrObject, preText, postText) { return bpLineEdit_encloseSelection(idOrObject, preText, postText); }


/*
** sets the value of the specified element to the current date
**
** requires padNumber() from bpCommon.js
*/
function setToNow( idOrObject ) {
	var e = idOrObjectToObject(idOrObject);

	if(e) {
		var d = new Date();
		e.value = padNumber(d.getDate(), 2) + '/' + padNumber(d.getMonth() + 1, 2) + '/' + String(d.getFullYear());
	}
}


function setValue( idOrObject, v ) {
	var e = idOrObjectToObject(idOrObject);
	if(e) e.value = String(v);
}


function bpValidateCharacters( v, validationList, notInList ) {
	if(!v) return "";
	var c;
	
	for(var i = 0; i < v.length; i++) {
		c = v.charAt(i);
		
		if((validationList.indexOf(c) == -1) != notInList)
			return false;
	}
	
	return true;
}


function bpFormElement_setAlert( idOrObject, msg ) {
	var object = idOrObjectToObject(idOrObject);
	var id = object.id;
	var alertElement = document.getElementById("bpformelement_" + id + "_alert");
	
	if(!alertElement || !alertElement.style)
		return false;

	if((typeof msg) == "string") {
		alertElement.style.display = "inline";
		alertElement.title = msg;
	}
	else {
		alertElement.style.display = "none";
		alertElement.title = "";
	}

	return true;
}


function bpFormElement_getAlert( id ) {
	var alertElement = document.getElementById("bpformelement_" + id + "_alert");
	
	if(!alertElement)
		return null;

	if(alertElement.title && (typeof alertElement.title) == "string") return alertElement.title;
	return "";
}


function bpSetFormStatusMessage( id, msg ) {
	var statusElement = document.getElementById("bpform_" + id + "_statusarea");

	if(statusElement) {
		if((typeof msg) == "string") {
			if(msg != statusElement.innerHtml) {
				statusElement.innerHTML = msg;
				if($ && msg != "") new Effect.Highlight($(statusElement.id + "_container"), {queue: {position: "end", scope: id, limit: 1}, duration: 0.5});
			}
		}
		else
			statusElement.innerHTML = "";
		
		return true;
	}
	//else
	//	alert("unable to find the status area for the form with id " + id);
	
	return false;
}


/*
 * bpLineEdit
 */
function bpLineEdit_onkeypress( idOrObject, event, chars, notInList ) {
	var object = idOrObjectToObject(idOrObject);
	
	if(object) {
		var c = event.charCode ? event.charCode : (event.keyCode ? event.keyCode : (event.which ? event.which : 0));

		/* allow through tabs, returns etc. */
		if(c < 13) return true;
		c = String.fromCharCode(c);
		
		if(!bpValidateCharacters(c, chars, notInList)) {
			bpFormElement_setAlert(object, "Your input \"" + c + "\" was not valid.");
			return false;
		}
		else {
			bpFormElement_setAlert(object, null);
			return true;
		}
	}
	
	return false;
}


function bpLineEdit_onchange( idOrObject, chars, notInList ) {
	var object = idOrObjectToObject(idOrObject);
	
	if(object && (object.type == "text" || object.type == "textarea")) {
		if(!object.lastKnownGoodValue) object.lastKnownGoodValue = object.defaultValue;
		
		/* always allow empty */
		if(object.value.length > 0 && !bpValidateCharacters(object.value, chars, notInList)) {
			bpFormElement_setAlert(object, "\"" + object.value + "\" is not valid");
		}
		else {
			bpFormElement_setAlert(object, null);
			object.lastKnownGoodValue = object.value;
		}
		
		return object.value;
	}

	return "";
}


/*
 * bpListBox
 */
function bplistbox_selectall( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	
	if(typeOf(object) != "object") {
		alert("could not find list object to select all (given id or object " + idOrObject + ")");
		return false;
	}

	var id = object.id;
	var i = 0;
	var item = null;

	if(object.nodeName == "SELECT") {
		/* bpListBox */
		for(i; i < object.options.length; i++)
			object.options[i].selected = true;
	}
	else if(object.nodeName == "UL" && object.className == "bplibrary_checkablelistbox") {
		/* bpCheckableListBox */
		while(item = document.getElementById(id + "_option_" + (i++)))
			item.checked = true;
	}
	
	return true;
}


function bplistbox_selectnone( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	
	if(typeOf(object) != "object" || object.nodeName != "UL") {
		alert("could not find list object to select all (given id or object " + idOrObject + ")");
		return false;
	}

	var id = object.id;
	var i = 0;
	var item = null;
	
	while(item = document.getElementById(id + "_option_" + (i++)))
		item.checked = false;
	
	return true;
}


/* get the minimum number of selections allowed in a list box. -1 means no minimum */
function bpListBox_minSelections( object ) {
	object = document.getElementById("bplistbox_" + object.id + "_min");
	
	if(!object)
		return -1;
	
	return parseInt(object.value);
}


/* get the maximum number of selections allowed in a list box. -1 means no maximum */
function bpListBox_maxSelections( object ) {
	object = document.getElementById("bplistbox_" + object.id + "_max");
	
	if(!object)
		return -1;
	
	return parseInt(object.value);
}


/* count the number of options selected in a list box */
function bpListBox_selectionCount( object ) {
	var n = 0;
	
	for(var i = 0; i < object.options.length; i++)
		if(object.options[i].selected) n++;

	return n;
}


/* function that actually does the work for event callbacks for list boxes and checkable
   list boxes */
function bpListBox_onchange_real( idOrObject, countCallback ) {
	var object = idOrObjectToObject(idOrObject);
	if(!object) return;
	var min = bpListBox_minSelections(object);
	var max = bpListBox_maxSelections(object);
	
	if(min > -1 || max > -1) {
		var n = countCallback(object);
		
		/* TODO make the OOBMSG configurable */
		if((min > -1 && n < min) || (max > -1 && n > max))
			bpFormElement_setAlert(object, "You must select" + ((min > -1) ? (" at least " + min + ((max > -1) ? " and" : "")) : "") + ((max > -1) ? (" no more than " + max) : "") + " items from the list.");
		else
			bpFormElement_setAlert(object, null);
	}
}


/* event callback for onchange on list box elements */
function bpListBox_onchange( idOrObject ) {
	bpListBox_onchange_real(idOrObject, bpListBox_selectionCount);
}


/*
** bpCheckableListBox
*/
/* event callback for onchange on checkable list box elements */
function bpCheckableListBox_onchange( idOrObject ) {
	bpListBox_onchange_real(idOrObject, bpCheckableListBox_selectionCount);
}


/* count the number of options selected in a checkable list box */
function bpCheckableListBox_selectionCount( object ) {
	var n = 0;
	
	for(var i = 0; i < object.childNodes.length; i++) {
		var cb = document.getElementById(object.id + "_option_" + i);
		if(cb && cb.nodeName == "INPUT" && cb.type == "checkbox" && cb.checked)
			n++;
	}

	return n;
}


function bpcheckablelistbox_ontoggle( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeOf(object) != "object" || !object.type || object.type != "checkbox") {
		//alert("Object with which ontoggle called was not valid: " + object.type);
		return false;
	}

	var listObject = document.getElementById("bpcheckablelistbox_" + object.id + "_listitem");
	
	if(typeOf(listObject) != "object" || listObject.nodeName != "LI") {
		//alert("could not find list item for toggled item: id=" + object.id + ", list item: " + listObject + (listObject ? " nodeName = " + listObject.nodeName : ""));
		return false;
	}

	if(object.checked) {
		listObject.className = "selected";
	}
	else {
		listObject.className = "";
	}
}


/* this function enables all options present in the day and month combo
 * boxes for a date edit object. */
function bpdateedit_enableAllDaysAndMonths(object) {
	var id = object.id;
	var month = document.getElementById("bpdateedit_" + id + "_month");
	var day = document.getElementById("bpdateedit_" + id + "_day");
	
	if(month) {
		for(var i = 0; i < month.options.length; i++)
			month.options[i].disabled = false;
	}

	if(day) {
		for(var i = 0; i < day.options.length; i++)
			day.options[i].disabled = false;
	}
}


function bpDateEdit_emptyAllowed( idOrObject ) {
	var o = idOrObjectToObject(idOrObject);
	if(!o) return false;
	o = document.getElementById("bpdateedit_" + o.id + "_allowempty");
	return (o && "1" == o.value);
}


function bpDateEdit_clear( idOrObject ) {
	var o = idOrObjectToObject(idOrObject);
	if(o && bpDateEdit_emptyAllowed(o)) {
		var day = document.getElementById("bpdateedit_" + o.id + "_day");
		var month = document.getElementById("bpdateedit_" + o.id + "_month");
		var year = document.getElementById("bpdateedit_" + o.id + "_year");
		
		if(day && month && year) {
			day.selectedIndex = month.selectedIndex = year.selectedIndex = 0;
			o.value = "";
		}
	}
}


/* this function does not honour the date range restriction for
 * the widget. it must therefore be called before adjustForLimits()
 * otherwise it could re-enable dates that should be disabled */
function bpdateedit_setDaysForMonth( object ) {
	var id = object.id;
	var year = document.getElementById("bpdateedit_" + id + "_year");
	var month = document.getElementById("bpdateedit_" + id + "_month");
	var day = document.getElementById("bpdateedit_" + id + "_day");

	if(!year || !month || !day)
		return;

	var days = maxDaysForMonth(parseInt(month.value, 10), parseInt(year.value, 10));

	/* if empty is allowed, each combo has an extra "--" item at the start, so the actual day, month
	 * etc. indices are one higher */
	var indexOffset = (bpDateEdit_emptyAllowed(object) ? 1 : 0);
	
	if(parseInt(day.value, 10) > days) day.selectedIndex = days + indexOffset - 1;

	for(var i = 0; i < days; i++)
		day.options[i + indexOffset].disabled = false;

	for(i = days; i < 31; i++)
		day.options[i + indexOffset].disabled = true;
}


/* this function assumes that all dates are enabled. it will only
 * disable dates outside the range limit, it will not ensure that
 * dates inside the range limit are enabled. this is done so that
 * it does not re-enable dates that have been disabled by
 * setDaysForMonth(). */
function bpdateedit_adjustForLimits( object ) {
	var id = object.id;
	var year = document.getElementById("bpdateedit_" + id + "_year");
	var month = document.getElementById("bpdateedit_" + id + "_month");
	var day = document.getElementById("bpdateedit_" + id + "_day");
	var minDate = document.getElementById("bpdateedit_" + id + "_mindate");
	var maxDate = document.getElementById("bpdateedit_" + id + "_maxdate");

	if(!year || !month || !day || (!minDate && !maxDate))
		return;

	if(minDate) {
		var date = new Date();
		date.setDate(parseInt(minDate.value.substring(6,8), 10));
		date.setMonth(parseInt(minDate.value.substring(4,6), 10) - 1);	/* setMonth() expects 0-11 not 1-12 */
		date.setYear(parseInt(minDate.value.substring(0,4), 10));
		
 		if(date.getFullYear() == parseInt(year.value, 10))
 		{
			/* if empty is allowed, each combo has an extra "--" item at the start, so the actual day, month
			* etc. indices are one higher */
			var indexOffset = (bpDateEdit_emptyAllowed(object) ? 1 : 0);
			
			for(var i = 0; i < date.getMonth(); i++)
				month.options[i + indexOffset].disabled = true;
				
 			if(date.getMonth() == parseInt(month.value, 10) - 1)
 			{
				/* disable any invalid days */
				for(i = 0; i < date.getDate() - 1; i++)
					day.options[i + indexOffset].disabled = true;
			}
		}
	}
	
	if(maxDate) {
		var date = new Date();
		date.setDate(parseInt(maxDate.value.substring(6,8), 10));
		date.setMonth(parseInt(maxDate.value.substring(4,6), 10) - 1);	/* setMonth() expects 0-11 not 1-12 */
		date.setYear(parseInt(maxDate.value.substring(0,4), 10));
		
		if(date.getFullYear() == parseInt(year.value, 10)) {
			/* if empty is allowed, each combo has an extra "--" item at the start, so the actual day, month
			* etc. indices are one higher */
			var indexOffset = (bpDateEdit_emptyAllowed(object) ? 1 : 0);
			
			/* disable any invalid months */
			for(var i = date.getMonth() + 1; i < 12; i++)
				month.options[i + indexOffset].disabled = true;
				
			if(date.getMonth() == month.selectedIndex) {
				/* disable any invalid days */
				for(i = date.getDate(); i < 31; i++)
					day.options[i + indexOffset].disabled = true;
			}
		}
	}
}


/* receives the "base" id or object reference for the bpDateEdit
 * element, not the id or object reference for the month combo */
function bpDateEdit_month_onblur( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeof object != "object" || !object.type || object.type != "hidden")
		return;

	bpdateedit_enableAllDaysAndMonths(object);
	bpdateedit_setDaysForMonth(object);
	bpdateedit_adjustForLimits(object);
	
	/* propagate blur event to bpDateEdit general event handler */
	bpDateEdit_onblur(object);
}


/* receives the "base" id or object reference for the bpDateEdit
 * element, not the id or object reference for the month combo */
function bpDateEdit_year_onblur( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeof object != "object" || !object.type || object.type != "hidden")
		return;

	bpdateedit_enableAllDaysAndMonths(object);
	bpdateedit_setDaysForMonth(object);
	bpdateedit_adjustForLimits(object);
	
	/* propagate blur event to bpDateEdit general event handler */
	bpDateEdit_onblur(object);
}


function bpcalendar_toggle( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeof object != "object" || !object.type || object.type != "hidden") {
		cancelEvent(e);
		return;
	}

	var id = object.id;
	var calendarWidget = document.getElementById("bpdateedit_" + id + "_calendarwidget");
	var toggleButton = document.getElementById("bpdateedit_" + id + "_togglecalendar");

	if(calendarWidget && calendarWidget.style) {
		if(calendarWidget.style.display == "none") {
			calendarWidget.style.display = "";
			if(toggleButton) toggleButton.title = "Close popup calendar";
		}
		else {
			calendarWidget.style.display = "none";
			if(toggleButton) toggleButton.title = "Open popup calendar";
		}
	}
}


function bpCalendar_inputFormat( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	var id = object.id;
	var formatElement = document.getElementById("bpdateedit_" + id + "_inputformat");
	
	if(formatElement) {
		var format = parseInt(formatElement.value, 10);
		if(format > 0 && format < BPDATEEDIT_INPUTFORMAT_INVALID) return format;
	}
	
	return BPDATEEDIT_INPUTFORMAT_DEFAULT;
}


function bpCalendarEdit_disable( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	var id = object.id;

	object = document.getElementById("bpdateedit_" + id + "_display");
	if(object) object.disabled = true;
	
	object = document.getElementById("bpdateedit_" + id + "_togglecalendar");
	
	if(object && "IMG" == object.nodeName) {
		object.onclick = "";
		object.onmouseover = "";
		object.onmouseout = "";
		object.style.cursor = "";
	}

	object = document.getElementById("bpdateedit_" + id + "_clear");
	
	if(object && "IMG" == object.nodeName) {
		object.onclick = "";
		object.onmouseover = "";
		object.onmouseout = "";
		object.style.cursor = "";
	}
	
	document.getElementById("bpdateedit_" + id + "_calendarwidget").style.display = "none";
}


function bpCalendarEdit_enable( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	var id = object.id;

	object = document.getElementById("bpdateedit_" + id + "_display");
	if(object) object.disabled = false;
	
	object = document.getElementById("bpdateedit_" + id + "_togglecalendar");
	
	if(object && "IMG" == object.nodeName) {
		object.onclick = function(){bpcalendar_toggle(id);};
		object.onmouseover = function(){bpRollImage("bpdateedit_" + id + "_togglecalendar");};
		object.onmouseout = function(){bpRollImage("bpdateedit_" + id + "_togglecalendar");};
		object.style.cursor = "pointer";
	}

	object = document.getElementById("bpdateedit_" + id + "_clear");
	
	if(object && "IMG" == object.nodeName) {
		object.onclick = function(){bpCalendarEdit_clear(id);};
		object.onmouseover = function(){bpRollImage("bpdateedit_" + id + "_clear");};
		object.onmouseout = function(){bpRollImage("bpdateedit_" + id + "_clear");};
		object.style.cursor = "pointer";
	}
	
	document.getElementById("bpdateedit_" + id + "_calendarwidget").style.display = "none";
}


/*
 * checks a date for a bpdateedit object. returns 0 if date is ok,
 * 1 if date is not valid, 2 if date is out of bounds. does not
 * alter the value of the date object.
 */
function bpdateedit_checkDate( idOrObject, day, month, year ) {
	var object = idOrObjectToObject(idOrObject);
	if(!bpDateEdit_emptyAllowed(object) || (day != -1 && month != -1 && year != -1)) {
		if(!isValidDate(day, month, year))
			return 1;
	}
	else {
		return 0;	/* for now, empty date accepts anything */
	}

	/* is valid date, check bounds */
	var id = object.id;
	var min = document.getElementById("bpdateedit_" + id + "_mindate");
	var max = document.getElementById("bpdateedit_" + id + "_maxdate");
	var newdate = parseInt("" + year + padNumber(month, 2) + padNumber(day, 2), 10);

	if((min && (newdate < parseInt(min.value, 10))) || (max && (newdate > parseInt(max.value, 10))))
		return 2;

	return 0;
}


function bpcalendar_yearChanged( idOrObject ) {
	bpcalendar_calendarChanged(idOrObject);
}


function bpcalendar_monthChanged( idOrObject ) {
	bpcalendar_calendarChanged(idOrObject);
}


function bpcalendar_dayClicked( idOrObject, day ) {
	var object = idOrObjectToObject(idOrObject);
	var id = object.id;
	document.getElementById("bpdateedit_" + id + "_day").value = day;
	bpcalendar_calendarChanged(idOrObject);
}


function bpcalendar_formatHint( idOrObject ) {
	switch(bpCalendar_inputFormat(idOrObject)) {
		default:
		case BPDATEEDIT_INPUTFORMAT_EU:
			return "EU format (DD/MM/YYYY).";
			break;

		case BPDATEEDIT_INPUTFORMAT_UK:
			return "UK format (DD/MM/YYYY).";
			break;

		case BPDATEEDIT_INPUTFORMAT_US:
			return "US format (MM/DD/YYYY).";
			break;
	}
}


function bpcalendar_displayEdited( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	var id = object.id;
	var displayObject = document.getElementById("bpdateedit_" + id + "_display");

	if(!displayObject) {
		var alertMessage = "Internal error: could not find the calendar widget to update.";
		if(!bpFormElement_setAlert(object, alertMessage)) alert(alertMessage);
		bpcalendar_updateDisplay(object);	/* reset display to previous setting */
		return;
	}
	else if(0 == displayObject.value.length && bpDateEdit_emptyAllowed(object)) {
		return;
	}
	else if(displayObject.value.length != 10) {
		var alertMessage = "The date you entered is not valid. Make sure you enter it in " + bpcalendar_formatHint(object);
		if(!bpFormElement_setAlert(object, alertMessage)) alert(alertMessage);
		bpcalendar_updateDisplay(object);	/* reset display to previous setting */
		return;
	}
	else if(displayObject.value.substring(2,3) != "/" || displayObject.value.substring(5,6) != "/") {
		var alertMessage = "The date you entered is not valid. Make sure you enter it in " + bpcalendar_formatHint(object);
		if(!bpFormElement_setAlert(object, alertMessage)) alert(alertMessage);
		bpcalendar_updateDisplay(object);	/* reset display to previous setting */
		return;
	}
	
	var day = 0;
	var month = 0;
	var year = 0;
	var inputFormat = bpCalendar_inputFormat(object);
	switch(inputFormat) {
		default:
		case BPDATEEDIT_INPUTFORMAT_EU:
		case BPDATEEDIT_INPUTFORMAT_UK:
			day = parseInt(displayObject.value.substring(0,2), 10);
			month = parseInt(displayObject.value.substring(3,5), 10);
			year = parseInt(displayObject.value.substring(6,10), 10)
			break;
			
		case BPDATEEDIT_INPUTFORMAT_US:
			month = parseInt(displayObject.value.substring(0,2), 10);
			day = parseInt(displayObject.value.substring(3,5), 10);
			year = parseInt(displayObject.value.substring(6,10), 10)
			break;
	}

	if(bpdateedit_checkDate(object, day, month, year)) {
		var alertMessage = "The date you entered is not valid. Make sure you enter it in " + bpcalendar_formatHint(object);
		if(!bpFormElement_setAlert(object, alertMessage)) alert(alertMessage);
		bpcalendar_updateDisplay(object);	/* reset display to previous setting */
		return;
	}
	
	var dayElement = document.getElementById("bpdateedit_" + id + "_day");
	var monthCombo = document.getElementById("bpdateedit_" + id + "_month");
	var yearCombo = document.getElementById("bpdateedit_" + id + "_year");
	dayElement.value = "" + day;
	
	for(var i = 0; i < yearCombo.options.length; i++) {
		if(yearCombo.options[i].text == year)
			break;
	}
	
	yearCombo.selectedIndex = i;
	monthCombo.selectedIndex = month - 1;
	bpcalendar_calendarChanged(object);
}


function bpcalendar_calendarChanged( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	var id = object.id;
	var day = parseInt(document.getElementById("bpdateedit_" + id + "_day").value, 10);
	var monthCombo = document.getElementById("bpdateedit_" + id + "_month");
	var month = monthCombo.selectedIndex + 1;
	var year = parseInt(document.getElementById("bpdateedit_" + id + "_year").value, 10);
	
	switch(bpdateedit_checkDate(object, day, month, year)) {
		/* invalid */
		case 1:
			var alertMessage = "The date " + day + " " + monthCombo.options[month - 1].text + " " + year + " is not valid."
			
			if(!bpFormElement_setAlert(object, alertMessage))
				alert(alertMessage);

			if(!object.lastKnownGoodValue)
				object.lastKnownGoodValue = document.getElementById("bpdateedit_" + id + "_default").value;

			object.value = object.lastKnownGoodValue;
			return;
			
		/* out of bounds */
		case 2:
			/* check for a custom out-of-bounds message (oobmsg) */
			var oobmsg = document.getElementById("bpdateedit_" + id + "_oobmsg");
			if(oobmsg && oobmsg.value) oobmsg = oobmsg.value;

			if(oobmsg == null || oobmsg == "") {
				var min = document.getElementById("bpdateedit_" + id + "_mindate");
				var max = document.getElementById("bpdateedit_" + id + "_maxdate");

				if(min) min = "" + parseInt(min.value.substring(6, 8), 10) + " " + monthCombo.options[parseInt(min.value.substring(4, 6), 10) - 1].text + " " + min.value.substring(0,4);
				if(max) max = "" + parseInt(max.value.substring(6, 8), 10) + " " + monthCombo.options[parseInt(max.value.substring(4, 6), 10) - 1].text + " " + max.value.substring(0,4);
				oobmsg = "The date " + day + " " + monthCombo.options[month - 1].text + " " + year + " is out of bounds: ";

				if(min && max)
					oobmsg = oobmsg + "it must be between " + min + " and " + max + " inclusive.";
				else if(min)
					oobmsg = oobmsg + "it must be after " + min.value + ".";
				else
					oobmsg = oobmsg + "it must be before " + max.value + ".";
			}

			if(!bpFormElement_setAlert(object, oobmsg))
				alert(oobmsg);
			
			//alert("Object last known good value is " + object.lastKnownGoodValue);
			if(!object.lastKnownGoodValue)
				object.lastKnownGoodValue = document.getElementById("bpdateedit_" + id + "_default").value;

			object.value = object.lastKnownGoodValue;
			//bpcalendar_toggle(id);
			return;
	}
	
	bpcalendar_setDate(object, day, month, year);
}


function bpcalendar_updateDisplay( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeof object != "object" || !object.type || object.type != "hidden") {
		var oobmsg = "Internal error: could not find the form element in which to set the date.";
		if(!bpFormElement_setAlert(object, oobmsg)) alert(oobmsg);
		//bpcalendar_toggle(object);
		return;
	}

	var id  = object.id;
	var displayObject = document.getElementById("bpdateedit_" + id + "_display");

	if(displayObject) {
		switch(bpCalendar_inputFormat(object)) {
			default:
			case BPDATEEDIT_INPUTFORMAT_EU:
			case BPDATEEDIT_INPUTFORMAT_UK:
				displayObject.value = object.value.substring(6, 8) + "/" + object.value.substring(4, 6) + "/" + object.value.substring(0, 4);
				break;
				
			case BPDATEEDIT_INPUTFORMAT_US:
				displayObject.value = object.value.substring(4, 6) + "/" + object.value.substring(6, 8) + "/" + object.value.substring(0, 4);
				break;
		}
	}
}

function bpCalendarEdit_clear( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	
	if(typeof object != "object" || !object.type || object.type != "hidden") {
		var oobmsg = "Internal error: could not find the form element in which to set the date.";

		if(!bpFormElement_setAlert(object, oobmsg))
			alert(oobmsg);

		bpcalendar_toggle(object);
		return;
	}

	if(bpDateEdit_emptyAllowed(object)) {
		var displayObject = document.getElementById("bpdateedit_" + object.id + "_display");
		bpFormElement_setAlert(object, null);
		object.value = object.lastKnownGoodValue = "";
		displayObject.value = "";
	}
}


function bpcalendar_setDate( idOrObject, day, month, year ) {
	var object = idOrObjectToObject(idOrObject);
	
	if(typeof object != "object" || !object.type || object.type != "hidden") {
		var oobmsg = "Internal error: could not find the form element in which to set the date.";

		if(!bpFormElement_setAlert(object, oobmsg))
			alert(oobmsg);

		bpcalendar_toggle(object);
		return;
	}

	var newDate = "";
	
	if(day != -1 && month != -1 && year != -1) {
		newDate = "" + padNumber(year, 4) + padNumber(month, 2) + padNumber(day, 2);
	}
	
	bpFormElement_setAlert(object, null);
	object.value = object.lastKnownGoodValue = newDate;
	bpcalendar_updateDisplay(object);
}


function bpcalendar_previousMonth( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	
	if(typeof object != "object" || !object.type || object.type != "hidden")
		return;

	var id = object.id;
	var month = document.getElementById("bpdateedit_" + id + "_month");

	if(!month)
		return;

	if(month.selectedIndex == 0) {
		month.selectedIndex = 11;
		bpcalendar_previousYear(object);
	}
	else
		month.selectedIndex--;
}


function bpcalendar_nextMonth( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	
	if(typeof object != "object" || !object.type || object.type != "hidden")
		return;

	var id = object.id;
	var month = document.getElementById("bpdateedit_" + id + "_month");

	if(!month)
		return;

	if(month.selectedIndex == 11) {
		month.selectedIndex = 0;
		bpcalendar_nextYear(object);
	}
	else
		month.selectedIndex++;
}


function bpcalendar_previousYear( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	
	if(typeof object != "object" || !object.type || object.type != "hidden")
		return;

	var id = object.id;
	var year = document.getElementById("bpdateedit_" + id + "_year");

	if(!year)
		return;

	if(year.selectedIndex > 0)
		year.selectedIndex--;
}


function bpcalendar_nextYear( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	
	if(typeof object != "object" || !object.type || object.type != "hidden")
		return;

	var id = object.id;
	var year = document.getElementById("bpdateedit_" + id + "_year");

	if(!year)
		return;

	if(year.selectedIndex < (year.options.length - 1))
		year.selectedIndex++;
}


function bpDateEdit_onblur( idOrObject ) {
	var o = idOrObjectToObject(idOrObject);
	if(!o) return;
	var empty = bpDateEdit_emptyAllowed(o);
	var day = document.getElementById("bpdateedit_" + o.id + "_day");
	var month = document.getElementById("bpdateedit_" + o.id + "_month");
	var year = document.getElementById("bpdateedit_" + o.id + "_year");
	if(!day || !month || !year) return;
	day = (day.value == -1 && empty ? "  " : padNumber(day.value, 2));
	month = (month.value == -1 && empty ? "  " : padNumber(month.value, 2));
	year = (year.value == -1 && empty ? "    " : padNumber(year.value, 4));
	o.value = o.lastKnownGoodValue = "" + year + month + day;
}


function bpTimeEdit_clear( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	
	if(typeof object != "object" || !object.type || object.type != "hidden")
		return;

	var id = object.id;
	
	if(bpDateEdit_emptyAllowed(object)) {
		object.value = object.lastKnownGoodValue = "";
		var hour = document.getElementById("bptimeedit_" + id + "_hour");
		var minute = document.getElementById("bptimeedit_" + id + "_minute");
		var second = document.getElementById("bptimeedit_" + id + "_second");
		var millisecond = document.getElementById("bptimeedit_" + id + "_millisecond");
		
		hour.value = minute.value = "";
		if(second) minute.value = "";
		if(millisecond) millisecond.value = "";
		bpFormElement_setAlert(id, null);
	}
	else
		bpFormElement_setAlert(id, "This time field may not be left empty.");
}


function bpTimeEdit_onblur( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeof object != "object" || !object.type || object.type != "hidden")
		return;

	var id = object.id;
	var hour = document.getElementById("bptimeedit_" + id + "_hour");
	var minute = document.getElementById("bptimeedit_" + id + "_minute");
	var second = document.getElementById("bptimeedit_" + id + "_second");
	var millisecond = document.getElementById("bptimeedit_" + id + "_millisecond");
	var minTime = document.getElementById("bptimeedit_" + id + "_mintime");
	var maxTime = document.getElementById("bptimeedit_" + id + "_maxtime");
	
	emptyAllowed = bpDateEdit_emptyAllowed(object);
	
	var newTime = "" + (emptyAllowed && hour.value == "" ? "  " : padNumber(hour.value, 2));
	newTime += (emptyAllowed && minute.value == "" ? "  " : padNumber(minute.value, 2));
	newTime += (second ? (emptyAllowed && second.value == "" ? "  " : padNumber(second.value, 2)) : "00");
	newTime += (millisecond ? (emptyAllowed && millisecond.value == "" ? "   " : padNumber(millisecond.value, 3)) : "000");

	/* check selected time is within limits, except if it contains spaces and empties are allowed */
	if((!emptyAllowed || newTime.indexOf(" ") == -1)  && ((minTime && newTime < minTime.value) || (maxTime && newTime > maxTime.value))) {
		/* check for a custom out-of-bounds message (oobmsg) */
		var oobmsg = document.getElementById("bptimeedit_" + id + "_oobmsg");
		if(oobmsg && oobmsg.value) oobmsg = oobmsg.value;

		if(oobmsg == null || oobmsg == "") {
			if(minTime) minTime = "" + minTime.value.substring(0,2) + ":" + minTime.value.substring(2,4) + ":" + minTime.value.substring(4,6) + "." + minTime.value.substring(6,9);
			if(maxTime) maxTime = "" + maxTime.value.substring(0,2) + ":" + maxTime.value.substring(2,4) + ":" + maxTime.value.substring(4,6) + "." + maxTime.value.substring(6,9);
			oobmsg = "The time " + newTime.substring(0,2) + ":" + newTime.substring(2,4) + ":" + newTime.substring(4,6) + "." + newTime.substring(6,9) + " is out of bounds: ";

			if(minTime && maxTime)
				oobmsg = oobmsg + "it must be between " + minTime + " and " + maxTime + " inclusive.";
			else if(minTime)
				oobmsg = oobmsg + "it must be after " + minTime + ".";
			else
				oobmsg = oobmsg + "it must be before " + maxTime + ".";
		}

		if(!bpFormElement_setAlert(object, oobmsg))
			alert(oobmsg);

		if(!object.lastKnownGoodValue)
			object.lastKnownGoodValue = document.getElementById("bptimeedit_" + id + "_default").value;

		hour.value = object.lastKnownGoodValue.substring(0,2);
		minute.value = object.lastKnownGoodValue.substring(2,4);
		if(second) second.value = object.lastKnownGoodValue.substring(4,6);
		if(millisecond) millisecond.value = object.lastKnownGoodValue.substring(6,8);
		return;
	}
	
	bpFormElement_setAlert(object, null)
	object.lastKnownGoodValue = newTime;
	object.value = newTime;
}


/* TODO refactor this so that control chars are only processed if c == "" */
function bpNumberEdit_onkeypress( e, idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	//var cCode = (e.charCode ? e.charCode : (e.keyCode ? e.keyCode : (e.which ? e.which : 0)));
	var cCode = (e.which ? e.which : e.keyCode);
	var c = String.fromCharCode(cCode).valueOf();

	//alert("cCode = " + cCode + "; e.keyCode = " + e.keyCode);
	
	if(typeof object != "object" || !object.type || object.type != "text") {
		cancelEvent(e);
		return false;
	}
	
	/* accept control chars: 36-41 = arrows; 8 = backspace */
	if(e.altKey || e.ctrlKey || (cCode < 32 && cCode != 8) || /*cCode == 127 || */(cCode > 36 && cCode < 41)) {
		bpFormElement_setAlert(object, null);
		return true;
	}

	/* reject if not digits or - or . (- or . only accepted if not ints only); 127 = del; 46 = del on FF(!) */
	var intsOnly = bpNumberEdit_intsOnly(object);
	
	if((cCode < 48 || cCode > 57) && (intsOnly || (cCode != 45 && cCode != 46)) && cCode != 8 && cCode != 127) {
		cancelEvent(e);
		return false;
	}

	/* check the char, when inserted into the value, makes a valid float */
	var newvalue = object.value.substring(0, object.selectionStart) + c + object.value.substring(object.selectionEnd, object.value.length);

	/* simulate del */
	if(127 == cCode || (46 == cCode && "" == c)) {
		if(object.selectionStart == object.selectionEnd) newvalue = object.value.substring(0, object.selectionStart) + object.value.substring(object.selectionEnd + 1, object.value.length);
		else newvalue = object.value.substring(0, object.selectionStart) + object.value.substring(object.selectionEnd, object.value.length);
	}
	
	/* simulate backspace */
	if(8 == cCode) {
		if(object.selectionStart == object.selectionEnd) newvalue = object.value.substring(0, object.selectionStart - 1) + object.value.substring(object.selectionEnd, object.value.length);
		else newvalue = object.value.substring(0, object.selectionStart) + object.value.substring(object.selectionEnd, object.value.length);
	}

	/* can't actually do this check because things like 12. would be rejected
	 * even though it's valid as a step towards entering a real number */
/*	if(!isValidNumber(newvalue)) {
		cancelEvent(e);
		return false;
	}*/
	
	var id = object.id;
	var min = document.getElementById("bpnumberedit_" + id + "_minvalue");
	var max = document.getElementById("bpnumberedit_" + id + "_maxvalue");
	
	/* can't cancel on breaches of min because this would forbid initial entry of digits (e.g. if min is 1900, user would
	 * not be able to type "1" for the first digit as 1 is less than 1900. min can only be checked on exit from box */
	var under = (min && parseFloat(newvalue) < parseFloat(min.value));
	var over = (max && parseFloat(newvalue) > parseFloat(max.value));
	
	if(under || over) {
		var oobmsg = document.getElementById("bpnumberedit_" + id + "_oobmsg");
		if(oobmsg && oobmsg.value) oobmsg = oobmsg.value;

		if(oobmsg == null || oobmsg == "") {
			oobmsg = "The value " + newvalue + " is out of bounds: ";

			if(min && max)
				oobmsg = oobmsg + "it must be between " + min.value + " and " + max.value + " inclusive.";
			else if(min)
				oobmsg = oobmsg + "it must be greater than " + min.value + ".";
			else
				oobmsg = oobmsg + "it must be less than " + max.value + ".";
		}

		/* only cancel and reject keypress if new value is over bounds - if it's under, user might be about to type another digit */
		bpFormElement_setAlert(object, oobmsg);
		if(over) {
			cancelEvent(e);
			return false;
		}
		
		/* new value is under, so accept keypress in hope that user might type some more to get it into the valid range */
		return true;
	}
	
	bpFormElement_setAlert(object, null);
	object.lastKnownGoodValue = newvalue;
	return true;
}


function bpNumberEdit_incrementInterval( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeof object != "object" || !object.type || object.type != "text")
		return 0;

	var id = object.id;
	var interval = document.getElementById("bpnumberedit_" + id + "_interval");
	return (interval ? parseFloat(interval.value) : 1);
}


function bpNumberEdit_maxValue( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeof object != "object" || !object.type || object.type != "text")
		return null;

	var id = object.id;
	var max = document.getElementById("bpnumberedit_" + id + "_maxvalue");
	return (max ? parseFloat(max.value) : null);
}


function bpNumberEdit_minValue( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeof object != "object" || !object.type || object.type != "text")
		return null;

	var id = object.id;
	var min = document.getElementById("bpnumberedit_" + id + "_minvalue");
	return (min ? parseFloat(min.value) : null);
}


function bpNumberEdit_intsOnly( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeof object != "object" || !object.type || object.type != "text")
		return null;

	var id = object.id;
	var intsOnly = document.getElementById("bpnumberedit_" + id + "_intsonly");
	return (intsOnly ? intsOnly.value != "0" : false);
}


function bpNumberEdit_increment( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeof object != "object" || !object.type || object.type != "text")
		return false;

	if(object.value == "") object.value = "0";
	var result = parseFloat(object.value) + bpNumberEdit_incrementInterval(object);
	if(result === NaN) return false;
	
	/* have to check both min and max in case default content is below min or above max */
	var max = bpNumberEdit_maxValue(object);
	if(max !== null && result > max) result = max;
	var min = bpNumberEdit_minValue(object);
	if(min !== null && result < min) result = min;

	if(bpNumberEdit_intsOnly(object)) result = parseInt(result);
	object.value = result;
	return true;
}


function bpNumberEdit_decrement( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);

	if(typeof object != "object" || !object.type || object.type != "text")
		return false;

	if(object.value == "") object.value = "0";
	var result = parseFloat(object.value) - bpNumberEdit_incrementInterval(object);
	if(result === NaN) return false;
	
	/* have to check both min and max in case default content is below min or above max */
	var max = bpNumberEdit_maxValue(object);
	if(max !== null && result > max) result = max;
	var min = bpNumberEdit_minValue(object);
	if(min !== null && result < min) result = min;

	if(bpNumberEdit_intsOnly(object)) result = parseInt(result);
	object.value = result;
	return true;
}


