///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                       Copyright (c) 2003-2006                             //
//                            Marc Peterson                                  //
//                     marc.s.peterson at gmail.com                          //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

// Last updated:
//   2007-04-09 - created cloneEditRow() to help create dynamic forms with repeating edit rows
//   2007-04-09 - get/set/clearInputValue() now uses isArray() and removed alert 
//				  on type mismatch so anything can be passed to it and non-inputs are ignored
//   2007-03-09 - Added checkDate() and clearDate()
//   2007-03-07 - Made setInputValue() ignore buttons
//   2007-02-01 - Added checkDecimal()
//   2006-07-08 - Changed setError() so that the message is optional
//   2006-06-26 - fixed bug in setError() that caused class typo to be set permanantly
//   2006-06-12 - added type "file" to getInputValue()
//   2006-04-26 - fixed bug in initFormOnChange() that prevented IE from attaching events
//   2006-04-17 - added textCounter()
//   2006-04-16 - made enableButton()/disableButton() accept the button object as well as the id

// VALIDATION FUNCTIONS
// checkAlphaNum(str)		validates alpha numeric (a-z, 0-9)
// checkDate(inp,str)		validates a date in the form "mm/dd/yyyy"
// checkEmail(str)			validates email address
// clearDate(inp)			clears an input if it contains "mm/dd/yyyy"
// submitOnEnter(fn)		calls a function (usually validation) if enter is pressed
// textCounter(inp,inp,max) limits number of characters allowed in a text field

// FORM INPUT/OUTPUT FUNCTIONS
// getInputValue(inp)		returns value of input element
// setInputValue(inp, str)	puts value into input
// selCheckbox(cbox, str)	checks matching checkbox (if any), unchecks any non-matching
// selDropdown(drop, str)	selects matching dropdown value (if any)
// selRadio(radio, str)		checks matching radio value (if any)
// clearInputValue(inp)		clears the given input
// trim(str)				removes whitespace (spaces, tabs) from start and end of string

// ERROR LIST CLASS
// ErrorList()				constructor
//   addError(str, [line])	appends the error to the errors array using with the optional line
//   getNumErrors()			returns number of errors in list
//   showErrors()			shows alert message with all errors
// setError(inp, err, str, line)	saves the error string in the error object and sets the input classname
// clearInputErrors(form)	Reverts all the inputs in the given form to their previous non-error class
// Check for alpha-numeric characters

// SAVE BUTTON ENABLING/DISABLING FUNCTIONS
// initFormOnChange(str, str, str)	Gives every input in a form the handlers needed to enable the button upon change
// enableButton(evt, str, str)	Checks event (if any) for correct conditions before enabling button
// disableButton(str, str)		Disables button

var gForm_lock = false;		// Global variable to lock a form so double-clicks will be ignored


function checkAlphaNum(string)
{
	var good="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
	for (var i = 0; i < string.length; i++) {
		if (good.indexOf(string.charAt(i)) == -1) return false;
	}
	return true;
} 


// Checks a date in the form "mm/dd/yyyy".  Fills in the year's beginning "20" if missing.
// The date's input field itself must be passed to the function
function checkDate(oInput, deliminator)
{
	var str_date = getInputValue(oInput);
	if ( deliminator === undefined ) deliminator = "/";
	if ( !checkTheseChars(str_date, "0123456789-/"+deliminator) ) return false;

	// Switch deliminators, if needed
	if ( deliminator == "/" ) str_date = str_date.replace(/-/g, "/");
	else if ( deliminator == "-" ) str_date = str_date.replace(/\//g, "-");

	var parts = str_date.split(deliminator);
	if ( parts.length != 3 ) return false;
	var m = parseInt(parts[0]);
	var d = parseInt(parts[1]);
	var y = parseInt(parts[2]);
	if ( m < 1 || m > 12 ) return false;
	if ( d < 1 || d > 31 ) return false;
	if ( y < 10 ) y = "200"+y;
	else if ( y < 100 ) y = "20"+y;
	else if ( y < 1000 ) return false;

	// The date is good, recombine it in case of missing 0's or bad deliminator
	setInputValue(oInput, m+deliminator+d+deliminator+y);

	return true;
}


// Check for a valid email address
function checkEmail(string)
{
	string				= trim(string);
	var pos_at			= string.indexOf("@");
	var pos_last_at		= string.lastIndexOf("@");
	var pos_last_dot	= string.lastIndexOf(".");
	var pos_space		= string.indexOf(" ");
	var str_len			= string.length;

	// Make sure all characters are valid
	var good=".@-+_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
	for (var i = 0; i < string.length; i++)
	{
		if (good.indexOf(string.charAt(i)) == -1) return false;
	}

	// At least one @ must be present and not at start of string
	if ( pos_at < 1 ) return false;

	// Make sure there is only one @
	if ( pos_at != pos_last_at ) return false;

	// At least one "." afer the @ is required
	if ( pos_last_dot < pos_at ) return false;

	// At least two characters must occur after the last dot
	if ( str_len - pos_last_dot <= 2 ) return false;

	return true;
}


// Clears an input's field if set as the example date "mm/dd/yyyy".  Usually used
// on an input like so: onfocus="clearDate(this)"
function clearDate(oInput)
{
	if ( getInputValue(oInput) == "mm/dd/yyyy" ) setInputValue(oInput, "");
}


function submitOnEnter(fn, e)
{
	var keycode;
	if (window.event) keycode = window.event.keyCode;
	else if (e) keycode = e.which;
	else return true;

	// If enter was pressed, call the passed function
	if (keycode == 13) fn();
}

// Used to limit number of charactes in a textarea
function textCounter(field, countfield, maxlimit)
{
	if ( field.value.length > maxlimit)
	{
		// if too long...trim it!
		field.value = field.value.substring(0, maxlimit);
	} else {
		// otherwise, update 'characters left' counter
		countfield.value = maxlimit - field.value.length;
	}
}


//////////////////////////////////////////////////
//												//
// Form get/set functions						//
//												//
//////////////////////////////////////////////////

// Gets the value(s) of any input element passed to it.  If the input allows multiple selections (such as a
// multiple-select or a checkbox) then results are returned comma-deliminated.  If retrieving from a select
// that has no options, then "" is returned.
function getInputValue(oInput)
{
	var i;
	var ret_val = "";										// Default return value is blank

	if ( oInput )
	{
		if ( oInput.type ) type = oInput.type;				// Type is: text, select
		else if ( isArray(oInput) && oInput[0].type ) type = oInput[0].type;	// Array indicates checkbox or option
		else type = null;									// Type cannot be determined, skip input

		switch (type)										// Switch on the input type
		{
			case "text":
			case "hidden":
			case "password":
			case "file":
			case "textarea":
				ret_val = oInput.value;
				break;
			case "select-one":
				var len = oInput.options.length;			// Number of options
				if ( len == 0 ) ret_val = "";				// If no options, return blank
				else ret_val = oInput.options[oInput.options.selectedIndex].value;
				break;
			case "select-mulitiple":
				var len = oInput.options.length;			// Number of options
				if ( len == 0 ) ret_val = "";				// If no options, return blank
				else {
					for (i=0; i<ret_val; i++)
					{
						if ( oInput.options[i].checked )
						{
							if ( ret_val == "" ) ret_val = oInput.options[i].value;
							else ret_val += ", "+oInput.options[i].value;
						}
					}
				}
				break;
			case "radio":
				if ( oInput.length )
				{
					for (i=0; i<oInput.length; i++)
					{
						if ( oInput[i].checked ) ret_val = oInput[i].value;
					}
				}
				else if ( oInput.checked ) ret_val = oInput.value;
				break;
			case "checkbox":
				if ( oInput.length )
				{
					for (i=0; i<oInput.length; i++)
					{
						if ( oInput[i].checked )
						{
							if ( ret_val == "" ) ret_val = oInput[i].value;
							else ret_val += ", "+oInput[i].value;
						}
					}
				}
				else if ( oInput.checked ) ret_val = oInput.value;
				break;
			default:
//				alert("getInputValue() unknown type: "+type);
				break;
		}
	}
	return trim(ret_val);
}


// Sets the value of any input element passed to it.
function setInputValue(oInput, new_val)
{
	if ( oInput )
	{
		if ( oInput.type ) type = oInput.type;				// Type is: text, select
		else if ( isArray(oInput) && oInput[0].type ) type = oInput[0].type;	// Array indicates checkbox or option
		else type = null;									// Type cannot be determined, skip input

		switch (type)										// Switch on the input type
		{
			case "text":
			case "hidden":
			case "password":
				oInput.value = new_val;
				break;
			case "select-one":
			case "select-mulitiple":
				selDropdown(oInput, new_val);
				break;
			case "radio":
				selRadio(oInput, new_val);
				break;
			case "checkbox":
				selCheckbox(oInput, new_val);
				break;
			case "textarea":
				oInput.value = new_val;
				break;
			case "button":		// do nothing on a button
				break;
			default:
//				alert("setInputValue() unknown type: "+type);
				break;
		}
	}
}


// Takes a radio and some text and select the radio found with that text.  Matching 
// is case insensitive.
function selCheckbox(oCbox, txt) {
	var i, txt_to_match;

	if (typeof(txt) == "string") txt_to_match = txt.toLowerCase();			// If txt is a string, make it lowercase
	else if (typeof(txt) == "number") txt_to_match = txt.toString();		// If txt is a number, convert to a string

	if (oCbox) {
		if (oCbox.length) {													// If checkbox has more than one item
			for (i=0; i<oCbox.length; i++) {
				if (oCbox[i].value.toLowerCase() == txt_to_match) {			// If matching radio is found
					oCbox[i].checked = true;								//   check it
				} else {
					oCbox[i].checked = false;								// Otherwise uncheck it
				}
			}
		} else {
			if (oCbox.value.toLowerCase() == txt_to_match) {				// If matching checkbox is found
				oCbox.checked = true;										//   check it
			} else {
				oCbox.checked = false;										// Otherwise uncheck it
			}
		}
	}
}


// Takes a dropdown and some text and selects any dropdown found with that text.  Matching 
// is case insensitive.
function selDropdown(oDropdown, txt)
{
	var i, txt_to_match;

	if ( typeof(txt) == "string" ) txt_to_match = txt.toLowerCase();		// If txt is a string, make it lowercase
	else if ( typeof(txt) == "number" ) txt_to_match = txt.toString();		// If txt is a number, convert to a string

	if ( oDropdown )
	{
		for (i=0; i<oDropdown.options.length; i++)
		{
			// Select the first matching dropdown and exit loop
			if ( oDropdown.options[i].value.toLowerCase() == txt_to_match )
			{
				oDropdown.options[i].selected = true;
				break;
			}
		}
	}
}


// Takes a radio and some text and select the radio found with that text.  Matching 
// is case insensitive.
function selRadio(oRadio, txt)
{
	var i, txt_to_match;

	if ( typeof(txt) == "string" ) txt_to_match = txt.toLowerCase();			// If txt is a string, make it lowercase
	else if ( typeof(txt) == "number" ) txt_to_match = txt.toString();			// If txt is a number, convert to a string

	if ( oRadio )
	{
		// If multiple radios
		if ( oRadio.length )
		{
			for (i=0; i<oRadio.length; i++)
			{
				// If matching radio is found check it.  Otherwise uncheck it.
				if ( oRadio[i].value.toLowerCase() == txt_to_match )
				{
					oRadio[i].checked = true;
				} else {
					oRadio[i].checked = false;
				}
			}
		}
		// If a single radio
		else if ( oRadio.value.toLowerCase() == txt_to_match ) oRadio.checked = true;
		else oRadio.checked = false;
	}
}


// Clears any input.  Textfields are blanked, dropdowns reset to first option
// checkboxes and radio boxes are unchecked.
function clearInputValue(oInput)
{
	if ( oInput )
	{
		if ( oInput.type ) type = oInput.type;				// Type of input, select
		else if ( isArray(oInput) && oInput[0].type ) type = oInput[0].type;	// Array indicates checkbox or option
		else type = null;									// Type cannot be determined, skip input

		switch (type)										// Switch on the input type
		{
			case "text":
			case "hidden":
				oInput.value = "";
				break;
			case "select-one":
			case "select-mulitiple":
				for (var i=0; i<oInput.length; i++)
				{
					oInput[i].selected = false;
				}
				oInput[0].selected = true;
				break;
			case "radio":
			case "checkbox":
				if ( oInput.length )
				{
					for (var i=0; i<oInput.length; i++)
					{
						oInput[i].checked = false;
					}
				}
				else oInput.checked = false;
				break;
			case "button":
				break;
			case "textarea":
				ret_val = oInput.value = "";
				break;
			default:
//				alert("clearInputValue() unknown type: "+type);
				break;
		}
	}
}

// This function is also in common.js
// Strip whitespace (space, tabs) from the beginning and end of a string
function trim(txt)
{
	// Handle special cases
	if ( !txt || txt == "" || txt == null || txt === undefined) return "";
	else
	{
		txt = txt.replace(/^[\s\t]*/, "");
		txt = txt.replace(/[\s\t]*$/, "");
		return txt;
	}
}

// ErrorList object
function ErrorList()
{
	// Public variables

	// Private variables
	var num_errors = 0;
	var list = new Array();

	// Public methods
	this.addError = function(msg, line)
	{
		if ( line === undefined || line == null || line == "" ) line = 0;
		else line = parseInt(line);

		if ( !list[line] ) list[line] = new Array();
		list[line].push(msg);
		num_errors++;
	}

	// Returns number of errors
	this.getNumErrors = function()
	{
		return num_errors;
	}

	// Shows any errors stored in the list.
	this.showErrors = function()
	{
		var error_msg = "";

		for (var i=0; i<list.length; i++)
		{
			if ( list[i] )
			{
				for (err in list[i])
				{
					error_msg += list[i][err];
				}
			}
		}

		if ( error_msg != "" )
		{
			alert(error_msg);
		}
	}
}

// Adds an error to the error list and updates the input's class
// VARIABLES:
//   oInput			Optional input to change the class to "typo".  May be null or "" if no input should be changed.
//   [oErrorList]	Error object to add an error to.  If not passed then only the input class is changed.
//   [msg]			Error string to add
//   [line]			Optional line number of error
function setError(oInput, oErrorList, msg, line)
{
	// Save the error
	if ( line === undefined || line == null || line == "" ) line = 0;
	if ( msg === undefined || msg == null ) msg = "";
	if ( oErrorList !== undefined && oErrorList != null && oErrorList != "" )
	{
		if ( msg != "" ) oErrorList.addError(msg, line);
	}

	// Set the input's class
	if ( oInput != null && oInput != "" )
	{
		if ( oInput.className && oInput.className != "typo" ) oInput.prevClassName = oInput.className;
		oInput.className = "typo";
	}
}

// Takes all the inputs in the passed form and sets any with the error class to their
// previous class.  If no previous class, assume the class was blank.
function clearInputErrors(oForm)
{
	oInputs = oForm.getElementsByTagName("INPUT");
	for (var i=0; i<oInputs.length; i++)
	{
		if ( oInputs[i].className == "typo" )
		{
			if ( oInputs[i].prevClassName )
			{
				oInputs[i].className = oInputs[i].prevClassName;
				oInputs[i].prevClassName = null;
			}
			else
			{
				oInputs[i].className = "";
			}
		}
	}

	oInputs = oForm.getElementsByTagName("SELECT");
	for (var i=0; i<oInputs.length; i++)
	{
		if ( oInputs[i].className == "typo" )
		{
			if ( oInputs[i].prevClassName )
			{
				oInputs[i].className = oInputs[i].prevClassName;
				oInputs[i].prevClassName = null;
			}
			else
			{
				oInputs[i].className = "";
			}
		}
	}
}

// SAVE BUTTON ENABLING/DISABLING FUNCTIONS
//
// Used to apply an onchange function to every input in a form.  The function will enable
// the button when anything in the form has been changed.  It does not overwrite any
// existing events on the form fields.
//
// This function provides an easy way to apply the onchange event to all elements of a form.
// Some forms with multiple sections or more complexity may need the onchange event
// manually applied to the wanted fields without using this function.
//
//   Variables:
// 		form_id		- ID of the form to apply the onChange event to
//		btn_id		- ID of the button to enable/disable (the id must be unique for the entire page)
//		className	- class the button should receive when active
function initFormOnChange(form_id, btn_id, className)
{
	var oForm;
	if ( oForm = document.getElementById(form_id) )
	{
		for (var i=0; i<oForm.elements.length; i++)
		{
			// Radios and checkboxes get an onClick event
			if ( oForm.elements[i].type == "radio" || oForm.elements[i].type == "checkbox" )
			{
				if ( document.addEventListener )	// Moz
				{
					oForm.elements[i].addEventListener("click", function(e) {enableButton(e, btn_id, className);}, false);
				}
				else if ( document.attachEvent )	// IE
				{
					oForm.elements[i].attachEvent("onclick", function(e) {enableButton(e, btn_id, className);} );
				}
			}
			// All other input elements get an onChange and onKeyPress event
			else if ( oForm.elements[i].type != "button" )
			{
				if ( document.addEventListener )	// Moz
				{
					oForm.elements[i].addEventListener("change", function(e) {enableButton(e, btn_id, className);}, false);
					oForm.elements[i].addEventListener("keyup", function(e) {enableButton(e, btn_id, className);}, false);
				}
				else if ( document.attachEvent )	// IE
				{
					oForm.elements[i].attachEvent("onchange", function(e) {enableButton(e, btn_id, className);} );
					oForm.elements[i].attachEvent("onkeyup", function(e) {enableButton(e, btn_id, className);} );
				}
			}
		}
	}
}

// Enables the button with the given ID and applies the passed class to it.
//  evt			- event that initiated this action.  null means it was called manually.
//  btn_id		- ID (or button object) of button to enable
//  className	- the class to give the button
function enableButton(evt, btn_id, className)
{
	var obj, key_pressed;

	var evt = (evt) ? evt : ((window.event) ? window.event : null);

	// If evt is not null, get the key pressed
	if ( evt != null )
	{
		if ( evt.which ) key_pressed = evt.which;
		else key_pressed = evt.keyCode;
	}

	// Allow any event without an event handler since this means the function was called manually.
	// Allow any onChange event.  Ignore tabs (9), return (13) and arrow keys (33-40).
	if ( evt == null || evt.type == "change" ||
	   (key_pressed != 9 && key_pressed != 13 && !(key_pressed >= 33 && key_pressed <= 40)) )
	{
		obj = ( typeof(btn_id) == "string" ) ? document.getElementById(btn_id) : btn_id;
		if ( obj )
		{
			// Only change if not already set.  This removes flicker on IE.
			if ( obj.disabled ) obj.disabled = false;
			if ( obj.className != className ) obj.className = className;
		}
	}
}

// Disables the button with the given ID and applies the passed class to it
//  btn_id		- ID (or button object) of button to enable
//  className	- the class to give the button
function disableButton(btn_id, className)
{
	var obj;
	obj = ( typeof(btn_id) == "string" ) ? document.getElementById(btn_id) : btn_id;
	if ( obj )
	{
		obj.disabled = true;
		obj.className = className;
	}
}

// Strip whitespace (space, tabs) from the beginning and end of a string
function trim(txt)
{
	// Handle special cases
	if ( !txt || txt == "" || txt == null || txt === undefined) return "";
	else
	{
		txt = txt.replace(/^[\s\t]*/, "");
		txt = txt.replace(/[\s\t]*$/, "");
		return txt;
	}
}

// Given the passed row, clone it and rename all id's that end with a given string to the original id
// appended with the new end.  So this:					<tr id="row_0"><td><input name="name_0" /></td></tr>
// called with cloneEditRow(oTr, "_0", "_5") becomes	<tr id="row_5"><td><input name="name_5" /></td></tr>
function cloneEditRow(oTr_source, old_end, new_end)
{
	oTr		= oTr_source.cloneNode(true);
	oTr.id	= "";

	// Rename any id or name ending with "_0" to the new count
	oItems = findChildrenById(oTr, old_end, "ends_with")
	for (i=0; i<oItems.length; i++)
	{
		if (oItems[i].name && oItems[i].name.lastIndexOf(old_end) == (oItems[i].name.length - old_end.length))
		{
			oItems[i].name = oItems[i].name.substring(0,oItems[i].name.lastIndexOf(old_end)) + new_end;
		}

		if (oItems[i].id && oItems[i].id.lastIndexOf(old_end) == (oItems[i].id.length - old_end.length))
		{
			oItems[i].id = oItems[i].id.substring(0,oItems[i].id.lastIndexOf(old_end)) + new_end;
		}

		clearInputValue(oItems[i]);		// will clear any input, ignores non-inputs
	}
	return oTr;
}


