/*-----------------------------------------------------------------------------
 * Element object wrapper.  This object adds functionality used by much of the
 * menu and selection code to the supplied element.  
 * 
 * NOTES: prototype functions can be added to the element.  Some functions are
 * not done as prototypes because they need to call other functions and prototypes
 * can NOT call other object functions - but they can access the object's variables.
 *
 * Modifications:
 * Date        By          Description
 * ----------  ----------  -------------------------------------------------
 *  2/04/2008  Garth       Fixed "tagNam." to "tagName.".
 * 11/10/2006  Garth       Initial split from general.js file.
 * 12/22/2006  Garth       Added _EnhancedElementSerialNumber counter.
 * 03/07/2007  Garth       Changed class name stuff to use more complete regexp.
 * 04/09/2007  Garth       Added ability to refine get child elements by classname.
 *---------------------------------------------------------------------------*/


// Counter used when creating elements so that they can be uniquely identified.
var _EnhancedElementSerialNumber = 0;


// Contructor.  Gets an object based on the ID passed in.  This function centralizes 
// the browser get object type code and extends/adds many common methods.  
// See: http://www.quirksmode.org/ DHTML Micro API
// Arguments: elementIDorObject = HTML element's ID attribute value or an actual
//   object to be extended.
// Returns: Object element passed in with the new extended methods attached.
function EnhancedElement(elementIDorObject)
{
	var elementToExtend = null;		// temporary private variable.

	// get the object to enchance
	if(typeof(elementIDorObject) == "string")
	{
		if(document.getElementById)
			elementToExtend = document.getElementById(elementIDorObject);
		else if(document.all)
			elementToExtend = document.all[elementIDorObject];
		else if(document.layers)
	   		elementToExtend = document.layers[elementIDorObject];
//alert("Element.byID, idString in: " +elementIDorObject+", object: " +elementToExtend);
	}
	else if(typeof(elementIDorObject) == "object")
		elementToExtend = elementIDorObject;

	// if element does not exist then throw exception.  Tried to get return(null)
	// to work to keep things simplier but this wouldn't work.  Also tried this=null;
	// but that doesn't work either.  A try catch is a little more work but it's
	// really the best way to handle in either case.
	if(!elementToExtend) throw("Problem creating EnhancedElement.  Base element ("+elementIDorObject+") does not exist.");

//alert("EnhancedElement constructor, idString in: " +elementIDorObject+", object: " +elementToExtend);
	elementToExtend.getNodeName               = this.getNodeName;
	elementToExtend.hasChildElements          = this.hasChildElements;
	elementToExtend.getChildElements          = this.getChildElements;
	elementToExtend.getChildElement           = this.getChildElement;
	elementToExtend.setChildElementsClassName = this.setChildElementsClassName;

	elementToExtend.addClassName              = this.addClassName;
	elementToExtend.removeClassName           = this.removeClassName;
	elementToExtend.replaceClassName          = this.replaceClassName;
	elementToExtend.containsClassName         = this.containsClassName;
	elementToExtend.addClassNameSuffix        = this.addClassNameSuffix;
	elementToExtend.removeClassNameSuffix     = this.removeClassNameSuffix;

	elementToExtend.getAbsoluteTopPosition    = this.getAbsoluteTopPosition;
	elementToExtend.getAbsoluteLeftPosition   = this.getAbsoluteLeftPosition;
	elementToExtend.getTopPosition            = this.getTopPosition;
	elementToExtend.getLeftPosition           = this.getLeftPosition;
	elementToExtend.scrollToShow              = this.scrollToShow;

	elementToExtend.isCheckable               = this.isCheckable;

	elementToExtend.isDisplayNone             = this.isDisplayNone; 
	elementToExtend.setDisplayBlock           = this.setDisplayBlock;
	elementToExtend.setDisplayNone            = this.setDisplayNone;
	elementToExtend.toggleDisplayBlockNone    = this.toggleDisplayBlockNone;

	// incremement the element serial number and set this's serialNumber property.
	_EnhancedElementSerialNumber++;
	elementToExtend.serialNumber = _EnhancedElementSerialNumber;
	return(elementToExtend);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Constructor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~





// Simple function that doesn't add much real value other than converting to 
// lower case.  This can be confusing since this.nodeName typically returns an
// upper case tag name.  However, for future and xhtml all elements are supposed
// to be lower case so it is converted here so future js code is more ready.
// returns: tries the nodeName property first.  If doesn't exist then tries the 
//   tagName property.  Returns lower case value so to match xhtml.  
EnhancedElement.prototype.getNodeName = function()
{
	if(this.nodeName && (this.nodeName.length > 0)) return(this.nodeName.toLowerCase());
	if(this.tagName  && (this.tagName.length  > 0)) return(this.tagName.toLowerCase());
	return(null);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~




// Checks if an element is a radio button or a checkbox.
// Returns: true if radio or checkbox input element.
EnhancedElement.prototype.isCheckable = function()
{
	var nodeName = this.getNodeName();
	if(nodeName && (nodeName == "input"))
	{
		var inputType = null;
		if(this.elements && (this.elements.length > 1))
			inputType = this.elements[0].getAttribute("type");
		else if(this.getAttribute)
			inputType = this.getAttribute("type");

		if(inputType)
		{
			inputType = inputType.toLowerCase();
			return((inputType == "radio") || (inputType == "checkbox"));
		}
	}
	return(false);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~




// Simple function that localizes checking this element's child element count.
// Arguments: 
// Returns: true if there are one or more child elements.
EnhancedElement.prototype.hasChildElements = function()
{
	return(this.childNodes.length > 0);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Gets this object's child elements based on an optional tagName.
// Arguments: 
// - tagName = Optional value that is used to filter out for certain child elements.
//   If not specified, then all child elements are returned based on the optional 
//   className parameter.
// - className = Optional value that is used to filter out for certain child elements.
//   If not specified, then elements are filtered by the optional tagName parameter.
// Returns: Array of child elements.  If neither tagName or className is not specified, 
//   then all child elements are returned.
EnhancedElement.prototype.getChildElements = function(tagName, className)
{
	if(!tagName && !className) return(this.childNodes);
	if(tagName) tagName = tagName.toLowerCase();

	var regExp = null;
	if(className) regExp = new RegExp("\\b" + className + "\\b");

	var elements = new Array();
	var classMatches, tagMatches;
	if(this.childNodes)
	{
		for(var i=0; i<this.childNodes.length; i++)
		{
			classMatches = (!className || (this.childNodes[i].className && this.childNodes[i].className.match(regExp)));
			tagMatches   = (!tagName   || (this.childNodes[i].nodeName.toLowerCase() == tagName));
			if(classMatches && tagMatches) elements[elements.length] = this.childNodes[i];
		}
	}
	return(elements);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Gets this object's first child element based on an optional tagName and className.
// Arguments: 
// - tagName = Optional value that is used to filter out for certain child elements.
//   If not specified, then all child elements are returned based on the optional 
//   className parameter.
// - className = Optional value that is used to filter out for certain child elements.
//   If not specified, then elements are filtered by the optional tagName parameter.
// Returns: First child element that matches the tagName and/or className.  If neither 
//   tagName or className is not specified, then the first child element is returned.
EnhancedElement.prototype.getChildElement = function(tagName, className)
{
	var elements = this.getChildElements(tagName, className);
	if(elements) return(elements[0]);
	return(null);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Sets the class name for the given child nodes based on optional tag name.
// Arguments: 
// - className = Class name string to set the child elements to.
// - tagName = Optional value that is used to filter out for certain child elements.
//   If not specified, then ALL child elements are changed.
// Returns: Child element.
EnhancedElement.prototype.setChildElementsClassName = function(className, tagName) 
{
	var elements = this.getChildElements(tagName);
	for(var i=0; i<elements.length; i++) elements[i].className = className;
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~




// Adds a " " + classname to the current element's classname attribute.  This checks
// for the existance of the class already being set and if so does not re add it.
// Arguments: 
//   - className = String to be added.
// Returns: Nothing.
EnhancedElement.prototype.addClassName = function(className)
{
	if( !this.containsClassName(className) ) this.className = this.className + " " + className;
//textAreaDebugOutput.writeln("EnhancedElement.prototype.addClassName - className: "+this.className );
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Removes the classname as well as any extra " " characters from the current 
// element's classname attribute.  
// Arguments: 
//   - className = String to be removed.
// Returns: Nothing.
EnhancedElement.prototype.removeClassName = function(className)
{
	var regExp = new RegExp("\\b" + className + "\\b", "g");
	this.className = this.className.replace(regExp, "");

	regExp = new RegExp("/  /", "g");
	this.className = this.className.replace(regExp, " ");
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Checks for the complete classname where as an "indexof" only checks for a sub
// string value.
// Arguments: 
//   - className = String to be removed.
// Returns: Nothing.
EnhancedElement.prototype.containsClassName = function(className)
{
	var regExp = new RegExp("\\b" + className + "\\b");
	return( this.className.match(regExp) );
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Modifies the element's classname attribute via the string replace function.  
// Arguments: 
//   - searchClassName = Classname string to be searched for.
//   - replaceClassName = Classname string to replace the searched for text with.
// Returns: Nothing.
EnhancedElement.prototype.replaceClassName = function(searchClassName, replaceClassName)
{
	if(this.className) this.className = this.className.replace(searchClassName, replaceClassName);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Function ~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Adds the specified suffix to the matched whole classname.  If classname is not
// specified then the suffix is simply added to whatever the classname is currently
// set to.
// Arguments: 
//   - className = Classname to be matched and have suffix added to.
//   - suffix = String to be added to the optional className value.
// Returns: Nothing.
EnhancedElement.prototype.addClassNameSuffix = function(className, suffix)
{
	if(className)
	{
		var regExp = new RegExp("\\b" + className + "\\b", "g");
		this.className = this.className.replace(regExp, className + suffix);
	}
	else
		this.className = this.className + suffix;
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Removes the specified suffix from the matched whole classname.  If classname is 
// not specified then the suffix is simply removed from whatever the classname is 
// currently set to.
// Arguments: 
//   - className = Classname to be matched and have suffix removed from.
//   - suffix = String to be removed from the optional className value.
// Returns: Nothing.
EnhancedElement.prototype.removeClassNameSuffix = function(className, suffix)
{
	if(className)
	{
		var regExp = new RegExp("\\b" + className + suffix + "\\b", "g");
		this.className = this.className.replace(regExp, className);
	}
	else
		this.className = this.className.replace(suffix, "");
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~





// Checks this element's "display" style and if collapsed/hidden (set to "none")
// returns true.
// Arguments: None
// Returns: true if this object's display style is set to none, false otherwise.
//   If there is no style.display attribute.property then a null is returned.
EnhancedElement.prototype.isDisplayNone = function() 
{
	try
	{
		return(this.style.display == "none");
	}
	catch(error) {}

	return(null);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Sets the HTML element object's "display" style to "block" so that it's visible.  
// NOTE: Using display and not visibility property because visibility property
// causes the page to be rendered with the space occupied even if hidden.  Display
// collapses if not shown and expands if shown.  This was explicitly named so to 
// clearify exactly what was happening.  Previously it was named expand but if used 
// by a positioned element didn't really fit and show doesn't really give a true 
// picture either.
// Arguments: None
// Returns: true if successful, false otherwise.
EnhancedElement.prototype.setDisplayBlock = function() 
{
//alert("show.  element: "+element);
	try
	{
		this.style.display = "block"; 
		return(true);
	}
	catch(error) {}
	return(false);
//	if(this.style && this.style.display && (this.style.display == "none")) 
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Sets the HTML element object's "display" style to "none" so that it's NOT visible.  
// NOTE: Using display and not visibility property because visibility property
// causes the page to be rendered with the space occupied even if hidden.  Display
// collapses if not shown and expands if shown.  As with the setDisplayBlock being
// named expand and possibly show, this was named collapse and could have been hide.
// Arguments: None
// Returns: true if successful, false otherwise.
EnhancedElement.prototype.setDisplayNone = function() 
{
	try
	{
		this.style.display = "none"; 
		return(true);
	}
	catch(error) {}
	return(false);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Toggles this element's object's "display" style so that if it is currently
// hidden (none) then set to visible (block) and vice versa.  Note that this 
// toggle has an issue when the item's display property is set via "class"
// as there's no way to check the value set via class property settings.
// Arguments: None
// Returns: true if successful, false otherwise.
EnhancedElement.prototype.toggleDisplayBlockNone = function() 
{
	try
	{
		if(this.style.display == "block")
			this.style.display = "none"; 
		else
			this.style.display = "block"; 
	}
	catch(error) {}
	return(false);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~





// Gets the left most position of this element based on ALL container positions.  
// Arguments: 
// Returns: Sum of all parent container element's left positions.
EnhancedElement.prototype.getAbsoluteLeftPosition = function()
{
	var left = 0;
	var element = this;
	if(element.offsetParent) 
	{
		left = element.offsetLeft;
		while(element = element.offsetParent) 
			left += element.offsetLeft;
	}
	return(left);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Gets the top most position of this element based on ALL container positions.  
// Arguments: 
// Returns: Sum of all parent container element's top positions.
EnhancedElement.prototype.getAbsoluteTopPosition = function()
{
	var top = 0;
	var element = this;
	if(element.offsetParent) 
	{
		top  = element.offsetTop;
		while(element = element.offsetParent) 
			top += element.offsetTop;
	}
	return(top);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Gets this element's current left position.
// Arguments: 
// Returns: Current left position, 0 if there are problems getting the attribute.
EnhancedElement.prototype.getLeftPosition = function()
{
	if(this.left) 
		return(this.left);
	else if(this.pixelLeft)
		return(this.pixelLeft);
	else if(this.offsetLeft) 
		return(this.offsetLeft);
	else
		return(0);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// Gets this element's current top position.
// Arguments: 
// Returns: Current top position, 0 if there are problems getting the attribute.
EnhancedElement.prototype.getTopPosition = function()
{
	if(this.top) 
		return(this.top);
	else if(this.pixelTop)
		return(this.pixelTop);
	else if(this.offsetTop) 
		return(this.offsetTop);
	else
		return(0);
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



EnhancedElement.prototype.scrollToShow = function()
{
	var windowHeight = 0;
	if(typeof(window.innerWidth) == "number") 									// Non-IE
		windowHeight = window.innerHeight;
	else if(document.documentElement && document.documentElement.clientHeight)	// IE 6+ in 'standards compliant mode'
		windowHeight = document.documentElement.clientHeight;
	else if(document.body && (document.body.clientWidth || document.body.clientHeight)) //IE 4 compatible
		windowHeight = document.body.clientHeight;

	var ieBody = (document.compatMode && (document.compatMode != "BackCompat")) ? document.documentElement : document.body;
	var viewTop = document.all ? ieBody.scrollTop : window.pageYOffset;

//	containerElement = new EnhancedElement(containerElementID);
	var elementTop    = this.getAbsoluteTopPosition();
	var elementHeight = this.offsetHeight;
/*
alert(
	"scrollToShow:"
	+ "\n  view top: " + viewTop
	+ "\n  window height: " + windowHeight
	+ "\n  container top: " + elementTop
	+ "\n  container height: "+ elementHeight 
);
*/
	// if element height greater than the window height then position the container 
	//   element top to the top of the window.  
	// else if element is not totally showing then set the window view to include 
	//   the element.
	if(elementHeight > windowHeight)
		window.scrollTo(0, elementTop);
	else 
	{
		var elementBottom = elementTop  + elementHeight;
		var viewBottom    = viewTop + windowHeight;
		if(elementBottom > viewBottom) window.scrollTo(0, viewTop + (elementBottom - viewBottom));
	}
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Prototype ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/*=============================== End of File ================================*/


