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

// Last updated:
//   2007-06-26 - setDropdown() now uses selectedIndex to make Safari happy
//   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)
// checkDomain(str)			validates domain (www.domain.com)
// checkEmail(str)			validates email address
// checkHex(str)			validates hex number
// checkHexRange(h,min,max)	ensures hex h is between hex min and max
// hex2dec(str)				converts hex number to decimal number
// checkIp(str)				validates IP address
// checkIpAndDomain(str)	validates IP first, domain if not an IP
// checkIpWithMask(str)		validates IP address with a mask (1.2.3.4/32)
// checkLink(str)			validates link (http://www.domain.com/path)
// checkMac(str)			validates MAC address
// checkNumber(str)			validates string is an pos/neg integer number (0-9 and "-")
// checkNumRange(x,min,max)	ensures x is between min and max
// checkPortList(str)		validates comma-deliminated list of ports (1, 3, 23, 4096)
// checkTheseChars(str,str)	validates for user-supplied characters
// checkTime(str)			validates time (3:45)
// checkDate(inp,str)		validates a date in the form "mm/dd/yyyy"
// clearDate(inp)			clears an input if it contains "mm/dd/yyyy"
// isNum(str)				returns true if string is a number
// 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;
} 


// Check for a valid domain
function checkDomain(string)
{
	var good = "-.0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

	for (var i = 0; i < string.length; i++)
	{
		if (good.indexOf(string.charAt(i)) == -1) return false;
	}
	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;
}


// Checks if a number is hexadecimal.  Validates a number with or without leading "0x". 0x00F0 and 00F0 are valid.
function checkHex(string)
{
	var good = "0123456789abcdef";
	var str = trim(string).toLowerCase();

	if ( str.substring(0,2) == "0x") str = str.substring(2);
	
	for (var i = 0; i < str.length; i++) {
		if (good.indexOf(str.charAt(i)) == -1) return false;
	}
	return true;
}


// Ensures the string is between the min and max hexadecimal values
function checkHexRange(string, min_val, max_val)
{
	var str		= hex2dec(string);
	var min_val	= hex2dec(minVal);
	var max_val	= hex2dec(maxVal);

	if ( str < min_val ) return false;
	else if ( str > max_val ) return false;
	else return true;
}


// Takes a hexidecimal number and returns a decimal
function hex2dec(hex)
{
	return parseInt(hex,16);
}


// Check for valid characters in an IP address
function checkIp(string)
{
	var good = ".0123456789";

	if (string.length == 0) return false;	// If blank, return false

	for (var i = 0; i < string.length; i++) {
		if (good.indexOf(string.charAt(i)) == -1) return false;
	}

	// Check that the groups of numbers are from 0 to 255
	var indexStart = 0;
	var indexEnd = string.indexOf(".");
	if (indexEnd < 0) return false;
	if (parseInt(string.substring(indexStart, indexEnd)) > 255) return false;

	indexStart = indexEnd+1;
	indexEnd = string.indexOf(".", indexStart);
	if (indexEnd < 0) return false;
	if (parseInt(string.substring(indexStart, indexEnd)) > 255) return false;

	indexStart = indexEnd+1;
	indexEnd = string.indexOf(".", indexStart);
	if (indexEnd < 0) return false;
	if (parseInt(string.substring(indexStart, indexEnd)) > 255) return false;

	indexStart = indexEnd+1;
	if (parseInt(string.substring(indexStart)) > 255) return false;
	
	// Check that there are not too many dots "."
	if (string.indexOf(".", indexStart) != -1) return false;

	// Check that there is something after the final "."
	if (indexStart == string.length) return false;
	return true;
} 


// Check for a domain or IP
function checkIpAndDomain(string)
{
	string = trim(string);

	// Bad if string starts/ends with "."
	if ( string.indexOf(".") == 0 || string.lastIndexOf(".") == (string.length-1) ) return false;

	// Perform a series of tests to see if string is an IP address.  If not, check string as a domain
	var parts = string.split(".");
	if ( parts.length != 4 ) return checkDomain(string);

	for (var i=0; i<parts.length; i++)
	{
		if ( !checkNumRange(parts[i], 0, 255) ) return checkDomain(string);
	}
	return checkIp(string);
}


// Check for valid characters in an IP address AND allows for a mask
// ie) 111.222.333.444/32
function checkIpWithMask(string)
{
	var slashPos = string.indexOf("/");

	if ( slashPos >= 0 )
	{
		// If there is a mask
		if (slashPos < 7) return false;				// 1.2.3.4/ is the smallest IP
		var mask = string.substring(slashPos+1);
		var ip = string.substring(0, slashPos);		// Get the IP address
		if (parseInt(mask) != mask) return false;	// Ensure mask is an integer
		if (mask < 0 || mask > 32) return false;	// The mask must be between 0 and 32 bits
		return checkIp(ip);							// Mask is ok, now check the IP address
	}
	else
	{
		// If no mask
		return checkIp(string);						// Just check string as an IP address
	}
}


// Check for a valid link (ie http://www.domain.com)
function checkLink(string)
{
	var good="~/:.-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
	for (var i = 0; i < string.length; i++) {
		if (good.indexOf(string.charAt(i)) == -1) return false;
	}
	return true;
} 


// MAC Address validation checks for valid characters and number of colons.
function checkMAC(string)
{
	var i, group, value;
	var good = ":0123456789abcdefABCDEF";
	var numColons = 0;

	if (string.length == 0) return false;	// If blank, return false

	// Ensures all the characters are valid and counts the number of colons ":"
	for (var i=0; i<string.length; i++)
	{
		if (good.indexOf(string.charAt(i)) == -1) return false;
		if (string.charAt(i) == ":") numColons++;
	}
	if (numColons != 5) return false;

	// Checks each group of 2 characters is <= 255
	for (var i=0; i<string.length; i=i+3)
	{
		group = string.substring(i, i+2);
		value = hex2dec(group);
		if (value >= 0 && value <= 255) return true;
		else return false;
	}
}


// Check for valid characters in a number
function checkNumber(string)
{
	var good = "-0123456789";
	for (var i = 0; i < string.length; i++) {
		if (good.indexOf(string.charAt(i)) == -1) return false;
	}
	return true;
}

// Check for valid characters in a decimal number
function checkDecimal(string)
{
	var good = "-0123456789.";
	for (var i = 0; i < string.length; i++) {
		if (good.indexOf(string.charAt(i)) == -1) return false;
	}
	return true;
}


// Ensures the string is between the min and max values
function checkNumRange(string, min_val, max_val)
{
	var good = "-0123456789";

	if (string.length == 0) return false;	// If blank, return false

	// Check all characters
	for (var i = 0; i < string.length; i++)
	{
		if (good.indexOf(string.charAt(i)) == -1) return false;
	}

	// If min/max values are incorrect, swap 'em
	if ( min_val > max_val )
	{
		var temp = min_val;
		min_val = max_val;
		max_val = temp;
	}

	string = Number(string);
	if (string < min_val) return false;
	else if (string > max_val) return false;
	else return true;
}


// Check for valid characters in a list of ports
function checkPortList(string)
{
	var good=" 0123456789-,";
	for (var i = 0; i < string.length; i++) {
		if (good.indexOf(string.charAt(i)) == -1) return false;
	}
	return true;
}


// Ensures all the characters in string are in good.
function checkTheseChars(string, good)
{
	for (var i = 0; i < string.length; i++) {
		if (good.indexOf(string.charAt(i)) == -1) return false;
	}
	return true;
}


// Check for valid characters in a time component
function checkTime(string)
{
	var good="0123456789:";
	for (var i = 0; i < string.length; i++) {
		if (good.indexOf(string.charAt(i)) == -1) return false;
	}
	// Make sure hours are less than 13
	var indexStart = 0;
	var indexEnd = string.indexOf(":");
	if (parseInt(string.substring(indexStart, indexEnd)) > 12) return false;
	indexStart = indexEnd+1;

	// Check that there is not anoter ":"
	if (string.indexOf(":", indexStart) != -1) return false;
	
	// Make sure minutes are less than 60
	if (parseInt(string.substring(indexStart)) > 59) 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;
}


// 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, "");
}


// Determine is a variable contains a number or not
function isNum(i)
{
	if ( i == parseInt(i) ) return true;
	else return false;
}

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.selectedIndex = i;			// for Safari
//				oDropdown.options[i].selected = true;	// for all others
				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;
}



