

//---------------------------------------------------------------------------------------        
// The "base class" for a tree control that dynamically generates nodes on an 
// as-needed-basis and is used to provide an intuitive way to visualize 
// hierarchical data.
//---------------------------------------------------------------------------------------
function BaseTreeControl(
                    // The URL that is asynchronously accessed for data. 
                    asyncURL, 
                    
                    // The parameter name that is passed to the async URL along with a parameter value (uid).    
                    nodeKey,       
                    
                    // The DOM ID of the panel where this tree will be dynamically generated. 
                    panelID,       
                    
                    // This should be a name=value pair that is always passed to the async URL in order 
                    // to uniquely identify the tree. 
                    treeIdentity) {   

    //--------------------------------------------------------------    
	// Define member "constants".
	//--------------------------------------------------------------    
	
    // Node attribute names (note that these need to match XML attributes generated by server-side code!).
    this.ATTR_ACCESSION = "ac";
	this.ATTR_CHILDCOUNT = "cc";
	this.ATTR_CHILDCOUNTTEXT = "cct";
	this.ATTR_CHILDDATAPOPULATED = "cdp";
	this.ATTR_DEFINITION = "def";
	this.ATTR_DEPTH = "d";
	this.ATTR_DISPLAYNAME = "fn";  
	this.ATTR_HELPTEXT = "ht";
	this.ATTR_INFOURL = "iurl";
	this.ATTR_ISEXPANDED = "ie";
	this.ATTR_ISHIDDEN = "ih";
	this.ATTR_ISREFERENCE = "ir";
	this.ATTR_ISSELECTED = "is";
	this.ATTR_NUMCHILDRENSELECTED = "ncs";
	this.ATTR_PARENTUID = "puid"; 
	this.ATTR_STRUCTURL = "surl";
	this.ATTR_SYNONYMS = "syn"; 
	this.ATTR_TYPE = "t";  
	this.ATTR_UID = "uid"; 
	this.ATTR_URL = "url";
    
   
    // Format types related to node states.
    this.FORMAT_COLLAPSED = 0;
    this.FORMAT_DESELECTED = 1;
    this.FORMAT_EXPANDED = 2;
    this.FORMAT_HIGHLIGHTED = 3;
    this.FORMAT_SELECTED = 4;
    
    // Tree mode options.
    this.MODE_CHECK_ANY = 0;
    this.MODE_CHECK_LEAVES = 1;
    this.MODE_SELECT_LEAVES = 2;
    
 
    
    //--------------------------------------------------------------    
	// Define member variables.
	//--------------------------------------------------------------    
	
	// "Self" fixes loss-of-scope problem in inner functions.		
	var self = this; 
	
    // The URL to asynchronously access for data updates.
    this._asyncURL = "";
    
    // A reference to a currently open dialog (if one is currently open).
	this._currentDialog = null;
	
    // Custom events used by the tree.
    // http://developer.yahoo.com/yui/event/#customevent
    this._event_noDataAvailable = new YAHOO.util.CustomEvent("noDataAvailable", self);
    this._event_treeIsPopulated = new YAHOO.util.CustomEvent("treeIsPopulated", self);
    
    // Callback functions that can be registered to particular types of nodes.
    this._childDataCallbacks = new Array();
    this._formatNodeCallbacks = new Array();
    this._nodeDisplayCallbacks = new Array();
     
    // The labels to use for singular and plural children.
    this._label_child = "child"; 
    this._label_children = "children"; 
    
    // The left side horizontal offset of a node under another node.
    this._leftSideNodeOffset = 25;
    
    // By default, any node can be checked.
    this._mode = self.MODE_SELECT_LEAVES;
    
    // The parameter name that is passed to the async URL along with a parameter value (uid). 
    this._nodeKey = "";
    
    // The name of this control (mostly used when specifying CSS styles/classnames).
    this._objectName = "TreeControl";
    
    // The DOM ID of the panel where this tree will be dynamically generated. 
    this._panelID = "";
    
    // A pointer to the "parent" panel Element.
    this._panelElement = null;
    
    
    // When the tree is to be automatically expanded to a particular node (which
    // is then selected) we will run an initial query which will ultimately populate
    // this queue Array with (an ordered list of) the uid's of the nodes that need 
    // to be selected. As each is selected, it is removed from the queue and the 
    // selection process continues until a node of type "target level to select" is
    // selected. At this point, the selection is complete and the tree fires a
    // "treeIsPopulated" event. 
    this._queue_of_uids_to_select = new Array();
    this._target_level_to_select = "";
    
    
    // The release year of the currently displayed taxonomy.
    this._releaseYear = "";
    
    // A unique list of selected uids.
    this._selectedNodes = new Array();
    
    // The source that determines what release of the taxonomy is displayed (Elsevier8, 2007, 2008, 2009).
    this._taxonomySource = "";
    
    // This should be a name=value pair that is always passed to the async URL in 
    // order to uniquely identify the tree. 
    this._treeIdentity = "";
    
    
    //--------------------------------------------------------------    
	// Define member methods.
	//--------------------------------------------------------------    
    
    // Register a callback function to a certain type to replace the default way to retrieve child data.
    this.addChildDataCallback = function (nodeType_, callbackFunction_) {
    
        if (isEmpty(nodeType_)) { return self.displayError("Invalid node type","addChildDataCallback"); }
        if (typeof(callbackFunction_) != "function") { return self.displayError("Invalid callbackFunction","addChildDataCallback"); }
    
        self._childDataCallbacks[nodeType_] = callbackFunction_;
    };
    
    
    // Register a callback function to a certain type to replace the default formatting of a node
    // from selection, etc.
    this.addFormatNodeCallback = function (nodeType_, callbackFunction_) {
    
        if (isEmpty(nodeType_)) { return self.displayError("Invalid node type","addFormatNodeCallback"); }
        if (typeof(callbackFunction_) != "function") { return self.displayError("Invalid callbackFunction","addFormatNodeCallback"); }
    
        //var numCallbacks = self._formatNodeCallbacks.length;
        self._formatNodeCallbacks[nodeType_] = callbackFunction_;
    };
    
    
    // Register a callback function to a certain type to replace the default way to generate a node's display.
    this.addNodeDisplayCallback = function (nodeType_, callbackFunction_) {
    
        if (isEmpty(nodeType_)) { return self.displayError("Invalid node type","addNodeDisplayCallback"); }
        if (typeof(callbackFunction_) != "function") { return self.displayError("Invalid callbackFunction","addNodeDisplayCallback"); }
    
        self._nodeDisplayCallbacks[nodeType_] = callbackFunction_;
    };
    
    
    // Collapse the node.
    this.collapse = function (uid, nodeElement) {
    
        var childrenElement;
        var imageElement;
        var tableElement;
        var type;
        
        // Validate input parameters.
        if (isEmpty(uid)) {return self.displayError("Invalid uid","collapse");}
        if (nodeElement == null) {return self.displayError("Invalid nodeElement","collapse");}
        
        nodeElement.setAttribute(self.ATTR_ISEXPANDED, "false");
    
        // Specify the "collapsed" image.
        imageElement = document.getElementById(uid + "__plusminus");
        if (imageElement == null) {return self.displayError("Invalid imageElement","collapse");}
        imageElement.className = self._objectName + "_image_contracted";
        
        childrenElement = document.getElementById(uid + "__children");
        if (childrenElement == null) {return self.displayError("Invalid childrenElement","collapse");}
        
        childrenElement.className = self._objectName + "_hiddenChildren";
    };
    
   
    // Delete a tree control that has been populated and displayed.
    this.deleteControl = function () {
        if (self._panelElement.hasChildNodes()) {
            while (self._panelElement.childNodes.length >= 1 ) {
                self._panelElement.removeChild(self._panelElement.firstChild);       
            } 
        }
    };
    
    
    // Deselect all selected nodes.
    this.deselectAll = function () {
    
        var ctrlElement;
        var uid;
        var uidList = new Array();
        
        // Copy the selected nodes list (array).
        for (var s=0; s<self._selectedNodes.length; s++) {
            uid = self._selectedNodes[s];
            if (isEmpty(uid)) {return self.displayError("Invalid uid","deselectAll");} 
            uidList.push(uid);
        }
          
        for (var u=0; u<uidList.length; u++) {
            
            uid = uidList[u];
            if (isEmpty(uid)) {return self.displayError("Invalid uid","deselectAll");} 
            
            ctrlElement = document.getElementById(uid + "__selectCtrl");
            if (ctrlElement == null) {return self.displayError("Invalid ctrlElement","deselectAll");} 
            
            ctrlElement.checked = false;
            
            self.selectNode(ctrlElement);
        }
    };
    
    
    // An "overridden" version of the global display error function.
    this.displayError = function (message, functionName) {
        return global_displayError(message, functionName, self._objectName);
    };
    
    
    // Expand the node.
    this.expand = function (uid, nodeElement) {
    
        var childCount;
        var childData;
        var childDataCollection;
        var childDataPopulated;
        var childGenomeCount;
        var childType;
        var imageElement;
        var parentDepth;
        var tableElement;
        var type;
     
        // Validate input parameters.
        if (isEmpty(uid)) {return self.displayError("Invalid uid","expand");}
        if (nodeElement == null) {
            nodeElement = document.getElementById(uid);
            if (nodeElement == null) { return false; } 
        }
      
        // Update this node to be "expanded".
        nodeElement.setAttribute(self.ATTR_ISEXPANDED, "true");
   
        // Have the node's children already been populated?
        childDataPopulated = nodeElement.getAttribute(self.ATTR_CHILDDATAPOPULATED);
        if (isEmpty(childDataPopulated)) {childDataPopulated = "false";}
        
        // Update image to "expanded".
        imageElement = document.getElementById(uid + "__plusminus");
        if (imageElement == null) {return self.displayError("Invalid imageElement","expand");}
        imageElement.className = self._objectName + "_image_expanded";
        
        // Make child nodes visible.
        childrenElement = document.getElementById(uid + "__children");
        if (childrenElement == null) { return self.displayError("Invalid childrenElement","expand"); }
        childrenElement.className = self._objectName + "_visibleChildren";
        
        if (childDataPopulated.toUpperCase() == "FALSE") {
    
            // Has a callback function been specified for this type?
            var callback = self._childDataCallbacks[type];
            if (callback != null && typeof(callback) == "function") {
                callback(uid);
            } else {
                // Retrieve the data asynchronously.
                self.getChildNodes(uid);
            }
        }        
    };
    
    
    // Dynamically determine whether this node should be expanded or collapsed.
    this.expandOrCollapse = function (uid, forceExpand) {
    
        var isExpanded;
        var nodeElement;
 
        // Validate input parameters.
        if (isEmpty(uid)) {return self.displayError("Invalid uid","expandOrCollapse");}
        if (isEmpty(forceExpand)) {forceExpand = "false";}
        
        nodeElement = document.getElementById(uid);
        if (nodeElement == null) {return self.displayError("Invalid node Element","expandOrCollapse");}
        
        // Determine whether the node is currently expanded or collapsed (taking "force expand" into account).
        if (forceExpand.toUpperCase() == "TRUE") {
            isExpanded = "false";
        } else {
            isExpanded = nodeElement.getAttribute(self.ATTR_ISEXPANDED);
            if (isEmpty(isExpanded)) {isExpanded = "false";}
        }
      
        if (isExpanded.toUpperCase() == "TRUE") {
           
            // Collapse the expanded Element.
            self.collapse(uid, nodeElement);
            
        } else {
            
            // Expand the collapsed Element.
            self.expand(uid, nodeElement);
        }
    };
   
   
    // Modify the node's formatting.
    this.formatNode = function (action, type, uid) {
    
        var className = self._objectName + "_display_table ";
        var tableElement;
        
        if (isEmpty(type)) {return self.displayError("Invalid type","formatNode");} 
        if (typeof(action) != "number") {return self.displayError("Invalid action","formatNode");} 
        
        // Was a format node callback defined for this type?
        if (self._formatNodeCallbacks != null) {
            var formatCallback = self._formatNodeCallbacks[type];
            if (formatCallback != null && typeof(formatCallback) == "function") {
                formatCallback(action, type, uid);
                return true;
            }
        }
        
        switch (action) {
        
            case self.FORMAT_COLLAPSED: className += "collapsed" + type; break;
            case self.FORMAT_DESELECTED: className += "deselected" + type; break;
            case self.FORMAT_EXPANDED: className += "expanded" + type; break;
            case self.FORMAT_HIGHLIGHTED: className += "highlighted" + type; break;
            case self.FORMAT_SELECTED: className += "selected" + type; break;
            default: return self.displayError("Unknown action","formatNode"); 
        }
        
        tableElement = document.getElementById(uid + "__table");
        if (tableElement == null) {return self.displayError("Invalid tableElement","formatNode");} 

        tableElement.className = className;
    };
    
   
    // Generate the XHTML representation of the tree node and display it.
    this.generateDisplayNode = function (data) {
     
        /*
        Example XHTML that will be generated for a tree node:

        <div class="treeNode">
            <table cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td class="treeNode_image"></td>
                        <td class="treeNode_text">
                            <span class="treeNode_text_type">Type:</span>
                            <span class="treeNode_text_name">dmd test</span>
                        </td>
                        <td class="treeNode_other">(something else)</td>
                    </tr>
                </tbody>
            </table>
            <div class="treeNode_children"></div>
        </div>
        */
        
        var childCount;
        var childCountText;
        var definition;
        var depth;
        var displayName;
        var displayOffset;
        var displayType;
        var helpText;
        var infoURL;
        var isHidden;
        var isReference;
        var parentDepth;
        var parentElement;
        var parentUID;
        var structURL;
        var type;
        var uid;
        
        var treeNodeDIV;
        var treeNodeTABLE;
        var treeNodeTBODY;
        var treeNodeTR;
        var treeNode_childCountTD;
        var treeNode_imageTD;
        var treeNode_textTD;
        var treeNode_nodeCtrlsTD;
        var treeNode_textTypeSPAN;
        var treeNode_textNameSPAN;
        var treeNode_childrenDIV;
        
        // Initialize data values.
        childCount = 0;
        
        // Validate input parameter.
        if (data == null) {return self.displayError("Invalid data","generateDisplayNode");}
        
        // Get the uid from the data.
        uid = data[self.ATTR_UID];
        if (uid == "0") { return true; }
        if (isEmpty(uid)) {return self.displayError("Invalid uid in data","generateDisplayNode");}
       
        // If this node has already been added to the page there's no need to re-add it.
        var testElement = document.getElementById(uid);
        if (testElement != null) { return true; }
        
        // Get the parent UID from the data.
        parentUID = data[self.ATTR_PARENTUID];
        if (isEmpty(parentUID)) {
            parentUID = self._panelID;
        }
        
        // Get the parent Element.
        parentElement = document.getElementById(parentUID + "__children");
        if (parentElement == null) {
        
            // Use the root parent Element instead (the parent panel).
            parentElement = self._panelElement;
            if (parentElement == null) {return self.displayError("Invalid parentElement","generateDisplayNode");}
            
            // Default the parent node's depth to zero.
            parentDepth = 0;
            
            // Update the parent's depth attribute (possibly not necessary?).
            parentElement.setAttribute(self.ATTR_DEPTH, parentDepth);
            
        } else {
        
            // Get the parent node depth.
            parentDepth = parentElement.getAttribute(self.ATTR_DEPTH);
            
            // Validate the depth.
            if (typeof(parentDepth) != "number") {
                if (isEmpty(parentDepth)) { 
                    parentDepth = 0; 
                    
                    // Update the parent's depth attribute.
                    parentElement.setAttribute(self.ATTR_DEPTH, parentDepth);  
                      
                } else if (!isInteger(parentDepth)) {
                    return self.displayError("Invalid parentDepth","generateDisplayNode");
                }
            }   
            if (parseInt(parentDepth) < 0) { parentDepth = 0; }            
        }
    
        // Create the Elements.
        treeNodeDIV = document.createElement("div");
        treeNodeTABLE = document.createElement("table");
        treeNodeTBODY = document.createElement("tbody");
        treeNodeTR = document.createElement("tr");
        treeNode_imageTD = document.createElement("td");
        treeNode_textTD = document.createElement("td");
        treeNode_textTypeSPAN = document.createElement("span");
        treeNode_textNameSPAN = document.createElement("span");
        treeNode_nodeCtrlsTD = document.createElement("td");
        treeNode_childCountTD = document.createElement("td");
        treeNode_childrenDIV = document.createElement("div");
    
        // Assign CSS style (class) names. Note that treeNode_imageTD and treeNodeTABLE get assigned later.
        treeNodeDIV.className = self._objectName;
        treeNode_textTD.className = self._objectName + "_text";
        treeNode_textTypeSPAN.className = self._objectName + "_text_type";
        treeNode_textNameSPAN.className = self._objectName + "_text_name";
        treeNode_nodeCtrlsTD.className = self._objectName + "_node_ctrls";
        treeNode_childCountTD.className = self._objectName + "_childcount";
        treeNode_childrenDIV.className = self._objectName + "_hiddenChildren"; 
        
    
        // Retrieve data values.
        childCount = data[self.ATTR_CHILDCOUNT];
        childCountText = data[self.ATTR_CHILDCOUNTTEXT];
        definition = data[self.ATTR_DEFINITION];
        displayName = data[self.ATTR_DISPLAYNAME];
        helpText = data[self.ATTR_HELPTEXT];
        infoURL = data[self.ATTR_INFOURL];
        isHidden = data[self.ATTR_ISHIDDEN];
        isReference = data[self.ATTR_ISREFERENCE];
        structURL = data[self.ATTR_STRUCTURL];
        type = data[self.ATTR_TYPE];
    
        // The default display for the type is just the type followed by a colon and space.
        displayType = type + ": ";
        
        // Make sure the display name is properly formatted.
    //    displayName = xmlEncode(displayName); 

        if (displayName.toUpperCase() == "UNASSIGNED") {
        
            // Don't italicize "unassigned" and if the type is "order", replace with different text.
            if (type.toUpperCase() == "ORDER") { 
                displayType = "Virus families not assigned to an order";
                displayName = "&nbsp;";
            }
        } else if (displayName.search(/-like viruses/i) > -1) {
   
            // Don't italicize that ends in "-like viruses".
            displayName = "'" + displayName + "'";
        
        } else {
            // By default, italicize the name.
            displayName = "<i>" + displayName + "</i>";
        }
        
        // Make reference strains bold.
        if (isReference == "1") { displayName = "<b>" + displayName + "</b>"; }
        
        // Set the main node Element's attributes.
        treeNodeDIV.setAttribute("id", uid);
        treeNodeDIV.setAttribute(self.ATTR_TYPE, type);
        treeNodeDIV.setAttribute(self.ATTR_DISPLAYNAME, displayName);
        treeNodeDIV.setAttribute(self.ATTR_DEFINITION, definition);
        treeNodeDIV.setAttribute(self.ATTR_HELPTEXT, helpText);
        
        if (isEmpty(isHidden) || isHidden == "1") {
        
            // Configure the node as being hidden.
            treeNodeDIV.className = self._objectName + "_hiddenNode";
            treeNodeDIV.setAttribute(self.ATTR_DEPTH, parentDepth);
            
            // Configure the "child container" node as being hidden.
            treeNode_childrenDIV.setAttribute("id", uid + "__children");
            treeNode_childrenDIV.className = self._objectName + "_hiddenNode"; 
            
            treeNodeDIV.appendChild(treeNode_childrenDIV);
            parentElement.appendChild(treeNodeDIV);
            return true;
        }
        
        
	    // Generate dependent values.
	    depth = parseInt(parentDepth) + 1;
	    
	    // Validate data values.
	    if (isEmpty(childCount)) { childCount = 0; }
        else if (!isInteger(childCount)) { childCount = 0; }
	    if (parseInt(childCount) < 0) { childCount = 0; } 
	    // TODO: others!
	    
	    // Add the display text for type and name.
        treeNode_textTypeSPAN.innerHTML = displayType;
        treeNode_textNameSPAN.innerHTML = displayName;
         
        // Use the child count to determine the image and whether or not it's clickable.
        // If this is a type strain, add a star icon.
        if (isReference == "1") {
            treeNode_imageTD.className = self._objectName + "_image_typeStrain";
        } else if (parseInt(childCount) == 0) {
            treeNode_imageTD.className = self._objectName + "_image_nochildren";
        } else {
            treeNode_imageTD.className = self._objectName + "_image_contracted";
            eval("treeNode_imageTD.onclick = function () {self.expandOrCollapse(uid,'false');};");
        }
        
        
        /* TODO: uncomment this if the URL's are to be displayed on the node.
        // Add the links if they aren't empty.
        if (!isEmpty(infoURL) && !isEmpty(structURL)) {
            treeNode_nodeCtrlsTD.innerHTML = "ICTVdb:&nbsp;&nbsp;" +
                "<a href=\"" + infoURL + "\" target=\"_blank\">Virus Index</a>" +
                "&nbsp;&nbsp;&nbsp;<a href=\"" + structURL + "\" target=\"_blank\">Description</a>"
        } else {*/
        treeNode_nodeCtrlsTD.innerHTML = "&nbsp;";
        //}
        
        // Assign the standard table class and also customize based on the type.
        // TODO: what if the type has non-alphanumeric characters??? Use some kind of lookup table OR 
        // maintain two kinds of type data (display type and typeID)? Better yet, just type ID and lookup display.
        treeNodeTABLE.className = self._objectName + "_display_table deselected" + type;
        treeNodeTABLE.setAttribute("cellpadding","0");
        treeNodeTABLE.setAttribute("cellspacing","0");
             
        // Calculate the left-side offset.
        displayOffset = parseInt(depth) * parseInt(self._leftSideNodeOffset);
        treeNodeDIV.style.marginLeft = String(displayOffset) + "px";     
        
        // Set id attributes.
        treeNodeTABLE.setAttribute("id", uid + "__table");
        treeNode_imageTD.setAttribute("id", uid + "__plusminus");  
        treeNode_childrenDIV.setAttribute("id", uid + "__children");
        
        // Assign the child count TD an ID in case it needs to be modified later.
        treeNode_childCountTD.setAttribute("id", uid + "__childcount");
        
        // Add text indicating the node's number of child nodes.
        if (!isEmpty(childCountText)) { childCountText = "(" + childCountText + ")"; } 
        treeNode_childCountTD.innerHTML = childCountText;
        
        
        // Assemble the Elements.
        treeNode_textTD.appendChild(treeNode_textTypeSPAN);
        treeNode_textTD.appendChild(treeNode_textNameSPAN);
        treeNodeTR.appendChild(treeNode_imageTD);
        treeNodeTR.appendChild(treeNode_textTD);
        treeNodeTR.appendChild(treeNode_nodeCtrlsTD);
        
        treeNodeTR.appendChild(treeNode_childCountTD);
        
        treeNodeTBODY.appendChild(treeNodeTR);
        treeNodeTABLE.appendChild(treeNodeTBODY);
        
        treeNodeDIV.appendChild(treeNodeTABLE);
        treeNodeDIV.appendChild(treeNode_childrenDIV);
        
        parentElement.appendChild(treeNodeDIV);
    };
    
    
    // Generate an asynchronous request for node data.
    this.getChildNodes = function (uid) {
    
        var postData = "";
           
        // Add parameters to the URL: the action code and the "tree identity" name=value pair.
        postData = "action_code=" + ActionCode.GET_NODES;
        
        if (!isEmpty(self._treeIdentity)) { postData += "&" + self._treeIdentity; }
        if (!isEmpty(self._releaseYear)) { postData += "&year=" + self._releaseYear; }
        if (!isEmpty(self._taxonomySource)) { postData += "&src=" + self._taxonomySource; }
        if (!isEmpty(uid)) { postData += "&" + self._nodeKey + "=" + uid; }
 
        // Generate the request, providing the URL and a callback "handler" method. Note that 
        // the "sendAsyncRequest()" function is defined in utilityFunctions.js.
        sendAsyncRequest(self._asyncURL, self.processAsyncNodeData, postData);
    
        // Display the "progress" message/image.
        displayProgressMessage(false, "progressPanel", "Loading...") ;
    };
    
    
    // Generate an asynchronous request for node data (by ictv_id).
    this.getNodesByICTVID = function (ictvID_) {
    
        var postData = "";
           
        // Validate the ictv_id parameter.
        if (isEmpty(ictvID_)) { return self.displayError("Invalid ictv_id","getNodesByICTVID"); }
        
 // NOTE: previously, this used the action code get_specific_node (which didn't display nodes not
 // not in the lineage)...might be useful in the future! Also, this used the callback self.processAsyncSelectedNodeData.
        
        // Add the action code and the ictv_id as parameters to the URL.
        // "tree identity" name=value pair.
        postData = "action_code=" + ActionCode.EXPAND_TO_NODE + "&accession=" + ictvID_;
        
        // Add optional parameters.
        if (!isEmpty(self._treeIdentity)) { postData += "&" + self._treeIdentity; } 
        if (!isEmpty(self._releaseYear)) { postData += "&year=" + self._releaseYear; }
        if (!isEmpty(self._taxonomySource)) { postData += "&src=" + self._taxonomySource; } 
 
        // Generate the request, providing the URL and a callback "handler" method. Note that 
        // the "sendAsyncRequest()" function is defined in utilityFunctions.js.
        sendAsyncRequest(self._asyncURL, self.processAsyncExpandToNodeData, postData);
    };
    
    
    // Generate an asynchronous request for search data.
    this.getSearchData = function (searchText_, includeAllReleases_) {
    
        var postData = "";
           
        // Validate the search text.
        if (isEmpty(searchText_)) { alert("Please enter valid search text"); return false; }
        
        // Add parameters to the URL: the action code and search text.
        postData = "action_code=" + ActionCode.SEARCH_DATA + "&search_text=" + searchText_;
        
        if (includeAllReleases_) { postData += "&include_all_releases=true"; } 
        else { postData += "&include_all_releases=false"; }  
        
        // Add optional parameters.
        if (!isEmpty(self._treeIdentity)) { postData += "&" + self._treeIdentity; } 
        if (!isEmpty(self._releaseYear)) { postData += "&year=" + self._releaseYear; }
        if (!isEmpty(self._taxonomySource)) { postData += "&src=" + self._taxonomySource; } 
        
        // URI encode the post data (don't forget to decode later!).
        postData = encodeURI(postData);
        
        // Generate the request, providing the URL and a callback "handler" method. Note that 
        // the "sendAsyncRequest()" function is defined in utilityFunctions.js.
        sendAsyncRequest(self._asyncURL, self.processAsyncSearchData, postData);
        
        // Display the "progress" message/image.
        displayProgressMessage(false, "progressPanel", "Searching...") ;
    };
      
    
    // Handle an asynchronous request for a specific node (requested via ictv_id).
    this.processAsyncSelectedNodeData = function (result) {
    
        var childCountText;
        var childData;
        var childNode;
        var dataID;
        var dataNode;
        var dataNodes;
        var parentElement;
        var parentUID = "";
        var taxNodeID;
        var taxonomyData = null;
        var xmlDoc;
        
        // Validate the input.
        if (result == null) {return self.displayError("Invalid result","processAsyncSelectedNodeData");}
        
        // The responseXML attribute of the result should be an XML document.
        xmlDoc = result.responseXML;
        
        // Get and validate the taxonomy_data Element.
        taxonomyData = xmlDoc.getElementsByTagName("taxonomy_data");
        if (taxonomyData == null || taxonomyData.length < 1) { return self.displayError("Invalid taxonomy_data Element","processAsyncSelectedNodeData"); }
        
        // Look for a release attribute.
        var taxonomySource = taxonomyData[0].getAttribute("src");
        self.setTaxonomySource(taxonomySource);
        
        // Get and validate the data nodes.
        dataNodes = xmlDoc.getElementsByTagName("data"); 
        if (dataNodes == null || dataNodes.length < 1) {
     
            // Fire an event that indicates that no data is available.
            self._event_noDataAvailable.fire(null);
            return true;
        }
  
        // Iterate thru the data nodes.
        for (var c=0; c<dataNodes.length; c++) {
        
            dataNode = dataNodes[c];
            if (dataNode == null || dataNode.nodeType != 1) {continue;}
   
            childData = new Array();
            
            childData[self.ATTR_ACCESSION] = dataNode.getAttribute(self.ATTR_ACCESSION);
            childData[self.ATTR_CHILDCOUNT] = dataNode.getAttribute(self.ATTR_CHILDCOUNT);
            childData[self.ATTR_CHILDCOUNTTEXT] = dataNode.getAttribute(self.ATTR_CHILDCOUNTTEXT);
            childData[self.ATTR_CHILDDATAPOPULATED] = dataNode.getAttribute(self.ATTR_CHILDDATAPOPULATED);
            childData[self.ATTR_DEFINITION] = dataNode.getAttribute(self.ATTR_DEFINITION);
            childData[self.ATTR_DEPTH] = dataNode.getAttribute(self.ATTR_DEPTH);
            childData[self.ATTR_DISPLAYNAME] = dataNode.getAttribute(self.ATTR_DISPLAYNAME);
            childData[self.ATTR_HELPTEXT] = dataNode.getAttribute(self.ATTR_HELPTEXT);
            childData[self.ATTR_INFOURL] = dataNode.getAttribute(self.ATTR_INFOURL);
            childData[self.ATTR_ISEXPANDED] = dataNode.getAttribute(self.ATTR_ISEXPANDED);
            childData[self.ATTR_ISHIDDEN] = dataNode.getAttribute(self.ATTR_ISHIDDEN);
            childData[self.ATTR_ISREFERENCE] = dataNode.getAttribute(self.ATTR_ISREFERENCE);
            childData[self.ATTR_PARENTUID] = dataNode.getAttribute(self.ATTR_PARENTUID);
            childData[self.ATTR_STRUCTURL] = dataNode.getAttribute(self.ATTR_STRUCTURL);
            childData[self.ATTR_SYNONYMS] = dataNode.getAttribute(self.ATTR_SYNONYMS);
            childData[self.ATTR_TYPE] = dataNode.getAttribute(self.ATTR_TYPE);
            childData[self.ATTR_UID] = dataNode.getAttribute(self.ATTR_UID);
            childData[self.ATTR_URL] = dataNode.getAttribute(self.ATTR_URL);
     
            // Try to retrieve the parent node's data Element.
            parentUID = childData[self.ATTR_PARENTUID];
            if (isEmpty(parentUID)) { childData[self.ATTR_PARENTUID] = self._panelID; }
            
            var nodeDisplayCallback = null;

            if (!isEmpty(childData[self.ATTR_TYPE])) {
                nodeDisplayCallback = self._nodeDisplayCallbacks[childData[self.ATTR_TYPE]];
            }
            
            if (nodeDisplayCallback != null && typeof(nodeDisplayCallback) != "undefined") {
            
                // Call the callback function.
                nodeDisplayCallback(childData);
                
            } else {  
            
                // Call the standard formatting function.
                self.generateDisplayNode(childData);
            }
             
            // Expand the parent node (if the parent node isn't the parent panel).
            if (!isEmpty(parentUID) && parentUID != "0" && parentUID.substr(4,4) != "0000") { //&& parentUID != self._panelID 
   
                parentElement = document.getElementById(parentUID);
                if (parentElement == null) {return self.displayError("Invalid parentElement","processAsyncSelectedNodeData");}
          
                // Update this node to be "expanded".
                parentElement.setAttribute(self.ATTR_ISEXPANDED, "true");
           
                // Have the node's children already been populated?
                //childDataPopulated = parentElement.getAttribute(self.ATTR_CHILDDATAPOPULATED);
                //if (isEmpty(childDataPopulated)) {childDataPopulated = "false";}
                
                // Update image to "expanded".
                imageElement = document.getElementById(parentUID + "__plusminus");
                if (imageElement != null) { imageElement.className = self._objectName + "_image_expanded"; }
                
                // Make child nodes visible.
                childrenElement = document.getElementById(parentUID + "__children");
                if (childrenElement != null) { childrenElement.className = self._objectName + "_visibleChildren"; }
            }    
        }

        // Fire an event that indicates that the tree has been populated.
        self._event_treeIsPopulated.fire(null);
    };
    
    
    // dmd 022610
    this.processAsyncExpandToNodeData = function (result) {
 
        var childCountText;
        var childData;
        var childNode;
        var dataNode;
        var dataNodes;
        var parentUID = "";
        var taxNodeID;
        var taxonomyData = null;
        var xmlDoc;
       
        // Validate the input.
        if (result == null) {return self.displayError("Invalid result","processAsyncExpandToNodeData");}
        
        // The responseXML attribute of the result should be an XML document.
        xmlDoc = result.responseXML;
        
        // Get and validate the taxonomy_data Element.
        taxonomyData = xmlDoc.getElementsByTagName("taxonomy_data");
        if (taxonomyData == null || taxonomyData.length < 1) { return self.displayError("Invalid taxonomy_data Element","processAsyncExpandToNodeData"); }
        
        // Look for a release attribute.
        var taxonomySource = taxonomyData[0].getAttribute("src");
        self.setTaxonomySource(taxonomySource);
        
        // Get the parent UID.
        parentUID = taxonomyData[0].getAttribute("parent_id");
        
        // Get and validate the data nodes.
        dataNodes = xmlDoc.getElementsByTagName("data"); 
        if (dataNodes == null || dataNodes.length < 1) {
     
            // Fire an event that indicates that no data is available.
            self._event_noDataAvailable.fire(null);
            return true;
        }
  
        // Iterate thru the data nodes.
        for (var c=0; c<dataNodes.length; c++) {
        
            dataNode = dataNodes[c];
            if (dataNode == null || dataNode.nodeType != 1) {continue;}
   
            childData = new Array();
            
            childData[self.ATTR_ACCESSION] = dataNode.getAttribute(self.ATTR_ACCESSION);
            childData[self.ATTR_CHILDCOUNT] = dataNode.getAttribute(self.ATTR_CHILDCOUNT);
            childData[self.ATTR_CHILDCOUNTTEXT] = dataNode.getAttribute(self.ATTR_CHILDCOUNTTEXT);
            childData[self.ATTR_CHILDDATAPOPULATED] = dataNode.getAttribute(self.ATTR_CHILDDATAPOPULATED);
            childData[self.ATTR_DEFINITION] = dataNode.getAttribute(self.ATTR_DEFINITION);
            childData[self.ATTR_DEPTH] = dataNode.getAttribute(self.ATTR_DEPTH);
            childData[self.ATTR_DISPLAYNAME] = dataNode.getAttribute(self.ATTR_DISPLAYNAME);
            childData[self.ATTR_HELPTEXT] = dataNode.getAttribute(self.ATTR_HELPTEXT);
            childData[self.ATTR_INFOURL] = dataNode.getAttribute(self.ATTR_INFOURL);
            childData[self.ATTR_ISEXPANDED] = dataNode.getAttribute(self.ATTR_ISEXPANDED);
            childData[self.ATTR_ISHIDDEN] = dataNode.getAttribute(self.ATTR_ISHIDDEN);
            childData[self.ATTR_ISREFERENCE] = dataNode.getAttribute(self.ATTR_ISREFERENCE);
            childData[self.ATTR_PARENTUID] = dataNode.getAttribute(self.ATTR_PARENTUID);
            childData[self.ATTR_STRUCTURL] = dataNode.getAttribute(self.ATTR_STRUCTURL);
            childData[self.ATTR_SYNONYMS] = dataNode.getAttribute(self.ATTR_SYNONYMS);
            childData[self.ATTR_TYPE] = dataNode.getAttribute(self.ATTR_TYPE);
            childData[self.ATTR_UID] = dataNode.getAttribute(self.ATTR_UID);
            childData[self.ATTR_URL] = dataNode.getAttribute(self.ATTR_URL);
     
            // Try to retrieve the parent node's data Element.
            parentUID = childData[self.ATTR_PARENTUID];
            if (isEmpty(parentUID)) { childData[self.ATTR_PARENTUID] = self._panelID; }
                      
            var nodeDisplayCallback = null;

            if (!isEmpty(childData[self.ATTR_TYPE])) {
                nodeDisplayCallback = self._nodeDisplayCallbacks[childData[self.ATTR_TYPE]];
            }
            
            if (nodeDisplayCallback != null && typeof(nodeDisplayCallback) != "undefined") {
            
                // Call the callback function.
                nodeDisplayCallback(childData);
            } else {  
            
                // Call the standard formatting function.
                self.generateDisplayNode(childData);
            }
        }

        if (!isEmpty(parentUID)) {
            
            var parentElement = document.getElementById(parentUID);
            if (parentElement != null) { parentElement.setAttribute(self.ATTR_CHILDDATAPOPULATED,"true"); }
            
            if (!isEmpty(childCountText)) {
            
                // Get the child count Element (of the parent).
                var childCountElement = document.getElementById(parentUID + "__childcount");
                if (childCountElement != null) { 
                    childCountElement.innerHTML = "(" + childCountText + ")";
                }
            }
        }
        
        var nodeInfo = xmlDoc.getElementsByTagName("node_info");
        if (nodeInfo != null && nodeInfo.length > 0) {
        
            var order_id = nodeInfo[0].getAttribute("order_id");
            var family_id = nodeInfo[0].getAttribute("family_id");
            var subfamily_id = nodeInfo[0].getAttribute("subfamily_id");
            var genus_id = nodeInfo[0].getAttribute("genus_id");
            var species_id = nodeInfo[0].getAttribute("species_id");
            var selected_level = nodeInfo[0].getAttribute("selected_level");
          
            // Initialize the array of node ID's that need to be selected.
            self._queue_of_uids_to_select = new Array();
            
            // Update the queue with the ID's to be selected.
            if (!isEmpty(family_id)) { self._queue_of_uids_to_select.push(family_id); }
            if (!isEmpty(subfamily_id)) { self._queue_of_uids_to_select.push(subfamily_id); }
            if (!isEmpty(genus_id)) { self._queue_of_uids_to_select.push(genus_id); }
            if (!isEmpty(species_id)) { self._queue_of_uids_to_select.push(species_id); }
            
            // Set the target level to select.
            self._target_level_to_select = selected_level;
       
            // Begin selecting the specified nodes.
            self.selectNode(order_id, null);
        }
    };
    
    
    // Handle the result from an asynchronous request for node data.
    this.processAsyncNodeData = function (result) {
 
        var childCountText;
        var childData;
        var childNode;
        var dataNode;
        var dataNodes;
        var parentUID = "";
        var taxNodeID;
        var taxonomyData = null;
        var xmlDoc;
        
        // Validate the input.
        if (result == null) {return self.displayError("Invalid result","processAsyncNodeData");}
        
        // The responseXML attribute of the result should be an XML document.
        xmlDoc = result.responseXML;
        
        // Get and validate the taxonomy_data Element.
        taxonomyData = xmlDoc.getElementsByTagName("taxonomy_data");
        if (taxonomyData == null || taxonomyData.length < 1) { return self.displayError("Invalid taxonomy_data Element","processAsyncNodeData"); }
        
        // Look for a release attribute.
        var taxonomySource = taxonomyData[0].getAttribute("src");
        self.setTaxonomySource(taxonomySource);
        
        // Get the parent UID.
        parentUID = taxonomyData[0].getAttribute("parent_id");
        
        // Get and validate the data nodes.
        dataNodes = xmlDoc.getElementsByTagName("data"); 
        if (dataNodes == null || dataNodes.length < 1) {
     
            // Fire an event that indicates that no data is available.
            self._event_noDataAvailable.fire(null);
            return true;
        }
  
        // Iterate thru the data nodes.
        for (var c=0; c<dataNodes.length; c++) {
        
            dataNode = dataNodes[c];
            if (dataNode == null || dataNode.nodeType != 1) {continue;}
   
            childData = new Array();
            
            childData[self.ATTR_ACCESSION] = dataNode.getAttribute(self.ATTR_ACCESSION);
            childData[self.ATTR_CHILDCOUNT] = dataNode.getAttribute(self.ATTR_CHILDCOUNT);
            childData[self.ATTR_CHILDCOUNTTEXT] = dataNode.getAttribute(self.ATTR_CHILDCOUNTTEXT);
            childData[self.ATTR_CHILDDATAPOPULATED] = dataNode.getAttribute(self.ATTR_CHILDDATAPOPULATED);
            childData[self.ATTR_DEFINITION] = dataNode.getAttribute(self.ATTR_DEFINITION);
            childData[self.ATTR_DEPTH] = dataNode.getAttribute(self.ATTR_DEPTH);
            childData[self.ATTR_DISPLAYNAME] = dataNode.getAttribute(self.ATTR_DISPLAYNAME);
            childData[self.ATTR_HELPTEXT] = dataNode.getAttribute(self.ATTR_HELPTEXT);
            childData[self.ATTR_INFOURL] = dataNode.getAttribute(self.ATTR_INFOURL);
            childData[self.ATTR_ISEXPANDED] = dataNode.getAttribute(self.ATTR_ISEXPANDED);
            childData[self.ATTR_ISHIDDEN] = dataNode.getAttribute(self.ATTR_ISHIDDEN);
            childData[self.ATTR_ISREFERENCE] = dataNode.getAttribute(self.ATTR_ISREFERENCE);
            childData[self.ATTR_PARENTUID] = dataNode.getAttribute(self.ATTR_PARENTUID);
            childData[self.ATTR_STRUCTURL] = dataNode.getAttribute(self.ATTR_STRUCTURL);
            childData[self.ATTR_SYNONYMS] = dataNode.getAttribute(self.ATTR_SYNONYMS);
            childData[self.ATTR_TYPE] = dataNode.getAttribute(self.ATTR_TYPE);
            childData[self.ATTR_UID] = dataNode.getAttribute(self.ATTR_UID);
            childData[self.ATTR_URL] = dataNode.getAttribute(self.ATTR_URL);
     
            // Try to retrieve the parent node's data Element.
            parentUID = childData[self.ATTR_PARENTUID];
            if (isEmpty(parentUID)) { childData[self.ATTR_PARENTUID] = self._panelID; }
                      
            var nodeDisplayCallback = null;

            if (!isEmpty(childData[self.ATTR_TYPE])) {
                nodeDisplayCallback = self._nodeDisplayCallbacks[childData[self.ATTR_TYPE]];
            }
            
            if (nodeDisplayCallback != null && typeof(nodeDisplayCallback) != "undefined") {
            
                // Call the callback function.
                nodeDisplayCallback(childData);
            } else {  
            
                // Call the standard formatting function.
                self.generateDisplayNode(childData);
            }
        }

        if (!isEmpty(parentUID)) {
            
            var parentElement = document.getElementById(parentUID);
            if (parentElement != null) { parentElement.setAttribute(self.ATTR_CHILDDATAPOPULATED,"true"); }
            
            if (!isEmpty(childCountText)) {
            
                // Get the child count Element (of the parent).
                var childCountElement = document.getElementById(parentUID + "__childcount");
                if (childCountElement != null) { 
                    childCountElement.innerHTML = "(" + childCountText + ")";
                }
            }
        }
        
        
        // Are there any nodes that are to be selected (and auto-expanded)?
        if (self._queue_of_uids_to_select != null && self._queue_of_uids_to_select.length > 0) {
        
            var uidToExpand = self._queue_of_uids_to_select[0];
            self._queue_of_uids_to_select = self._queue_of_uids_to_select.slice(1);
            
            self.selectNode(uidToExpand);
        
        } else {
            // Fire an event that indicates that the tree has been populated.
            self._event_treeIsPopulated.fire(null);
        }
        
        // Hide the "progress" message/image.
        displayProgressMessage(true, "progressPanel", null);
    };
    
    
    // The callback method when a user has done a text search.
    this.processAsyncSearchData = function (result) {
      
        var childData;
        var childNode;
        var dataNode;
        var dataNodes;
        var parentElement;
        var parentUID = "";
        var taxNodeID;
        var searchData = null;
        var searchResultsPanel = null;
        var xmlDoc;
        
        // Validate the input.
        if (result == null) {return self.displayError("Invalid result","processAsyncSearchData");}
        
        // The responseXML attribute of the result should be an XML document.
        xmlDoc = result.responseXML;
        
        // Get and validate the search_data Element.
        searchData = xmlDoc.getElementsByTagName("search_data");
        if (searchData == null || searchData.length < 1) { return self.displayError("Invalid search_data Element","processAsyncSearchData"); }
        
        // Get the number of results from search data.
        var resultCount = searchData[0].getAttribute("result_count");
        
        // Hide the "progress" message/image.
        displayProgressMessage(true, "progressPanel", null);
        
        // TODO: make ID a member variable?
        var searchResultsPanel = document.getElementById("searchResultsPanel");
        if (searchResultsPanel == null) { return self.displayError("Invalid search panel Element","processAsyncSearchData"); }
        
        // Remove any existing controls/data.
        if (searchResultsPanel.hasChildNodes()) {
            while (searchResultsPanel.childNodes.length >= 1 ) {
                searchResultsPanel.removeChild(searchResultsPanel.firstChild);       
            } 
        }
        
        // Create a panel that displays info about how many results were found.
        var resultCountPanel = document.createElement("div");
        resultCountPanel.className = self._objectName + "_resultCount";
        
        // Get and validate the data nodes.
        dataNodes = xmlDoc.getElementsByTagName("data"); 
        if (dataNodes == null || dataNodes.length < 1) {
     
            // Let the user know there's no data.
            resultCountPanel.innerHTML = "No matching results";
            
            searchResultsPanel.appendChild(resultCountPanel);
            
            // Display the search results panel.
            searchResultsPanel.className = self._objectName + "_searchResultsPanel";
            
            return true;
        }
        
        // Use the result count to determine the text that's displayed.
        if (parseInt(resultCount) == 0) {
            resultCountPanel.innerHTML = "(no results found)";
        } else if (parseInt(resultCount) == 1) {
            resultCountPanel.innerHTML = "(1 result found)";
        } else {
            resultCountPanel.innerHTML = "(" + resultCount + " results found)";
        }
        
        searchResultsPanel.appendChild(resultCountPanel);
        
        // Build the search results header columns as their own table.
        var headerTable = document.createElement("table");
        var headerTbody = document.createElement("tbody");
        var headerRow = document.createElement("tr");
        
        headerTable.className = self._objectName + "_searchHeaderTable";
        headerTable.cellPadding = "0";
        headerTable.cellSpacing = "1";
        
        headerRow.className = self._objectName + "_headerRow";
        
        var buttonTH = document.createElement("th");
        var treeNameTH = document.createElement("th"); 
        var lineageTH = document.createElement("th");
        
        buttonTH.className = self._objectName + "_buttonColumn";
        treeNameTH.className = self._objectName + "_releaseColumn";
        lineageTH.className = self._objectName + "_resultsColumn";
        
        buttonTH.innerHTML = "Click to view"; 
        treeNameTH.innerHTML = "Release"; 
        lineageTH.innerHTML = "Search results"; 
        
        headerRow.appendChild(buttonTH);
        headerRow.appendChild(treeNameTH);
        headerRow.appendChild(lineageTH);
        
        headerTbody.appendChild(headerRow);
        headerTable.appendChild(headerTbody);
        searchResultsPanel.appendChild(headerTable);
        
    
        // Build the scrollable results table.
        var scrollingContainer = document.createElement("div");
        var searchTable = document.createElement("table");
        var searchTbody = document.createElement("tbody");
        
        scrollingContainer.className = self._objectName + "_scrollableSearchTable";
        
        if (dataNodes.length > 5) { 
            scrollingContainer.style.height = "193px";
        }
        
        
        searchTable.className = self._objectName + "_searchTable";
        searchTable.cellPadding = "0";
        searchTable.cellSpacing = "1";
        
        
        // Iterate thru the data nodes.
        for (var c=0; c<dataNodes.length; c++) {
        
            dataNode = dataNodes[c];
            if (dataNode == null || dataNode.nodeType != 1) {continue;}
            
            var ictv_id = dataNode.getAttribute("ictv_id");
            var level_name = dataNode.getAttribute("level_name");
            var lineage_text = dataNode.getAttribute("lineage_text");
            var taxnode_id = dataNode.getAttribute("taxnode_id");
            var tree_name = dataNode.getAttribute("tree_name");
            var tree_id = dataNode.getAttribute("tree_id");
            
            // TODO: validate!
            
            var lineageRow = document.createElement("tr");
            var viewButton = document.createElement("input");
            var buttonTD = document.createElement("td");
            var treeNameTD = document.createElement("td"); 
            var lineageTD = document.createElement("td"); 
            
            // Specify the class names.
            buttonTD.className = self._objectName + "_buttonColumn";
            treeNameTD.className = self._objectName + "_releaseColumn";
            lineageTD.className = self._objectName + "_resultsColumn";
        
            if (c % 2 == 0) { lineageRow.className = self._objectName + "_rowClass"; }
            else { lineageRow.className = self._objectName + "_altRowClass"; }
            
            viewButton.setAttribute("type","button");
            viewButton.setAttribute("value","View " + level_name);
            viewButton.className = self._objectName + "_viewButton";
            eval("viewButton.onclick = function () { self.viewSearchResult('" + ictv_id + "','" + tree_name + "'); };");
            
            buttonTD.appendChild(viewButton);
            
            treeNameTD.innerHTML = tree_name;
            lineageTD.innerHTML = lineage_text;
            
            lineageRow.appendChild(buttonTD);
            lineageRow.appendChild(treeNameTD);
            lineageRow.appendChild(lineageTD);
            
            searchTbody.appendChild(lineageRow);
        }
        
        searchTable.appendChild(searchTbody);
        scrollingContainer.appendChild(searchTable);
        searchResultsPanel.appendChild(scrollingContainer);
        
        // Display the search results panel.
        searchResultsPanel.className = self._objectName + "_searchResultsPanel";
    };
    
    
    // Select and expand the node.
    this.selectNode = function (uid, nodeElement) {
    
        var childCount;
        var childData;
        var childDataCollection;
        var childDataPopulated;
        var childGenomeCount;
        var childType;
        var imageElement;
        var parentDepth;
        var tableElement;
        var type;
     
        // Validate input parameters.
        if (isEmpty(uid)) {return self.displayError("Invalid uid","expand");}
        if (nodeElement == null) {
            // If a nodeElement wasn't provided, look it up by its ID.
            nodeElement = document.getElementById(uid);
            if (nodeElement == null) { return false; } 
        }
      
        // Get the node's type.
        type = nodeElement.getAttribute(self.ATTR_TYPE);
        if (!isEmpty(type)) {   
            if (!isEmpty(self._target_level_to_select) &&
                self._target_level_to_select.toUpperCase() == type.toUpperCase()) {
            
                // If this is the target level to select, format the node as selected.
                self.formatNode(self.FORMAT_SELECTED, type, uid);
                
                // If we've reached the target level, there's no need to 
                // continue; reinitialize the queue of ID's and the target level
                // and then return.
                self._target_level_to_select = "";
                self._queue_of_uids_to_select = null;
                
                // Fire an event that indicates that the tree has been populated.
                self._event_treeIsPopulated.fire(null);
        
                return true;
            }
        }
      
        // Update this node to be "expanded".
        nodeElement.setAttribute(self.ATTR_ISEXPANDED, "true");
   
        // Have the node's children already been populated?
        childDataPopulated = nodeElement.getAttribute(self.ATTR_CHILDDATAPOPULATED);
        if (isEmpty(childDataPopulated)) {childDataPopulated = "false";}
        
        // Update image to "expanded".
        imageElement = document.getElementById(uid + "__plusminus");
        if (imageElement == null) {return self.displayError("Invalid imageElement","expand");}
        imageElement.className = self._objectName + "_image_expanded";
        
        // Make child nodes visible.
        childrenElement = document.getElementById(uid + "__children");
        if (childrenElement == null) { return self.displayError("Invalid childrenElement","expand"); }
        childrenElement.className = self._objectName + "_visibleChildren";
        
        if (childDataPopulated.toUpperCase() == "FALSE") {
    
            // Has a callback function been specified for this type?
            var callback = self._childDataCallbacks[type];
            if (callback != null && typeof(callback) == "function") {
                callback(uid);
            } else {
                // Retrieve the data asynchronously.
                self.getChildNodes(uid);
            }
        }        
    };
    
    
    // Set the taxonomy source member variable using this parameter.
    this.setTaxonomySource = function (src_) {

        // Set the "taxonomy source" member attribute using the value provided.
        if (isEmpty(src_) || src_.toUpperCase() == "CURRENT") { 
            self._taxonomySource = CURRENT_TAXONOMY_RELEASE; 
        } else { 
            self._taxonomySource = src_; 
        }
        
        // Handle special cases of source.
        if (self._taxonomySource.toUpperCase() == "ELSEVIER8" || 
            self._taxonomySource.toUpperCase() == "ELESVIER8") 
        { 
            self._releaseYear = "2007"; 
            
        } else if (self._taxonomySource.toUpperCase() == "NCBI") {
            // dmd 092710
            // "NCBI" is the same as current (for now, at least).
            self._taxonomySource = CURRENT_TAXONOMY_RELEASE; 
        } else { 
            self._releaseYear = self._taxonomySource; 
        }
    };
    
    
    // The user has clicked on a view button for a search result.
    this.viewSearchResult = function (ictv_id_, src_) {

        // Clear any existing tree structure.
        self.deleteControl();
     
        // Set the taxonomy source from the selected result so that it will be propagated
        // to the asynchronous "handler page".
        self.setTaxonomySource(src_);
        
        // Generate an asynchronous request for node data.
        self.getNodesByICTVID(ictv_id_);
    };
    

    //---------------------------------------------------------------------------
    // This is executed when the object is instantiated.
    //---------------------------------------------------------------------------

    // Validate input parameters.
    if (isEmpty(asyncURL)) { return self.displayError("Invalid asyncURL", ""); }
    if (isEmpty(nodeKey)) { return self.displayError("Invalid nodeKey", ""); }
    if (isEmpty(panelID)) { return self.displayError("Invalid panelID", ""); }
    if (treeIdentity == null) { treeIdentity = ""; }
    
    self._asyncURL = asyncURL;
    self._nodeKey = nodeKey;
    self._panelID = panelID;
    self._treeIdentity = treeIdentity;
    
    // Get and validate the panel Element where the tree will be appended.
    self._panelElement = document.getElementById(panelID);
    if (self._panelElement == null) { return self.displayError("Invalid panelElement", ""); }
  
    // Initialize the taxonomy source member variable to the default.
    self.setTaxonomySource(null);
};

