//////////////////////////////////////////////////////////////
//
// YOU SHOULD NEVER 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.
//
//////////////////////////////////////////////////////////////

// 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];
   }
}

/* 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();
		if (element != null)
		{
		  range.selectNodeContents(element);
		  range.deleteContents();
			htmlFragment = range.createContextualFragment(text);
			element.appendChild(htmlFragment);
		}
	}
	else
	{
		if (element != null)
		{
			element.innerHTML = text;
		}
	}
}

/* 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 = new Image();
actionImage.src = httpSkinRoot + "ActionsIcon.gif";
var actionImageOver = new Image();
actionImageOver.src = httpSkinRoot + "ActionsIcon_over.gif";

function showPopup(id, parentId, event)
{
	closePopups();
	currentPopup = document.getElementById(id);
	currentParentId = parentId;
	var parentImage = document.getElementById(parentId);

	displayMenu(parentImage, currentPopup, "horizontal")

	//*** change Actions image src if applicable
	if (parentImage.src)
		parentImage.src = actionImageOver.src;

	//*** register the mouseout handlers used to close the popup
	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;
	}
}

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)
{
	var titleSpanOb = parentCell.getElementsByTagName("SPAN")[0];

	if (titleSpanOb.firstChild.nodeValue.indexOf("...") != -1)
	{
		var titlePopup = document.getElementById(titlePopupId);
		hideMenu(titlePopup, event);
	}
}


/////////////////////////////////////////////////////////////////
//  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, placement)
{
	if (element.getElementsByTagName("div").length > 0)
	{
		var childMenu = element.getElementsByTagName("div")[0];

		// DISPLAY MENU
		if (childMenu.style.display == "none")
		{
			resetMenus();	// Hide any open menus

			displayMenu(element, childMenu, placement);
		}

		// HIDE MENU
		else if (childMenu.style.display == "block")
		{
			resetMenus();

			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, placement)
{
	if (its.ie)
	{
		e.cancelBubble = true;
	}
	else
	{
		if (element.getElementsByTagName("div").length > 0)
		{
			var childMenu = element.getElementsByTagName("div")[0];

			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)))
	{
		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.className.indexOf("_over") == -1)
		itemTable.className = itemTable.className + "_over";
	if (labelCell.className.indexOf("_over") == -1)
		labelCell.className = labelCell.className + "_over";
	if (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 menuTable = childMenu.getElementsByTagName("TABLE")[0];
	var menuWidth = menuTable.offsetWidth;
	var 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.makeOpeningHtml = new Function();
	this.makeCoreHtml = new Function();
	this.makeClosingHtml = new Function();
	this.htmlString = "";

	// 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.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)
{
	this.topLevelNodes[this.topLevelNodes.length] = xmlNodeId;
}

/*  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];
		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);
	var childList = node.childNodes;
	for (var i = childList.length-1; i>=0; i--)
		{ node.removeChild(childList[i]); }

	// remap the parent's decendents
	mapXMLTree(node, this.xmlNodeMap);
}

/* 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;
	}
}

function _navBuilder_buildHtmlStringFromXml(parentElement, parentPath)
/* Note: the "parentPath" argument should be left blank when parentElement is the root of its
	XML document. */
{
	/* Create a variable containing all children of the parent 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).*/
	this.makeOpeningHtml(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 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];

		if (parentPath)
		{
			var nodePath = parentPath + "." + i;
		}
		else
		{
			var nodePath = i.toString();
		}

		if (childNode.nodeType == 1)
		{
			this.makeCoreHtml(childNode, nodePath);

			if (childNode.tagName == "collection")
			{
				this.buildHtmlStringFromXml(childNode, 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).*/
	this.makeClosingHtml(parentElement);
}

/* 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); }
		}
	}

}


/////////////////////////////////////////////////////////////////
//  Utilities
/////////////////////////////////////////////////////////////////

/* set the query text based on the search engine */
function setMiniSearchQueryText(frm)
{
	var text = frm.MiniSearchText.value;
	var queryText = "";
	if (text != null && text.length > 0)
	{
		// default Verity query syntax
		queryText = "dDocTitle <substring> `" + text + "` OR dDocName <substring> `" + text + "` OR (" + text + ")";
		if (quickSearchQuery != null && quickSearchQuery.length > 0)
		{
			queryText = quickSearchQuery.replace(/%V/g, text);	
		}
		else if (searchIndexerEngineName=="DATABASE")
		{
			queryText = "dDocTitle LIKE '%" + text + "%' OR dDocName LIKE '%" + text + "%'";

		}
		else if (searchIndexerEngineName=="DATABASEFULLTEXT")
		{
			queryText = "dDocTitle LIKE '%" + text + "%' OR dDocName LIKE '%" + text + "%' OR CONTAINS(dDocFullText, '" + text + "')" + dbSuffix;
		}
		else if (searchIndexerEngineName=="ALTAVISTA")
		{
			queryText = "dDocTitle:{**" + text + "**} OR dDocName:{**" + text + "**} OR " + text;
		}
	}
	frm.QueryText.value = queryText;
	return true;
}

/* 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.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);
	}

	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.getComputedStyle) // get calculated CSS position value using W3C method
			subParentPositioning = document.defaultView.getComputedStyle(subParent, "").getPropertyValue("position");
		else // get in-line CSS position value
			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;
		}
	}
}



