/**
 * This file contains a library of common functions used throughout the PrayerTracker website.
 * Other JavaScript files should not be expected to function without it.  It, in turn,
 * depends on jQuery.
 *
 * @author Daniel J. Summers <daniel@djs-consulting.com>
 * @package PrayerTracker
 * @subpackage View
 */
var pt = {
	
	/**
	 * Escape a jQuery ID.
	 */
	jqId : function(psId) {
		return "#" + psId.replace(/:/g,"\\:").replace(/\./g,"\\.");
	},

	/**
	 * Add an event handler to an object.
	 *
	 * @param poObject The object to which the event handler should be added.
	 * @param psEventType The event which should be handled.
	 * @param poFunction The name of the function to handle the event.
	 */
	addEventHandler : function(poObject, psEventType, poFunction) {
		
		if (poObject.addEventListener){ 
			poObject.addEventListener(psEventType, poFunction, false); 
			return true; 
		}
		else if (poObject.attachEvent) {
			var r = poObject.attachEvent("on" + psEventType, poFunction);
			return r;
		}
		else {
			return false;
		} 
	},

	/**
	 * Remove all white space from both sides of a string.
	 *
	 * @param psString The string to trim.
	 * @return The trimmed string.
	 */
	trim : function(psString) {
		return this.ltrim(this.rtrim(psString));
	},

	/**
	 * Remove white space from the front (left side) of a string.
	 *
	 * @param psString The string to trim.
	 * @return The trimmed string.
	 */
	ltrim : function(psString) {
		while (" " == psString.substring(0,1)) {
			psString = psString.substring(1);
		}
		return psString;
	},

	/**
	 * Remove white space from the back (right side) of a string.
	 *
	 * @param psString The string to trim.
	 * @return The trimmed string.
	 */
	rtrim : function(psString) {
		while (" " == psString.substring(psString.length - 1)) {
			psString = psString.substring(0, psString.length - 1);
		}
		return psString;
	},

	/**
	 * Shortcut for "document.getElementById".
	 *
	 * @return Element The specified element.
	 */
	getElement : function(psElementId) {
		return document.getElementById(psElementId);
	},

	/**
	 * Set a field with the error classes.
	 *
	 * @param psFieldId The ID of the error field.
	 * @param psMessage A message to display (optional).
	 * @return boolean False.
	 */
	errorField : function(psFieldId, psMessage) {
		if ("" != psMessage) alert(psMessage);
		if ("" != psFieldId) {
			$(this.jqId(psFieldId)).addClass("errorField");
			$(this.jqId(psFieldId)).focus();
		}
		return false;
	},

	/**
	 * Remove the error class from a field.
	 * 
	 * @param psFieldId The ID of the good field.
	 * @return boolean True.
	 */
	validField : function(psFieldId) {
		$(this.jqId(psFieldId)).removeClass("errorField");
		return true;
	},

	/**
	 * Validate a string field, ensuring it has some text.
	 *
	 * @param psElementId The ID of the HTML element to validate.
	 * @param psErrorMessage The message to show the user if it's blank.
	 * @return True if it's good, false if not.
	 */
	validateString : function(psElementId, psErrorMessage) {
	
		if ("" == this.trim($(this.jqId(psElementId)).val())) {
			return this.errorField(psElementId, psErrorMessage);
		}
	
		return this.validField(psElementId);
	},

	/**
	 * Ensure that at least one box in a set has been selected.
	 *
	 * @param psElementName The name of the option group to validate.
	 * @param psErrorMessage The error message to display if one isn't selected.  (Passing a
	 * 		blank error message causes the function to simply return true or false.)
	 * @param psLabelName The name of the label to change if there is an error.
	 * @param psClassName The CSS class name of the label.
	 * @return True if one is selected, false if none are.
	 */
	validateOptionGroup : function(psElementName, psErrorMessage, psLabelName, psClassName) {
	
		var oOpts = document.getElementsByName(psElementName);
	
		for (i = 0; i < oOpts.length; i++) {
			if (oOpts[i].checked) {
				if ("" != psLabelName) {
					return this.validField(psLabelName);
				}
				else {
					return true;
				}
			}
		}
		
		return this.errorField(psElementId, psErrorMessage);
	},

	/**
	 * Validate a dropdown list, ensuring the top option is not selected.
	 *
	 * @param psElementId The ID of the element to validate.
	 * @param psErrorMessage The error message to display if the top option is selected.
	 * @return True if a selection has been made, false if not.
	 */
	validateDropdown : function(psElementId, psErrorMessage) {
	
		if (0 == $(this.jqId(psElementId)).attr("selectedIndex")) {
			return this.errorField(psElementId, psErrorMessage);
		}
		
		return this.validField(psElementId);
	},
	
	/**
	 * Validate a numeric string with a set length.
	 *
	 * @param psElementId The ID of the element to validate.
	 * @param piLength The length of the string.
	 * @param psErrorMessage The error message to display if it is not valid.
	 * @return True if it's valid, false if not.
	 */
	validateNumericString : function(psElementId, piLength, psErrorMessage) {
		
		var sValue = this.trim($(this.jqId(psElementId)).val());
		
		if ((sValue.length == piLength) && (this.isNumeric(sValue))) {
			return this.validField(psElementId);
		}
		
		return this.errorField(psElementId, psErrorMessage);
	},
	
	/**
	 * Validate Date.
	 *
	 * Quickly validates a date in the form "YYYY-MM-DD".
	 *
	 * @param psElementId The ID of the element to validate.
	 * @param psErrorMessage The error message to display if it is not valid.
	 * @return True if valid, false if not.
	 */
	validateDate : function(psElementId, psErrorMessage) {
		
		var oPieces = $(this.jqId(psElementId)).val().split("-");
		
		if ((3 == oPieces.length)
				&& (this.isNumeric(oPieces[0]))
				&& ((1 <= oPieces[1]) && (12 >= oPieces[1]))
				&& ((1 <= oPieces[2]) && (31 >= oPieces[2]))) {
			// Why does the date have to be in this format?  ;(
			if (!isNaN(parseInt(Date.parse(oPieces[1] + "/" + oPieces[2] + "/" + oPieces[0])))) {
				// Good enough for government work.
				return this.validField(psElementId);
			}
		}
		
		return this.errorField(psElementId, psErrorMessage);
	},
	
	/**
	 * Checks a string for all numeric characters.
	 *
	 * @param psString The string to check.
	 * @return True if all characters are numbers, false otherwise.
	 */
	isNumeric : function(psString) {
		
		var sNumbers = "0123456789";
		
		for (x = 0; x < psString.length; x++) {
			if (0 > sNumbers.indexOf(psString.charAt(x))) {
				return false;
			}
		}
		
		return true;
	},
	
	/**
	 * Validate an e-mail address.
	 *
	 * @param string The ID of the element with the e-mail address to validate.
	 * @param string Whether this is a required field or not.
	 * @return True if it's good, false if not.
	 */
	validateEmailAddress : function(psAddress, pbRequired) {
		
		var sEmail = this.trim($(this.jqId(psAddress)).val());
		
		if (pbRequired) {
			// Make sure an e-mail address has been entered.
			if (!this.validateString(psAddress, "You must enter an e-mail address.")) {
				return false;
			}
		}
		else {
			// If it's not entered and not required, it's good to go.
			if ("" == sEmail) {
				return this.validField(psAddress);
			} 
		}
		
		// Make sure that the e-mail address is valid.
		if ((0 > sEmail.indexOf("@"))
				|| (0 > sEmail.substring(sEmail.indexOf("@")).indexOf("."))) {
			return this.errorField(psAddress, "The e-mail address is not valid - it must be "
					+ "in the form of \"name@example.com\".");
		}
		
		return this.validField(psAddress);
	},
	
	/**
	 * Enable an element.
	 *
	 * @param psString The ID of the element to enable.
	 */
	enable : function(psString) {
		this.getElement(psString).disabled = false;
	},
	
	/**
	 * Disable an element.
	 *
	 * @param psString The ID of the element to disable.
	 */
	disable : function(psString) {
		this.getElement(psString).disabled = true;
	},

	/**
	 * Test whether a checkbox is checked or not.
	 *
	 * @param psElementId The ID of the element to test.
	 * @return True if checked, false if cleared.
	 */
	isChecked : function(psElementId) {
		return $(this.jqId(psElementId)).is(":checked");
	},
	
	/**
	 * Open a window with help.
	 *
	 * @param string The URL for the help page.
	 */
	showHelp : function(psUrl) {
		window.open(getElement("url").value + "/help/" + psUrl, "helpWindow",
			"height=600px,width=450px,toolbar=0,menubar=0,scrollbars=1");
	}
};

// NOTE: Legacy functins below - please use above instead.
/**
 * Add an event handler to an object.
 *
 * @param poObject The object to which the event handler should be added.
 * @param psEventType The event which should be handled.
 * @param poFunction The name of the function to handle the event.
 */
function addEventHandler(poObject, psEventType, poFunction) {
	
	if (poObject.addEventListener){ 
		poObject.addEventListener(psEventType, poFunction, false); 
		return true; 
	}
	else if (poObject.attachEvent) {
		var r = poObject.attachEvent("on" + psEventType, poFunction);
		return r;
	}
	else {
		return false;
	} 
}

/**
 * Remove all white space from both sides of a string.
 *
 * @param psString The string to trim.
 * @return The trimmed string.
 */
function trim(psString) {
	return ltrim(rtrim(psString));
}

/**
 * Remove white space from the front (left side) of a string.
 *
 * @param psString The string to trim.
 * @return The trimmed string.
 */
function ltrim(psString) {
	while (" " == psString.substring(0,1)) {
		psString = psString.substring(1);
	}
	return psString;
}

/**
 * Remove white space from the back (right side) of a string.
 *
 * @param psString The string to trim.
 * @return The trimmed string.
 */
function rtrim(psString) {
	while (" " == psString.substring(psString.length - 1)) {
		psString = psString.substring(0, psString.length - 1);
	}
	return psString;
}

/**
 * Shortcut for "document.getElementById".
 *
 * @return Element The specified element.
 */
function getElement(psElementId) {
	return document.getElementById(psElementId);
}

/**
 * Validate a string field, ensuring it has some text.
 *
 * @param psElementId The ID of the HTML element to validate.
 * @param psErrorMessage The message to show the user if it's blank.
 * @return True if it's good, false if not.
 */
function validateString(psElementId, psErrorMessage) {
	
	var oElement = getElement(psElementId);
	
	if ("" == trim(oElement.value)) {
		alert(psErrorMessage);
		oElement.className = "errorField";
		oElement.focus();
		return false;
	}
	
	oElement.className = "";
	return true;
}

/**
 * Ensure that at least one box in a set has been selected.
 *
 * @param psElementName The name of the option group to validate.
 * @param psErrorMessage The error message to display if one isn't selected.
 *		(passing a blank error message causes the function to simply return true
 *		or false.)
 * @param psLabelName The name of the label to change if there is an error.
 * @param psClassName The CSS class name of the label.
 * @return True if one is selected, false if none are.
 */
function validateOptionGroup(psElementName, psErrorMessage, psLabelName, psClassName) {
	
	var oOpts = document.getElementsByName(psElementName);
	
	for (i = 0; i < oOpts.length; i++) {
		if (oOpts[i].checked) {
			if ("" != psLabelName) {
				getElement(psLabelName).className = psClassName;
			}
			return true;
		}
	}
	
	if ("" < psErrorMessage) {
		alert(psErrorMessage);
	}
	if ("" != psLabelName) {
		getElement(psLabelName).className = psClassName + " errorField";
	}
	return false;
}

/**
 * Validate a dropdown list, ensuring the top option is not selected.
 *
 * @param psElementId The ID of the element to validate.
 * @param psErrorMessage The error message to display if the top option is selected.
 * @return True if a selection has been made, false if not.
 */
function validateDropdown(psElementId, psErrorMessage) {
	
	var oElement = getElement(psElementId);
	
	if (0 == oElement.selectedIndex) {
		alert(psErrorMessage);
		oElement.className = "errorField";
		oElement.focus();
		return false;
	}
	
	oElement.className = "";
	return true;
}

/**
 * Validate a numeric string with a set length.
 *
 * @param psElementId The ID of the element to validate.
 * @param piLength The length of the string.
 * @param psErrorMessage The error message to display if it is not valid.
 * @return True if it's valid, false if not.
 */
function validateNumericString(psElementId, piLength, psErrorMessage) {
	
	var oElement = getElement(psElementId);
	
	if (piLength == trim(oElement.value).length) {
		if (isNumeric(oElement.value)) {
			oElement.className = "";
			return true;
		}
	}
	
	alert(psErrorMessage);
	oElement.className = "errorField";
	oElement.focus();
	return false;
}

/**
 * Validate Date.
 *
 * Quickly validates a date in the form "YYYY-MM-DD".
 *
 * @param psElementId The ID of the element to validate.
 * @param psErrorMessage The error message to display if it is not valid.
 * @return True if valid, false if not.
 */
function validateDate(psElementId, psErrorMessage) {
	
	var oElement = getElement(psElementId);
	var oPieces = oElement.value.split("-");
	
	if ((3 == oPieces.length)
			&& (isNumeric(oPieces[0]))
			&& ((1 <= oPieces[1]) && (12 >= oPieces[1]))
			&& ((1 <= oPieces[2]) && (31 >= oPieces[2]))) {
		// Why does the date have to be in this format?  ;(
		sDate = oPieces[1] + "/" + oPieces[2] + "/" + oPieces[0];
		if (!isNaN(parseInt(Date.parse(sDate)))) {
			// Good enough for government work.
			oElement.className = "";
			return true;
		}
	}
	
	// Must not be good if we got here...
	alert(psErrorMessage);
	oElement.className = "errorField";
	oElement.focus();
	return false;
}

/**
 * Checks a string for all numeric characters.
 *
 * @param psString The string to check.
 * @return True if all characters are numbers, false otherwise.
 */
function isNumeric(psString) {
	
	var sNumbers = "0123456789";
	
	for (x = 0; x < psString.length; x++) {
		if (0 > sNumbers.indexOf(psString.charAt(x))) {
			return false;
		}
	}
	
	return true;
}

/**
 * Validate an e-mail address.
 *
 * @param string The ID of the element with the e-mail address to validate.
 * @param string Whether this is a required field or not.
 * @return True if it's good, false if not.
 */
function validateEmailAddress(psAddress, pbRequired) {
	
	oEmail = getElement(psAddress);
	
	if (pbRequired) {
		// Make sure an e-mail address has been entered.
		if (!validateString(psAddress, "You must enter an e-mail address.")) {
			return false;
		}
	}
	else {
		// If it's not entered and not required, it's good to go.
		if ("" == trim(oEmail.value)) {
			oEmail.className = "";
			return true;
		} 
	}
	
	// Make sure that the e-mail address is valid.
	if ((0 > oEmail.value.indexOf("@"))
	|| (0 > oEmail.value.substring(oEmail.value.indexOf("@")).indexOf("."))) {
		alert("The e-mail address is not valid - it must be in the form of "
				+ "\"name@example.com\".");
		oEmail.className = "errorField";
		oEmail.focus();
		return false;
	}
	else {
		oEmail.className = "";
	}
	
	return true;
}

/**
 * Enable an element.
 *
 * @param psString The ID of the element to enable.
 */
function enable(psString) {
	getElement(psString).disabled = false;
}

/**
 * Disable an element.
 *
 * @param psString The ID of the element to disable.
 */
function disable(psString) {
	getElement(psString).disabled = true;
}

/**
 * Test whether a checkbox is checked or not.
 *
 * @param psElementId The ID of the element to test.
 * @return True if checked, false if cleared.
 */
function isChecked(psElementId) {
	return getElement(psElementId).checked;
}

/**
 * Moves an individual from an Available list to an Assigned list.
 */
function moveRight() {
	
	var oAvail = getElement("txtAvailable");
	var oAsg = getElement("txtAssigned");
	
	if (0 <= oAvail.selectedIndex) {
		var iIndex = oAvail.selectedIndex;
		var oOption = oAvail.options[iIndex];
		oAvail.remove(iIndex);
		try {
			// IE doesn't like this.
			oAsg.add(oOption, null);
		}
		catch (oException) {
			// IE likes this better.
			oAsg.add(oOption);
		}
		if (iIndex >= oAvail.options.length) {
			oAvail.selectedIndex = iIndex;
		}
	}
	
}

/**
 * Moves an individual from an Assigned list to an Available list.
 */
function moveLeft() {
	
	var oAvail = getElement("txtAvailable");
	var oAsg = getElement("txtAssigned");
	
	if (0 <= oAsg.selectedIndex) {
		var oOption = oAsg.options[oAsg.selectedIndex];
		oAsg.remove(oAsg.selectedIndex);
		try {
			// IE doesn't like this.
			oAvail.add(oOption, null);
		}
		catch (oException) {
			// IE likes this better.
			oAvail.add(oOption);
		}
	}
	
}

/**
 * Convert Multiple-Select Box to Array.
 *
 * Multiple select boxes do not get posted with the form.  This creates an
 * array of input elements with the values in the box specified.
 *
 * @param psSelectBox The ID of the select box.
 * @param psFormId The ID of the form.
 * @param psName The name the array should have.
 */
function convertMultiSelectToArray(psSelectBox, psFormId, psName) {
	
	var oBox = getElement(psSelectBox);
	
	for (i = 0; i < oBox.options.length; i++) {
		var oInput = document.createElement("input");
		oInput.type = "hidden";
		oInput.name = psName + "[]";
		oInput.value = oBox.options[i].value;
		getElement(psFormId).appendChild(oInput);
	}
}

/**
 * Runs an AJAX request.
 *
 * @param psUrl The URL of the page to retrieve.
 * @param poFunction The function to handle the return data.
 * @return False if the object can't be instantiated
 */
var oXmlRequest;
function runAjaxRequest(psUrl, poFunction) {
	
	try {
		// Everything but IE
		oXmlRequest = new XMLHttpRequest();
	} catch (e) {
		try {
			// IE
			oXmlRequest = new ActiveXObject("Msxml2.XMLHTTP");
		} catch (e) {
			try {
				oXmlRequest = new ActiveXObject("Microsoft.XMLHTTP");
			} catch (e) {
				alert("Sorry - your browser does not support the lookup designed for this page.");
				return false;
			}
		}
	}
	
	oXmlRequest.onreadystatechange = poFunction;
	oXmlRequest.open("GET", psUrl, true);
	oXmlRequest.send(null);
}

/**
 * Parse XML Document.
 *
 * Parse a string into an XML document (used with AJAX requests).
 *
 * @param psString The string with the XML text.
 * @return A DOM document.
 */
function parseXmlDocument(psString) {
	
	var doc;
	
	if (document.implementation.createDocument) {
		// Not IE.
		var parser = new DOMParser();
		doc = parser.parseFromString(psString, "text/xml");
	}
	else {
		 if (window.ActiveXObject) {
			// IE.
			doc = new ActiveXObject("Microsoft.XMLDOM");
			doc.async="false";
			doc.loadXML(psString);
		}
	}
	return doc;
}

/**
 * Open a window with help.
 *
 * @param string The URL for the help page.
 */
function showHelp(psUrl) {
	window.open(getElement("url").value + "/help/" + psUrl,
		"helpWindow",
		"height=600px,width=450px,toolbar=0,menubar=0,scrollbars=1");
} 