/*****************************************************************
 * This library contains a number of usefull functions that make
 * working with the JS DOM much easier/faster
 		mydh = new domHelper();
		mydh.opacityFader("messages", 100, 5);
		window.setTimeout('hideMessages()',3000);
 *****************************************************************/

function domHelper() {
	this.appendStyleClass = appendStyleClass;
	this.hideElement = hideElement;
	this.showElement = showElement;
	this.showBlockElement = showBlockElement;
	this.opacityFader = opacityFader;
	this.recursiveOpacityFader = recursiveOpacityFader;
	this.includeJavaScript = includeJavaScript;
	this.includeStyleSheet = includeStyleSheet;
	this.addEvent = addEvent;
	//this.addLoadEvent = addLoadEvent;


	this.cloneTree = cloneTree;
	this.cloneElementNode = cloneElementNode;
	this.cloneTextNode = cloneTextNode;
	this.getPreviousElementNode = getPreviousElementNode;
	this.getNextElementNode = getNextElementNode;
	this.getElementNode = getElementNode;
	this.setTextNodeValue = setTextNodeValue;
	this.getFirstNonHiddenFormFieldElement = getFirstNonHiddenFormFieldElement;
	this.duplicate = duplicate;
	this.duplicateFieldSet = duplicateFieldSet;
	this.duplicateFieldSet2 = duplicateFieldSet2;
	this.getElementByClass = getElementByClass;
	this.getSerializedFieldSetStructs = getSerializedFieldSetStructs;
	this.getFieldSetStructs = getFieldSetStructs;


	var ELEMENT_NODE = 1;
	var TEXT_NODE = 3

	
	/**
	 * Add/Append an additional style class to the DOM ELEMENT
	 * that matches the specified id
	 *
	 * @param elementID	[String]	The DOM ID of the element ot be modified
	 * @param styleClass [String]	The name of the style class to add to the element
	 */
	function appendStyleClass(elementID, styleClass) {
		if (document.getElementById) {
			var element = document.getElementById(elementID);
			element.className = element.className + ' ' + styleClass;
		}

		return;
	}
	
	
	/**
	 * Make an element/block disappear by forcing the display style to "none"
	 * This only works well for BLOCK elements (DIV, P, TABLE, ....)
	 *
	 * @param element	[String or Object]	The DOM ID or DOM Object of the element to be modified
	 */
	function hideElement(element) {
		if (document.getElementById) {	
			var myElement = element;
			if (isSimpleValue(element))
				myElement = document.getElementById(element);
	
			myElement.style.display = 'none';
		}
		return;
	}
	
	
	/**
	 * Make an element/block disappear by forcing the display style to "none"
	 * This only works well for BLOCK elements (DIV, P, TABLE, ....)
	 *
	 * @param element	[String or Object]	The DOM ID or DOM Object of the element to be modified
	 */
	function showElement(element) {
		if (document.getElementById) {
			var myElement = element;
			if (isSimpleValue(element))
				myElement = document.getElementById(element);
				
			myElement.style.display = '';
		}

		return;
	}
	
	
	/**
	 * Make an element/block disappear by forcing the display style to "none"
	 * This only works well for BLOCK elements (DIV, P, TABLE, ....)
	 *
	 * @param element	[String or Object]	The DOM ID or DOM Object of the element to be modified
	 */
	function showBlockElement(element) {
		if (document.getElementById) {
			var myElement = element;
			if (isSimpleValue(element))
				myElement = document.getElementById(element);

			myElement.style.display = 'block';
		}

		return;
	}	
	
	
	/**
	 * Make an element fade away by changing it's opacity from 100% to 0%
	 *
	 * @param elementID	[String]	The DOM ID of the element to be modified
	 * @param timeout [Integer]		The number of MILLISECONDS between opacity decrementing iterations
	 * @param step [Integer]		The number (%) by which the opacity is decremented each iteration
	 */
	function opacityFader(elementID, timeout, step) {
		if (document.getElementById) {
			var element = document.getElementById(elementID);
			
			if(!document.opacityFader) {
				document.opacityFader = new Array();

				var func = 'function opacityFaderWorker(opacityID) { var domH = new domHelper(); domH.recursiveOpacityFader(opacityID); }';
				document.writeln('<script>' + func + '<' + '/script>');				
			}
			
			// Get a random fader ID
			var opacityFaderID = Math.floor(Math.random() * 100);
			document.opacityFader[opacityFaderID] = new Array();

			var opacityArray = document.opacityFader[opacityFaderID];
			opacityArray["element"] = element;
			opacityArray["timeout"] = timeout;
			opacityArray["step"] = step;
			opacityArray["lastOpacity"] = 100;

			recursiveOpacityFader(opacityFaderID);
			
			//window.setTimeout('dh.recursiveOpacityFader(' + opacityFaderID + ')', 2000);			
		}

		return;
	}
	
	
	/**
	 * This method SHOULD NOT BE CALLED DIRECLTY !!!!! 
	 * Make an element fade away by changing it's opacity from 100% to 0%
	 *
	 * @param elementID	[String]	The DOM ID of the element to be modified
	 * @param timeout [Integer]		The number of MILLISECONDS between opacity decrementing iterations
	 * @param step [Integer]		The number (%) by which the opacity is decremented each iteration
	 */
	function recursiveOpacityFader(faderID) {	
			// Read in info for this fader profile
			var opacityArray = document.opacityFader[faderID];
			var element = opacityArray["element"];
			var timeout = parseInt(opacityArray["timeout"]);
			var step = parseInt(opacityArray["step"]);
			var lastOpacity = parseInt(opacityArray["lastOpacity"]);

			if(lastOpacity == 0) {
				return;
			}

			var newOpacity = lastOpacity - step;
			if(newOpacity < 0) {
				newOpacity = 0;
			}


			// Set "filter" attribute, format is:  alpha(opacity=50)			// 50%
			element.style.filter = 'alpha(opacity=' + newOpacity + ')';
			
			// Set "-moz-opacity" attriubte, format is:  .5     				// 50%
			element.style["-moz-opacity"] = newOpacity/100;
			
			// Set "opacity" attribute, format is:  .5							// 50%
			element.style.opacity = newOpacity/100;


			// Reford the last value for opacity in the "Global Array" or we'll always be at 100%
			opacityArray["lastOpacity"] = newOpacity;
			
			window.setTimeout('opacityFaderWorker(' + faderID + ')', timeout);
	}


	/**
	 * This function duplicates a tree/block of HTML, updates all the IDs and NAME
	 * attributes on any of the elements in the tree to have the specified suffix
	 * to avoid naming conflicts, and then inserts the new tree/block either
	 * before the insertionPoint, after the insertionPoint, or replaces the
	 * insertionPoint with the new tree/block
	 *
	 * @param element		The element object or id that we wish to duplicate
	 * @param suffix		The suffix to apply to any ID or NAME attributes in the new tree
	 * @param insertionPoint	The element object or id that we wish to use as the point of reference
	 *							for where the new tree/block should be inserted into the document
	 * @param insertionMethod	Valid values are:  before, after, replace
	 * @return 				A reference to the newly created tree/block
	 */
	function duplicate(element, suffix, insertionPoint, insertionMethod) {
		var myElement = element;
		if (isSimpleValue(element))
			myElement = document.getElementById(element);

		var newElement = cloneTree(myElement, suffix);
		insertElement(newElement, insertionPoint, insertionMethod);

		return newElement;
	}
	

	/**
	 * This function inserts an element either before the insertionPoint,
	 * after the insertionPoint, or replaces the insertionPoint with the new element
	 *
	 * @param element			The element object or id that we wish to duplicate
	 * @param insertionPoint	The element object or id that we wish to use as the point of reference
	 *							for where the new tree/block should be inserted into the document
	 * @param insertionMethod	Valid values are:  before, after, replace
	 * @return 					Void
	 */	
	function insertElement(element, insertionPoint, insertionMethod) {
	    var myElement = element;
		var myInsertionPoint = insertionPoint;

		if (isSimpleValue(element))
			myElement = document.getElementById(element);

	 	if (isSimpleValue(insertionPoint))
			myInsertionPoint = document.getElementById(insertionPoint);

		var pNode = myInsertionPoint.parentNode;
		var myInsertionMethod = insertionMethod.toLowerCase();

		if(myInsertionMethod == 'before') {
			pNode.insertBefore(myElement, myInsertionPoint);
		} else if(myInsertionMethod == 'after') {
			pNode.insertAfter(myElement, myInsertionPoint);
		} else {	// Replace
			pNode.insertBefore(myElement, myInsertionPoint);
			pNode.removeChild(myInsertionPoint);
		}	
	}
	
	
	/**
	 * This function is a shortcut for copying an entire ROW/SET of fields.
	 * It assumes that the ROW/SET of fields is bound by some wrapping element, like a TR,
	 * and if you have a column holding and index (i.e. 1-10), this method will update
	 * that index for you if you so choose. This method will also update a form field that
	 * keeps track of the number of ROWs/FIELD SETs on the screen.
	 *
	 * @param element		The element object or id that we wish to duplicate
	 * @param suffix		The suffix to apply to any ID or NAME attributes in the new tree, really the row/set index delimiter
	 * @param insertionPoint	The element object or id that we wish to use as the point of reference
	 *							for where the new tree/block should be inserted into the document
	 * @param insertionMethod	Valid values are:  before, after, replace
	 * @param counterFieldID	The NAME/ID of the form field that will track the # of ROWs/FIELD SETs
	 * @param counterColumnIndex	The index of the element node beneath the newly created tree that holds the
	 *								ROW/FIELD SET index. Zero based, if the number is < 0, no action is taken
	 * @return 				A reference to the newly created tree/block
	 */	
	function duplicateFieldSet(element, suffix, insertionPoint, insertionMethod, counterFieldID, counterColumnIndex) {
		myCounterField = document.getElementById(counterFieldID);
		
		if(!document[counterFieldID]) {
			document[counterFieldID] = 0;
		}
		
		var newIndex = parseInt(document[counterFieldID]); //myCounterField.value
		newIndex++;		

		var newElement = duplicate(element, suffix + newIndex, insertionPoint, insertionMethod);

		var ccNode = null;
		for(var i=0; i<=counterColumnIndex; i++) {
			if(ccNode == null) {	
				ccNode = getElementNode(newElement.firstChild);
			} else {
				ccNode = getElementNode(ccNode.nextSibling);
			}
			
			if(ccNode == null) break;
		}

		if(counterColumnIndex >= 0 && ccNode != null) {
			setTextNodeValue(ccNode, newIndex + '.');
		}		

		myCounterField.value = newIndex;
		document[counterFieldID] = newIndex;

		newElement.style.display = '';
		
		var field = getFirstNonHiddenFormFieldElement(newElement);		
		if(field != null) {
			//field.focus();
		}
		
// ---------------------------		
		// BLANK OUT all form field values first
// ---------------------------				

		
		return newElement;
	}		



	/**
	 * This function is a shortcut for copying an entire ROW/SET of fields.
	 * It assumes that the ROW/SET of fields is bound by some wrapping element, like a TR,
	 * and assumes you will be placing a suffix to the IDs and Names of elements (i.e. _1-10),
	 *
	 * @param element			The element object or id that we wish to duplicate
	 * @param prefix			The prefix used to keep the field sets separate from other field sets
	 * @param suffix			The suffix to apply to any ID or NAME attributes in the new tree
	 * @param insertionPoint	The element object or id that we wish to use as the point of reference
	 *							for where the new tree/block should be inserted into the document
	 * @param insertionMethod	Valid values are:  before, after, replace
	 * @param dataSet			NUll or an Object/Structure holding the default data for the form fields in this set
	 * @param setFocus			True/False for whether or NOT to setFocus to the newest form element created.
	 *
	 * @return 				A reference to the newly created tree/block
	 */
	function duplicateFieldSet2(element, prefix, suffix, insertionPoint, insertionMethod, dataSet, pSetFocus) {
		// Duplicate the element and insert it into the DOM
		var newElement = duplicate(element, '_' + suffix, insertionPoint, insertionMethod);

		// Set default data
		if(dataSet != null) {
			setDefaultData(newElement, dataSet);
		}

		// Make the new element visible
		showElement(newElement);

		// Set focus to the first active/visible form field in this field set
		if(pSetFocus) {		// if(eval(pSetFocus)) {
			//var e = Form.findFirstElement(newElement);
			var e = getFirstNonHiddenFormFieldElement(newElement);
			if(e != null) {
				e.focus();
			}
		}

		return newElement;		
	}
	
	
	/**
	 * This method searches for all the (child) form elements within the given element
	 * and sets their default data to match the values in the data set specified
	 *
	 * @param element		The element (object) whose CHILD form elements we want to default
	 * @param dataSet		The set of data to use as the default data for the CHILD form elements
	 * @return void
	 */
	function setDefaultData(element, dataSet) {
		var frmElements = Form.getElements(element);
		
		for(var i=0; i<frmElements.length; i++) {
			var frmElement = frmElements[i];	
			var fldParts = frmElement.id.split('_');
			
			if(fldParts.length >= 2) {
				// Find the name of the member for this "indexed field" and see if we have information for it
				var member = fldParts[1];
				if(!isUndefined(dataSet[member])) {
					var frmElTag = frmElement.tagName.toLowerCase();
					var data = dataSet[member];
					
					if(frmElTag == 'textarea') {
						setTextAreaDefault(frmElement, data);
					} else if (frmElTag == 'select') {
						setSelectBoxDefault(frmElement, data);
					} else {
						var type = frmElement.type.toLowerCase();
						if(type == 'checkbox') {
							setCheckboxDefault(frmElement, data);
						} else if(type == 'radio') {
							setRadioButtonDefault(frmElement, data);
						} else if (type == 'file') {
							// Don't touch, due to security restrictions in browsers, scripts shouldn't set this value
						} else {
							setTextboxDefault(frmElement, data);
						}
					}
				}				
			}
			
		}
	}
	
	
	/**
	 * Set the default data for the specified element
	 * @param element	The element (object) whose default data we need to set
	 * @param data		The data (string) we're going to set this element's default value to
	 */
	function setTextAreaDefault(element, data) {
		element.value = data;
	}
	
	/**
	 * Set the default data for the specified element
	 * @param element	The element (object) whose default data we need to set
	 * @param data		The data (string or an array of strings) we're going to set this element's default value to
	 */
	function setSelectBoxDefault(element, data) {
		var values = data;	
		if(isSimpleValue(data)) {
			values = [ data ];
		}
		
		var sbOptions = element.options;
		for(var i=0; i<sbOptions.length; i++) {
			for(var j=0; j<values.length; j++) {
				if(sbOptions[i].value == values[j]) {
					sbOptions[i].selected = true;
				}
			}
		}
	}	

	/**
	 * Set the default data for the specified element
	 * @param element	The element (object) whose default data we need to set
	 * @param data		The data (string) we're going to set this element's default value to
	 */	
	function setRadioButtonDefault(element, data) {
		if(element.value == data) element.checked = true;	
	}		

	/**
	 * Set the default data for the specified element
	 * @param element	The element (object) whose default data we need to set
	 * @param data		The data (string or an array of strings) we're going to set this element's default value to
	 */
	function setCheckboxDefault(element, data) {
		var values = data;	
		if(isSimpleValue(data)) {
			values = [ data ];
		}
		
		for(var i=0; i<values.length; i++) {
			if(element.value == values[i]) {
				element.checked = true;
			}
		}
	}	

	/**
	 * Set the default data for the specified element
	 * @param element	The element (object) whose default data we need to set
	 * @param data		The data (string) we're going to set this element's default value to
	 */	
	function setTextboxDefault(element, data) {
		element.value = data;
	}			
	

	function isSimpleValue(value) {
		if(typeof value == 'number' || typeof value == 'string' || typeof value == 'boolean') {
			return true;
		}
		
		return false;
	}
	
	
	function isUndefined(value) {
		if(typeof value == "undefined") {
			return true;
		}
		
		return false;
	}

	
	/**
	 * Convert the field sets contained by the parent element into an array of structures
	 * so we can easily persist the field sets. Each field set will be stored as an object/structure
	 * with in an array of fieldsets. Only the "member" name will be saved, not the actual HTML ID.
	 *
	 * @param element	The PARENT element containing field sets that shoudl be serialized
	 * @param className	The className used to identify the parent/container for EACH set of fields in the super set
	 * @return 			An array of structures/objects containing the values for the CHILD form elements for the specified element
	 */
	function getFieldSetStructs(element, className) {
		var convertedSets = [];
		var sets = getFieldSets(element, className);
		
		for(var i=0; i<sets.length; i++) {
			convertedSets.push(fieldSetToStruct(sets[i]));
		}
		
		return convertedSets;
	}
	

	/**
	 * Serialize the field sets contained by the parent element into JSON objects
	 * so we can easily persist them. Each field set will be stored as an object/structure
	 * with in an array of fieldsets. Only the "member" name will be saved, not the actual HTML ID.
	 *
	 * @param element	The PARENT element containing field sets that shoudl be serialized
	 * @param className	The className used to identify the parent/container for EACH set of fields in the super set
	 * @return 			A JSON string for the array of structures/objects representing the fieldSet's values
	 */
	function getSerializedFieldSetStructs(element, className) {	
		return JSON.stringify(getFieldSetStructs(element, className));	
	}	

	
	/**
	 * Replicate the given field set as a Structure containing all
	 * of the members that there are form fields for
	 * @param	element		The top/parent FieldSet element whose child form elements will be placed in a structure
	 * @return				A structure/object with keys for each form field member in the field set
	 */
	function fieldSetToStruct(element) {
		var dataStruct = {};
		var frmElements = Form.getElements(element);
		
		for(var i=0; i<frmElements.length; i++) {
			var frmElement = frmElements[i];	
			var fldParts = frmElement.id.split('_');
			
			if(fldParts.length >= 2) {
				// Find the name of the member for this "indexed field" and see if we have information for it
				var member = fldParts[1];
				var fldValue = '';
				var currValue = null;
				if(!isUndefined(dataStruct[member])) {
					currValue = dataStruct[member];
				}
	
				var frmElTag = frmElement.tagName.toLowerCase();
				
				if(frmElTag == 'textarea') {
					fldValue = frmElement.value;
				} else if (frmElTag == 'select') {
					fldValue = getSelectBoxData(frmElement);
				} else {
					var type = frmElement.type.toLowerCase();
					if(type == 'checkbox') {
						if(frmElement.checked) { fldValue = appendFormData(currValue, frmElement.value); }
					} else if(type == 'radio') {
						if(frmElement.checked) { fldValue = frmElement.value; }
					} else if (type == 'file') {
						// Don't touch, due to security restrictions in browsers, scripts shouldn't set this value
					} else {
						fldValue = frmElement.value;
					}
				}
				
				dataStruct[member] = fldValue;
			}
		}

		return dataStruct;		
	}
	
	
	/**
	 * Appaned a new value to the current value
	 * If there is NO current value (null), then just return the newValue as a string
	 * If there IS a current value (string), then return an array with the original first value, and the new second value and return the array
	 * If there IS a current value (array), append the new value to the array and return the array
 	 *
	 * @param currValue	The current value that we already have for this form element/member
	 * @param newValue	The new value we have found for this form element/menber
	 * @return			The selected values for the data member (string or array of strings)
	 */
	function appendFormData(currValue, newValue) {
		if(currValue == null) {
			return newValue;
		}
		
		if(isSimpleValue(currValue)) {
			newArr = [ currValue ];
			newArr.push(newValue);
			return newArr;
		}
		
		// Assumed currValue is an array at this point
		currValue.push(newValue);
		return currValue;
	}

	/**
	 * Get all the "selected" selections
	 * If there are MULTIPLE selections, then they should
	 * be stored as an array
	 * 
	 * @param element	The form element we're getting the value for
	 * @param currValue	The current value that we already have for this form element/member
	 *					If it's NOT emtpy, and there's another selected value, make sure the values
	 *					get stored as an array.
	 * @return			The selected values for the data member (string or array of strings)
	 */	
	function getSelectBoxData(element) {
		var currValue = null;

		var sbOptions = element.options;
		for(var i=0; i<sbOptions.length; i++) {
			if(sbOptions[i].selected) {
				currValue = appendFormData(currValue, sbOptions[i].value);
			}
		}
		
		return currValue;	
	}
	
	/**
	 * Get the field sets contained by the parent element. ONLY return the visible field sets.
	 * Do NOT return the prototype field set. The way we tell the parent field set is that IT"S
	 * ALWAYS the FIRST field set returned, so we just skip it.
	 *
	 * @param element	The PARENT element containing field sets that shoudl be serialized
	 * @param className	The className used to identify the parent/container for EACH set of fields in the super set
	 */
	function getFieldSets(element, className) {
		var elements = document.getElementsByClassName(className, element);

		var newElements = [];
		for(var i=1; i<elements.length; i++) {
			newElements.push(elements[i]);
		}
		
		return newElements;
	}	


	/**
	 * This function creates an identical DOM hierarchy for the given element
	 *
	 * @param origTopElement	The Element (DOM Node) whose hieararchy we will
	 *							be duplicating
	 * @parem suffix			The suffix that will be appended to any node's
	 *							'id' attribute to prevent a document having multiple
	 *							elements with the same 'id'
	 * @return					An identical DOM Node/hierarchy unattached to the document
	 */
	function cloneTree(origTopElement, suffix) {
		var newTopElement = origTopElement.cloneNode(true);
		updateAttributesInTree(newTopElement, suffix);
		
		//var newTopElement = cloneElementNode(origTopElement, suffix);
		//cloneTreeRecursive(origTopElement, newTopElement, suffix);
		
		return newTopElement;
	}


	/**
	 * Update the IDs and Name attributes in the ENTIRE TREE to avoid naming conflicts
	 * Use the pattern: "orig_value" + "suffix" for the new value
	 *
	 * @param element	The top node of the tree to updat
	 * @param suffix	The value to append to the current ID and NAME attribute values
	 * @return 			void
	 */
	function updateAttributesInTree(element, suffix) {
		var cNodes = element.childNodes;
		
		for(var i=0; i<cNodes.length; i++) {
			if(cNodes[i].nodeType == ELEMENT_NODE) {
				updateAttributesInTree(cNodes[i], suffix);
			}
		}
		
		//alert(element.nodeName);
		updateElementAttributes(element, suffix);
		
		return;	
	}


	/**
	 * Update the ID and NAME attributes for a SINGLE ELEMENT to avoid naming conflicts
	 * Use the pattern: "orig_value" + "suffix" for the new value
	 *
	 * @param element	Element node whose attributes we will modify
	 * @param suffix	The value to append to the current ID and NAME attribute values
	 * @return 			void
	 */	 
	function updateElementAttributes(element, suffix) {
		/* Attempted IE fix for duplicate NAME attributes*/
		/*
			element.nodeName.toLowerCase() == 'input'
			|| element.nodeName.toLowerCase() == 'textarea'
			|| element.nodeName.toLowerCase() == 'form'
			|| element.nodeName.toLowerCase() == 'img'	
		*/

		if(
			element.nodeName.toLowerCase() == 'label'			
		) {

		var nn = cloneElementNode(element, suffix);
		var p = element.parentNode;
		
		var cNodes = element.childNodes;
		for(var j=0; j<cNodes.length; j++) {
			nn.appendChild(cNodes[j]);
		}
		
		p.insertBefore(nn, element);
		p.removeChild(element);
		return;
		
		}
		

		var atts = element.attributes;
		var idSet = false;
		var nameSet = false;
		var forSet = false;
		
		
		for(i=0; i<atts.length; i++) {
			var attName = atts[i].name;
			var attValue = atts[i].value;

			if(attValue.length > 0 && attValue != 'null' && attValue != 'false' && attValue != 'undefined') {
				attValue = attValue + suffix;
			
				if(attName.toLowerCase() == 'id') {
					element.id = attValue;
				} else if(attName.toLowerCase() == 'name') {
					element.name = attValue;
				} else if(attName.toLowerCase() == 'for' && !forSet) {
					alert(atts[i].value + "  :: " + attValue);
					element.setAttribute(attName, attValue);
					forSet = true;
				}
			}
		}
				
		return;
	}


	/**
	 * This function recursively creates a duplicate, parallel DOM hierarchy
	 * for the given element
	 *
	 * @param origParent		The Element (DOM Node) whose hieararchy we will
	 *							be duplicating
	 * @param newParent			The Element (DOM Node) that the duplicate hieararchy will
	 *							be attached to
	 * @parem suffix			The suffix that will be appended to any node's
	 *							'id' attribute to prevent a document having multiple
	 *							elements with the same 'id'
	 */
	function cloneTreeRecursive(origParent, newParent, suffix) {
		var cNodes = origParent.childNodes;
		
		for(var i=0; i<cNodes.length; i++) {
			var origNode = cNodes[i];
			var newNode = null;

			if(origNode.nodeType == ELEMENT_NODE) {
				newNode = cloneElementNode(origNode, suffix);	
				cloneTreeRecursive(origNode, newNode, suffix);
			} else {
				newNode = cloneTextNode(origNode);	
			}
			
			newParent.appendChild(newNode);
		}
		
		return;
	}
	

	
	/**
	 * Create a duplicate TAG node with identical attributes as the original.
	 * The TAG node returned has no parents or children
	 *
	 * @param element		The TAG node we will be duplicating
	 * @param suffix		The value that will be appended to the ID attribute value
	 *						if we come across one to avoid having 2 elements with the same ID
	 * @return				A TAG node with attributes assigned
	 */
	function cloneElementNode(element, suffix) {
		var newElement = document.createElement(element.nodeName);
		var atts = element.attributes;
		var hwElements = ',img,table,';
		var hwAttributes = ',height,width,hspace,vspace';
		
		for(i=0; i<atts.length; i++) {
			var attName = atts[i].name;
			var attValue = atts[i].value;
			
			var applyStyle = false;
			
			if(attValue != null) {
				if(attValue.length > 0 && attValue != 'null' && attValue != 'false' && attValue != 'undefined') {				
					if(attName.toLowerCase() == 'tabindex') {
						// Do nothing
					} else if(hwAttributes.indexOf(',' + attName.toLowerCase() + ',') >= 0) {
						if(hwElements.indexOf(element.nodeName) >= 0) {	
							applyStyle = true;
						} 				
					} else {
						applyStyle = true;
					}
				}
			}
			
			if(applyStyle) {
				if(attName.toLowerCase() == 'id') {
					newElement.id = attValue + suffix;
				} else if(attName.toLowerCase() == 'name') {
					newElement.name = attValue + suffix;
					newElement.id = attValue + suffix;		
				} else if(attName.toLowerCase() == 'for') {
					newElement.setAttribute('for', attValue + suffix);
				} else {
					newElement.setAttribute(attName,attValue);														
				}
				
				/*
				if(attName.toLowerCase() == 'id' || attName.toLowerCase() == 'name') {
					alert(attName);
					attValue += suffix;
				}
				newElement.setAttribute(attName,attValue);										
				*/
			}
			
		}
		
		return newElement;		
	}
	


	/**
	 * Create a duplicate TEXT node with identical content as the original.
	 *
	 * @param element		The TEXT node we will be duplicating
	 * @return				A TEXT node
	 */	
	function cloneTextNode(element) {
		return document.createTextNode(element.nodeValue);
	}	
	

	/**
	 * This function gets the NEXT MOST PREVIOUS SIBLING ELEMENT Node in relation to the given node
	 * This is usefull for retreiving the next element node in DOMs where excess text nodes
	 * are inserted where you would normally expect an element node.
	 *
	 * @param element	The node we want to get the previous sibling ELEMENT node for
	 * @return			NULL or a SIBLING ELEMENT node
	 */
	function getPreviousElementNode(element) {
		var cNode = element;
		// Keep checking the previous sibling
		// until we find a NULL or ELEMENT NODE	
		while(true) {
			cNode = cNode.previousSibling;

			if(cNode == null || cNode == undefined) {
				cNode = null;
				break;
			} else if(cNode.nodeType == ELEMENT_NODE) {
				break;
			}
			
		}
		
		return cNode;		
	}


	/**
	 * This function gets the NEXT SIBLING ELEMENT Node in relation to the given node, or the GIVEN node if it's an ELEMENT node.
	 * This is usefull for retreiving the first ELEMENT node the next level deeper in the DOM hierarchy than you currently are.
	 * For instance  n = getElementNode(currentNode.firstChild);, this will get the first element node at the next deepest level in the DOM.
	 *
	 * @param element	The node we want to get the next sibling ELEMENT node for
	 * @return			NULL or a SIBLING ELEMENT node
	 */	
	function getElementNode(element) {
		if(element.nodeType == ELEMENT_NODE) {
			return element;
		}
		
		return getNextElementNode(element);
	}


	/**
	 * This function gets the NEXT SIBLING ELEMENT Node in relation to the given node
	 * This is usefull for retreiving the next element node in DOMs where excess text nodes
	 * are inserted where you would normally expect an element node.
	 *
	 * @param element	The node we want to get the next sibling ELEMENT node for
	 * @return			NULL or a SIBLING ELEMENT node
	 */
	function getNextElementNode(element) {
		var cNode = element;
		// Keep checking the previous sibling
		// until we find a NULL or ELEMENT NODE	
		while(true) {
			cNode = cNode.nextSibling;

			if(cNode == null || cNode == undefined) {
				cNode = null;
				break;
			} else if(cNode.nodeType == ELEMENT_NODE) {
				break;
			}
			
		}
		
		return cNode;			
	}	
	
	
	function getPreviousElementNode(element) {
		var cNode = element;
		// Keep checking the previous sibling
		// until we find a NULL or ELEMENT NODE	
		while(true) {
			cNode = cNode.previousSibling;

			if(cNode == null || cNode == undefined) {
				cNode = null;
				break;
			} else if(cNode.nodeType == ELEMENT_NODE) {
				break;
			}
			
		}
		
		return cNode;			
	}	
	
	
	/**
	 * This function updates the value for where you would expect a TEXT node to be.
	 * It's really a conenience method so that you can say setTextNodeValue(myTableCellNode, myNewValue)
	 * instead of having to do:   myTableCellNode.getFirstChild.setNodeValue(myNewValue)
	 * and it's more compatable with many more DOMs
	 *
	 * @param element	The node whose closest TEXT node we want to update
	 * @param textValue	The value the next node should hold
	 * @return			void
	 */
	function setTextNodeValue(element, textValue) {
		if(element.nodeType == TEXT_NODE) {
			element.parentNode.appendChild(document.createTextNode(textValue));
			element.nodeValue = textValue;
		
		} else {
			var children = element.childNodes;
			for(var i=0; i<children.length; i++) {
				var cNode = children[i];
				if(cNode.nodeType == TEXT_NODE) {
					cNode.nodeValue = textValue;
					break;
				}
			}
		}		
		
		return;
	}
	
	
	/**
	 * This function gets the first Non Hidden or Disabled Form FIELD element it comes across in a given DOM tree/branch
	 * If you're replicating rows of fields, this is very convenient to use to get a reference to the first
	 * form field in the row so you can set focus to it after replication.
	 *
	 * @param element	The node whose closest form FIELD object we want to get
	 * @return			NULL or a Form FIELD object
	 */	
	function getFirstNonHiddenFormFieldElement(element) {
		var cNodes = element.childNodes;
		var cNode = null;
		
		for(var i=0; i<cNodes.length; i++) {
			cNode = cNodes[i];
			if(cNode.nodeType == ELEMENT_NODE) {
				if(isFormFieldElement(cNode)) {
					if(!isFormFieldHiddenOrDisabled(cNode)) {
						return cNode;
					}
				}

				cNode = getFirstNonHiddenFormFieldElement(cNode);
				
				if(cNode != null) return cNode;
			}
		}
		
		return null;
	}
	
	

	/**
	 * Determine if the given element is HIDDEN or DISABLED
	 *
	 * @param element	The node we want to examine
	 * @return			True if hidden or disabled, false otherwise
	 */	
	function isFormFieldHiddenOrDisabled(element) {
		if(element.nodeType == ELEMENT_NODE) {
			if(isElementHidden(element)) return true;
			
			var atts = element.attributes;
			for(var i=0; i<atts.length; i++) {
				var attName = atts[i].name;		
				var attValue = atts[i].value;		

				if(attName.toLowerCase() == 'type') {
					if(attValue.toLowerCase() == 'hidden') {
						return true;
					}
				}
				
				if(attName.toLowerCase() == 'hidden') {
					return true;
				}
				
				if(attName.toLowerCase() == 'disabled') {
					if(attValue.toLowerCase() == 'true') {
						return true;
					}
				}
			}
		}
		
		return false;
	}
	
	
	/**
	 * Determine if the given node is a form FIELD (excludes buttons, field sets, etc...)
	 * Only editable fields will return true, buttons, field sets, etc... will return false.
	 *
	 * @param element	The node we want to examine
	 * @return			True if the element is a Form FIELD object, false otherwise
	 */	
	function isFormFieldElement(element) {
		if(element.nodeType == ELEMENT_NODE) {
			if(element.nodeName.toLowerCase() == 'input') {
				var iType = element.attributes.type.value;
				iType = iType.toLowerCase();
				
				if(iType == 'button' || iType == 'submit' || iType == 'reset' || iType == 'image' || iType == 'cancel') {
					return false;
				}
				
				return true;
				
			} else if(element.nodeName.toLowerCase() == 'select') {
				return true;			
			
			} else if(element.nodeName.toLowerCase() == 'textarea') {
				return true;
			}
		}
		
		return false;
	}
	
	
	
	/**
	 * This function retreives a DOM node be seeing if it has
	 * a class attribute that matches the specified class name.
	 * The script can choose to move up or down the DOM relative
	 * to a given node to find the next closest element with a
	 * matching class name.
	 *
	 * @param	className	The name of the style class we're looking to match
	 * @param	startNode	The dom node we plan to start at
	 * @param	direction	Valid values are "up" or "down", the direction we will traverse the DOM
	 * @return	NULL or an ELEMENT node that has a class name that matches the specified class
	 */
	function getElementByClass(className, startNode, direction) {
		var cNode = null;
		
		if(startNode.nodeType == ELEMENT_NODE) {
			
			// See if START NODE has a matching class name
			var atts = startNode.attributes;
			
			for(var i=0; i<atts.length; i++) {
				var attName = atts[i].name;
				var attValue = atts[i].value;
				
				if(attName.toLowerCase() == 'class') {
					if(attValue == className) {
						return startNode;
					}
				}
			}
			
			
			// Start node didn't match, but see if the next DOM elemement does			
			var sib = null;
			if(direction == 'up') {
				sib = getPreviousElementNode(startNode); //startNode.previousSibling;
			} else {
				sib = getNextElementNode(startNode); //startNode.nextSibling;
			}
			
			if(sib != null) {
				return getElementByClass(className, sib, direction);
			}
			
			// No siblings, so try parent or children
			if(direction == 'up') {
				return getElementByClass(className, startNode.parentNode, direction);
			} else {
				var nds = startNode.childNodes();
				for(var i=0; i<nds.length; i++) {
					return getElementByClass(className, nds[i], direction);
				}
			}
						
		}
		
		return null;	
	}
	
	
	
	/**
	 * Dynamically include another external javascript library into the current space
	 *
	 * @param	sript_filename	The URL PATH to the external javascript file to inclue
	 * @return	void
	 */
	function includeJavaScript(script_filename) {
		var html_doc = document.getElementsByTagName('head').item(0);
		var js = document.createElement('script');
		js.setAttribute('language', 'javascript');
		js.setAttribute('type', 'text/javascript');
		js.setAttribute('src', script_filename);
		html_doc.appendChild(js);
		return false;
	}
	
	
	/**
	 * Dynamically include another external javascript library into the current space
	 *
	 * @param	sript_filename	The URL PATH to the external javascript file to inclue
	 * @param	tag_attributes	A hash of additional tag attributes that can be passed to the tag creation script
	 * @return	void
	 */
	function includeStyleSheet(css_filename, tag_attributes) {
		var html_doc = document.getElementsByTagName('head').item(0);
		var css = document.createElement('link');
		css.setAttribute('rel', 'stylesheet');		
		css.setAttribute('type','text/css');
		css.setAttribute('href', css_filename);

		for (var att in tag_attributes) {
			css.setAttribute(att, tag_attributes[att]);
		}

		html_doc.appendChild(css);
		return false;
	}	
	
	
	/**
	 * This is a simple way to set a function to run on a specific event
	 *
	 * @param	elm		The actual element object to work with
	 * @param	evType	The event for the specified element we want to work with
	 * @param	fn		The function to execute when the event triggers
	 * @param	userCapture	?
	 * @return	void
	 *
	 * example:   addEvent(window,'load',func1,false);
	 */
	function addEvent(elm, evType, fn, useCapture) {
		if (elm.addEventListener) {
			elm.addEventListener(evType, fn, useCapture);
			return true;
		}
		else if (elm.attachEvent) {
			var r = elm.attachEvent('on' + evType, fn);
			return r;
		}
		else {
			elm['on' + evType] = fn;
		}
	}
	
	
	/**
	 * This is a simple way to set a function to run when the page loads
	 * it makes use of the addEvent function above, and greatly reduces
	 * the number of parameters needed to simply add a function to the onload event
	 * of the page
	 *
	 * @param	func	The function to execute when the event triggers
	 * @return	void
	 */
	/*
	function addLoadEvent(func) {
		var oldonload = window.onload;
		if (typeof window.onload != 'function') {
			window.onload = func;
		}
		else {
			window.onload = function() {
				oldonload();
				func();
			}
		}
	}
	*/	
	
	
	
	/**
	 * This is a simple way to detect if an element is hidden or not. It can only tell you if it's hidden
	 * or not if it the element itself, or one of it's parents, has a CSS setting of display: none;
	 * that was set DIRECTLY and not through a style class.
	 *
	 * @param	e	The element to check and see if it is hidden or if its parents are hidden
	 * @param	s	A Stop-At element to prevent going ALL the way up the DOM hierarchy
	 * @return	void
	 */	
	function isElementHidden(e) {
		var element = e;
		var stopAtElement = null;
		if(arguments.length > 1) stopAtElement = arguments[1];
		
		// If we hit the outer limits of our search
		if(element == stopAtElement) {
			return false;
		}
	
		
		// If the current element is hidden, we're done
		if(element != null) {
			if(element.style) {
				if(element.style.display == 'none') return true;
			}
	
			// If there are more parents to check
			var pn = element.parentNode;
			if(pn != null) {
				return isElementHidden(pn, stopAtElement);
			}
		}
	
	
		return false;
	}	
	
	
}
