/***********************************************************************
Elsevier Ajax Tree

DHTML tree restyles UL/LI tags to represent a tree widget.

Supports prepopulated tree data delivered from the server, or incremental
delivery of the tree using Ajax. 

When using Ajax, the UL/LI HTML fragments are returned (this is sometimes
referred to as an Ajax subset called "AHAH").
 
This code has been *heavily* (unrecognizably :) rewritten from the 
"static folder tree" code originally from dhtmlgoodies.com.
	
************************************************************************/	
	
function elsAjaxTree(rootULId) {
	
	// Include the elsCookie javascript object: (for now include via page)	
	// document.write('<script type="text/javascript" src="elsCookie.js"></script>');	
		
	//////////////////////////////////////////////////////////
	// Overridable Settings:		
		
	// The html id of the root UL tag for the tree
	// as it resides on the current page	
	this.treeRootULId = rootULId;
	
	// The path to where your image icons are stored:
	this.imageFolder = '../global/images/icons/';
	
	// The default icon images (override to customize):
	this.openImage = 'toc_plus.gif';
	this.closeImage = 'toc_minus.gif';
	this.bulletImage = 'toc_bullet.gif';
	
	// The text message and CSS style when loading via AJAX:
	this.ajaxLoadingMsg = 'loading...';
	this.ajaxLoadingCSSStyle = 'ajaxLoading';		
	
	// Text message and CSS style when error occurs loading AJAX:
	this.ajaxErrorMsg = 'System error.  Refresh your browser, or try again later.';
	this.ajaxErrorCSSStyle = 'ajaxError';		
	
	// Text message and CSS style when timeout occurs loading AJAX:
	this.ajaxTimeoutMsg = 'System timeout.  Refresh your browser, or try again later.';
	this.ajaxTimeoutCSSStyle = 'ajaxTimeout';		
	
	// The URL to request children via AJAX
	this.ajaxRequestNodesBaseURL = '';		
	
	// Param passed to the server to identify which nodes children
	this.ajaxNodeIdParamName = 'node';
	
	// Param passed to the server to identify the root of a subtree to
	// open a path beneath:
	this.ajaxBeneathNodeIdParamName	= 'beneathNode';
	
	// Param passed to the server to identify which tree on the page	
	this.ajaxTreeParamName = 'tree';
	
	// Params to include when parents/top nodes aren't requested	
	this.ajaxOmitParentsParam = '';
	
	// Params to include when parents/top nodes are requested
	this.ajaxRequestParentsParam = '&parents=true';
	
	// By default don't display the tree root as a node	
	this.expandableRootNode = false;	
	
	// By default don't retain the state of open nodes after a selection	
	this.closeAllButSelected = true;
		
	// If 'selected' is used, allow multiple selections to highlight?	
	this.allowMultipleSelections = false;		

	// Normally we automatically highlight the node they click on:	
	this.highlightOnClick = true;	

	// And normally we automatically open a node they click on (as
	// opposed to them having to click the + icon):	
	this.openOnClick = true;

	// An optional (client) function to be called when the *user* selects
	// on an anchor within one of the tree nodes:
	this.onSelect = null; 

	// An optional (client) function to be called whenever the current
	// selection truly changes (no matter what causes the change).
	this.onSelectionChange = null;

	// An optional (client) function to be called to custom preprocess
	// the anchor tags within one of the tree nodes (i.e. when first
	// received via Ajax):
	this.preProcessAnchor = null; 

	// An optional (client) function to determine if the ajax response
	// was an error or not (return true if error, false otherwise).
	this.checkForAjaxError = null;

	// An optional (client) function to be called prior to opening/closing a node
	// The function should return true to proceed, false to prevent the open/close.
	this.toggleOpenOrClosed = null;

	// The selected nodes cookie object:	
	this.selectedNodesCookie = null;
	this.rememberSelectedNodes = false;
	
	// The expanded nodes cookie object:	
	this.expandedNodesCookie = null;
	this.rememberExpandedNodes = false;

	//////////////////////////////////////////////////////////
	// Not Overridable:
	
	// The root UL of the entire tree:
	var rootUL;
	
	// What nodes have been expanded according to the cookie
	// (not used if closeAllButSelected is true).		
	var cookieExpandedNodes = '';	
	
	// What nodes are selected:	
	var cookieSelectedNodes = '';		
	
	// This is used to ensure uniqueness when generating ids for the UL tags:
	// i.e. we'd like to use the nodeIds directly, but you could have two trees
	// displaying the same node so that would not guarantee uniqueness.
	var treeLIIdCounter = 0;

	// An associative array of the UL tags which have been highlighted using
	// the "selectNodeId" attribute which can be placed at any level of the 
	// tree (e.g. the root can indicate that a certain node beneath it should
	// be highlighted).
	var selectedLINodes = new Object();

	// Process a tree/sub-tree (e.g. received via Ajax), by:
	//    adding bullet/+/- icons where needed.
	//    assigning unique ids to LI tags that don't have them
	//    auto-loading via Ajax any open nodes whose children were not pre-populated
	//    opening any nodes which the cookie says should be open.
	this.processULTree = function (ul) {	
			// Process the html by adding bullet/+/- icons, etc.
			this.processTags(ul);	
			
			// "Auto load" (i.e. use Ajax to load) any open (i.e. visible) nodes 
			// that have a nodeId but no pre-populated children:
			this.autoLoadNodes(ul);			
			
			// Open any nodes that the cookie indicates should be...
			this.openCookieNodes();
			
			// Highlight any nodes indicated via the selected attribute.
			this.highlightSelected();	
			
			// And make sure the selected node is open and visible.
			this.openSelected();					
	};

	// Post-process the html under the specific UL tag, adding 
	// bullet/+/- icons, and assigning unique id's to any LIs that don't otherwise
	// have them already. 
	this.processTags = function (ul) {
		
		// They can specify which LI nodes are initially selected beneath this UL
		// Note:  It's very possible that the indicated node isn't loaded yet,
		//        yet we store it's id away and do the actual highlighting later.
		var selected = ul.getAttribute('selected');
		if(!selected) selected = ul.selected;		
		if(selected) {		
			this.setLIToBeSelected(selected);
		}
		
		// Process the LI Tags beneath this UL,
		// adding the +/- icon, etc.
		this.processLITags(ul);
		
		// Process the anchor tags, attaching event
		// handlers to allow us to record the selection 
		// in a cookie:
		this.processAnchorTags(ul);		
	};				

	// Process any LI Tags beneath the given UL.
	// Note:  This is really just a helper method for processTags
	this.processLITags = function (ul) {	
		// Find all LI tags beneath this UL:
		var liTags = ul.getElementsByTagName('LI');
		
		// For each:
		for(var no=0;no<liTags.length;no++){
		
			// Has this one been processed before?
			var processed = liTags[no].getAttribute('processed');
			if(!processed) processed = liTags[no].processed;
			if(processed == 'true') {
				// move on to the next LI
				continue;
			}
			
			// okay go ahead:
			liTags[no].setAttribute('processed', 'true');

			// If they've assigned no id to this LI tag:
			if(!liTags[no].id) {			
				// Create a unique id for it...
				// This becomes the html id for the UL tag, and this
				// is what (e.g.) is stored in the open nodes cookie.
				liTags[no].id = this.treeRootULId + '_LI' + treeLIIdCounter;												
				treeLIIdCounter++;						
			}

			// If this is to be selected, remember this LI for later:
			if(selectedLINodes[liTags[no].id] == '?' &&
				liTags[no].parentNode != rootUL)
			{
				// Note: We don't actually highlight here because
				//       we aren't assuming anything about the order
				//       that the nodes are received.  e.g. We support
				//       the possibility that an Ajax delivered node
				//       could actually cause one of it's *parents*
				//       to be highlighted.
				this.setLIToBeSelected(liTags[no].id);
			}

			// Does the LI have any children?
			var firstChild = liTags[no].firstChild;
			if(firstChild) {

				// We add a bullet/+/- icon to the LI
				var icon = document.createElement('IMG');
									
				// Is there any UL tag, indicating this LI is expandable?
				var childUL = this.getChildrenULForLI(liTags[no]);
				if(!childUL) {
					// If there's no children then show a bullet:
					icon.src = this.imageFolder + this.bulletImage;			
				} else {
					// Set the icon based on the current visibility of it's children
					if(childUL.style.display!='none') {
						icon.src = this.imageFolder + this.closeImage;						
					}
					else {
						icon.src = this.imageFolder + this.openImage;						
					}
							
					// Make sure the hand shows up on mouse over		
					icon.className = 'hoverable';				
					
					// When they click they should toggle open or closed
					icon.onclick = toggleOpenOrClosed;
					icon.tree = this;  // make the tree accessible to the click handler					 											
				}
				
				// Insert the icon at the beginning within this LI:
				// (but not if the root node and they don't want it expandable)
				if(!(liTags[no].parentNode == rootUL && !this.expandableRootNode)) {				
					liTags[no].insertBefore(icon,firstChild);
				}
			}		
		}
	};

	// Record the selected LI in the "selectedLINodes" hash.
	// Note:  This can cause the highlight to change on nodes downloaded
	// 			*later* via Ajax.
	this.setLIToBeSelected = function (id) {

		// Make sure we at least got an id:
		if(!id || id == '') {
			return;
		}

		// Try and find the LI with the specified id	
		var li = document.getElementById(id);
		
		if(!this.allowMultipleSelections) {
			// If we don't allow multiple selections
			// then clear the prior selections so that
			// we only remember one at a time:
			selectedLINodes = new Object();				
		}
			
		if(!li) {
			// This might be a yet-to-be-loaded node, the
			// question mark indicates to set this later
			// once it is loaded via ajax:		
			li = '?'; 
		}
							
		// Record it in the hash					
		selectedLINodes[id] = li;
	}

	// Remove the selected LI from the "selectedLINodes" hash
	this.removeSelectedLI = function (id) {

		// Make sure we at least got an id:
		if(!id || id == '') {
			return;
		}
									
		// Remove it from the hash					
		delete selectedLINodes[id];
	}

	// Process any anchor tags beneath the given UL.
	// Note:  This is really just a helper method for processTags
	this.processAnchorTags = function (ul) {	
		// Find all anchors beneath this UL:
		var anchorTags = ul.getElementsByTagName('A');
		
		// For each:
		for(var no=0;no<anchorTags.length;no++) {
		
			// Has this one been processed before?
			var processed = anchorTags[no].getAttribute('processed');
			if(!processed) processed = anchorTags[no].processed;
			if(processed == 'true') {
				// move on to the next anchor
				continue;
			}
			
			// okay go ahead:
			anchorTags[no].setAttribute('processed', 'true');

			// I'D LIKE TO USE THE DOJO EVENT SYSTEM HERE SO THAT I DON'T
			// *REPLACE* AN EXISTING ONCLICK - BUT IT WASN'T WORKING FOR ME
    		// dojo.event.connect(anchorTags[no], 'onclick', 'anchorSelected');	   		
			anchorTags[no].onclick = anchorSelected;
			anchorTags[no].tree = this;  // make the tree accessible to the click handler
			
			if(this.preProcessAnchor != null)
			{
				this.preProcessAnchor(anchorTags[no]);
			}				
		}
	};

	// Handle when the user clicks on an anchor within one of the tree nodes	
	// This records the id of the selected node in the cookie, and optionally calls
	// the client's "onSelect" function, if they've provided one.  
	// Note:  This is an "onclick" method, it's "this" is the actual anchor they clicked on.
	function anchorSelected(evt)
	{			
		// Get the tree off the anchor where we've stored it
		var  theTree = this.tree; 
		if(!theTree) {
			return;
		}
									
		// Get the parent LI to be selected:
		var li = theTree.getParentLI(this);
		var followHref = true;		
		if(li) {		
			// And highlight it using the "selectedTreeNode" class:
			if(theTree.highlightOnClick) {
				theTree.highlight(li);
			}
			
			// Do they want it to "auto-open" when they click the link?
			if(theTree.openOnClick) {			
				theTree.openLINode(li);
			}			
			
			// Did they register an "onSelect" call back with us?
			if(theTree.onSelect != null)
			{
				// It returns whether or not follow the href...
				followHref = theTree.onSelect(evt, theTree, li.id, this);
			}								
		}
		
		// Return whether to follow the anchor tag's href or not:
		return followHref;
	};

	// Highlight a specified node (and updates the cookie)
	this.highlight = function (li) {		
		if(li) {

			// And highlight it using the "selectedTreeNode" class:
			var className = li.className;
			
			// Does it already have the highlight?
			var selectedClassPos = className.indexOf('selectedTreeNode');
			if(selectedClassPos == -1) {
				// Nope... we need to highlight it then...

				// But clear the prior selection if they only allow one:
				if(!this.allowMultipleSelections) {
					this.unhighlightSelected();				
					
					// And clear the cookie:
					cookieSelectedNodes = ',';								
				}

				// Add the CSS class that causes the highlight
				className = className + ' selectedTreeNode';
				li.className = className;	
								
				// And add this li to the list of selected nodes:
				this.setLIToBeSelected(li.id);		
		
				// And add it to the cookie (if it's not already there):
				if(cookieSelectedNodes.indexOf(',' + li.id + ',')<0) {
					cookieSelectedNodes = cookieSelectedNodes + li.id + ',';
				}	
				
				// Remember what nodes are currently expanded	
				if(this.selectedNodesCookie) {
					this.selectedNodesCookie.set(cookieSelectedNodes);
				}
												
				// Call their "onSelectionChange" callback if they've provided one...
				// Note:  This should *only* be called if the selection has 
				// truly *changed*, but the highlight method can be called 
				// multiple times (without actually *changing* the selection).
				// This is why this is only called here when we see we're truly
				// changing the CSS style to show a different element highlighted.
				if(this.onSelectionChange) {
					this.onSelectionChange(this, li);
				}				
			}	
		}
	};

	// Unhighlight a specified node (does not affect the cookie)
	this.unhighlight = function (li) {
		if(li) {	

			// Remove this li from the list of selected nodes:
			this.removeSelectedLI(li.id);		
					
			// And if it's there, remove it:
			if(cookieSelectedNodes) {
				var selectedNodePos = cookieSelectedNodes.indexOf(',' + li.id + ',');			
				if(selectedNodePos != -1) {
					cookieSelectedNodes = cookieSelectedNodes.replace(li.id + ',',"");
					
					// Now the currently expanded nodes are one less
					if(this.selectedNodesCookie) {
						this.selectedNodesCookie.set(cookieSelectedNodes);	
					}
				}	
			}
							
			// Remove the highlight:
			var className = li.className;
			var selectedClassPos = className.indexOf(' selectedTreeNode');
			if(selectedClassPos > -1) {
				className = className.substring(0, selectedClassPos);
			}			
			li.className = className;	
		}
	};

	// Highlight any selected nodes (does not affect the cookie)
	this.highlightSelected = function () {			
		// For each selected LI
		for(var selected in selectedLINodes) {
			// if it's not a reference to a yet to be delivered node...
			var selectedLI = selectedLINodes[selected];
			if(selectedLI != '?') {
				this.highlight(selectedLI);
			}
		}
	};
	
	// Clear the highlight from all selected nodes (does not affect the cookie)
	this.unhighlightSelected = function () {	
		// For each selected LI
		for(var selected in selectedLINodes) {
			// if it's not a reference to a yet to be delivered node...
			var selectedLI = selectedLINodes[selected];
			if(selectedLI != '?') {
				this.unhighlight(selectedLI);
			}
		}
	};	
		
	// Open the selected node(s)
	this.openSelected = function () {	
		// For each selected LI's id
		for(var selected in selectedLINodes) {
			// if it's not a reference to a yet to be delivered node...
			var selectedLI = selectedLINodes[selected];
			if(selectedLI != '?') {
				this.openPathToLINode(selectedLI);			
			}
		}
	};	
	
	// Open the specified node and all of it's parent folders, so that
	// the path from the tree root to the node is open.
	this.openPathToLINode = function (li) {
	
		// Make sure we have a node:
		if(!li) {
			return;
		}			
	
		// And make sure it really is an LI:
		if(li.tagName == 'LI') {
		
			// Open it:				
			this.openLINode(li);
		
			// Get it's parent which should be a UL:
			var parentUL = li.parentNode;
			if(parentUL.tagName == 'UL') {
				// Once we hit the root we're done:
				if(parentUL != this.rootUL) {
					// They can put an attribute "virtualRoot=true" on a UL that they
					// wish to be treated as the highest openable node in the tree:	
					var virtualRoot = parentUL.getAttribute('virtualRoot');
					if(!virtualRoot) virtualRoot = parentUL.virtualRoot;
					if(!virtualRoot || virtualRoot != "true") {						
						this.openPathToLINode(parentUL.parentNode);
					}
				}
			}
		}
	};	
			
	// Auto load any UL tags which are empty (i.e. not pre-generated by the server), 
	// but whose parent LI has a nodeId for requesting their children via Ajax: 
	this.autoLoadNodes = function (ul) {

		// First try it with the node itself:
		this.autoLoadNode(ul);

		// Now try it with all it's children ULs:
		var ulTags = ul.getElementsByTagName('UL');
		
		// For each:
		for(var node=0;node<ulTags.length;node++){
			this.autoLoadNode(ulTags[node]);
		}
	};
		

	// Auto load the specified UL DOM node, if it has no children, yet is 
	// visible and has the nodeId needed for requesting it's children
	// via Ajax.
	this.autoLoadNode = function (ul) {

		// Is this UL actually "open" (i.e. visible)?
		if(ul.style.display!='none') {	
			// Yes - does it already have children?
			var liTags = ul.getElementsByTagName('LI');				
			if(liTags.length == 0) {			
					// Nope - open it using Ajax:
					this.openULNode(ul);				 
			}
		}
	};	
	
	// Open an LI node	
	// (optionally:  opening the path to a specified descendent node) 
	this.openLINode = function (li, openPathToNodeId) {
		
		if(!li) {
			return;
		}
		
		// Get the UL for it's children:
		var ul = this.getChildrenULForLI(li);
		if(ul) {
			this.openULNode(ul, openPathToNodeId);
		}		
	};	
		
	// Open the specified UL node	
	// (optionally:  opening the path to a specified descendent node) 	
	this.openULNode = function (ul, openPathToNodeId) {

		if(!ul) {
			return;
		}	
		
		// Set the icon...
		var icon = this.getIconForUL(ul);
		if(icon) {		
			icon.src = this.imageFolder + this.closeImage;
		}
		
		// Make sure it's really "open" (i.e. that it's children are displayed):
		ul.style.display='block';
						
		// Has this been expanded before? (i.e. are there any children?)
		var liTags = ul.getElementsByTagName('LI');				
		if(liTags.length == 0) {
			// Nope - load the children of this node:			
			this.loadChildNodesViaAjax(ul, openPathToNodeId);				
		}		
		
	};
	
	// Close the specified LI node:	
	this.closeLINode = function (li) {
		
		if(!li) {
			return;
		}
		
		// Get the UL for it's children:
		var ul = this.getChildrenULForLI(li);
		if(ul) {
			this.closeULNode(ul);
		}		
	};	
		
	// Close the specified UL node:	
	this.closeULNode = function (ul) {
		
		// Set the icon...
		var icon = this.getIconForUL(ul);
		if(icon) {		
			icon.src = this.imageFolder + this.openImage;
		}
		
		// Make sure it's really "closed" (i.e. that it's children are hidden):
		ul.style.display='none';	
	};	
	
	// Open whatever nodes are indicated by the opened nodes cookie
	this.openCookieNodes = function () {	

		if(cookieExpandedNodes){			
			var opened = cookieExpandedNodes.split(',');
			for(var node=0; node<opened.length; node++) {
				this.openLINode(document.getElementById(opened[node]));
			}
		}	
	};
	
	// Get the UL (if any) for the specified LI. 	
	this.getChildrenULForLI = function (li) {
		var ulTags = li.getElementsByTagName('UL');
		if(ulTags.length > 0) {
			return ulTags[0];
		}
		else {
			return undefined;
		}
	};	
	
	// Get the parent LI for the specified DOM node. 	
	this.getParentLI = function (node) {
		var parent = node.parentNode;
		while(parent && parent.tagName != 'LI') {
			parent = parent.parentNode;
		}
		
		return parent;
	};		
				
	// Get the icon corresponding to the specified ul (actual UL DOM node, not it's id). 	
	this.getIconForUL = function (ul) {
		var parentNode = ul.parentNode;
		var imgs = parentNode.getElementsByTagName('IMG');
		if(imgs.length > 0) {
			return imgs[0];
		}
		else {
			return undefined;
		}
	};	
		
	// Get the icon corresponding to the specified ul (actual UL DOM node, not it's id). 	
	this.getULForIcon = function (icon) {
		var parentNode = icon.parentNode;
		var ulTags = parentNode.getElementsByTagName('UL');
		if(ulTags.length > 0) {
			return ulTags[0];
		}
		else {
			return undefined;
		}
	};			
			
	// Toggle the node from closed to open or vice versa	
	// Note: This is called as the onclick of the +/- icon.
	function toggleOpenOrClosed()
	{
		// Get the tree off the icon where we've stored it
		var  theTree = this.tree; 
		if(!theTree) {
			return;
		}
				
		// Get the UL tag to be opened/closed
		var ul = theTree.getULForIcon(this);
		if(!ul) {
			return;
		}
		
		// Did they give a custom toggle open/close handler?
		var proceedWithOpenOrClose = true;		
		if(theTree.toggleOpenOrClosed) {		
			proceedWithOpenOrClose = theTree.toggleOpenOrClosed(ul, theTree);		
		}	

		if(!proceedWithOpenOrClose) {
			return;
		}
						
		// Is this node open?
		if(ul.style.display!='none') {
			// Yup - close it:
			theTree.closeULWithCookie(ul);									
		}
		else {
			// Nope - open it:
			theTree.openULWithCookie(ul);									
		}
	};	

	// Open an LI node and remember it in the cookie	
	// (optionally:  opening the path to a specified descendent node) 
	this.openLIWithCookie = function (li, openPathToNodeId) {
		
		if(!li) {
			return;
		}
		
		// Get the UL for it's children:
		var ul = this.getChildrenULForLI(li);
		if(ul) {
			this.openULWithCookie(ul, openPathToNodeId);
		}		
	};	

	// Open a UL and remember it in the cookie 
	// (optionally:  opening the path to a specified descendent node)	
	this.openULWithCookie = function(ul, openPathToNodeId)
	{
		if(!ul) {
			// Nothing to be done...
			return;
		}
			
		// Open it:
		this.openULNode(ul, openPathToNodeId);	
		
		// And include this node in the cookie list of expanded nodes:
		var li = this.getParentLI(ul);					
		if(!cookieExpandedNodes)cookieExpandedNodes = ',';
		if(cookieExpandedNodes.indexOf(',' + li.id + ',')<0) {
			cookieExpandedNodes = cookieExpandedNodes + li.id + ',';
		}								
									
		// Remember what nodes are currently expanded	
		if(this.expandedNodesCookie) {
			this.expandedNodesCookie.set(cookieExpandedNodes);
		}
	};	

	// Close the specified LI node and remember it in the cookie:	
	this.closeLIWithCookie = function (li) {
		
		if(!li) {
			return;
		}
		
		// Get the UL for it's children:
		var ul = this.getChildrenULForLI(li);
		if(ul) {
			this.closeULWithCookie(ul);
		}		
	};	

	// Close a UL and remember it in the cookie 
	this.closeULWithCookie = function(ul)
	{
		if(!ul) {
			// Nothing to be done...
			return;
		}
						
		// Close it:
		this.closeULNode(ul);
		
		// Remove this node from the list of expanded nodes:
		var li = this.getParentLI(ul);					
		cookieExpandedNodes = cookieExpandedNodes.replace(',' + li.id,'');								
									
		// And remember this node has been closed	
		if(this.expandedNodesCookie) {
			this.expandedNodesCookie.set(cookieExpandedNodes);
		}
	};

	// Return the first child element (ignoring e.g. white space nodes)
	this.getFirstChildElement = function(node)
  	{
		var x = node.firstChild;
		while(x.nodeType!=1)
		{
			x=x.nextSibling;
		}
		return x;
	};

	// A Dojo Ajax handler 
	// (Dojo defines this parameter signature) 
	function handleFragment(type, data, evt, args) {
		// Dojo is not recognizing an error response as an
		// error (i.e. even when firebug showed a 404 http 
		// response code).  Without checking, the server 
		// can be flooded with requests as elsAjaxTree tries
		// to "open" LI tags on the error page thinking they
		// are actual tree nodes!!

		// Did they give a custom error checker?
		var errorFromServer = false;		
		if(args.tree.checkForAjaxError) {		
			errorFromServer = args.tree.checkForAjaxError(data, args.tree, args.element);		
		}	
					
		if(!errorFromServer) {
			if(args.element.innerHTML) {
				// Set it's contents to the fragment
				args.element.innerHTML = data;
			}
		
			args.tree.processULTree(args.element);
		}
		else if(args.element) {
			//	displaySystemMsgBeneath(args.tree, args.element, 
			//		args.tree.ajaxErrorMsg, args.tree.ajaxErrorCSSStyle);
		}
		
	};
			
	// A Dojo Ajax handler 
	// (Dojo defines this parameter signature) 
	function handleFragmentError(type, data, evt, args) {
		// After quite a bit of testing, it appears that we more often than
		// not get this message, then apparently the dojo.io.bind retry succeeds
		// so displaying this error message is doing nothing but uglying up the
		// screen with not benefit to the user otherwise.  Comment it for now:
		//		alert("elsAjaxTree: got the handleFragmentError call!");	
		//		displaySystemMsgBeneath(args.tree, args.element, 
		//			args.tree.ajaxErrorMsg, args.tree.ajaxErrorCSSStyle);	
	};		
	
	// A Dojo Ajax handler 
	// (Dojo defines this parameter signature) 
	function handleFragmentTimeout(type, data, evt, args) {
		// alert("elsAjaxTree: got the handleFragmentTimeout call!");
		displaySystemMsgBeneath(args.tree, args.element, 
		args.tree.ajaxTimeoutMsg, args.tree.ajaxTimeoutCSSStyle);		
	};		

	// Display the specified text message beneath the specified element in the tree
	function displaySystemMsgBeneath(tree, element, msg, className) {
		if(element) {
			// Put out the little "timeout" icon and message:			
			var msgElement = document.createElement('div');
			msgElement.className = className;
			var textOfMsg = document.createTextNode(msg);
			msgElement.appendChild(textOfMsg);				
			element.replaceChild(msgElement,
				tree.getFirstChildElement(element));													
		}
	};

	// Find the loading spinner beneath the given ul (if it exists), otherwise null.
	this.getLoadingSpinnerBeneath = function(ul) {
	// It looks bad to have more than one spinner 
		// (this can happen sometimes when it's loading a full path down to a node)
		var ajaxLoading;		
		
		// Find all span tags beneath this UL:
		var spanTags = ul.getElementsByTagName('span');
		
		// For each:
		for(var no=0;!ajaxLoading && no<spanTags.length;no++) {
		
			var className = spanTags[no].className;
			if(className && className == this.ajaxLoadingCSSStyle) {
				ajaxLoading = spanTags[no];
			}		
		}
		
		return ajaxLoading;
	}

	// Display the "loading spinner" beneath the node they're expanding.
	// (we don't bother hiding it since the expanded children simply replace it)
	this.displayLoadingSpinnerBeneath = function(ul) {		
		// But don't show two:
		var ajaxLoading = this.getLoadingSpinnerBeneath(ul);
		if(ajaxLoading) {
			return;
		}
 		
		// Put it all in a span since we expect it on one line:			
		ajaxLoading = document.createElement('span');
		
		// Note: we depend on the className to recognize this element
		//       within getLoadingSpinnerBeneath()
		ajaxLoading.className = this.ajaxLoadingCSSStyle;	
				
		// Followed by a text message:
		if(this.ajaxLoadingMsg && this.ajaxLoadingMsg != '') {
			var loadingMsg = document.createTextNode(this.ajaxLoadingMsg);			
			ajaxLoading.appendChild(loadingMsg);				
		} 
			
		// Show it beneath the ul they specified:	
		ul.appendChild (ajaxLoading);	
	}

	// Load the child nodes of the given UL via Ajax
	// Display the configured "ajaxLoadingMsg" using the configured CSS style
	// (optionally:  opening the path to a specified descendent node) 	
	this.loadChildNodesViaAjax = function (ul, openPathToNodeId)
	{	
		// Put out the little "loading" icon:
		this.displayLoadingSpinnerBeneath(ul);
							
		// Get the containing LI for this UL
		var li = this.getParentLI(ul);
		
		// They can optionally indicate whether parents should
		// be included with the children (e.g. on a root node)
		var parents = li.getAttribute('parents');
		if(!parents) parents = li.parents;		

		// If they're getting parents they specify the "childId"
		// otherwise they specify the parent's id as simple the "id"
		var treeNameAndNodeId;
		if(parents == 'true') {
			treeNameAndNodeId = li.getAttribute('childId');
			if(!treeNameAndNodeId) treeNameAndNodeId = li.childId;				
		}
		else {
			treeNameAndNodeId = li.id;			
		}
		
		// Extract the "nodeId" portion for the server
		var nodeId = this.getNodeId(treeNameAndNodeId);
		var childrenUrl;		
		if(nodeId){
			// Are we reading an html file (useful for debugging)
			var baseUrl = this.ajaxRequestNodesBaseURL;
			var dotPos = baseUrl.lastIndexOf(".");
			var ext = baseUrl.substr(dotPos+1);
			if(dotPos > -1 && (ext == "html" || ext == "htm")) {
					// Load the html file that ends with the given nodeId										
					var fileNamePrefix = baseUrl.substr(0, dotPos);
					childrenUrl = fileNamePrefix + nodeId + ".html";				
			} 
			else {				
				if(!openPathToNodeId) {
								
					// We're just wanting the direct children of this node:									
					childrenUrl = this.ajaxRequestNodesBaseURL + 
									'&' + this.ajaxNodeIdParamName + '=' + nodeId +
									'&' + this.ajaxTreeParamName + '=' + this.treeRootULId;										
									
					if(parents == 'true') {
						childrenUrl = childrenUrl + this.ajaxRequestParentsParam;
					}
					else {
						childrenUrl = childrenUrl + this.ajaxOmitParentsParam;			
					}									
				}
				else {
					// We're wanting the children of this node, but with the 
					// path to the specified node open (note:  the server *requires*
					// the parents for this to work - we use an XSL to grab whats beneath
					// the specified "beneath" nodeId after getting it all from XCR).
					
					// Extract the "nodeId" portion for the server (with the tree name prefix)
					var beneathNodeId = this.getNodeId(openPathToNodeId);
																		
					childrenUrl = this.ajaxRequestNodesBaseURL + 
									'&' + this.ajaxBeneathNodeIdParamName + '=' + nodeId +					
									'&' + this.ajaxNodeIdParamName + '=' + beneathNodeId +
									'&' + this.ajaxTreeParamName + '=' + this.treeRootULId +
									this.ajaxRequestParentsParam;										
				}			
			}
									
			var bindArgs = {
				method: "GET",			
				url: childrenUrl,
				load: handleFragment,
				error: handleFragmentError,
				timeoutSeconds: 180,
				timeout: handleFragmentTimeout,		
				element: ul,
				tree: this				
			}
						
			dojo.io.bind(bindArgs);						
		}	
	};
	
	// Get the node id from an LI id following the "treeName_nodeId" naming convention:
	this.getNodeId = function (liId) {
		if(!liId) {
			return '?'
		}
		
		var underScorePos = liId.indexOf('_');
		if(underScorePos != -1) {
			return liId.substr(underScorePos+1);			
		}
		else {
			// They have not uniqified their ids by preprending the tree name!
			return liId;
		}		
	};	
							
	this.insertImageBefore = function (element, before, imageName) {	
		var imgElem = document.createElement('IMG');
		if(element.className){
			imgElem.src = this.imageFolder + element.className;
		}else{
			imgElem.src = this.imageFolder + imageName;
		}
		element.insertBefore(imgElem, before);
	};	

	this.expandAll = function ()
	{
		var liTags = document.getElementById(this.treeRootULId).getElementsByTagName('LI');
		for(var no=0;no<liTags.length;no++){
			var ulTags = liTags[no].getElementsByTagName('UL');
			if(ulTags.length>0 && ulTags[0].style.display!='block'){
				this.openULWithCookie(ulTags[0]);
			}			
		}
	};
	
	this.collapseAll = function ()
	{
		var liTags = document.getElementById(this.treeRootULId).getElementsByTagName('LI');
		for(var no=0;no<liTags.length;no++){
			var ulTags = liTags[no].getElementsByTagName('UL');
			if(ulTags.length>0 && ulTags[0].style.display=='block'){
				this.closeULWithCookie(ulTags[0]);
			}			
		}		
	};	
	
	// Clear the children of the specified tree node and reload them from
	// the server.  If the markup indicating the node/id/eid for the children
	// is changed, this can be used to change the children displayed beneath
	// a particular tree node (up to and including the root node if desired).
	this.reloadULTree = function (ul)
	{					
		if(ul) {
			// Get the containing LI for this UL
			var li = this.getParentLI(ul);
			
			// They can optionally indicate whether parents should
			// be included with the children (e.g. on a root node)
			var parents = li.getAttribute('parents');
			if(!parents) parents = li.parents;		
	
			// If they're getting parents they specify the "childId"
			// otherwise they specify the parent's id as simple the "id"
			var treeNameAndNodeId;
			if(parents == 'true') {
				treeNameAndNodeId = li.getAttribute('childId');
				if(!treeNameAndNodeId) treeNameAndNodeId = li.childId;				
			}
			else {
				treeNameAndNodeId = li.id;			
			}
			
			// Extract the "nodeId" portion for the server
			var nodeId = this.getNodeId(treeNameAndNodeId);	
			
			if(nodeId && nodeId.match('/\d+/ ')) {
						
				// Remove all the children of this ul
				while(ul.firstChild) {
					ul.removeChild(ul.firstChild);
				}			
						
				// Reload the nodes children, reset the highlight, etc...	
				this.processULTree(ul);
			}
		}	
	};		
	
	// Find an element of a specified type with a given attribute value
	this.getElementsByAttribute = function (beneath, tagName, attrName, attrValue)
	{
		var arrElements = (tagName == "*" && beneath.all)? beneath.all : beneath.getElementsByTagName(tagName);
		var arrReturnElements = new Array();
		var oAttributeValue = (typeof attrValue != "undefined")? new RegExp("(^|\s)" + attrValue + "(\s|$)") : null;
		var oCurrent;
		var oAttribute;
		for(var i=0; i<arrElements.length; i++){
			oCurrent = arrElements[i];
			oAttribute = oCurrent.getAttribute && oCurrent.getAttribute(attrName);
			if(typeof oAttribute == "string" && oAttribute.length > 0){
				if(typeof attrValue == "undefined" || (oAttributeValue && oAttributeValue.test(oAttribute))){
					arrReturnElements.push(oCurrent);
				}
			}
		}
		return arrReturnElements;
	};	
								
	// "Activate" the tree on the page.
	// Note:  Call activateTree after you've created your elsAjaxTree object, 
	// and then overridden any the configuration settings that you need to.		
	this.activateTree = function ()
	{
		// Store away the root UL tag that surrounds the entire tree
		rootUL = document.getElementById(this.treeRootULId);
		
		// And conversely, let other javascript find us off this root UL:
		rootUL.tocTree = this;
		
		// Create the cookies (if not overridden by the client):
		if(this.rememberSelectedNodes && !this.selectedNodesCookie) {
			this.selectedNodesCookie = 
				new elsCookie(this.treeRootULId + 'SelectedNodes',500);
		}					
		if(this.rememberExpandedNodes && !this.expandedNodesCookie) {
			this.expandedNodesCookie = 
				new elsCookie(this.treeRootULId + 'ExpandedNodes',500);
		}				
				
		// If we're not intentionally forgetting the previous selections:
		if(!this.closeAllButSelected) {
			// Then get the previously expanded nodes from the cookie:
			cookieExpandedNodes = this.expandedNodesCookie.get();	
		}
	
		// Get the selected node(s) from the cookie:
		if(this.selectedNodesCookie) {
			cookieSelectedNodes = this.selectedNodesCookie.get();		
			if(cookieSelectedNodes) {			
				var selected = cookieSelectedNodes.split(',');
				for(var node=0; node<selected.length; node++) {
					this.setLIToBeSelected(selected[node]);
				}
			}	
		}
	
		// Process the tree identified via treeRootULId...
		if(rootUL) {	
			this.processULTree(rootUL);
		}	
	};	
}	

	
