//////////////////////////////////////////////////////////////
//
// YOU SHOULD NEVER EVER MODIFY THIS FILE!
// =======================================
//
// If you wish to modify a layout, you should place all your code
//   in the function 'initCustomNodes' in the 'nav.js' file for
//   that specific layout.
//
// If you are writing a component, you should override the include
//   'custom_finish_layout_init' with the 'super' tag, and modify
//   the 'navBuilder' object there to modify the layout.
//
// If there is a method in this file that you wish to modify, then
//   you should write the new function in a seperate 'js' file.
//   Use a component to modify 'std_js_bootstrap_vars' and load
//   the 'js' file at the end of the include.
//
//////////////////////////////////////////////////////////////

// Declare global variables for core XML document, root element, and associated node map object.
var coreNav  = null;
var its = new sniffer();

/* Load the XML nav stuff from the commonNav.js */
function loadNavshell()
{
	if (coreNav == null)
	{
		coreNav = new navBuilder();
	}

	/* Verify that XML is loaded before firing the navigation routines. */
	if (coreNav.xmlRoot)
	{
		generateNavigation(coreNav);
	}
	else
	{
		setTimeout("loadNavshell()", 100);
	}
}


/////////////////////////////////////////////////////////////////
//  XML/HTML DOM Utility functions
/////////////////////////////////////////////////////////////////

/* return the frame window based on ID */
function frameWindow(id)
{
   var elt = document.getElementById(id);
   if (elt)  // IE 5.5, NN7 7+, Moz 1.1+
   {
      if (elt.contentWindow)
      {
          return elt.contentWindow;
      }
   }
   else // IE 5.0
   {
       return window.frames[id];
   }
}

/* This function is called by the page's onload function. */
function createXMLFromString (string)
{
	var xmlDoc = null;

	if (its.ie && its.win)
	{
		// IE 5 has far too many bugs in its XML parser, so use a JavaScript one
		if (its.major < 6)
		{
			xmlDoc = new jsXMLDocument();
			xmlDoc.loadXML(string);
		}
		else
		{
			xmlDoc = new ActiveXObject('Msxml2.DOMDocument');
			xmlDoc.async = true;
			xmlDoc.loadXML(string);
		}
	}

	else if (its.mozilla || (its.nn && its.major > 6))
	{
		var xmlParser = new DOMParser();
		xmlDoc = xmlParser.parseFromString(string, 'text/xml');
	}

	else // no native XML parser
	{
		xmlDoc = new jsXMLDocument();
		xmlDoc.loadXML(string);
	}

	return xmlDoc;
}

function walkXMLTree(parentElement, openingHtmlFunction, coreHtmlFunction, closingHtmlFunction, parentPath)
/* Note: the "parentPath" argument is not to be used when calling the funciton from another function
	it is only used when the function recursively calls itself. */
{
	/* Create a variable containing all children of the passed-in element. */
	var childList = parentElement.childNodes;

	/* Call the function that generates the opening html code for a collection (the generated
	code will typically be an opening <div> tag that will act as a container for the collection).*/
	openingHtmlFunction(parentElement, parentPath);

	/* Loop through the children of the passed-in element. For each child, run the core html function,
		and if that child has children of its own, recursively call the walkXMLTree function.
		Note: since the standards-based browsers don't ignore whitespace, the nodeType value of
		each node must be checked. */
	for (var i=0; i<childList.length; i++)
	{
		var childNode = childList[i];

		if (parentPath)
		{
			var nodePath = parentPath + "." + i;
		}
		else
		{
			var nodePath = i.toString();
		}

		if (childNode.nodeType == 1)
		{
			coreHtmlFunction(childNode, nodePath);

			if (childNode.tagName == "collection")
			{
				walkXMLTree(childNode, openingHtmlFunction, coreHtmlFunction, closingHtmlFunction, nodePath);
			}
		}
	}

/* Call the function that generates the closing html code for a collection (the generated
	code will typically be a closing </div> tag for the collection container).*/
	closingHtmlFunction(parentElement);
}

/* This function had to be used to find the next element-type node in a collection of children
	since the standards-based browsers interpret whitespace as additional text nodes in the hierarchy. */
function nextSiblingElement(element)
{
	var foundSiblingElement = false;
	var testNode = element.nextSibling;
	while (testNode != null)
	{
		if (testNode.nodeType == 1)
		{
			foundSiblingElement = true;
			break
		}
		testNode = testNode.nextSibling;
	}
	if (foundSiblingElement) { return testNode }
	else { return null }
}

/* This function had to be used to find the previous element-type node in a collection of children
	since the standards-based browsers interpret whitespace as additional text nodes in the hierarchy. */
function previousSiblingElement(element)
{
	var foundSiblingElement = false;
	var testNode = element.previousSibling;
	while (testNode != null)
	{
		if (testNode.nodeType == 1)
		{
			foundSiblingElement = true;
			break
		}
		testNode = testNode.previousSibling;
	}
	if (foundSiblingElement) { return testNode }
	else { return null }
}

/* make a iterable map of the XML nodes */
function mapXMLTree(element, nodeMapObject)
{
	if (nodeMapObject[element.getAttribute("id")])
	{
		var parentPath = nodeMapObject[element.getAttribute("id")].location;
	}
	else
	{
		var parentPath = null;
	}

	var childList = element.childNodes;

	for (var i=0; i<childList.length; i++)
	{
		var childNode = childList[i];

		if (childNode.nodeType == 1)
		{
			if (parentPath)
			{
				var nodePath = parentPath + "." + i;
			}
			else
			{
				// Had to use toString() function here to prevent mixing number and string types in array.
				var nodePath = i.toString();
			}

			var nodeID = childNode.getAttribute("id");
			nodeMapObject[nodeID] = new nodePointer(nodePath);

			if (childNode.hasChildNodes())
			{
				mapXMLTree(childNode, nodeMapObject);
			}
		}
	}
}

/* object for the XML tree */
function nodePointer(nodePath)
{
	this.location = nodePath;
}

/* This is a debug function that dumps a node map listing into a new browser window. */
function DEBUG_displayNodeMap(nodeMapObject)
{
	var pointerStringArray = new Array();
	var i = 0;

	for (var nodePointer in nodeMapObject)
	{
		pointerStringArray[i] = nodeMapObject[nodePointer].location + " : " + nodePointer;
		i++;
	}
	pointerStringArray.sort();

	var testString = "<font size='2'>";
	testString += pointerStringArray.join("<br>");
	testString += "</font>"

	window.open("javascript:document.write(\"" + testString + "\");document.close();","testWindow");
}



/////////////////////////////////////////////////////////////////
//  Script for generating action popups.
/////////////////////////////////////////////////////////////////

var currentPopup;
var currentPopupRow;
var currentParentId;
var timeoutId;
var actionImage;
var actionImageOver;
var currentParentImage;

function initActionImages()
{
  currentParentImage = document.getElementById(currentParentId);
  
  if (typeof actionImage == "undefined")
  {
    actionImage = new Image();
    actionImageOver = new Image();
  }
  
  if (currentParentImage.src.indexOf("ActionsIcon") != -1 && actionImage.src != currentParentImage.src)
  {
  	actionImage.src = httpSkinRoot + "ActionsIcon.gif";
  	actionImageOver.src = httpSkinRoot + "ActionsIcon_over.gif";
  }
  
  // Use include 'custom_action_image_init_code' to insert custom image definition code
  // into this hook function.
  if (typeof customActionImageInit != "undefined")
    customActionImageInit();
}

// 'id' is the id property of the popup menu's top-level container element
// 'parentId' is the id of icon image element whose onclick event displays the popup
// 'event' is the window's current event object (should always be set as event)
// 'parameterString' is an optional comma-delimited string of display parameters in the form:
// 'name1=value1,name2=value2'; the following optional display parameters are available:
//    'position' - allowable values: 'horizontal' (default), 'vertical', or 'overlay'
//    'closeMode' - allowable values: 'auto' (default) or 'manual'
function showPopup(id, parentId, event, parameterString)
{
	// Define parameter object and default values for all optional parameters
  var parameters = new Object();
  parameters.position = "horizontal";
  parameters.closeMode = "auto";
   
  // Set parameter values based upon contents of optional parameterString argument
  if (parameterString)
  {
    var parameterArray = parameterString.split(",");     
    for (var i=0; i<parameterArray.length; i++)
      parameters[parameterArray[i].split("=")[0]] = parameterArray[i].split("=")[1];
  }  	
  
  closePopups();
  
  currentParentId = parentId;  
  initActionImages();
  var parentImage = document.getElementById(parentId);
  currentPopup = document.getElementById(id);
  
  displayMenu(parentImage, currentPopup, parameters.position)

	//*** change Actions image src if applicable
	if (parentImage.src)
		parentImage.src = actionImageOver.src;

	//*** register the mouseout handlers used to close the popup
  if (parameters.closeMode == "auto")
  {
  	if (document.addEventListener)
  	{ // DOM Level 2 Event Model
  		currentPopup.addEventListener("mouseout", popupMouseOut, true);
  		currentPopup.addEventListener("mouseover", popupMouseOver, true);
  		parentImage.addEventListener("mouseout", parentMouseOut, true);
  	}
  	else if (document.attachEvent)
  	{ // Older event model
  		currentPopup.onmouseout = popupMouseOut;
  		currentPopup.onmouseover = popupMouseOver;
  		parentImage.onmouseout = parentMouseOut;
  	}
  }
}

function popupMouseOut(e)
{
	var curNode;
	var newNode;

	if (typeof(window.event) != 'undefined')
	{
		curNode = this;
		newNode = window.event.toElement;
	}
	else
	{
		curNode = e.currentTarget;
		newNode = e.relatedTarget;
	}

	if (curNode != newNode && !containsNode(curNode, newNode))
		closePopups();
}


function parentMouseOut(e)
{
	var newNode;

	if (typeof(window.event) != 'undefined')
		newNode = window.event.toElement;
	else
		newNode = e.relatedTarget;

	if (newNode != currentPopup && !containsNode(currentPopup, newNode))
		timeoutId = setTimeout("closePopups()", 300);
}

function popupMouseOver(e)
{
	window.clearTimeout(timeoutId);
}

function containsNode(containerNode, testNode)
{
	if (testNode == null)
		return false;
	while (testNode.parentNode)
	{
		testNode = testNode.parentNode;
		if (testNode == containerNode)
			return true;
	}
	return false;
}

function closePopups()
{	
	if (currentPopup != null)
	{
		hideMenu(currentPopup);
	}
	if (currentParentId)
	{
		var parent = document.getElementById(currentParentId);
		parent.src = actionImage.src;
	}
}

// This event handler closes an open popup when the user clicks anywhere on the page
if (document.addEventListener) // DOM Event Model
	document.addEventListener("click", clearPopups, false);
else if (document.attachEvent) // IE 5+ Event Model
	document.attachEvent("onclick", clearPopups);
  
function clearPopups(e)
{
  if (currentPopup != null && currentPopup.style.display == "block")
  {
    if (!e) e = event;

    if (e.target != null)
      var targetElmt = e.target;
    else
      var targetElmt = e.srcElement;
      
    var skipAction = false;
    
    if (targetElmt.onclick != null && targetElmt.onclick.toString().indexOf("showPopup") > -1)
      skipAction = true;
      
    if (!skipAction)
      closePopups();
  }
}  

function glowPopupRow(obj, glowClass, fadeClass)
{
	fadePopupRow(currentPopupRow, fadeClass);
	currentPopupRow = obj;
	currentPopupRow.className = glowClass;

	var linkObj = obj.getElementsByTagName("A")[0];
	if (linkObj != null)
		linkObj.className = linkObj.className + "_over";
}

function fadePopupRow(obj, fadeClass)
{
	if (obj != null)
	{
		obj.className = fadeClass;

		var linkObj = obj.getElementsByTagName("A")[0];
		if (linkObj != null)
			linkObj.className = linkObj.className.replace(/_over/, "");
	}
}


/////////////////////////////////////////////////////////////////
//  Script for resizing table columns
/////////////////////////////////////////////////////////////////

function resizeColumns_horizontal(leftColId, rightColId, event)
{
	// Get right and left table cell objects to be resized.
	var leftCol = document.getElementById(leftColId);
	var rightCol = document.getElementById(rightColId);

	// Set the starting X-position of the mouse.
	var startPosX = event.clientX;

	// Register the event handlers that will respond to the mousemove events
	// and the mouseup event that follow this mousedown event.
	if (document.addEventListener)
	{ // DOM Level 2 Event Model
		document.addEventListener("mousemove", moveHandler, true);
		document.addEventListener("mouseup", upHandler, true);
	}
	else if (document.attachEvent)
	{ // IE 5+ Event Model
		document.attachEvent("onmousemove", moveHandler);
		document.attachEvent("onmouseup", upHandler);
	}
	else
	{ // IE 4 Event Model
		// In IE 4 we can't use attachEvent(), so assign the event handlers
		// directly after storing any previously assigned handlers so they
		// can be restored.  Note that this also relies on event bubbling.
		//var oldmovehandler = document.onmousemove;
		//var olduphandler = document.onmouseup;
		//document.onmousemove = moveHandler;
		//document.onmouseup = upHandler;
	}

	// We've handled this event.  Don't let anybody else see it.
	if (event.stopPropagation) event.stopPropagation();   // DOM Level 2
	else event.cancelBubble = true;                       // IE

	// Now prevent any default action.
	if (event.preventDefault) event.preventDefault();     // DOM Level 2
	else event.returnValue = false;                       // IE

	/**
	 * This is the handler that captures mousemove events when an element
	 * is being dragged.  It is responsible for moving the element.
	 **/
	function moveHandler(e)
	{
		if (!e) e = window.event;  // IE event model

		// Set starting widths of table cell objects to be resized.
		var startLeftColWidth, startRightColWidth;
		if ((its.mozilla || its.nn) && its.rv > 1.3)
		{	// Mozilla & Netscape:
			startLeftColWidth = parseInt(document.defaultView.getComputedStyle(leftCol, "").getPropertyValue("width"));
			startRightColWidth = parseInt(document.defaultView.getComputedStyle(rightCol, "").getPropertyValue("width"));
		}
		else
		{ 	// IE, Safari
			startLeftColWidth = parseInt(leftCol.offsetWidth);
			startRightColWidth = parseInt(rightCol.offsetWidth);
		}

		/* Use conditional test to prevent columns from being re-sized smaller than
			a pre-defined minimum width (10px in this case). */
		if ((startLeftColWidth - (startPosX - e.clientX) > 10) && (startRightColWidth + (startPosX - e.clientX) > 10))
		{
			// Resize table cells based upon mouse poistion
			leftCol.style.width = (startLeftColWidth - (startPosX - e.clientX)) + "px";
			rightCol.style.width = (startRightColWidth + (startPosX - e.clientX)) + "px";

			// Reset starting mouse position.
			startPosX = e.clientX;
		}

		// And don't let anyone else see this event.
		if (e.stopPropagation) e.stopPropagation();       // DOM Level 2
		else e.cancelBubble = true;                       // IE
	}

	/**
	 * This is the handler that captures the final mouseup event that
	 * occurs at the end of a drag.
	 **/
	function upHandler(e)
	{
		if (!e) e = window.event;  // IE event model

		// Unregister the capturing event handlers.
		if (document.removeEventListener)
		{	// DOM Event Model
			document.removeEventListener("mouseup", upHandler, true);
			document.removeEventListener("mousemove", moveHandler, true);
		}
		else if (document.detachEvent)
		{	// IE 5+ Event Model
			document.detachEvent("onmouseup", upHandler);
			document.detachEvent("onmousemove", moveHandler);

			// deselect any accidentally selected text
			var r = document.selection.createRange();
			r.collapse();
			r.select();
		}
		else
		{	// IE 4 Event Model
			//document.onmouseup = olduphandler;
			//document.onmousemove = oldmovehandler;
		}

		// And don't let the event propagate any further.
		if (e.stopPropagation) e.stopPropagation();       // DOM Level 2
		else e.cancelBubble = true;                       // IE
	}
}


/////////////////////////////////////////////////////////////////
//  Script for showing full titles for DAM search results
/////////////////////////////////////////////////////////////////

function dam_showFullTitle(parentCell, titlePopupId)
{
	var titleSpanOb = parentCell.getElementsByTagName("SPAN")[0];

	if (titleSpanOb.firstChild.nodeValue.indexOf("...") != -1)
	{
		parentCell.style.cursor = "default";
		var titlePopup = document.getElementById(titlePopupId);
		displayMenu(titleSpanOb, titlePopup, "overlay");
	}
}


function dam_hideFullTitle(parentCell, titlePopupId, e)
{
	if (!e) e = window.event;
	var titleSpanOb = parentCell.getElementsByTagName("SPAN")[0];

	if (titleSpanOb.firstChild.nodeValue.indexOf("...") != -1)
	{
		var titlePopup = document.getElementById(titlePopupId);
		hideMenu(titlePopup, e);
	}
}


/////////////////////////////////////////////////////////////////
//  Menu Bar DHTML
/////////////////////////////////////////////////////////////////

function topCollectionItem_mouseover(e, element)
{
	if (element.getElementsByTagName("div").length > 0)
	{
		var childMenu = element.getElementsByTagName("div")[0];
		if (childMenu.style.display == "none")
		{
			resetMenus();
		}
	}
	highlightItem(element);
}


function topCollectionItem_mouseout(e, element)
{
	if (element.getElementsByTagName("div").length > 0)
	{
		var childMenu = element.getElementsByTagName("div")[0];
		if (childMenu.style.display == "none")
		{
			unhighlightItem(element);
		}
	}
}


function topCollectionItem_onclick(e, element, childMenuId, placement)
{
	var childMenu = document.getElementById(childMenuId);
	if (element != null && childMenu != null)
	{
		// DISPLAY MENU
		if (childMenu.style.display == "none")
		{
			resetMenus();	// Hide any open menus
			displayMenu(element, childMenu, placement);
		}

		// HIDE MENU
		else if (childMenu.style.display == "block")
		{
			hideMenu(childMenu);
			highlightItem(element);
			/* Note: had to re-highlight the parent element since the mouse is logically
				still over this element, and the resetMenus() function loop sets parent
				elements of ALL child menus (including the target) back to regular style */
		}
	}

	if (its.ie) e.cancelBubble = true;
	else e.stopPropagation();
}


function childCollectionItem_mouseover(e, element, placement)
{
	if (!e) e = window.event;

	if (its.ie && !containsNode(e.fromElement, element))
	{
		highlightItem(element);

		if (element.getElementsByTagName("div").length > 0)
		{
			var childMenu = element.getElementsByTagName("div")[0];

			if (childMenu.style.display == "none")
			{
				displayMenu(element, childMenu, placement);
			}
		}
	}
	else (navigator.appName == "Netscape")
	{
		highlightItem(element);
	}
}


function childCollectionItem_mouseout(e, element)
{
	if (!e) e = window.event;

	if (its.ie && !containsNode(element, e.toElement))
	{
		unhighlightItem(element);

		if (element.getElementsByTagName("div").length > 0)
		{
			var childMenu = element.getElementsByTagName("div")[0];
			hideMenu(childMenu, e);
			resetParentConflicts(childMenu); //re-hides any elements that conflict with a parent that's still displayed
		}
	}
	else
	{
		if (element.getElementsByTagName("div").length > 0)
		{
			var childMenu = element.getElementsByTagName("div")[0];
			if (childMenu.style.display == "none")
			{
				unhighlightItem(element);
			}
		}
	}
}


function childCollectionItem_onclick(e, element, childMenuId, placement)
{
	if (its.ie)
	{
		e.cancelBubble = true;
	}
	else
	{
		var childMenu = document.getElementById(childMenuId);
		if (childMenu != null)
		{
			if (childMenu.style.display == "none")
			{
				closeOpenSiblingsOf(element);
				displayMenu(element, childMenu, placement);
			}
			else if (childMenu.style.display == "block")
			{
				closeChildMenusOf(element);
				highlightItem(element);
				/* Note: have to rehighlight the element since the mouse is logically
					still over this element, and the closeChildMenusOf function loop sets parent
					elements of ALL child menus (including the target) back to regular style */
			}
		}
		e.stopPropagation();
	}
}


function linkItem_mouseover(element)
{
	if (element.className.indexOf("TopLinkItem") != -1)
		resetMenus();

	element.className = element.className + "_over";
	var linkObject = element.getElementsByTagName("A")[0];
	if (linkObject != null)
		linkObject.className = linkObject.className + "_over";
}


function linkItem_mouseout(element)
{
	element.className = element.className.replace(/_over/, "");
	var linkObject = element.getElementsByTagName("A")[0];
	if (linkObject != null)
		linkObject.className = linkObject.className.replace(/_over/, "");
}


function linkItem_onclick(e, element, url, target)
{
	// find the nested 'A' tag, if any
	var children = element.childNodes;
	var subLink = null;
	for (var i=0; i<children.length; i++)
	{
		if (children[i].tagName == "A")
		{
			subLink = children[i];
			url = subLink.href;
			target = subLink.target;
			break;
		}
	}

	// prevent bubble-up
	if (its.ie)
		e.cancelBubble = true;
	else
	{
		// sidestep a bug in mozilla
		if (element.className.indexOf("_over") != -1)
			element.className = element.className.replace(/_over/, "");
		if (subLink != null && subLink.className.indexOf("_over") != -1)
			subLink.className = subLink.className.replace(/_over/, "");
		e.stopPropagation();
	}

	// do nothing if the mouse is over a child 'A' tag
	if (subLink == null || !(theseElementsOverlap(e, subLink)) || its.safari)
	{
		if (target==null || target=="")
			window.location.href = url;
		else
			window.open(url, target);
	}

	resetMenus();
}


function highlightItem(element)
{
	var itemId = element.id;
	var itemTable = document.getElementById(itemId + "_itemTable");
	var labelCell = document.getElementById(itemId + "_labelCell");
	var arrowImg = document.getElementById(itemId + "_arrowImg");

	if (itemTable != null && itemTable.className.indexOf("_over") == -1)
		itemTable.className = itemTable.className + "_over";
	if (labelCell != null && labelCell.className.indexOf("_over") == -1)
		labelCell.className = labelCell.className + "_over";
	if (arrowImg != null && arrowImg.src.indexOf("_over") == -1)
		arrowImg.src = arrowImg.src.replace(/.gif/, "_over.gif");
}


function unhighlightItem(element)
{
	var itemId = element.id;
	var itemTable = document.getElementById(itemId + "_itemTable");
	var labelCell = document.getElementById(itemId + "_labelCell");
	var arrowImg = document.getElementById(itemId + "_arrowImg");

	itemTable.className = itemTable.className.replace(/_over/, "");
	labelCell.className = labelCell.className.replace(/_over/, "");
	arrowImg.src = arrowImg.src.replace(/_over/, "");
}


// Closes an element's associated child menu as well as all decendents of that menu.
function closeChildMenusOf(element)
{
	var decendentDivs = element.getElementsByTagName("div");

	for (var i=decendentDivs.length-1; i>=0; i--)
	{
		if (decendentDivs[i].id.indexOf("_menu") != -1)
		{
			var childMenu = decendentDivs[i];

			unhighlightItem(childMenu.parentNode);

			hideMenu(childMenu);
			resetParentConflicts(childMenu); //re-hides any elements that conflict with a parent that's still displayed
		}
	}
}


function closeOpenSiblingsOf(element)
{
	var idArray = element.id.split(".");
	if (idArray.length > 1)
	{
		idArray.length = idArray.length - 1;
		var parentId = idArray.join(".");
	}
	else
		parentId = "";

	var i = 0;
	var siblingElement = document.getElementById(parentId + "." + i);
	while (siblingElement != null)
	{
		var siblingMenu = document.getElementById(siblingElement.id + "_menu");
		if (siblingMenu != null)
		{
			if (siblingMenu.style.display == "block" && siblingElement != element)
				closeChildMenusOf(siblingElement);
		}
		i++;
		siblingElement = document.getElementById(parentId + "." + i);
	}
}


function resetMenus(e)
{
	var menuCharCode = 65; // initialize to capital "A"

	var firstMenuCell = document.getElementById("menu" + String.fromCharCode(menuCharCode) + "_0");

	while (firstMenuCell != null)
	{
		var menuItemCount = 0;
		var menuCell = firstMenuCell;

		while (menuCell != null)
		{
			var menuContainer = document.getElementById(menuCell.id + "_menu");
			if (menuContainer != null)
			{
				if (menuContainer.style.display == "block")
					closeChildMenusOf(menuCell);
			}

			menuItemCount++;
			menuCell = document.getElementById("menu" + String.fromCharCode(menuCharCode) + "_" + menuItemCount);
		}

		menuCharCode++;
		firstMenuCell = document.getElementById("menu" + String.fromCharCode(menuCharCode) + "_0");
	}
}


/* This function is called after a menu is hidden. When a menu gets hidden using the
	'hideMenu' function, elements that conflict with that menu are re-displayed. This function
	 loops through the parents of the hidden menu, and re-hides any elements that still
	 conflict with visible ancestor menus. */
function resetParentConflicts(childMenu)
{
	var idArray = childMenu.id.split(".");
	idArray.length = idArray.length - 1;
	parentId = idArray.join(".") + "_menu";
	parentMenu = document.getElementById(parentId);

	while (parentMenu != null)
	{
		setConflictingElements("hidden", parentMenu);

		idArray = parentId.split(".");
		idArray.length = idArray.length - 1;
		parentId = idArray.join(".") + "_menu";
		parentMenu = document.getElementById(parentId);
	}
}

/* This function takes a parent element and a hidden (display="none"), absolutely-positioned
	DTHML menu container as parameters; it displays the menu container adjacent to the parent
	element, adjusts position to ensure the menu remains on-screen, and hides any elements
	(such as applets or select lists) that may create layering conflicts with the menu.
	An optional third parameter 'placement' can be used to specify if the menu pops up beside
	the parent or under the parent by default; the legal values are ('vertical', 'horizontal', or 'overlay');
	if this parameter is left blank, the menu will appear beside the parent by default. */
function displayMenu(parentElement, childMenu, placement)
{
	// Get the browser's window dimensions and current scroll positions
	var windowWidth = document.body.clientWidth;
	var windowHeight = document.body.clientHeight;
	if (document.body.scrollLeft != null)
	{
		var scrollValue_x = document.body.scrollLeft;
		var scrollValue_y = document.body.scrollTop;
	}
	else if (window.pageXOffset != null)
	{
		var scrollValue_x = window.pageXOffset;
		var scrollValue_y = window.pageYOffset;
	}
	else
	{
		var scrollValue_x = 0;
		var scrollValue_y = 0;
	}

	//*** Get the dimensions and positioning values of the parent element
	 var parentData = new dimensionFinder(parentElement);

	//*** Initialize the menu position before rendering
	childMenu.style.top = 0;
	childMenu.style.left = 0;

	//*** Render the menu
	childMenu.style.display = "block";

	//*** Get the rendered dimensions of the menu
	var menuWidth = childMenu.offsetWidth;
	var menuHeight = childMenu.offsetHeight;
	var menuTable = childMenu.getElementsByTagName("TABLE")[0];
	if (menuTable != null)
	{
		menuWidth = menuTable.offsetWidth;
		menuHeight = menuTable.offsetHeight;
	}

	// FOR MENUS THAT DISPLAY BELOW THE PARENT
	if (placement == "vertical" || placement == "overlay")
	{
		//*** Move the menu to its final vertical postion
		if (placement == "vertical")
		{
			if (parentData.actualTop + parentData.height + menuHeight <= windowHeight + scrollValue_y)
				childMenu.style.top = parentData.relativeTop + parentData.height; // place below parent
			else
			{
				if (menuHeight < parentData.actualTop)
					childMenu.style.top = parentData.relativeTop - menuHeight; // place above parent
				else
					childMenu.style.top = parentData.relativeTop - parentData.actualTop; // place at top edge of window
			}
		}
		else // "overlay"
		{
			childMenu.style.top = parentData.relativeTop;
		}

		//*** Move the menu to its final horizontal postion
		if (parentData.actualLeft + menuWidth <= windowWidth + scrollValue_x)
			childMenu.style.left = parentData.relativeLeft; // align with left edge of parent
		else
		{
			if (menuWidth <= windowWidth)
				childMenu.style.left = parentData.relativeLeft - parentData.actualLeft + windowWidth + scrollValue_x - menuWidth - 3; // align with right edge of window
			else
				childMenu.style.left = parentData.relativeLeft - parentData.actualLeft; // align with left edge of window
		}
	}

	// FOR MENUS THAT DISPLAY BESIDE THE PARENT
	else if (placement == "horizontal" || placement == null)
	{
		//*** Move the menu to its final horizontal postion
		if (parentData.actualLeft + parentData.width + menuWidth <= windowWidth + scrollValue_x)
			childMenu.style.left = parentData.relativeLeft + parentData.width; // place to right of parent
		else
		{
			if (menuWidth < parentData.actualLeft)
				childMenu.style.left = parentData.relativeLeft - menuWidth; // place to left of parent
			else
				childMenu.style.left = parentData.relativeLeft - parentData.actualLeft; // align with left edge of window
		}

		//*** Move the menu to its final vertical postion
		if (parentData.actualTop + menuHeight <= windowHeight + scrollValue_y)
		{
			childMenu.style.top = parentData.relativeTop; // align with top edge of parent
		}
		else
		{
			if (menuHeight <= windowHeight)
				childMenu.style.top = parentData.relativeTop - parentData.actualTop + windowHeight + scrollValue_y - menuHeight - 3; // align with bottom edge of window
			else
				childMenu.style.top = parentData.relativeTop - parentData.actualTop + scrollValue_y; // align with top edge of window
		}
	}

	//*** Hide conflicting elements that overlap the menu
	setConflictingElements("hidden", childMenu);
}


/* This function is the logical opposite of the 'displayMenu' function above. It takes a
	currently-displayed popup menu container object as a parameter; it hides the menu object
	and re-displays any conflicting elements that were previously hidden when the menu was
	displayed. */
function hideMenu(childMenu, event)
{
	if (event != null)
	{
		// dont hide a menu if the mouse is over it - IE bug
		if(theseElementsOverlap(childMenu, event))
			return;
	}
	if (childMenu != null)
	{
		setConflictingElements("visible", childMenu);
		childMenu.style.display = "none";
	}
}


/////////////////////////////////////////////////////////////////
//  The XML Navigation Builder Object
/////////////////////////////////////////////////////////////////

var didPrototype = false;

/* declare constructor function used to build XML navigation trees */
function navBuilder(string)
{
	if (string==null)
		string = '<?xml version="1.0" encoding="iso-8859-1"?><navtree id="NAVTREE"></navtree>';

	if (!didPrototype)
		doPrototype();

	// members
	this.xmlDocument = createXMLFromString(string);
	this.xmlRoot = this.xmlDocument.documentElement;
	this.xmlNodeMap = new Object();
	this.topLevelNodes = new Array();
	this.htmlString = "";
	
	// all functions have the signature (node, nodeLocation)
	this.makeOpeningHtml = new Function();
	this.makeCoreHtml = new Function();
	this.makeClosingHtml = new Function();
	
	// sub navBuilder objects
	this.menuA = null;
	this.menuB = null;
	this.trayA = null;
}

function doPrototype()
{
	didPrototype = true;

	// call the constructor function to force creation of a prototype navBuilder object
	new navBuilder();

	// add common methods to prototype object to be inherited by all navBuilder instances
	navBuilder.prototype.addTopLevelNode = _navBuilder_addTopLevelNode;
	navBuilder.prototype.deleteTopLevelNode = _navBuilder_deleteTopLevelNode;
	navBuilder.prototype.addPrevSiblingNodeTo = _navBuilder_addPrevSiblingNodeTo;
	navBuilder.prototype.addChildNodeTo = _navBuilder_addChildNodeTo;
	navBuilder.prototype.moveItemInto = _navBuilder_moveItemInto;
	navBuilder.prototype.moveItemAbove = _navBuilder_moveItemAbove;
	navBuilder.prototype.setAttributeValue = _navBuilder_setAttributeValue;
	navBuilder.prototype.deleteItem = _navBuilder_deleteItem;
	navBuilder.prototype.deleteChildrenOf = _navBuilder_deleteChildrenOf;
	navBuilder.prototype.getNodeById = _navBuilder_getNodeById;
	navBuilder.prototype.buildHtmlStringFromXml = _navBuilder_buildHtmlStringFromXml;
}


/* Adds a node ID to a list of top-level nodes to display on a menu or tree */
function _navBuilder_addTopLevelNode(xmlNodeId, beforeNodeId)
{
	if (beforeNodeId != null)
	{
		var oldLength = this.topLevelNodes.length;
		var foundIndex = oldLength + 1;
		var i = 0;
		var id = this.topLevelNodes[i++];

		while (i < oldLength && id != beforeNodeId)
			id = this.topLevelNodes[i++];

		if (id == beforeNodeId)
		{
			foundIndex = i-1;
			i = oldLength + 1;
			while (i > foundIndex)
				this.topLevelNodes[i] = this.topLevelNodes[--i]
		}
		
		this.topLevelNodes[foundIndex] = xmlNodeId;
	}
	else
	{
		this.topLevelNodes[this.topLevelNodes.length] = xmlNodeId;
	}
}

/* Removes a node from the array of top-level nodes */
function _navBuilder_deleteTopLevelNode(xmlNodeId)
{
	var newNodes = new Array();
	var length = this.topLevelNodes.length;
	for (var index=0; index<length; index++)
	{
		if(this.topLevelNodes[index] != xmlNodeId)
			newNodes[newNodes.length] = this.topLevelNodes[index];
	}
	
	this.topLevelNodes = newNodes;
}

/*  This function adds a menu item before the existing item referenced by siblingId. newNodeName
	is the tag name of the new element to be created (in the current implementation, the only two
	legal values are "collection" or "item"). Subsequent attributes represent the name/value pairs
	of attributes to be added tot the new element (formatted as: "label==Some Name"). At a minimum,
	the attributes "id", "label", and "url" must be included (others may be added to address specific
	needs of a given layout).*/
function _navBuilder_addPrevSiblingNodeTo(siblingId, newNodeName)
{
	var argValues = _navBuilder_addPrevSiblingNodeTo.arguments;
	var argCount = _navBuilder_addPrevSiblingNodeTo.arguments.length;

	var newElement = this.xmlDocument.createElement(newNodeName);

	if (argCount > 2)
	{
		for (i=2; i<argCount; i++)
		{
			var attrName = argValues[i].split("==")[0];
			var attrValue = argValues[i].split("==")[1];
			newElement.setAttribute(attrName, attrValue);
		}
	}

	var siblingElement = this.getNodeById(siblingId);
	if (siblingElement)
	{
		var parentElement = siblingElement.parentNode;
		parentElement.insertBefore(newElement, siblingElement);

		mapXMLTree(parentElement, this.xmlNodeMap);
	}
}

/*  This function adds a menu item as a child of the existing collection item referenced by
	collectionId. newNodeName is the tag name of the new element to be created (in the current
	implementation, the only two legal values are "collection" or "item"). Subsequent attributes
	represent the name/value pairs of attributes to be added tot the new element (formatted as:
	"label==Some Name"). At a minimum, the attributes "id", "label", and "url" must be included
	(others may be added to address specific needs of a given layout). See the Explorer component
	for examples of how this function is called. */
function _navBuilder_addChildNodeTo(collectionId, newNodeName)
{
	var argValues = _navBuilder_addChildNodeTo.arguments;
	var argCount = _navBuilder_addChildNodeTo.arguments.length;
	var newElement = this.xmlDocument.createElement(newNodeName);

	// loop through the unspecified arguments that generate the node's attributes.
	if (argCount > 2)
	{
		for (i=2; i<argCount; i++)
		{
			var attrName = argValues[i].split("==")[0];
			var attrValue = argValues[i].split("==")[1];
			newElement.setAttribute(attrName, attrValue);
		}
	}

	// get a pointer to the parent element and establish a node path value for the new node in the xmlNodeMap.
	var parentElement;
	if (collectionId == "NAVTREE")
		parentElement = this.xmlRoot;
	else
		parentElement = this.getNodeById(collectionId);

	if (parentElement)
	{
		// append the new element onto the parent's child list.
		parentElement.appendChild(newElement);

		// Add a nodePointer object to the xmlNodeMap for the new node (since the new element was added
		// to the end of a collection, there's no need to recursively remap all the parent's decendents).
		var nodeId = newElement.getAttribute("id");
		var nodeIndex = (parentElement.childNodes.length-1).toString();
		if (collectionId == "NAVTREE")
			var nodePath = nodeIndex;
		else
			var nodePath = this.xmlNodeMap[collectionId].location + "." + nodeIndex;
		this.xmlNodeMap[nodeId] = new nodePointer(nodePath);
	}
}

/* This function moves an item to a different parent collection. If the item being moved is
	itself a collection, all of its children are moved with it. The first argument
	(newParentCollectionId) is the ID value of the collection that the item is being moved into.
	The second argument (nodeID) is the ID value of the item being moved. The third argument
	(clone) is a boolean value that determines if the item being moved is removed from its
	original location (false), or copied and moved (true). */
function _navBuilder_moveItemInto(newParentCollectionId, nodeId, clone)
{
	var newParentElement = this.getNodeById(newParentCollectionId);
	var node = this.getNodeById(nodeId);

	if (newParentElement && node)
	{

		if (clone == true)
		{
			// Creates a duplicate node to move and leaves the existing one.
			var newClone = createClone(node, this);
			newParentElement.appendChild(newClone);
		}
		else
		{
			// Grab a pointer to the original parent node before the node is removed.
			var oldParentElement = node.parentNode;
			// Remove the node from its previous location and move it to the new location.
			newParentElement.appendChild(node);
			// Remap the previous parent node's decendents if previous parent is different from new parent.
			if (oldParentElement != newParentElement)
			{
				mapXMLTree(oldParentElement, this.xmlNodeMap);
			}
		}
		// Remap the new parent node's decentdents.
		mapXMLTree(newParentElement, this.xmlNodeMap);
	}
}

/*  This function moves an item to a different parent collection. If the item being moved is
	itself a collection, all of its children are moved with it. The first argument
	(newParentCollectionId) is the ID value of the collection that the item is being moved into.
	The second argument (nodeID) is the ID value of the item being moved. The third argument
	(clone) is a boolean value that determines if the item being moved is removed from its
	original location (false), or copied and moved (true). */
function _navBuilder_moveItemAbove(siblingId, nodeId, clone)
{
	var siblingElement = this.getNodeById(siblingId);
	var node = this.getNodeById(nodeId);

	if (siblingElement && node)
	{
		var parentElement = siblingElement.parentNode;

		if (clone == true)
		{
			// Creates a duplicate node to move and leaves the existing one.
			var newClone = createClone(node, this);
			parentElement.insertBefore(newClone, siblingElement);
		}
		else
		{
			// must clone the node, then delete the old one, due to bugs in IE 5.5
			var oldParentElement = node.parentNode;
			var newClone = node.cloneNode(true);
			this.deleteItem(node.getAttribute("id"));
			parentElement.insertBefore(newClone, siblingElement);

			// remap the old parent elements, if the parent has changed
			if (oldParentElement != parentElement)
				mapXMLTree(oldParentElement, this.xmlNodeMap);
		}

		// Remap the new parent node's decentdents.
		mapXMLTree(parentElement, this.xmlNodeMap);
	}
}

/*  This function sets the value of a menu item's attribute. The first argument (nodeId) is the
	ID value of the menu item whose attribute you want to change. The second argument (attrName)
	a the name of the attribute you want to set (if no attribute by this name currently exists,
	a new attribute will be created). The third argument is the value to which the attibute
	will be set. This function will most frequenly be used to change the display label (label
	attribute) for a menu item. */
function _navBuilder_setAttributeValue(nodeId, attrName, attrValue)
{
	var node = this.getNodeById(nodeId);

	if (node)
	{
		node.setAttribute(attrName, attrValue);
	}
}

/*  Pass in the ID value of an XML node in the core XML tree, and it removes that
	node from the tree. */
function _navBuilder_deleteItem(nodeId)
{
	var node = this.getNodeById(nodeId);

	if (node)
	{
		// find the node's parent element
		var parentElement = node.parentNode;
		// delete the node from the collection
		parentElement.removeChild(node);

		// delete the node's pointer from the node map, and remap the parent's decendents
		delete this.xmlNodeMap[nodeId];
		
		// Note, future work needs to be done here because if deleting all children for a parent or we know that we are deleting
		// at the end of a list, then this call is unnecessary.
		mapXMLTree(parentElement, this.xmlNodeMap);
	}
}

/* Pass in the ID value of an XML node in the core XML tree, and it removes that
	children of this node from the tree. The node itself still remains.*/
function _navBuilder_deleteChildrenOf(nodeId)
{
	var node = this.getNodeById(nodeId);
	doDeleteChildrenOf(this, node);
}

/* Recursive deletion of child nodes, and their values from the node map */
function doDeleteChildrenOf(builder, node)
{
	if (builder == null || node == null)
		return;
	
	var childList = node.childNodes;
	for (var i = childList.length-1; i>=0; i--)
	{
		var subNodeId = childList[i].getAttribute("id");
		delete builder.xmlNodeMap[subNodeId];
		
		var subChildList = childList[i].childNodes;
		if (subChildList != null && subChildList.length > 0)
			doDeleteChildrenOf(builder, childList[i]);
		
		node.removeChild(childList[i]);
	}
}

/* get an XML node by its ID */
function _navBuilder_getNodeById(id)
{
	if (this.xmlNodeMap[id])
	{
		/* Split the array element associated with the ID value into an array of separate
			index numbers. */
		var nodeIndexArray = this.xmlNodeMap[id].location.split(".");

		/* Start with the root element within xml document */
		var node = this.xmlRoot;

		/* Loop through the index array, using the index numbers to navigate down to
			the requested node in the master XML document. */
		for (var i=0; i<nodeIndexArray.length; i++)
		{
			if (node != null)
				node = node.childNodes[nodeIndexArray[i]];
		}

		return node;
	}
	else if(id == "NAVTREE")
	{
		return this.xmlRoot;
	}
	else
	{
		return null;
	}
}

/* Note: the "parentLocation" argument should be left blank when parentElement is the root of its
	XML document. */
function _navBuilder_buildHtmlStringFromXml(node, nodeLocation)
{
	/* Create a variable containing all children of the parent element. */
	var childList = node.childNodes;

	/* Call the function that generates the opening html code for a collection (the generated
	code will typically be an opening <div> tag that will act as a container for the collection).*/
	this.makeOpeningHtml(node, nodeLocation);

	/* Loop through the children of the passed-in element. For each child, run the core html function,
		and if that child has children of its own, recursively call the buildHtmlStringFromXml method.
		Note: since the standards-based browsers don't ignore whitespace, the nodeType value of
		each node must be checked. */
	for (var i=0; i<childList.length; i++)
	{
		var childNode = childList[i];

		var childNodeLocation = i.toString();
		if (nodeLocation)
			childNodeLocation = nodeLocation + "." + i;
		if (childNode.nodeType == 1)
		{
			this.makeCoreHtml(childNode, childNodeLocation);

			if (childNode.tagName == "collection")
			{
				this.buildHtmlStringFromXml(childNode, childNodeLocation);
			}
		}
	}

/* Call the function that generates the closing html code for a collection (the generated
	code will typically be a closing </div> tag for the collection container).*/
	this.makeClosingHtml(node, nodeLocation);
}

/* This function creates a clone of a node, and ensures that it and its decendents
	all have unique ID values. */
function createClone(node, parentTree)
{
	var newClone = node.cloneNode(true);
	setCloneId(newClone);
	setCloneDecendentIds(newClone);
	return newClone;

	function setCloneId(clone)
	{
		var baseNodeId = clone.getAttribute("id").replace(/_clone[0-9]*/, "");
		var cloneCount = 1;
		var cloneId = baseNodeId + "_clone" + cloneCount;
		var previousClone = parentTree.getNodeById(cloneId);

		while (previousClone != null)
		{
			cloneCount++;
			cloneId = baseNodeId + "_clone" + cloneCount;
			previousClone = parentTree.getNodeById(cloneId);
		}

		clone.setAttribute("id", cloneId);
	}

	function setCloneDecendentIds(parentClone)
	{
		for (var i = 0; i < parentClone.childNodes.length; i++)
		{
			var childClone = parentClone.childNodes[i];
			setCloneId(childClone);
			if (childClone.hasChildNodes()) { setCloneDecendentIds(childClone); }
		}
	}

}


//////////////////////////////////////////////////////////////////////
//  sample HTML-drawing methods for a tree-control view of XML nodes
//////////////////////////////////////////////////////////////////////

/* images and sizes of the nav tree icons. Note - all 'closed' images below must
   have an associated 'open' image for when the icon is clicked */
var tree_control_config;

function initTreeControlConfig()
{
	if (typeof tree_control_config != "undefined")
		return;
	
	tree_control_config = new Array();
	tree_control_config["src_I"] = httpSkinRoot + "tree_I.gif";
	tree_control_config["src_Icon_ColClosed"] = httpSkinRoot + "tree_icon_collection_closed.gif";
	tree_control_config["src_Icon_Item"] = httpSkinRoot + "tree_icon_item.gif";
	tree_control_config["src_L_ColClosed"] = httpSkinRoot + "tree_L_collection_closed.gif";
	tree_control_config["src_T_ColClosed"] = httpSkinRoot + "tree_T_collection_closed.gif";
	tree_control_config["src_L_Item"] = httpSkinRoot + "tree_L_item.gif";
	tree_control_config["src_T_Item"] = httpSkinRoot + "tree_T_item.gif";
	tree_control_config["src_tree_space"] = httpSkinRoot + "tree_space.gif";
	tree_control_config["src_Top_ColClosed"] = httpSkinRoot + "tree_top_collection_closed.gif";
	tree_control_config["src_TopOnly_ColClosed"] = httpSkinRoot + "tree_top_only_collection_closed.gif";
	tree_control_config["src_Top_Item"] = httpSkinRoot + "tree_top_item.gif";
	tree_control_config["src_TopOnly_Item"] = httpSkinRoot + "tree_top_only_item.gif";
	tree_control_config["iconWidth_Col"] = "16";
	tree_control_config["iconHeight_Col"] = "16";
	tree_control_config["iconWidth_Item"] = "16";
	tree_control_config["iconHeight_Item"] = "16";
	tree_control_config["connectorWidth"] = "12";
	tree_control_config["connectorHeight"] = "20";
}

/* generate the core HTML for either a COLLECTION or an ITEM node */
function tree_control_CoreHtml(node, nodeLocation)
{
	initTreeControlConfig();
	var config = tree_control_config;
	/* Declare string variables for markup. */
	var iconImageHtml = "";
	var connectorImageHtml = "";
	var offsetImagesHtml = "";
	var imageCellWidth = 0;
	var labelTextHtml = "";
	var id = nodeLocation;

	// **** COLLECTION ****
	if (node.tagName == "collection")
	{
		/* Define icon image and image attributes for current element. */
		if (node.getAttribute("icon"))
		{
			var iconSrc = httpSkinRoot + node.getAttribute("icon");
		}
		else
		{
			var iconSrc = config["src_Icon_ColClosed"];
		}
		var iconWidth = config["iconWidth_Col"];
		var iconHeight = config["iconHeight_Col"];

		/* This block tests any top-level collections to see if they are the first collection in the listing
			(e.g. they have no previous sibling in their display group). This is used below to define
			the unique connector images used for the top collection. */
		var isFirstMenuItem = false;
		if (node.parentNode.tagName == "navtree" && previousSiblingElement(node) == null)
			isFirstMenuItem = true;

		/* Define connector image for the current collection. */
		if (isFirstMenuItem)
		{
			if (nextSiblingElement(node) == null)
			{
				var connectorSrc = config["src_TopOnly_ColClosed"];
			}
			else
			{
				var connectorSrc = config["src_Top_ColClosed"];
			}
		}
		else if (nextSiblingElement(node) != null)
		{
			var connectorSrc = config["src_T_ColClosed"];
		}
		else
		{
			var connectorSrc = config["src_L_ColClosed"];
		}
	}

	// **** ITEM ****
	else
	{
		/* Define icon image and image attributes for current element. */
		if (node.getAttribute("icon"))
		{
			var iconSrc = httpSkinRoot + node.getAttribute("icon");
		}
		else
		{
			var iconSrc = config["src_Icon_Item"];
		}
		var iconWidth = config["iconWidth_Item"];
		var iconHeight = config["iconHeight_Item"];

		/* This block tests any top-level items to see if they are the first item in the listing
			(e.g. they have no previous sibling in their display group). This is used below to define
			the unique connector images used for the top item. */
		var isFirstMenuItem = false;
		if (node.parentNode != null &&
			node.parentNode.tagName == "navtree" && previousSiblingElement(node) == null)
		{
			isFirstMenuItem = true;
		}

		/* Define connector image for the current item. */
		var connectorSrc = config["src_L_Item"];
		if (isFirstMenuItem)
		{
			if (nextSiblingElement(node) == null)
				connectorSrc = config["src_TopOnly_Item"];
			else
				connectorSrc = config["src_Top_Item"];
		}
		else if (nextSiblingElement(node) != null)
		{
			connectorSrc = config["src_T_Item"];
		}
	}

	/* Generate the html code for the icon and node-connection point graphics using image
		property values determined in the block above. */
	iconImageHtml = imageHtml(iconSrc, iconWidth, iconHeight);
	connectorImageHtml = imageHtml(connectorSrc, config["connectorWidth"], config["connectorHeight"]);

	/* Loop upward through the ancestors of the current element to generate html code for
		the offset image graphics (either dotted lines or spaces in this implementation).
		Also keep a count of the number of levels to use in calculating the width of the table
		cell that will contain the images. */
	var parent = node.parentNode;
	var offsetCount = 0;
	var referenceParent;
	while (parent != null && parent.tagName != "navtree")
	{
		referenceParent = parent;
		if (nextSiblingElement(parent) && referenceParent.tagName != "navtree")
			offsetImagesHtml = imageHtml(config["src_I"], config["connectorWidth"], config["connectorHeight"]) + offsetImagesHtml;
		else
			offsetImagesHtml = imageHtml(config["src_tree_space"], config["connectorWidth"], config["connectorHeight"]) + offsetImagesHtml;
		offsetCount++;
		parent = parent.parentNode;
	}

	/* Calculate the width of the table cell containing the images. First the total width of all offset
		images is calculated, then the widths of the node point connector and the icon are added. */
	imageCellWidth = (offsetCount * config["connectorWidth"]) + parseInt(config["connectorWidth"]) + parseInt(iconWidth);

	/* Generate target attribute code for the <a> tag (if a target attribute exists in the XML). */
	if (node.getAttribute("target") != null && node.getAttribute("target") != "")
		var linkTarget = ' target="' + node.getAttribute("target") + '"';
	else
		var linkTarget = "";

	/* Generate html code for the node point connector and icon graphics and associated links.
		In this implementation, the icon for an "item" will be a link to the url from the
		item's xml "url" attribute; a "collection" will have both the node point connector and icon contained in a common
		<span> tag with an onclick event that calls the function to open the collection folder. */
	if (node.tagName == "item")
	{
		var nodeImagesHtml = connectorImageHtml + '<a href="' + node.getAttribute("url") + 
			'"' + linkTarget + '>' + iconImageHtml + '</a>';
	}
	else
	{
		var collectionService = node.getAttribute("collection_service");
		var nodeImagesHtml = '<a id="' + node.getAttribute("id") + '_link" onClick="parent.toggleDisplay(\'' + 
			node.getAttribute("id") + '\', \'' + collectionService + '\', \'' + window.name + '\')">' + 
			connectorImageHtml + iconImageHtml + '</a>';
	}

	/* Generate the html code for the label text. A link will be included if the xml data contains
		a value for the "url" atrribute (either "items" or "collections" may contain a link). */
	if (node.getAttribute("url") != null && node.getAttribute("url") != "")
	{
		/* This branch had to be added because the addition of the mini search page in the tray in the
			left menu area seemed to confuse the link targeting mechanisms. */
		if (linkTarget == "" || linkTarget == "contentFrame")
		{
			labelTextHtml = '<a href="' + node.getAttribute("url") + '" class="navItemLink"'
				+ linkTarget + '>' + node.getAttribute("label") + '</a>';
		}
		else
		{
			labelTextHtml = '<a href="' + node.getAttribute("url") + '" class="navItemLink"'
				+ linkTarget + '>' + node.getAttribute("label") + '</a>';
		}
	}
	else
	{
		labelTextHtml = node.getAttribute("label");
	}

	/* Append the final html table code for the item to the global html string variable. */
	this.htmlString +=
		'<table id="' + id + '" class="navItemTable" border="0" cellspacing="0" cellpadding="0">\n' +
		'	<tr>\n' +
		'		<td style="width:' + imageCellWidth.toString() + 'px; text-align:left; white-space:nowrap" nowrap>' + 
					offsetImagesHtml + nodeImagesHtml + '</td>\n' +
		'		<td class="navItemText" nowrap>' + labelTextHtml + '</td>\n' +
		'	</tr>\n' +
		'</table>\n';
}


/* This function is passed as a parameter to the buildHtmlStringFromXml method. It generates the opening html
	code for a single display collection/folder in the nav tree. */
function tree_control_OpeningHtml(node, nodeLocation)
{
	if (node.tagName == "navtree") // root
	{
		this.htmlString +=
			'<table id="navtreeTable" class="trayA_topLevelTable" border="0" cellspacing="0" cellpadding="0">\n' +
			'	<tr>\n' +
			'		<td style="vertical-align:top;height:100%;width:100%">\n' +
			'			<div id="trayTableMainDiv" style="width:100%;height:100%;padding-left:5px;overflow:auto">\n';
	}
	else
	{
		/* Only create the container if it doesn't already exist. */
		if (document.getElementById(node.getAttribute("id")) == null)		
			this.htmlString += '<div id="' + node.getAttribute("id") + '" style="display:none">\n';
	}
}


/* close the HTML code block for a COLLECTION item */
function tree_control_ClosingHtml(node, nodeLocation)
{
	if (document.getElementById(node.getAttribute("id")) == null)
	{
		if (node.tagName == "navtree") // root
		{
			this.htmlString +=
				'  		</div>\n' +
				'  	</td>\n' +
				'  </tr>\n' +
				'</table>\n';
		}
		else
		{
			this.htmlString += '</div>\n';
		}
	}
}

/////////////////////////////////////////////////////////////////
//  Code for dynamicly drawing nodes
/////////////////////////////////////////////////////////////////

function toggleElemDisplay(id)
{
	var elem = document.getElementById(id);
	if (elem.style.display == 'none')
		elem.style.display='block';
	else
		elem.style.display='none';
}
	
/* Utility function that parses an html text string and inserts it into an element
	(replacing any existing contents). */
function insertHtml(text, element)
{
	var range;
	var htmlFragment;

	if (document.createRange) //This branch is the pure-W3C DOM approach (should have long-term forward-compatibility)
	{
		range = document.createRange();
		range.selectNodeContents(element);
		range.deleteContents();
		htmlFragment = range.createContextualFragment(text);
		element.appendChild(htmlFragment);
	}
	else
	{
		element.innerHTML = text;
	}
}

/* Standard function for generating html image tag code. */
function imageHtml(src, w, h)
{
	var imgHtml = '<img src="' + src + '" width="' + w + '" height="' + h + '" border="0" align="absmiddle">';
	return imgHtml
}

/* insertType parameter must be one of three values: "replace" or "addToTop" or "addToBottom" */
function insertParsedChildNodes(navBuilder, childList, destinationXmlParent, insertType)
{
	if (insertType == "replace")
	/* Clear existing children from parent node (otherwise, new children will be added to existing). */
	{
		var destinationChildList = destinationXmlParent.childNodes;
		for (var i=destinationChildList.length-1; i>=0; i--)
		{
			destinationXmlParent.removeChild(destinationChildList[i]);
		}
	}

	if (insertType == "replace" || insertType == "addToBottom")
	{
		for (var j=0; j<childList.length; j++)
		/* Loop through child list and insert nodes into parent XML collection node. Since this
			is done using the appendChild() method, the new nodes are added to the bottom, after any
			existing child nodes in the list. */
		{
			if (childList[j].nodeType == 1)
			{
				var childNodeClone = childList[j].cloneNode(true);
				destinationXmlParent.appendChild(childNodeClone);
			}
		}
	}
	else if (insertType == "addToTop")
	{
		var destinationChildList = destinationXmlParent.childNodes;
		var originalTopNode = destinationChildList[0];

		for (var j=0; j<childList.length; j++)
		/* Loop through child list and insert nodes into parent XML collection node. Each node is
			inserted before the top-most existing node. */
		{
			if (childList[j].nodeType == 1)
			{
				var childNodeClone = childList[j].cloneNode(true);
				destinationXmlParent.insertBefore(childNodeClone, originalTopNode);
			}
		}
	}

	/* Remap the treeMenu fragment after the new nodes have been inserted. */
	navBuilder.xmlNodeMap = new Object();
	mapXMLTree(navBuilder.xmlRoot, navBuilder.xmlNodeMap);
}

/* based on the nodes in the navBuilder, and the function pointers in the navBuilder
   (makeOpeningHtml, makeCoreHtml, makeClosingHtml), this will generate HTML from XML,
   and insert it into the page. TODO - add support for the 'NAVTREE' node */
function generateChildNodeDisplayCode(navBuilder, node, htmlContainer)
{
	/* Determine the location pointer value of the parent node (used as argument in buildHtmlStringFromXml method call). */
	var id = node.getAttribute("id");
	var nodeLocation = "";
	var path = navBuilder.xmlNodeMap[id];
	if (path != null)
		nodeLocation = path.location;

	/* Delete contents of htmlString variable before generating new code. */
	navBuilder.htmlString = "";

	/* Generate HTML markup string for the new nodes by walking the treeMenu XML fragment
		starting at the parent containing the new children. */
	navBuilder.buildHtmlStringFromXml(node, nodeLocation);

	/* Insert the markup for the new children into the appropriate tree menu <div> container. */
	insertHtml(navBuilder.htmlString, htmlContainer);

	if (navBuilder.htmlString == "") return false
	else return true
}

/* DHTML function that hides or displays the children of a collection on the tree. */
function toggleDisplay(nodeId, collectionService, frameName)
{
	var doc = window.document;
	if (frameName != null && frameName != "")
		doc = window.frames[frameName].document;

	/* Declare variables containing the two images that need to be swapped
		ASSUMPTION: The first (and usually only) two children of the linkObject container
		will be the tree connector and the collection/folder icon respectively. */
	var linkObject = doc.getElementById(nodeId + "_link");
	var treeConnectorImage = linkObject.childNodes[0];
	var treeIconImage = linkObject.childNodes[1];
	var divContainer = doc.getElementById(nodeId);

	/* Toggle the display status of the associated DIV container accordingly. */
	if (treeConnectorImage.src.indexOf("closed.gif") > -1)
	{
		/* The collection service is used to dynamically generate and insert child nodes
			for collections in the Library (index) sub-tree (for nodes that don't have a collection
			service, their child nodes were generated at the time the page is initially loaded). */

		if (collectionService && divContainer.getElementsByTagName("table").length == 0)
		/* Run the collection service routines only if the child nodes haven't been loaded already
			(e.g. the <div> container doesn't already contain any <table> code). */
		{
			if (collectionService.indexOf("javascript:") == 0)
			/* This conditional was added to allow use with Javascript-based collection services.
				This is included to enable a broaded range of customization; it isn't currently used
				in the standard implementation. */
			{
				jsLoadFunction = collectionService.split(":")[1];
				eval(jsLoadFunction);
			}
			else
			{
				/* Load hidden frame with lm_dir_page.htm template. The onload event of this page calls the
				parseChildNodesXml() function. This can potentially be used with other services for different
				customizations.*/
				hiddenActionWindow.location.replace(collectionService);
			}
			/* Note: the code that displays the container should be in the collection service functions.*/
		}
		else
		{
			if (divContainer.hasChildNodes())
			{
				divContainer.style.display = "block"; // Display existing child nodes.
			}
		}
		// swap to the 'open' icon
		treeConnectorImage.src = treeConnectorImage.src.replace(/closed\.gif/, "open.gif");
		treeIconImage.src = treeIconImage.src.replace(/closed\.gif/, "open.gif");
	}
	else
	{
		// Hide child nodes, swap the graphic to 'closed'
		divContainer.style.display = "none";
		treeConnectorImage.src = treeConnectorImage.src.replace(/open\.gif/, "closed.gif");
		treeIconImage.src = treeIconImage.src.replace(/open\.gif/, "closed.gif");
	}
}

// This function provides a quick way to change all item icons in a collection to a particular src
function changeItemIconsTo(iconSrc, parnentId, deep)
{
	var parentNode = coreNav.getNodeById(parnentId);
	if (parentNode == null)
		return;

	var node = parentNode.firstChild;
	var nodeId;

	while(node != null)
	{
		nodeId = node.getAttribute("id");
		if (node.nodeName == "item")
			coreNav.setAttributeValue(nodeId, "icon", iconSrc);

		else if (node.nodeName == "collection")
		{
			if (deep)
				changeItemIconsTo(iconSrc, nodeId);
		}
		node = node.nextSibling;
	}
}


/////////////////////////////////////////////////////////////////
//  Utilities
/////////////////////////////////////////////////////////////////

/* encode a string to an xml string */
function xmlEncode(string)
{
	string = string.split("&").join("&amp;");
	string = string.split("<").join("&lt;");
	string = string.split(">").join("&gt;");
	string = string.split("\"").join("&quot;");
	return string;
}

/* the ultimate browser sniffer object */
function sniffer()
{
	var n = navigator;
	// string comparisons are much easier if we lowercase everything now.
	// to make indexOf() tests more compact/readable, we prepend a space
	// to the userAgent string (to get around '-1' indexOf() comparison)
	var ua = ' ' + n.userAgent.toLowerCase();
	var pl = n.platform.toLowerCase(); // not supported in NS3.0
	var an = n.appName.toLowerCase();

	// browser version
	this.version = n.appVersion;

	// 'compatible' versions of mozilla aren't navigator
	this.nn = ua.indexOf('mozilla') > 0;
	if(ua.indexOf('compatible') > 0)
	{
		this.nn = false;
	}



	this.opera = ua.indexOf('opera') > 0;
	this.webtv = ua.indexOf('webtv') > 0;
	this.ie = ua.indexOf('msie') > 0;
	this.aol = ua.indexOf('aol') > 0;
	this.omniweb = ua.indexOf('omniweb') > 0;
	this.galeon = ua.indexOf('galeon') > 0;
	this.safari = ua.indexOf('safari') > 0;
	this.mozilla = ua.indexOf('gecko') > 0;
	this.firefox = ua.indexOf('firefox') > 0;

	this.major = parseInt( this.version );
	this.minor = parseFloat( this.version );

	// platform
	this.mac = ua.indexOf('mac') > 0;
	this.win = ua.indexOf('win') > 0;
	this.unix = ua.indexOf('x11') > 0;

	// workarounds
	// - IE always reports itself as version 4.0
	if (this.ie)
	{
		var actual_index = ua.indexOf("msie ");
		if (actual_index > 0)
		{
			var actual_major = ua.substring(actual_index + 5, actual_index + 6);
			var actual_version = ua.substring(actual_index + 6, actual_index + 8);
			this.major = parseInt(actual_major);
			this.minor = parseFloat(actual_version);
		}
	}
	if (this.mozilla && this.nn)
		this.nn = false;
	if (this.mozilla && this.safari)
		this.mozilla = false;

	if (this.mozilla || (this.nn && this.major > 4))
	{
		var start = ua.indexOf('rv:');
		var end = ua.indexOf(')', start);
		var rvStr = ua.substring(start + 3, end);
		this.rv = parseFloat(rvStr);
	}

	if (this.firefox)
	{
		var start = ua.indexOf('firefox/');
		var fvStr = ua.substring(start + 8);
		this.fv = parseFloat(fvStr);
	}

	return this;
}


/* This constructor function takes an element as its parameter, and creates an object with
	properties representing the element's width, height, actual X and Y positions within the
	browser window, and X and Y positions relative to its nearest CSS-positioned ancestor. */
function dimensionFinder(element)
{
	// if its an event
	if (element.cancelBubble != null)
	{
		this.width = 2;
		this.height = 2;
		this.actualLeft = element.clientX + document.body.scrollLeft - 1;
		this.actualTop = element.clientY + document.body.scrollTop - 1;
		this.relativeLeft = 0;
		this.relativeTop = 0;
		return;
	}

	this.width = element.offsetWidth;
	this.height = element.offsetHeight;
	this.actualLeft = 0; // X-position within browser window
	this.actualTop = 0; // Y-position within browser window
	this.relativeLeft = 0; // X-position within nearest CSS-positioned ancestor
	this.relativeTop = 0; // Y-position within nearest CSS-positioned ancestor
	var subParent = element;
	var subParentPositioning = ""; // CSS position value of subParent
	var insideContainerBlock = true;

	while(subParent != null)
	{
		if (subParent.currentStyle) // get calculated CSS position value using IE proprietary method
		{
			subParentPositioning = subParent.currentStyle.position;
		}
		else if (document.defaultView && document.defaultView.getComputedStyle) // get calculated CSS position value using W3C method
		{
			subParentPositioning = document.defaultView.getComputedStyle(subParent, "").getPropertyValue("position");
		}
		else // get in-line CSS position value, not reliable
		{
			// todo: grab the className, and the class that it
			// refers to. Parse it to see if its absolute
			// or relative. possible???
			subParentPositioning = subParent.style.position;			
		}

		if (subParentPositioning == "absolute" || subParentPositioning == "relative")
			insideContainerBlock = false;

		if (insideContainerBlock)
		{
			this.relativeLeft = this.relativeLeft + subParent.offsetLeft;
			this.relativeTop = this.relativeTop + subParent.offsetTop;
		}

		this.actualLeft = this.actualLeft + subParent.offsetLeft;
		this.actualTop = this.actualTop + subParent.offsetTop;
		subParent = subParent.offsetParent;
	}
}


/* This function takes two elements as its parameters; it returns true if the elements
	 overlap on-screen or false if they don't. */
function theseElementsOverlap(element1, element2)
{
	var element1Data = new dimensionFinder(element1);
	var element2Data = new dimensionFinder(element2);

	if ((element1Data.actualLeft + element1Data.width) > element2Data.actualLeft &&
			element1Data.actualLeft < (element2Data.actualLeft + element2Data.width) &&
			(element1Data.actualTop + element1Data.height) > element2Data.actualTop &&
			element1Data.actualTop < (element2Data.actualTop + element2Data.height))
		return true;
	else
		return false;
}


/* This function takes two parameters: a valid CSS display property value (either
	"visible" or "hidden"), and an element. It loops through collections of element
	types defined in an array at the top; if any of the elements in these collections
	overlap with the passed-in element, their display value is set to the passed-in
	display value. This is used to prevent layering conflicts between DHTML popups and
	certain types of elements that always display on top.
		Note: this function doen't work properly with versions of IE prior to 5.5;
		also, the applet hiding doesn't work with NN prior to 7.1 or Moz prior to 1.3.
		In either case it should fail quietly. */
function setConflictingElements(displayVal, element)
{
	// Initialize the array containing tag names of conflicting element types.
	// This function can easily be extended by adding items to the array if needed.
	var conflictingTagNames = new Array("APPLET");
	if (its.ie)
		conflictingTagNames[conflictingTagNames.length] = "SELECT";

	// Loop through collections of each of the conflicting element types, and hide any that
	// overlap with the menu.
	for (var i=0; i<conflictingTagNames.length; i++)
	{
		var conflictingElements = document.getElementsByTagName(conflictingTagNames[i]);

		for (var j=0; j<conflictingElements.length; j++)
		{
			if (theseElementsOverlap(conflictingElements[j], element))
				conflictingElements[j].style.visibility = displayVal;
		}
	}
}


function createHiddenInputElement(frm, elementName, elementValue)
{
  var newElement = document.createElement("input");
  newElement.setAttribute("type", "hidden");
  newElement.setAttribute("name", elementName);
  newElement.setAttribute("value", elementValue);
  frm.appendChild(newElement);
}


/////////////////////////////////////////////////////////////////
//  for back compatibility with IE 4 and 5
/////////////////////////////////////////////////////////////////

function _array_pop() 
{
	var objItem = null;
	if (this.length > 0) 
	{
		objItem = this[this.length - 1];
		this.length--;
	}

	return objItem;
}

function _array_push() 
{
	if (arguments[0].sort) 
	{
		for (var i=0; i < arguments[0].length; i++)
			this[this.length++] = arguments[0][i];
	} 
	else 
	{
		for (var i=0; i < arguments.length; i++)
			this[this.length++] = arguments[i];
	}
}

function _array_remove(iLoc) 
{
	var objItem = this[iLoc];
	var arrFront = this.slice(0, iLoc);
	var arrBack = this.slice(iLoc + 1, this.length);
	var arrMerged = arrFront.concat(arrBack);

	for (var i=0; i < arrMerged.length; i++)
		this[i] = arrMerged[i];
		
	this.length = arrMerged.length;
	return objItem;
}

if (Array.pop == null)
{
	Array.prototype.pop = _array_pop;
	Array.prototype.push = _array_push;
	Array.prototype.remove = _array_remove;
}
