// =============================================================================
// Copyright 2000-2004 General Electric Company.  All Rights Reserved. 
// This software product may only be used strictly in accordance 
// with the applicable written License Agreement.
//
// Initially written for Gateway 1.2(0) Connectivity Component
//
// =============================================================================

//hcfRoot.xbDEBUG.dump("defining LT");

//=======================================================================================================================================================
//=================================== The main tree object ==============================================================================================
//=======================================================================================================================================================
// The LazyTree object definition.
//
function LazyTree( /*string*/ serviceName,  /* Service name to call              REQUIRED */
                   /*frame*/  parentFrame,  /* Parent frame of tree display      REQUIRED */
                   /*string*/ XSLTransform, /* XSL stylesheet for data transform REQUIRED */
                   /*string*/ XSLDirectory, /* Directory of the stylesheet above - defaults to HCF standard */
                   /*string*/ imageDir,     /* Alternative images directory - defaults to extensions/lazy_tree/images */
                   /*integer*/openLevels,   /* Determines to what depth the tree will be opened when displayed - default is fully open */
                   /*boolean*/cache,        /* Determines whether to cache trees - defaults to true */
                   /*integer*/cachesize ){  /* How many trees to cache - default 5 */
                       
    if( serviceName != undefined ){ // serviceName may not be set in an initial call to this constructor.
    
        // The name of the service that LazyTree will call to build the tree - used for both the 
        // initial call and subsequent lazy calls.
        this.serviceName = serviceName;
        
        // The frame where the tree will be displayed. This makes the assumption that this code is loaded into the 
        // window where the tree is to be displayed.
        this.parentFrame = parentFrame;
        this.parentDocument = parentFrame.document;

        // Get the element in which the tree is to reside. 
        this.treeElement = this.parentDocument.getElementById( "lazy_tree_element" );
        
        // Get the main tree document. If the treeElement is an iframe this is the document belonging to the 
        // iframe; otherwise it is the parentDocument.
        ////hcfRoot.xbDEBUG.dump("**element: "+this.treeElement.tagName);
        var isFrame = false;
        if ( (this.treeElement.tagName == "IFRAME") ||
             (this.treeElement.tagName == "FRAME")    ){
            isFrame = true;     
            this.treeFrame    = this.parentFrame.lazy_tree;
            this.treeDocument = this.treeFrame.document;
            ////hcfRoot.xbDEBUG.dump("**using frame doc");
                        
            // Note that, if the tree element is a frame/iframe, we must
            // use its own body tag as the treeElement.
            this.treeElement = this.treeDocument.body;
        }
        else{
            this.treeFrame = parentFrame;
            this.treeDocument = this.parentDocument;
            //hcfRoot.xbDEBUG.dump("**using main doc");
        }
        
        // If the treeElement already contains a tree, get rid of it.
        var childs = this.treeElement.childNodes;
        for( var i = 0; i < childs.length; i++ ){
            if( childs[i].tagName == "DIV" ){
                this.treeElement.removeChild( childs[i] );
            }
        }

        this.hiddenTreeFrame = this.parentFrame.lazy_tree_hidden;
        this.hiddenTreeDocument = this.hiddenTreeFrame.document;

        //Hack the blank.html batch form so the target action is valid.
        this.scriptName = this.parentFrame.response.client_registry.script_name.value;
        var batch_form = hcfRoot.getBatchForm(this.hiddenTreeFrame);
        batch_form.action = this.scriptName;
        
        // The XSLT file to use for transformation of XSL.
        this.XSLTransform = XSLTransform;
        this.XSLDirectory = XSLDirectory; //The default of undefined will usually be ok - this is only needed for a stylesheet in an extension.
        
        // Internal variables.
        this.currentPopup = null; 
        this.popupToKill = null; 
        this.requestFired = false; // Flag to indicate if a request is in progress. To stop the user carrying out multiple parallel requests.
        this.treeData = 0; // The data structure which holds the main tree information.
        this.treeLet = 0; // The temporary data structure which holds the new tree information.
        this.dataSeparator = DEFAULT_DATASEPARATOR; // The criterion used to split the data field into an array.
        this.currentNode = null; // Stores a node reference in the main tree which has just been used for a lazy call.

        // Set up folderNode functions
        // Hack alert! Allows access to the subclassed nodeRenderer function from within folder nodes.
        var me = this;
        folderNode.prototype.renderText = function( span, label, ref, data, node ){ me.nodeRenderer( me.treeDocument, span, label, ref, data, node ); }  
        
        // Allows a click on a folder node to call a function in this object.
        folderNode.prototype.clickedNode = function( entity, event, node ){ me._clickedNode( event, node );}
        
        // Set up the correct images dir.
        var localImageDir = DEFAULT_IMAGES_DIR;
        if( imageDir ){
            localImageDir = imageDir;
        }
        else if( isFrame ){
            localImageDir = DEFAULT_FRAMES_IMAGES_DIR;
        }

        //hcfRoot.xbDEBUG.dump( "Image dir is: "+localImageDir );
        folderNode.prototype.imagesDir = localImageDir;

        // Set up the tree cache if it is required.
        this.TREECACHESIZE = DEFAULT_CACHESIZE;
        if( cache == undefined ){
            this.CACHE = true; // True if trees are to be cached: if so, a subsequent initial request with the same parameter set will show a cached tree rather then generating a new one.
        }
        else{
            this.CACHE = cache;
            if( cachesize != undefined ){
                this.TREECACHESIZE = cachesize;
            }
        }
        if( this.CACHE ){
            this.treeCache = new LazyTreeCache( this.serviceName, this.TREECACHESIZE ); // The cache object.
        }
        
        if( openLevels ){
            this.openLevels = openLevels;
        }
        else{
            this.openLevels = -1;
        }
        
        //hcfRoot.xbDEBUG.dump( "getInitialParams: " + this.getInitialParams );
        //hcfRoot.xbDEBUG.dump( "Lazy tree parameters: Service Name: " + serviceName );
        //hcfRoot.xbDEBUG.dump( "Initial Params: " );
        var params = this.getInitialParams();
    
        for( var paramName in params ){
           //hcfRoot.xbDEBUG.dump( paramName + ": " + params[ paramName ] );
        }
    
        hcfRoot._LazyTree = this; // Store this tree object at top level for access from the hidden IFrame.
    }
}

//hcfRoot.xbDEBUG.dump("setting protos for LT");

//*************************************************************************************************
//*************************************************************************************************
// ABSTRACT methods - MUST be subclassed.
//
LazyTree.prototype.getInitialParams = function(){ 
    alert( 'Abstract function: must be overridden' ); 
};

LazyTree.prototype.getLazyCallParams = function( label, reference, dataArray ){ 
    alert( 'Abstract function: must be overridden' ); 
};

//*************************************************************************************************
//*************************************************************************************************
// SUBCLASSABLE methods - CAN be subclassed.
//
LazyTree.prototype.nodeRenderer = function( document, textSpan, label, reference, dataArray ){
    
    var tree = this;

    textSpan.style.fontFamily = "Verdana"; 
    textSpan.style.fontSize   = "10px";
    textSpan.style.color      = "black";
    textSpan.style.cursor     = "default";
    
    // Set the status bar to the node's label.
    textSpan.onmouseover = function(){ tree.setStatusBar( label ); }  
    textSpan.onmouseout  = function(){ tree.setStatusBar( "" ); }  

    var txt = document.createTextNode( label ); 
    textSpan.appendChild( txt );
}


//*************************************************************************************************
//*************************************************************************************************
// INTERNAL methods - MUST NOT be subclassed.
//
// Public
LazyTree.prototype.execute = LazyTree_execute;
LazyTree.prototype.setStatusBar = LazyTree_setStatusBar; 
LazyTree.prototype.createPopup = LazyTree_createPopup;
LazyTree.prototype.destroyPopup = LazyTree_destroyPopup;

// Private
LazyTree.prototype._showInitialTree = LazyTree_showInitialTree;
LazyTree.prototype._makeServiceCall = LazyTree_makeServiceCall;
LazyTree.prototype._buildHiddenTree = LazyTree_buildHiddenTree;
LazyTree.prototype._buildTree = LazyTree_buildTree;
LazyTree.prototype._redrawTree = LazyTree_redrawTree;
LazyTree.prototype._transferData = LazyTree_transferData;
LazyTree.prototype._mergeAttributes = LazyTree_mergeAttributes;
LazyTree.prototype._timedPopupDelete = LazyTree_timedPopupDelete;
LazyTree.prototype._removePopup = LazyTree_removePopup;
LazyTree.prototype._clickedNode = LazyTree_clickedNode;


//**************************************************************************************************
// This handles a click on the node symbol i.e. to expand or collapse the 
// tree node. If child information is there, it is simply a matter of 
// changing the DIV display style between "block" and "none" (and 
// updating the data structure). However, it may need a lazy evaluation
// server request to be sent.
//
function LazyTree_clickedNode( event, node ){ 
	// Ignore clicks if we're waiting for a request to return
	if( !this.requestFired ){
		// This is called on a span tag in a table: we need to get the div parent
		var entity = node.entity.parentNode;
		
		while( entity.tagName != "DIV" ){
			entity = entity.parentNode;
		}
	
		if( node.hasAttrib( node.LAZYEVAL )){
            // Set the request flag to stop further requests
            this.requestFired = true;
            
            // Need to make a server call for the tree below here
            node.toggleAttrib( node.OPEN ); // Set it to open
            node.toggleAttrib( node.LAZYEVAL );
            var newDiv = this.treeDocument.createElement( "DIV" );
            this._mergeAttributes( newDiv, node.entity );
            //hcfRoot.xbDEBUG.dump("adding msg");
            node.addLoadingMesg( newDiv ); // Put in a loading message to inform the user what's going on
            //hcfRoot.xbDEBUG.dump("done");
            this.currentNode = node;
            
            // Make the lazy call to the server to get the new bit of tree.
            //hcfRoot.xbDEBUG.dump("making service call");
            this._makeServiceCall( this.getLazyCallParams( node.label, node.reference, node.dataArray ));
		}
		else{
			node.toggleAttrib( node.OPEN ); 
			//event.cancelBubble = true;
			
			// node attribs now give the new value for the node display
			if( node.hasAttrib( node.OPEN )) 
				node.expand();
			else 
				node.collapse();
	
			//event.cancelBubble = true;
		}
		
		node.setNodeImage();
	}
}


//**************************************************************************************************
// Popup handling.
//
function LazyTree_createPopup( entity, event, width ){
    // Remove any existing popup immediately.
    this._removePopup();

    // Determine the mouse position (browser-dependent, unfortunately).
    if( this.treeFrame.event ){
        // IE.
        mousePosX = this.treeFrame.event.clientX + this.treeFrame.document.body.scrollLeft;
        mousePosY = this.treeFrame.event.clientY + this.treeFrame.document.body.scrollTop;
    }
    else{
        // Moz.
        mousePosX = event.clientX + this.treeFrame.scrollX;
        mousePosY = event.clientY + this.treeFrame.scrollY;
    }
    
    // Create the new one.
    var me =this;
    this.currentPopup = new PopupMenu( this.treeDocument, entity, mousePosX, mousePosY, width, function(){ me._timedPopupDelete(); } );
    return this.currentPopup;
}

function LazyTree_destroyPopup(){
    this._timedPopupDelete();
}

function LazyTree_timedPopupDelete(){
    this.popupToKill = this.currentPopup;
    var me = this;
    setTimeout( function(){ me._removePopup(); }, 250 );
}

function LazyTree_removePopup(){
    if(( this.popupToKill != null )&&( this.popupToKill.mouseIsInside == false )){
        this.popupToKill.remove();
        this.popupToKill = null;
    }
}

//**************************************************************************************************
// Set the status bar.
//
function LazyTree_setStatusBar( label ){
    this.parentFrame.status = label;
}

//**************************************************************************************************
// The initial call which retrieves the first tree from the server.
//
function LazyTree_execute(){
    //hcfRoot.xbDEBUG.dump("executing");
    // Build the initial tree.
    this._showInitialTree( this.getInitialParams());
}

function LazyTree_showInitialTree( initialParams ){
    
   //hcfRoot.xbDEBUG.dump("showing initial tree");
    
    // Check if there's a tree in the cache for this object. If so, display it and don't do the request.
    var cacheRec = undefined;
    if( this.CACHE ){ 
        cacheRec = this.treeCache.getCachedRecord( initialParams );
    }
    if( cacheRec != undefined ){
        this.treeLet = cacheRec.treeData;

       //hcfRoot.xbDEBUG.dump("BUILDING CACHED TREE");
        
        this._buildTree();
    }
    else{
        this._makeServiceCall( initialParams );
    }
}

//**************************************************************************************************
// A generic function to make a service call.
//
function LazyTree_makeServiceCall( params ){
    this.requestFired = true;

    hcfRoot.header.loadServicePage( this.serviceName, 
                                    params.asParameters(), 
                                    this.hiddenTreeFrame, 
                                    // no panel header as target is hidden frame
                                    undefined,              
                                    this.XSLTransform,
                                    this.XSLDirectory );

}

//**************************************************************************************************
// Build the "treelet" returned by the server: this is done in the hidden frame.
//
function LazyTree_buildHiddenTree(){
    // ****Stop execution here if you want to see the generateTree() JavaScript function.****
    //return;
    
	// First check if the xslt has produced an error form
	var errorCondition = hcfRoot.getForm( this.hiddenTreeFrame, '', "errorForm" );

    //hcfRoot.xbDEBUG.dump("building tree");
	
	if( errorCondition ){
		// Report the error in a dialog as this frame is invisible
		alert(WMTERROR+"\n"+MESSAGE+errorCondition.message.value+"\n"+SEVERITY+errorCondition.severity.value+"\n"+TYPE+errorCondition.type.value);

		// Get rid of the "loading..." element etc. in the main tree (if there is one)
        if( this.currentNode ){
            this.currentNode.reset();
        }

		// Unset the request flag
		this.requestFired = false;
    }
    else{
		// Build the data representation of the new tree using the JavaScript generated function.
        this.treeLet = this.hiddenTreeFrame.generateTree( SCRIPT_LOCATION );
        
        // Open the new tree down to the appropriate level.
        this.treeLet.openToInitialLevel( this.openLevels );

        this._buildTree();
    }
    
}
        
function LazyTree_buildTree(){        

    // Get the left-side image HTML (this is what displays the vertical lines and spaces) from the main tree if there is one.
    var lsImg = "";
    if( this.currentNode ){ 
        lsImg = this.currentNode.getLeftImage();
    }
    
    // Redraw the new tree in the hidden window
    this._redrawTree( this.treeLet, lsImg );
    
    // If this is explorer, then do the tree merge straight away. If it is
    // Mozilla/Netscape, wait a bit for the hidden tree to render.
    // TODO: this is not an elegant solution to the problem - is there
    // another?
    var test = this.treeDocument.createElement( "DIV" );
    if( test.mergeAttributes ){
        this._transferData();
    }
    else{
        var me = this;
        setTimeout( function(){ me._transferData(); }, 500);
    }
}

//**************************************************************************************************
// Replaces the current contents of the given document with a new tree,
// built using the contents of the given tree data structure
//
function LazyTree_redrawTree( newTree, lsImg ){

    // Remove the window's onload event handler as it causes infinite recursion 
    // on Mozilla when re-run by doc.close() below 
    this.hiddenTreeFrame.onload = function(){};
    
    var doc = this.hiddenTreeDocument;
    var batch_form = hcfRoot.getBatchForm(this.hiddenTreeFrame).innerHTML;
    
    doc.clear();
	doc.write("<html><head>");
	doc.write("<link rel=\"stylesheet\" href=\"../styles/lazy_tree.css\" type=\"text/css\" />");
	doc.write("</head><body STYLE=\"background-color: #FFFFFF;\">");
    if( newTree != null ){
        newTree.redraw( doc, 0, true, lsImg );
    }
    
    // Add the batch form back in so that it can be used for future requests
    // (seems only to be necessary on IE)
    doc.write('<form method="post" name="BatchForm" action="');
    doc.write( this.scriptName );
    doc.write('">');

    doc.write(batch_form);
    doc.write("</form>");
    
	doc.write("</body></html>")	;
	doc.close();
}

//**************************************************************************************************
// Transfer data from the hidden tree to the visible one
//
function LazyTree_transferData(){
	
	var treeDoc = this.treeDocument;
	var rootElement = ( this.hiddenTreeDocument.getElementsByTagName( 'DIV' ))[0];

	// Get rid of the "loading..." element
    if( this.currentNode ){
        this.currentNode.removeLoadingEntity()
        
        // Add the new children to currentNode, thus completing the data tree.
        this.currentNode.removeChildren();
        for( var i = 0; i < this.treeLet.children.length; i++ ){
            this.currentNode.appendChild( this.treeLet.children[i] );
        }

        // Update the visible tree: step through the new tree root div's children and		
        // copy them to a new set of divs in the existing tree
        for(var i=0; i < rootElement.childNodes.length; i++) { 
            if(rootElement.childNodes[i].tagName == "DIV") {
                var newDiv = treeDoc.createElement("DIV"); // Create new element
                this._mergeAttributes( newDiv, rootElement.childNodes[i]); // Copy in old attributes
                this.currentNode.entity.appendChild(newDiv); // Add DIV to target element
                newDiv.innerHTML = rootElement.childNodes[i].innerHTML; // Substitute in copied HTML
            }
        }
    }
    else{
        // No current node means this is the first call to the service, not a lazy call. 
        // Therefore the root tree div must be created.
        this.currentNode = this.treeLet; 
        this.currentNode.entity = treeDoc.createElement( "DIV" );
        
        //hcfRoot.xbDEBUG.dump("new div: "+this.currentNode.entity.tagName);
        
        this.treeElement.appendChild( this.currentNode.entity );
        this._mergeAttributes( this.currentNode.entity, rootElement );
        this.currentNode.entity.innerHTML = rootElement.innerHTML;
        
        this.treeData = this.currentNode; 
    }
	
    // Traverse the new bit of tree, initialising the data and DOM structures.
    ////hcfRoot.xbDEBUG.dump("root entity: "+this.currentNode.entity.innerHTML );
    this.currentNode.traverseAndUpdate();
	
    if( this.CACHE ){ 
        this.treeCache.storeTreeState( this.getInitialParams(), this.treeData );
    }

	// Unset the request flag
	this.requestFired = false;
}

//**************************************************************************************************
// Perform an attributes merge. Unfortunately, the mergeAttributes
// method only exists on IE so we have to fake it for Netscape & Mozilla.
// NB the attributes array is very browser-specific, so keep the 
// implementations split.
//
function LazyTree_mergeAttributes( entity, mergee ){
	if( entity.mergeAttributes ){
		// Explorer therefore easy.
		entity.mergeAttributes( mergee )
	}
	else{
		// Navigator/Mozilla
		var attrs = mergee.attributes;
		for( var i = 0; i < attrs.length; i++ ){
			entity.setAttribute( attrs[i].name, attrs[i].value );
		}
	}
}


//=======================================================================================================================================================
//============================ folderNode Object ========================================================================================================
//=======================================================================================================================================================

//**************************************************************************************************
// Most of the tree functionality is in this object.
//
function folderNode( label, reference, data ){
    this.parent = null;

    // Remove entity references from the label text
    this.label = label.replace( this.regexGT, ">" );
    this.label = this.label.replace( this.regexLT, "<" );
    this.label = this.label.replace( this.regexAMP, "&" );
    
    this.reference = reference;
    this.dataArray = data.split( this.dataSeparator );
    
    this.entity = null; // The entity in the DOM which represents this node.
    this.loadingEntity = null; // The "loading..." entity added when the lazy step is called.

    // These are node attributes, stored in a bitset
    var bits = 1;
    this.LASTNODE       = bits;			// This is the last node in a list
    this.HASCHILDREN    = bits << 1;	// This node has children	
    this.LAZYEVAL       = bits << 2;	// This node will need a server call to get the tree below it
    this.OPEN           = bits << 3;	// This node is open (i.e. its children are visible)
    this.VISIBLE        = bits << 4;	// This node is visible
    this.FOLDEROPEN     = bits << 5;	// The folder icon for this node is open
    
    this.attributes = 0;
    
    // This stores the node's child nodes.
    this.children = new Array(0);
}

folderNode.prototype.regexLT = /\&lt\;/g;
folderNode.prototype.regexGT = /\&gt\;/g;
folderNode.prototype.regexAMP = /\&amp\;/g;

folderNode.prototype.reset = folderNode_reset;
folderNode.prototype.removeLoadingEntity = folderNode_removeLoadingEntity;
folderNode.prototype.setNodeImage = folderNode_setNodeImage;
folderNode.prototype.getNodeImg = folderNode_getNodeImg;
folderNode.prototype.getFolderImg = folderNode_getFolderImg;
folderNode.prototype.hasAttrib = folderNode_hasAttrib;
folderNode.prototype.toggleAttrib = folderNode_toggleAttrib;
folderNode.prototype.setAttrib = folderNode_setAttrib;
folderNode.prototype.openToInitialLevel = folderNode_openToInitialLevel;
folderNode.prototype.getLeftImage = folderNode_getLeftImage;
folderNode.prototype.redraw = folderNode_redraw;
folderNode.prototype.appendChild = folderNode_appendChild;
folderNode.prototype.removeChildren = folderNode_removeChildren;
folderNode.prototype.traverseAndUpdate = folderNode_traverseAndUpdate;
folderNode.prototype.addLoadingMesg = folderNode_addLoadingMesg;
folderNode.prototype.expand = folderNode_expand;
folderNode.prototype.collapse = folderNode_collapse;

//**************************************************************************************************
// Open the display node
//
function folderNode_expand() {
	var j = 0;
	
  	for(var i=0; i < this.entity.childNodes.length; i++) { // Get all the children of the Div tag
	
    	if(this.entity.childNodes[i].tagName == "DIV") {
			// Set the displayed values
      		this.entity.childNodes[i].style.display = "block";
		
			// Set the stored values
			this.children[j].setAttrib( this.VISIBLE ); // The entity should be displayed
			j++;
    	}
  	}
}

//**************************************************************************************************
// Close the display node
//
function folderNode_collapse() {
  	// collapse and hide children
	var j = 0;
	
  	for(var i=0; i < this.entity.childNodes.length; i++) {
      	if(this.entity.childNodes[i].tagName == "DIV") {
			// Set the displayed values
			this.entity.childNodes[i].style.display = "none";
		
			// Set the stored values
			this.children[j].toggleAttrib( this.VISIBLE ); // The entity should be hidden
            j++;
    	}
	}
}

//**************************************************************************************************
// Adds a display node to let the user know that a lazy evaluation
// server call is being carried out.
//
function folderNode_addLoadingMesg( newDiv ){
    var noOfChildren = this.children[0].dataArray[0];
    
    // If the data array contains a number, this is the number of children loading
    // so we can tell the user. Otherwise just say "loading".
    var itemStr = LOADING;
    if( noOfChildren != null && noOfChildren != "" ){
        itemStr = ITEMS; 
        if( noOfChildren == 1 ){
            itemStr = ITEM;
        }
        itemStr = LOADING + " " + noOfChildren + " "+itemStr;
    }
	var lsImg = this.getLeftImage();
	var iHtml = "";
	iHtml = "<span id=\"loadSpan\">";
	iHtml = iHtml + "<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\"><tr><td valign=\"middle\" nowrap=\"true\">";
	iHtml = iHtml + lsImg + "<img src=\""+this.imagesDir+"lastnode.gif\"></td><td valign=\"middle\">";
	iHtml = iHtml + "<img border=\"0\" id=\"image\" SRC=\""+this.imagesDir+"closedfolder.gif\"></td><td valign=\"middle\">"
	iHtml = iHtml + "<span id=\"loadSpan\" STYLE=\"font-family: Verdana; font-size: 10px; color: black;\">";
	iHtml = iHtml + itemStr+"<img border=\"0\" src=\""+this.imagesDir+"dots.gif\">";
	iHtml = iHtml + "</span></td></tr></table></span>";
	this.entity.appendChild( newDiv );
	newDiv.innerHTML = iHtml;
	this.loadingEntity = newDiv;
}

//**************************************************************************************************
// Scans the data and DOM trees, linking the two together and calling the function which renders
// the node text.
//
function folderNode_traverseAndUpdate(){
    // We assume that the initial call to this function is on a node which has
    // a valid entity.
    var node = this;
    var divCount = 0;
    
    //hcfRoot.xbDEBUG.dump("TRAVERSE: "+this.label);
    
    var tags = this.entity.childNodes;
    for( var i = 0; i < tags.length; i++ ){
        if( tags[i].tagName == "TABLE" ){ // The TABLE tag under the DIV entity.
            var spans = tags[i].getElementsByTagName( "SPAN" );
            for( var j = 0; j < spans.length; j++){
                if(( spans[j].id == "nodeSpan" )||( spans[j].id == "folderSpan" )){
                    spans[j].onclick = function(event) { 
                        event = event || window.event; // Moz vs IE event models
                        node.clickedNode( this, event, node ); 
                    } // "this" will be the node entity in scope.
                    spans[j].style.cursor = "pointer";
                }
                else if( spans[j].id == "textSpan" ){
                    // If this span has children already then remove them.
                    spans[j].innerHTML = "";
                        
                    // Render the node text if there isn't already some.
                    this.renderText( spans[j], this.label, this.reference, this.dataArray, this );
                }
            }
        }
        else if(( this.children.length > 0 )&&( tags[i].tagName == "DIV" )){
            this.children[ divCount ].entity = tags[i];
            this.children[ divCount ].traverseAndUpdate();
            divCount++;
		}
    }
}

//**************************************************************************************************
// Deals with node children.
//
function folderNode_appendChild( child ){
    child.parent = this;
    this.children.push( child );
}

function folderNode_removeChildren(){
    this.children = new Array(0);
}

//**************************************************************************************************
// Gets the left-side image from the display node. This is the 
// images (or set of images) which provide the vertical lines and spaces
// to the left of the displayed tree node.
//
function folderNode_getLeftImage(){
	var imgs = this.entity.getElementsByTagName( "IMG" );
	var j = 0;
	var lsImg = "";
	while( (imgs[j]).getAttribute('id') != "nodeImage" ){
		lsImg = lsImg + "<img src=\"" +(imgs[j]).getAttribute('src') + "\">";
		j++;
	}
	var udf;
	if(( this.entity.nextSibling != udf )&&( this.entity.nextSibling.tagName == "DIV" )){
		lsImg = lsImg + "<img src=\""+this.imagesDir+"vertline.gif\">";
	}
	else{
		lsImg = lsImg + "<img src=\""+this.imagesDir+"blank.gif\">";
	}
	return lsImg;
}

//**************************************************************************************************
// Open up the tree data structure down to the level specified below the 
// given starting point.
//
function folderNode_openToInitialLevel( levels ){

	this.setAttrib( this.VISIBLE ); 

	// If this node has children, set it to open and recurse.
	if(( this.children.length > 0 )&&( this.children[0].label != "has_children" )&& ( levels != 0 )){
		this.setAttrib( this.OPEN );
		
        ////hcfRoot.xbDEBUG.dump("opening - ref: "+this.children[0].reference+" dataArray[0]: "+this.children[0].dataArray[0]);
        
        if( levels != 0 ){
            for( var i = 0; i < this.children.length; i++ ){
                this.children[i].openToInitialLevel( levels-1 );
            }
        }
	}	
}

//**************************************************************************************************
// After an error in fetch of a treelet, return the lazy node to its original state.
//
function folderNode_reset(){
    this.removeLoadingEntity();
    
    // Reset the tree data to its former state
    this.toggleAttrib( this.OPEN ); 
    this.toggleAttrib( this.LAZYEVAL );
    
    // Set the node image back to the right state
    this.setNodeImage();
}

//**************************************************************************************************
// Remove the "Loading..." entity.
//
function folderNode_removeLoadingEntity(){
    this.entity.removeChild( this.loadingEntity );
}

//**************************************************************************************************
// Attribute handling.
//
function folderNode_hasAttrib( attribute ){
	var rval = 0; // False
	if(( this.attributes & attribute ) > 0 ){
		rval = 1; // True
	}
	return rval;
}

function folderNode_setAttrib( attribute ){
	this.attributes = (this.attributes | attribute); // OR
}
function folderNode_toggleAttrib( attribute ){
	this.attributes = (this.attributes ^ attribute); // XOR
}

//**************************************************************************************************
// Given a display entity and its corresponding data node, insert the
// correct tree image. This is the node expand/collapse icon rather than
// the folder icon.
//
function folderNode_setNodeImage(){
	var aImages = this.entity.getElementsByTagName("IMG");
	for( var i=0; i < aImages.length; i++ ){
		if( aImages[i].id == "nodeImage" ){
			aImages[i].setAttribute('src', this.getNodeImg());
			i=aImages.length; //quit the for loop
		}
	}
	for( var i=0; i < aImages.length; i++ ){
		if( aImages[i].id == "image" ){
			aImages[i].setAttribute('src', this.getFolderImg());
			i=aImages.length; //quit the for loop
		}
	}
}

//**************************************************************************************************
// Get the node image to use, based on the attributes set for the node.
//
function folderNode_getNodeImg(){
	var nodeImg = "node.gif"; 
	if( this.hasAttrib( this.LASTNODE )){
		nodeImg = "last" + nodeImg; // Last node
	}
	if( this.hasAttrib( this.HASCHILDREN )){
		nodeImg = "c_" + nodeImg; // Has children

		if( this.hasAttrib( this.LAZYEVAL )){
			nodeImg = "r" + nodeImg; // Needs lazy eval
		}
		if( this.hasAttrib( this.OPEN )){
			nodeImg = "open_" + nodeImg; // Node is open
		}
		else{
			nodeImg = "closed_" + nodeImg; // Node is closed
		}
	}
	nodeImg = this.imagesDir + nodeImg;
	return nodeImg;
}
	
//**************************************************************************************************
// Get the folder image to use, based on the attributes set for the node.
//
function folderNode_getFolderImg(){
	var nodeImg = "blob.gif"; 

	if( this.hasAttrib( this.HASCHILDREN )){

		if( this.hasAttrib( this.OPEN )){
			nodeImg = "openfolder.gif"; // Node is open
		}
		else{
			nodeImg = "closedfolder.gif"; // Node is closed
		}
	}
	nodeImg = this.imagesDir + nodeImg;
	return nodeImg;
}	

//**************************************************************************************************
// This function traverses the tree data (the foldersNode parameter)
// generating the HTML tree output. It is a recursive function. Note that
// it changes the LASTNODE, LAZYEVAL and HASCHILDREN attributes of the data 
// structure as it traverses. When a request is made to the server, one more level is 
// requested than the user asked for; this allows the HASCHILDREN attribute
// to be set correctly. This renders direct into HTML because it is impossible currently
// to produce exactly similar results in Moz/IE any other way.
//
function folderNode_redraw( doc, level, lastNode, leftSide )
{	
	var lineImg = "vertline.gif";
	var recurse = "false";
	
	// This bit of code decides which node and link images to display, 
	// dependent on whether this node has children, and whether this
	// node is the last in a list. It also checks if this recursion level
	// is the last required by the request, and sets a variable to stop recursion
	// if this is the case.
	if( lastNode ){
		// This is the last node in a list
		this.setAttrib( this.LASTNODE );
		lineImg = "blank.gif";
	}
	
	if( this.children.length > 0 ){
		// This node has children
		this.setAttrib( this.HASCHILDREN );
	
		var firstChild = this.children[0];
		if( firstChild.label == "has_children" ){
			// This is the children indicator node: set the lazy eval bit.
			this.setAttrib( this.LAZYEVAL );
		}
		else{
			// Set recurse to true.
			recurse = "true";
		}
	}
	
	// This bit of code writes out the HTML for the node, all contained within
	// a DIV tag. Note that there are two span links: one to expand and collapse the tree, and
	// one to request details for the listed database URN.
	
	// Write the DIV tag, and decide whether it is currently displayed or not
	doc.write("<div onselectstart=\"return false\" ondragstart=\"return false\" style=\"");
	if( this.hasAttrib( this.VISIBLE )) {
		doc.write("display: block;\">");
    }
	else{ 
		doc.write("display: none;\">");
    }
	
	// Table enclosing the images
    doc.write("<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\"><tr><td valign=\"middle\" nowrap=\"true\">" ); 

	// Put a span around the node image NODE SPAN
    doc.write("<span id=\"nodeSpan\">"); 
    // NODE SPAN
    
	// Write vertical lines if necessary
	doc.write(leftSide);
	
	if (level > 0){
		doc.write("<img border=\"0\" id=\"nodeImage\" SRC=\"" + this.getNodeImg() + "\">");
		leftSide = leftSide + "<img src='"+this.imagesDir+lineImg + "'>" ;
	}

	doc.write("<img border=\"0\" id=\"image\" SRC=\"" + this.getFolderImg() + "\">");
	doc.write("</img></span></td><td valign=\"middle\" nowrap=\"true\">");

    // This is the FOLDER SPAN
    doc.write("<span id=\"folderSpan\">");
    // FOLDER SPAN
    
	doc.write("</td><td valign=\"middle\" nowrap=\"true\">");
	
    // This is the TEXT SPAN
    doc.write("<span id=\"textSpan\">");
	doc.write("</span>");
    // TEXT SPAN
    
    doc.write("</td></tr></table>");
		
	// Recurse for each child of this node
	if( recurse == "true" ){
		var isEndNode = false;
		for( var i = 0; i < this.children.length; i = i + 1 ){
			if( i == this.children.length-1){
				isEndNode = true;
			}	
			this.children[i].redraw( doc, level+1, isEndNode, leftSide );
		}
	}

	doc.write("</div>\n");
}


//=======================================================================================================================================================
//==================================== A record for the cache ===========================================================================================
//=======================================================================================================================================================
function LazyTreeCacheRecord( service, paramSet, treeData ){
    this.treeData = treeData;
    this.paramSet = paramSet;
    this.key = paramSet.makeHash( service );
}


//=======================================================================================================================================================
//===================================== The top-level tree cache ========================================================================================
//=======================================================================================================================================================

//**************************************************************************************************
// The cache stores tree structures at top level for re-use. Note that this is global: if the cache
// size is set to 5, 5 lazy trees in total will be cached.
//
function LazyTreeCache( service, treeCacheSize ){
    this.cacheSize = treeCacheSize;
    this.service = service;
}

LazyTreeCache.prototype.storeTreeState = LazyTreeCache_storeTreeState; 
LazyTreeCache.prototype.getCache = LazyTreeCache_getCache;
LazyTreeCache.prototype.getCachedRecord = LazyTreeCache_getCachedRecord;
LazyTreeCache.prototype.storeCacheRecord = LazyTreeCache_storeCacheRecord;

function LazyTreeCache_storeTreeState( paramSet, treeData ){
    
    //hcfRoot.xbDEBUG.dump( "store tree state: "+paramSet);
 
	// Check if there is already a cache record for this tree, and if so, replace it.
	// Otherwise, create a new cache record.
	var currCacheRec = this.getCachedRecord( paramSet )
	if( currCacheRec != undefined ){
		currCacheRec.treeData = treeData;
	}
	else{
		currCacheRec = new LazyTreeCacheRecord( this.service, paramSet, treeData );
	}
	
	this.storeCacheRecord( currCacheRec );
}

//**************************************************************************************************
// Gets the tree cache from top. Creates a new one if none exists.
//
function LazyTreeCache_getCache(){

    //hcfRoot.xbDEBUG.dump("get cache" );

	var treeCacheArray = hcfRoot._LazyTreeCache; // TODO this is a hard code of hcfRoot and the cache name. Acceptable?
	if( treeCacheArray == undefined ){
       //hcfRoot.xbDEBUG.dump("Making new cache object at top level.");
		treeCacheArray = new Array();
		for( i = 0; i < this.cacheSize; i++ ){
			treeCacheArray[i] = "";
		}
		top._LazyTreeCache = treeCacheArray;
	}
	return treeCacheArray;
}	

//**************************************************************************************************
// Get a tree cache record for the current tree, or return undefined if one
// doesn't exist. Note that this removes the item from the cache.
//
function LazyTreeCache_getCachedRecord( paramSet ){
    
   //hcfRoot.xbDEBUG.dump("get cached record: " + paramSet.makeHash(this.service));
    
	var result = undefined;
	var cache = this.getCache();
	var key = paramSet.makeHash( this.service );
	for( var i = 0; i < cache.length; i++ ){
		var rec = cache[i];
		
		if( rec != undefined ){
			if( rec.key == key ){
				result = rec;
				cache[i] = undefined;
				//hcfRoot.xbDEBUG.dump("found cached rec: "+result)
			}
			if( result != undefined ){
				if( i < (cache.length-1)){
					cache[i] = cache[i+1];
				}
				else{
					cache[i] = undefined;
                    cache.length--;
                   //hcfRoot.xbDEBUG.dump("cache rec deleted: "+cache.length)
                    
				}
			}
		}
	}

    //hcfRoot.xbDEBUG.dump("returning cached rec" );

	return result;
}

//**************************************************************************************************
// Insert a record into the tree cache.
//
function LazyTreeCache_storeCacheRecord( record ){
	
	var cache = this.getCache();
	
	// Shuffle everything down, to make room for the new record.
   //hcfRoot.xbDEBUG.dump("Storing cache record. Size: "+cache.length+" "+this.cacheSize);
    var limit = cache.length-1;
    if( cache.length == this.cacheSize ){
        // Let the last item fall off.
        limit = this.cacheSize-2;
    }
	for( var i = limit; i >= 0; i-- ){
       //hcfRoot.xbDEBUG.dump("shuffling up");
		cache[i+1] = cache[i];
	}

	cache[0] = record;

	// Store the whole cache at top level.
	hcfRoot._LazyTreeCache = cache; // TODO see other instance
}



//=======================================================================================================================================================
//================================== The parameter set object ===========================================================================================
//=======================================================================================================================================================

//**************************************************************************************************
// The parameter set object stores and manipulates the user parameters to be sent to the server.
//
function ParameterSet(){
    this.params = new Object();
}

ParameterSet.prototype.add = ParameterSet_add;
ParameterSet.prototype.asArray = ParameterSet_asArray;
ParameterSet.prototype.valuesAsArray = ParameterSet_valuesAsArray;
ParameterSet.prototype.equals = ParameterSet_equals;
ParameterSet.prototype.size = ParameterSet_size;
ParameterSet.prototype.add = ParameterSet_add;
ParameterSet.prototype.makeHash = ParameterSet_makeHash;
ParameterSet.prototype.asParameters = ParameterSet_asParameters;

//**************************************************************************************************
// Makes a key for the cache by concatenating all the parameter values.
//
function ParameterSet_makeHash( service ){

    var array = ( this.valuesAsArray()).sort();
    var key = array.join();
	return service+key;
}

function ParameterSet_add( param, val ){
    this.params[ param ] = val;
}

function ParameterSet_asArray(){
    paramArray = new Array();
    
    for( var paramName in this.params ){
        paramArray.push( paramName );
        paramArray.push( this.params[ paramName ] );
    }

    return paramArray;
}

function ParameterSet_valuesAsArray(){

    paramArray = new Array();
    
    for( var paramName in this.params ){
        paramArray.push( this.params[ paramName ] );
    }

    return paramArray;
}

function ParameterSet_equals( anotherParamSet ){
    var result = true;
    var size = 0;
    var otherSize = anotherParamSet.size();
    
    for( var paramName in this.params ){
        if( this.params[ paramName ] != anotherParamSet[ paramName ] ){
            result = false;
        }
        size = size + 1;
    }
    if( size != otherSize ){
        result = false;
    }
    
    return result;
}

function ParameterSet_size(){
    var size = 0;
    
    for( var paramName in this.params ){
        size = size + 1;
    }
    
    return size;
 }
 

function ParameterSet_asParameters() {
     var params = new hcfRoot.Parameters;
     
     for (var paramName in this.params) {
         params.registerParameter(paramName, this.params[paramName]);
     }
     
     return params;
}     
 
//=======================================================================================================================================================
//================================ The popup menu object ================================================================================================
//=======================================================================================================================================================

function PopupMenu( document, entity, mousePosX, mousePosY, width, deleteFn ){
    this.deleteFn = deleteFn;
    this.width = width;
    this.mousePosX = mousePosX;
    this.mousePosY = mousePosY;
    this.doc = document;
    this.sourceElement = entity;
    this.mouseIsInside = false;
    this.entries = 0;
    this.fns = new Array();
    
    // Create the popup DIV.
    this.node = this.doc.createElement( "DIV" );
    this.node.className = "popupMenu";
    this.setStaticStyles();

    // Create event handlers.
    var menu = this;
    this.node.onmouseout = function(){ menu.deleteFn(); }
    
    // Position according to mouse location.
    this.node.style.top = this.mousePosY;
    this.node.style.left = this.mousePosX + 5;
    
    // Add the popup to the DOM.
    this.doc.body.appendChild( this.node );
 
}

// This is the only method expected to be called by a user.
PopupMenu.prototype.createPopupEntry = PopupMenu_createPopupEntry;

PopupMenu.prototype.executeFunction = PopupMenu_executeFunction;
PopupMenu.prototype.remove = PopupMenu_remove;
PopupMenu.prototype.setStaticStyles = PopupMenu_setStaticStyles;
    
function PopupMenu_executeFunction( idx ){
    var fn = this.fns[idx];

    //hcfRoot.xbDEBUG.dump("executing: "+fn );

    fn();
}

function PopupMenu_createPopupEntry( text, fn ){
    this.fns[ this.entries ] = fn;
    var entry = new PopupEntry( this, text, this.entries );
    this.entries = this.entries + 1;
}

function PopupMenu_remove(){
    //hcfRoot.xbDEBUG.dump("removing");    
    this.doc.body.removeChild( this.node );
}

// NB this could all be put in a css file. Use class "div.popupMenu".
function PopupMenu_setStaticStyles(){
    this.node.style.position = "absolute";
    this.node.style.zIndex = "100";
    this.node.style.width = this.width;
}



//=======================================================================================================================================================
//================================= Entry object for the popup menu =====================================================================================
//=======================================================================================================================================================
function PopupEntry( menu, text, indexNo ){
    this.ENTRYHEIGHT = 13;
    this.node = menu.doc.createElement( "DIV" );
    this.node.className = "popupEntry";
    this.node.idx = indexNo;
    this.setStaticStyles();
    
    this.node.appendChild( menu.doc.createTextNode( text ));
    
    var pos = menu.entries * this.ENTRYHEIGHT;
    this.node.style.top = pos;
    
    this.node.onmouseover = function(){
                                //hcfRoot.xbDEBUG.dump("in: "+menu);
                               menu.mouseIsInside = true;
                               this.style.color="#E7E7E7";
                               this.style.backgroundColor="black";
                                //hcfRoot.xbDEBUG.dump("done in");
                            }
                                
    this.node.onmouseout = function(){
                                //hcfRoot.xbDEBUG.dump("oot: "+menu);
                               menu.mouseIsInside = false;
                               this.style.color="black";
                               this.style.backgroundColor="#E7E7E7";
                                //hcfRoot.xbDEBUG.dump("done out");
                           }
 
    this.node.onclick = function(){
                            menu.executeFunction( this.idx );
                        }
 
    menu.node.appendChild( this.node );
}

PopupEntry.prototype.setStaticStyles = PopupEntry_setStaticStyles;
    
// NB this could all be put in a css file. Use class "div.popupEntry".
function PopupEntry_setStaticStyles(){
    this.node.style.position = "absolute";
    this.node.style.backgroundColor = "#E7E7E7";
    this.node.style.cursor = "hand"; // Works in IE5 but not Moz - can be worked around in css using {cursor: pointer; cursor: hand;}.
    this.node.style.fontFamily = "Verdana,Arial,Helvetica,sans-serif"; 
    this.node.style.fontSize = "10px"; 
    this.node.style.paddingLeft = "2px";
    this.node.style.paddingRight = "2px";
    this.node.style.color = "black";
    this.node.style.fontWeight = "bold";
    this.node.style.border = "1px solid black";
    this.node.style.zIndex = "200";
    this.node.style.width = "100%";
}



