<html>
        <head>

<script language="Javascript">

tree = null;
ready = false;


/***************************************************************************************
 * Javascript global variables                                                         *
 **************************************************************************************/

var NODE_EXPAND_BUTTON_FAKE = "<%Links::Browser::JFunction::node_expand_button_fake()%>";
var NODE_EXPAND_BUTTON_PLUS = "<%Links::Browser::JFunction::node_expand_button_plus()%>";
var NODE_EXPAND_BUTTON_LESS = "<%Links::Browser::JFunction::node_expand_button_less()%>";
var NODE_UNSELECTED_BUTTON  = "<%Links::Browser::JFunction::node_unselected_button()%>";
var MAX_LOAD_FULL           = <%max_load_full%>;
var TOTAL_CATEGORIES        = <%total_categories%>;

LEFT_FRAME_ID  = 0;
RIGHT_FRAME_ID = 1;
CODE_FRAME_ID  = 2;
CAN_UPDATE     = 0;

/***************************************************************************************
 * Javascript UnloadedNode class                                                       *
 **************************************************************************************/


/**
 * An unloaded node is a node which could potentially
 * be expanded but which information is not yet stored
 * in the memory. When clicked, it has to perform a
 * network access to upload the document state and re
 * draw the tree.
 */
function UnloadedNode (id, fatherId, name, nbLinks)
{
    this.setId (id);
    this.setFatherId (fatherId);
    this.setName (name);
    this.setNumberOfLinks (nbLinks);
    this.selected = false;
}


/**
 * Sets the number of links for an Unloaded Node.
 */
function UnloadedNode_setNumberOfLinks (nbLinks)
{
    this.numberOfLinks = nbLinks;
}
UnloadedNode.prototype.setNumberOfLinks = UnloadedNode_setNumberOfLinks;


/**
 * Gets the number of links for an Unloaded Node.
 */
function UnloadedNode_getNumberOfLinks()
{
    return this.numberOfLinks;
}
UnloadedNode.prototype.getNumberOfLinks = UnloadedNode_getNumberOfLinks;


/**
 * This method should always return
 * false because an UnloadedNode is
 * never loaded !
 */
function UnloadedNode_isLoaded()
{
    return false;
}
UnloadedNode.prototype.isLoaded = UnloadedNode_isLoaded;


/**
 * This method tells if a node is
 * the same as another according
 * to it's ID number
 */
function UnloadedNode_equals (aNode)
{
    return this.id == aNode.id;
}
UnloadedNode.prototype.equals = UnloadedNode_equals;


/**
 * This method sets the ID of a
 * node to the specified ID. Should
 * normally be used only by the
 * constructor
 */
function UnloadedNode_setId (id)
{
    this.id = id;
}
UnloadedNode.prototype.setId = UnloadedNode_setId;


/**
 * Returns the current node's ID.
 */
function UnloadedNode_getId()
{
    return this.id;
}
UnloadedNode.prototype.getId = UnloadedNode_getId;


/**
 * This method sets the fatherId of a
 * node to the specified fatherId. Should
 * normally be used only by the constructor.
 */
function UnloadedNode_setFatherId (fatherId)
{
    this.fatherId = fatherId;
}
UnloadedNode.prototype.setFatherId = UnloadedNode_setFatherId;


/**
 * returns the current node's fatherId.
 */
function UnloadedNode_getFatherId()
{
    return this.fatherId;
}
UnloadedNode.prototype.getFatherId = UnloadedNode_getFatherId;


/**
 * This method sets the name of the
 * current node.
 */
function UnloadedNode_setName (name)
{
    this.name = name;
}
UnloadedNode.prototype.setName = UnloadedNode_setName;


/**
 * This method returns the name of the
 * current node.
 */
function UnloadedNode_getName()
{
    return this.name;
}
UnloadedNode.prototype.getName = UnloadedNode_getName;


/**
 * Sets whether the node is expanded or not.
 */
function UnloadedNode_setExpanded (expanded)
{
    alert ("Cannot set the expanded property on Unloaded nodes !");
}
UnloadedNode.prototype.setExpanded = UnloadedNode_setExpanded;


/**
 * returns true if the node is expanded,
 * false otherwise. An unloaded node can never
 * be expanded because otherwise it would
 * necessarily be loaded !
 */
function UnloadedNode_isExpanded()
{
    return false;
}
UnloadedNode.prototype.isExpanded = UnloadedNode_isExpanded;


/**
 * Sets whether the node is selected or not.
 */
function UnloadedNode_setSelected (selected)
{
    this.selected = selected;
}
UnloadedNode.prototype.setSelected = UnloadedNode_setSelected;


/**
 * returns true if the node is currently
 * selected, false otherwise.
 */
function UnloadedNode_isSelected()
{
    return this.selected;
}
UnloadedNode.prototype.isSelected = UnloadedNode_isSelected;


/**
 * adds a child to this node. Does not
 * currently lookup in the sub-tree to
 * check if the node has been inserted.
 *
 * this function should never be called
 * on an unloaded node !
 */
function UnloadedNode_addChild (aChild)
{
    alert ("Software error : addChild should never be called on an Unloaded node!");
}
UnloadedNode.prototype.addChild = UnloadedNode_addChild;


/**
 * removes a child from a node. Does
 * not currently lookup in the sub-tree to
 * check if the node to remove is there
 * but only in the children array.
 *
 * this function should never be called
 * on an unloaded node !
 */
function UnloadedNode_delChild (aChild)
{
    alert ("Software error : addChild should never be called on an Unloaded node!");
}
UnloadedNode.prototype.delChild = UnloadedNode_delChild;


/**
 * Returns the array of current node's children.
 *
 * this function should never be called
 * on an unloaded node !
 */
function UnloadedNode_getChildArray()
{
   alert ("Software error : getChildArray should never be called on an Unloaded node!");
}
UnloadedNode.prototype.getChildArray = UnloadedNode_getChildArray;


/**
 * Returns true if the node is a leaf,
 * false otherwise. An unloaded node
 * shall never be a leaf !
 */
function UnloadedNode_isLeaf()
{
    return false;
}
UnloadedNode.prototype.isLeaf = UnloadedNode_isLeaf;


/**
 * Lookup for a node the id of which is
 * id in the current subtree. If the node
 * is found, returns it, otherwise returns
 * null.
 *
 * An unloaded node knows itself only,
 * therefore we do not search the
 * subtree.
 */
function UnloadedNode_lookup (id)
{
    if (this.id == id) return this;
    else return null;
}
UnloadedNode.prototype.lookup = UnloadedNode_lookup;


/**
 * Returns an HTML representation of this node.
 */
function UnloadedNode_toHtml()
{
    return '<a name="' + this.getId() + '"></a>' +
           this.expandButtonHtml() +
           this.folderHtml() +
           "&nbsp; " +
                   this.name +
           " (" +
                   this.getNumberOfLinks() +
                   ")</p>\n";
}
UnloadedNode.prototype.toHtml = UnloadedNode_toHtml;


/**
 * Returns HTML code that represents the
 * button which is being clicked to expand
 * or unexpand a node.
 */
function UnloadedNode_expandButtonHtml()
{
    var node_id = this.getId();
    if (this.isLeaf())
    {
        alert ("Software error: An Unloaded Node can never be a Leaf !");
    }
    else
    {
        if (this.isExpanded())
        {
            alert ("Software error: An Unloaded Node can never be expanded !");
        }
        else
        {
            return '<a href="#" onClick="return parent.clickEvent(' +
                   "'" + 'UnloadedNode' + "', "                  +
                   "'" + 'expand' + "', "                        +
                   node_id                                       +
                   ')"><img src="'                               + 
                   NODE_EXPAND_BUTTON_PLUS                   +
                   '" border=0></a>';
        }
    }
}
UnloadedNode.prototype.expandButtonHtml = UnloadedNode_expandButtonHtml;


/**
 * Returns HTML code that represents the
 * folder which is being clicked to be selected.
 */
function UnloadedNode_folderHtml()
{
    var node_id = this.getId();
        return '<a name="' + node_id + '" href="#" onClick="return parent.clickEvent(' +
               "'" + 'UnloadedNode' + "', "                  +
               "'" + 'select' + "', "                        +
               node_id                                       +
               ')"><img src="'                          + 
               NODE_UNSELECTED_BUTTON                    +
               '" border=0></a>';
}
UnloadedNode.prototype.folderHtml = UnloadedNode_folderHtml;



/***************************************************************************************
 * Javascript LoadedNode class                                                         *
 **************************************************************************************/


/**
 * A loaded node is a node which has been
 * already loaded in memory, i.e. a node which
 * knows its children (even if it has no children)
 */
function LoadedNode (id, fatherId, name, nbLinks)
{
    this.setId (id);
    this.setFatherId (fatherId);
    this.setName (name);
        if (nbLinks == null) this.setNumberOfLinks ("");
        else this.setNumberOfLinks (nbLinks);
    this.expanded = false;
    this.child = new Array();
}


/**
 * Sets the number of links for an Unloaded Node.
 */
function LoadedNode_setNumberOfLinks (nbLinks)
{
    this.numberOfLinks = nbLinks;
}
LoadedNode.prototype.setNumberOfLinks = LoadedNode_setNumberOfLinks;


/**
 * Gets the number of links for an Unloaded Node.
 */
function LoadedNode_getNumberOfLinks()
{
    return this.numberOfLinks;
}
LoadedNode.prototype.getNumberOfLinks = LoadedNode_getNumberOfLinks;


/**
 * This method should always return
 * true because a LoadedNode is always
 * loaded !
 */
function LoadedNode_isLoaded()
{
    return true;
}
LoadedNode.prototype.isLoaded = LoadedNode_isLoaded;


/**
 * This method tells if a node is
 * the same as another according
 * to it's ID number.
 */
function LoadedNode_equals (aNode)
{
    return this.id == aNode.id;
}
LoadedNode.prototype.equals = LoadedNode_equals;


/**
 * This method sets the ID of a
 * node to the specified ID. Should
 * normally be used only by the
 * constructor.
 */
function LoadedNode_setId (id)
{
    this.id = id;
}
LoadedNode.prototype.setId = LoadedNode_setId;


/**
 * returns the current node's ID.
 */
function LoadedNode_getId()
{
    return this.id;
}
LoadedNode.prototype.getId = LoadedNode_getId;


/**
 * This method sets the fatherId of a
 * node to the specified fatherId. Should
 * normally be used only by the constructor.
 */
function LoadedNode_setFatherId (fatherId)
{
    this.fatherId = fatherId;
}
LoadedNode.prototype.setFatherId = LoadedNode_setFatherId;


/**
 * returns the current node's fatherId.
 */
function LoadedNode_getFatherId()
{
    return this.fatherId;
}
LoadedNode.prototype.getFatherId = LoadedNode_getFatherId;


/**
 * this method sets the name of the
 * current node.
 */
function LoadedNode_setName (name)
{
    this.name = name;
}
LoadedNode.prototype.setName = LoadedNode_setName;


/**
 * this method returns the name of the
 * current node.
 */
function LoadedNode_getName()
{
    return this.name;
}
LoadedNode.prototype.getName = LoadedNode_getName;


/**
 * Sets whether the node is expanded or not.
 */
function LoadedNode_setExpanded (expanded)
{
    this.expanded = expanded;
}
LoadedNode.prototype.setExpanded = LoadedNode_setExpanded;


/**
 * returns true if the node is expanded,
 * false otherwise.
 */
function LoadedNode_isExpanded()
{
    return this.expanded;
}
LoadedNode.prototype.isExpanded = LoadedNode_isExpanded;


/**
 * Sets whether the node is selected or not.
 */
function LoadedNode_setSelected (selected)
{
    this.selected = selected;
}
LoadedNode.prototype.setSelected = LoadedNode_setSelected;


/**
 * returns true if the node is currently
 * selected, false otherwise.
 */
function LoadedNode_isSelected()
{
    return this.selected;
}
LoadedNode.prototype.isSelected = LoadedNode_isSelected;


/**
 * adds a child to this node. Does not
 * currently lookup in the sub-tree to
 * check if the node has been inserted.
 */
function LoadedNode_addChild (aChild)
{
    for (var i=0; i<this.child.length; i++)
    {
        if (this.child[i].equals (aChild)) return;
    }
    for (var i=this.child.length; i>0; i--)
    {
            if (this.child[i-1].getName().toUpperCase() < aChild.getName().toUpperCase())
            {
                this.child[i] = aChild;
                return;
            }
            else
            {
                this.child[i] = this.child[i-1];
            }
    }
    this.child[0] = aChild;
}
LoadedNode.prototype.addChild = LoadedNode_addChild;


/**
 * removes a child from a node. Does
 * not currently lookup in the sub-tree to
 * check if the node to remove is there
 * but only in the children array.
 */
function LoadedNode_delChild (aChild)
{
    var newChildArray = new Array();
    for (var i=0; i<this.child.length; i++)
    {
        if (this.child[i].equals (aChild))
        {
            ;
        }
        else
        {
            newChildArray[newChildArray.length] = this.child[i];
        }
    }
    this.child = newChildArray;
}
LoadedNode.prototype.delChild = LoadedNode_delChild;


/**
 * Returns the array of current node's children.
 */
function LoadedNode_getChildArray()
{
    return this.child;
}
LoadedNode.prototype.getChildArray = LoadedNode_getChildArray;


/**
 * Returns true if the node is a leaf,
 * false otherwise.
 */
function LoadedNode_isLeaf()
{
    return this.child.length == 0;
}
LoadedNode.prototype.isLeaf = LoadedNode_isLeaf;


/**
 * Lookup for a node the id of which is
 * id in the current subtree. If the node
 * is found, returns it, otherwise returns
 * null.
 */
function LoadedNode_lookup (id)
{
    if (this.id == id) return this;
    else
    {
        for (var i=0; i<this.child.length; i++)
        {
              var lookup = this.child[i].lookup(id);
              if (lookup != null) return lookup;
        } 
    }
    return null;
}
LoadedNode.prototype.lookup = LoadedNode_lookup;


/**
 * Returns an HTML representation of this node.
 */
function LoadedNode_toHtml()
{
        if (this.getId() == 0) {
                return this.expandButtonHtml() +
                   this.folderHtml() +
                   "&nbsp; " +
                   this.name   +
                   "</p>\n";    
        }
        else {
            return '<a name="' + this.getId() + '"></a>' +
            this.expandButtonHtml() +
            this.folderHtml() +
           "&nbsp; " +
           this.name   +
           " (" +
                   this.getNumberOfLinks() +
                   ")</p>\n";
        }
}
LoadedNode.prototype.toHtml = LoadedNode_toHtml;


/**
 * Returns HTML code that represents the
 * button which is being clicked to expand
 * or unexpand a node.
 */
function LoadedNode_expandButtonHtml()
{
    var node_id = this.getId();
    if (this.isLeaf())
    {
        return '<img src="' + NODE_EXPAND_BUTTON_FAKE + '" border=0></img>';
    }
    else
    {
        if (this.isExpanded())
        {
                return '<a href="#" onClick="return parent.clickEvent(' +
                           "'" + 'LoadedNode' + "', "                    +
                       "'" + 'unexpand' + "', "                      +
                       node_id                                       +
                       ')"><img src="'                               + 
                       NODE_EXPAND_BUTTON_LESS                   +
                       '" border=0></a>';
            }
                else
                {
                return '<a href="#" onClick="return parent.clickEvent(' +
                       "'" + 'LoadedNode' + "', "                    +
                       "'" + 'expand' + "', "                        +
                       node_id                                       +
                       ')"><img src="'                               + 
                               NODE_EXPAND_BUTTON_PLUS                       +
                       '" border=0></a>';
                }
    }
}
LoadedNode.prototype.expandButtonHtml = LoadedNode_expandButtonHtml;


/**
 * Returns HTML code that represents the
 * folder which is being clicked to be selected.
 */
function LoadedNode_folderHtml()
{
    var node_id = this.getId();
        if (<%is_rooted%> && (node_id == 0)) {
                return '<img src="'                  + 
                                   NODE_UNSELECTED_BUTTON    +
                           '" border=0>';
        }
        else {
                return '<a name="' + node_id + '" href="#" onClick="return parent.clickEvent(' +
                           "'" + 'LoadedNode' + "', "                    +
                           "'" + 'select' + "', "                        +
                           node_id                                       +
                                   ')"><img src="'                           + 
                                   NODE_UNSELECTED_BUTTON                    +
                          '" border=0></a>';
        }
}

LoadedNode.prototype.folderHtml = LoadedNode_folderHtml;



/***************************************************************************************
 * Javascript Tree class                                                               *
 **************************************************************************************/


/**
 * Builds a new Tree object
 */
function Tree()
{
    this.nodes = new Array();
    this.nodes[<%id%>] = new LoadedNode (<%id%>, <%fatherid%>, "<%name%>", <%nb_links%>);
    this.selectedNodeId = null;
    this.foo = this;
}


/**
 * This method unselects all the nodes. Added to
 * see if it can fix the netscape 4 / linux selection
 * "Feature".
 */
function Tree_unselect()
{
        for (var i=0; i < this.nodes.length; i++)
        {
                var node = this.nodes[i];
                if (node != null)
                {
                        node.setSelected (false);
                }
        }
}
Tree.prototype.unselect = Tree_unselect;


/**
 * Searches the tree for a node the
 * id of which would be aNodeId, null
 * if this node does not exist in the
 * tree.
 */
function Tree_lookup (aNodeId)
{
    return this.nodes[aNodeId];
}
Tree.prototype.lookup = Tree_lookup;


/**
 * This function takes as input a node id
 * and a boolean and sets the node machine 
 * the given node id 'expanded' property.
 */
function Tree_setExpandedNodeId (aNodeId, expanded)
{
    var node = this.lookup (aNodeId);
    if (node != null) node.setExpanded (expanded);
}
Tree.prototype.setExpandedNodeId = Tree_setExpandedNodeId;


/**
 * This function returns the currently
 * selected node, if it exists.
 */
function Tree_getSelectedNodeId()
{
    return this.selectedNodeId;
}
Tree.prototype.getSelectedNodeId = Tree_getSelectedNodeId;


/**
 * This function changes the currently
 * selected node.
 */
function Tree_setSelectedNodeId (aNodeId)
{
    var selectedNode = this.lookup (this.getSelectedNodeId());
    var nodeToSelect = this.lookup (aNodeId);
    if (nodeToSelect == null) return;
    else
    {
            if (selectedNode == null)
                {
            /* if the selected node is null,
               then we do nothing with it. */
                }
                else
                {
                    /* else, we deselect it. */
                        selectedNode.setSelected (false);
                }
                /* then we select the new node */
                nodeToSelect.setSelected (true);
    }
}
Tree.prototype.setSelectedNodeId = Tree_setSelectedNodeId;


/**
 * Adds a node to the tree.
 */
function Tree_addNode (aNode)
{
    var fathernode = this.lookup (aNode.getFatherId());
    if (fathernode == null)
        {
            alert ("Software error: Cannot add node " +
                       aNode.getName()                    +
                           " because node with id "           +
                           aNode.getFatherId()                +
                           " does not exist.");
                return;
        }
    
    this.delNode (aNode);
    if (fathernode.isLoaded())
    {
                fathernode.addChild (aNode);
                this.nodes[aNode.getId()] = aNode;
    }
    else
    {
                fathernode = new LoadedNode (fathernode.getId(),
                                                                         fathernode.getFatherId(),
                                                                         fathernode.getName(),
                                                                         fathernode.getNumberOfLinks());
                this.addNode (fathernode);
                this.addNode (aNode);
    }
}
Tree.prototype.addNode = Tree_addNode;


/**
 * deletes a node from the tree.
 */
function Tree_delNode (aNode)
{
    var node = this.lookup (aNode.getId());
    var fathernode = this.lookup (aNode.getFatherId());
    
    if (node == null || fathernode == null) return;
    else
    {
                (this.lookup (node.getFatherId())).delChild (node);
                this.nodes[node.getId()] = null;
    }
}
Tree.prototype.delNode = Tree_delNode;


/**
 * Returns an HTML representation of the Tree.
 */
function Tree_toHtml()
{
        return '<html><head>\n' +
               '<body bgcolor="#FFFFFF">' +
               '<p><font face="Tahoma,Arial,Helvetica" size="2"><%if not is_admin%><b>Editor:</b><br><%Name%> (<%owner_links%>)<br><br><%endif%><b>Category Browser:</b><br>' +
               this.toHtml_nodeLevel (<%id%>, 0) +
               '</font></p></body></html>';
}
Tree.prototype.toHtml = Tree_toHtml;


function Tree_toHtml_nodeLevel (nodeid, level)
{
    var node = this.lookup (nodeid);
    result = '<p style="margin-top:5;margin-bottom:5"><nobr>';
    if (node == null) ;
    else
    {
                var width     = 10 * level;
                var leftspace = '<img src="' + NODE_EXPAND_BUTTON_FAKE + '" width="' + width + '" height=1 border=0>';
            result += leftspace + node.toHtml() + "\n";
            if (node.isExpanded())
            {
                level++;
                var childArray = node.getChildArray();
                for (var i=0; i<childArray.length; i++)
                {
                        result += this.toHtml_nodeLevel (childArray[i].getId(), level);
                }
            }
    }
    return result;
}
Tree.prototype.toHtml_nodeLevel = Tree_toHtml_nodeLevel;



/**************************************************************************************
 * Javascript main control subroutines                                                *
 **************************************************************************************/


function refresh_tree_empty()
{
    frames[CODE_FRAME_ID].location.replace ("<%Links::Browser::JFunction::tree_reload_empty()%>&anticache=" + anticache());
        return false;
}


function refresh_tree_full()
{
    frames[CODE_FRAME_ID].location.replace ("<%Links::Browser::JFunction::tree_reload_full()%>&anticache=" + anticache());
        return false;
}


/**
 * This function is triggered when the user adds a node
 * in order to update the tree if necessary.
 */
function user_add_node (id)
{
        var node_to_refresh = tree.lookup (id);
        if (node_to_refresh.isLoaded())
        {
                frames[CODE_FRAME_ID].location.replace ("<%Links::Browser::JFunction::user_add_node()%>&category_id=" +
                                                                                          "&anticache=" + anticache());
        }
        else
        {
                /*
                 * if the node is not loaded, then we need not do anything
                 * because it will be updated whenever the user expands it.
                 */
                ;
        }
}


/**
 * This function redraws the javascript tree.
 */
function draw_tree()
{
    if (tree != null)
    {
        frames[LEFT_FRAME_ID].document.open();
        frames[LEFT_FRAME_ID].document.writeln (tree.toHtml());
        frames[LEFT_FRAME_ID].document.close();
    }
}


/**
 * This method loads the information which is related to node
 * with id id into the tree structure.
 */
function tree_loadnode (id)
{
        frames[CODE_FRAME_ID].location.replace ("<%Links::Browser::JFunction::tree_loadnode()%>&category_id=" + id +
                                                                                       "&anticache=" + anticache());
}

/**
 * A cool comment goes here
 */
function list_links (id)
{
        /* frames[RIGHT_FRAME_ID].location */
}

/**
 * Triggered when the user selects a node.
 */
function select_node (id)
{
        tree.setSelectedNodeId (id);
        frames[RIGHT_FRAME_ID].location.replace ("<%Links::Browser::JFunction::tree_selectnode()%>&category_id=" + id +
                                                                                          "&anticache=" + anticache());
        draw_tree();
        set_location (id);
}

/**
 * This function is triggered whenever an user wants to move
 * node with id from to node with id to.
 */
function move_node (from, to)
{
        frames[LEFT_FRAME_ID].document.body.innerHTML = '<H2><BLINK>Querying database...</BLINK></H2>';
        frames[RIGHT_FRAME_ID].location.replace ("<%Links::Browser::JFunction::tree_movenode()%>&category_from=" + from +
                                                        "&category_to=" + to + "&anticache="     + anticache());
}


/**
 * this function is moves a link from a category to
 * another category which is clicked by the user.
 */
function link_move (linkid, oldcategoryid, newcategoryid)
{
        frames[RIGHT_FRAME_ID].location.replace ("<%Links::Browser::JFunction::movelink()%>&old_category_id=" + oldcategoryid +
                                                        "&new_category_id=" + newcategoryid +
                                                        "&link_id=" + linkid + "&anticache=" + anticache());
}


/**
 * this function is copies a link from a category to
 * another category which is clicked by the user.
 */
function link_copy (linkid, oldcategoryid, newcategoryid)
{
        frames[RIGHT_FRAME_ID].location.replace ("<%Links::Browser::JFunction::copylink()%>&old_category_id=" + oldcategoryid +
                                                        "&new_category_id=" + newcategoryid +
                                                        "&link_id=" + linkid + "&anticache=" + anticache());
}


/**
 * clickEvent function. This is the main function, it
 * is supposed to trigger the right functions according
 * to the user actions and the state of the program.
 */
function clickEvent (classname, action, id)
{
    if (ready == null || !ready) return false;
        if (classname == "LoadedNode")
        {
                var node = tree.lookup (id);
                if (action == "expand")
                {
                        node.setExpanded (true);
                        draw_tree();
                        set_location (id);
                }
                else if (action == "unexpand")
                {
                        node.setExpanded (false);
                        draw_tree();
                        set_location (id);
                }
                else if (action == "select")
                {
                        /* 
                         * Can't click on root node if base is set.
                         */
                        if (<%is_rooted%> && (id == 0)) {
                                return false;
                        }

                        /*
                     * We test if the right frame is a move frame. If so, when
                         * user clicks on a node then we must trigger a move action,
                         * otherwise it is a normal select action.
                         */
                        if (frames[RIGHT_FRAME_ID].move_from && typeof(frames[RIGHT_FRAME_ID].move_from) != "function")
                        {
                                CAN_UPDATE = 1;
                                move_node (frames[RIGHT_FRAME_ID].move_from, id);
                        }
                        else if (frames[RIGHT_FRAME_ID].link_move && typeof(frames[RIGHT_FRAME_ID].link_move) != "function")
                        {
                                CAN_UPDATE = 1;
                                link_move (frames[RIGHT_FRAME_ID].link_move, frames[RIGHT_FRAME_ID].old_category_id, id)
                        }
                        else if (frames[RIGHT_FRAME_ID].link_copy && typeof(frames[RIGHT_FRAME_ID].link_copy) != "function")
                        {
                                CAN_UPDATE = 1;
                            link_copy (frames[RIGHT_FRAME_ID].link_copy, frames[RIGHT_FRAME_ID].old_category_id, id)
                        }
                        else if (frames[RIGHT_FRAME_ID].related_to && typeof(frames[RIGHT_FRAME_ID].related_to) != "function")
                        {
                                frames[RIGHT_FRAME_ID].document.related_form.related_to.value = id;
                                frames[RIGHT_FRAME_ID].document.related_form.category_id.value = frames[RIGHT_FRAME_ID].related_to;
                                frames[RIGHT_FRAME_ID].document.related_form.submit();
                        }
                        else
                        {
                                select_node (id);
                        }
                }
                else
                {
                        alert ("Software error: '" + action + "' is not a valid action");
                }
        }
        else if (classname == "UnloadedNode")
        {
                if (action == "expand")
                {
                        frames[LEFT_FRAME_ID].document.body.innerHTML = '<H2><BLINK>Querying database...</BLINK></H2>';
                        tree_loadnode (id);
                        return false;
                }
                else if (action == "unexpand")
                {
                        alert ("Software error: unexpand should never happen on an UnloadedNode !");
                }
                else if (action == "select")
                {
                        /*
                     * We test if the right frame is a move frame. If so, when
                         * user clicks on a node then we must trigger a move action,
                         * otherwise it is a normal select action.
                         */
                        if (frames[RIGHT_FRAME_ID].move_from && typeof(frames[RIGHT_FRAME_ID].move_from) != "function")
                        {
                                CAN_UPDATE = 1;
                                move_node (frames[RIGHT_FRAME_ID].move_from, id);
                        }
                        else if (frames[RIGHT_FRAME_ID].link_move && typeof(frames[RIGHT_FRAME_ID].link_move) != "function")
                        {
                                CAN_UPDATE = 1;
                            link_move (frames[RIGHT_FRAME_ID].link_move, frames[RIGHT_FRAME_ID].old_category_id, id)
                        }
                        else if (frames[RIGHT_FRAME_ID].link_copy && typeof(frames[RIGHT_FRAME_ID].link_copy) != "function")
                        {
                                CAN_UPDATE = 1;
                            link_copy (frames[RIGHT_FRAME_ID].link_copy, frames[RIGHT_FRAME_ID].old_category_id, id)
                        }
                        else if (frames[RIGHT_FRAME_ID].related_to && typeof(frames[RIGHT_FRAME_ID].related_to) != "function")
                        {
                                frames[RIGHT_FRAME_ID].document.related_form.related_to.value = id;
                                frames[RIGHT_FRAME_ID].document.related_form.category_id.value = frames[RIGHT_FRAME_ID].related_to;
                                frames[RIGHT_FRAME_ID].document.related_form.submit();
                        }
                        else
                        {
                                select_node (id);
                        }
                }
                else
                {
                        alert ("Software error: '" + action + "' is not a valid action");
                }
        }
        return false;
}

/**
 * This function returns a 10 bytes long random string
 * in order to cheat the navigator's cache.
 */
function anticache()
{
        var elements = [ 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z' ];
        var result   = "";
        for (var i=0; i<10; i++) result += elements[Math.round((elements.length - 1) * Math.random())];
        return result;
}


/**
 * This function is triggered when the whole frameset is loaded.
 */
function frameset_onLoad()
{
    tree = new Tree();
    frames[LEFT_FRAME_ID].location = "about:blank";
    frames[RIGHT_FRAME_ID].location = "about:blank";
    frames[CODE_FRAME_ID].location = "about:blank";
        setTimeout('frames[LEFT_FRAME_ID].location = "<%Links::Browser::JFunction::tree_panel_url%>&anticache=" + anticache();', 100);
    setTimeout('frames[RIGHT_FRAME_ID].location = "<%Links::Browser::JFunction::info_panel_url%>&anticache=" + anticache();', 100);
    setTimeout('frames[CODE_FRAME_ID].location = "<%Links::Browser::JFunction::code_panel_url%>&anticache=" + anticache();', 100);
        ready = true;
}

/**
 * Set the location, this prevents the tree from redrawing and positioning
 * up at the top again. Only works in IE 5.5, but Netscape behave nicely.
 */
function set_location (node_id) {
        if (document.all) {
                if (isIE()) {
                        frames[LEFT_FRAME_ID].location.hash = node_id;
                }
        }
}

function isIE() {
        var ver = window.navigator.appVersion;

        var pos = ver.search('MSIE ');
        var ie  = false;

        if ( pos == -1 ) { 
                return ie;
        }

        if ( ver.substr(pos + 5, 1) > 5 ) {
                ie = true;
        }
        else if ( ver.substr(pos + 5, 1) == 5 ) {
                var v = ver.substr(pos + 7, 1);  
                if ( !isNaN(v) && v >= 5 ) {
                ie = true;  
                }
        }
        return ie;
}

/***************************************************************************************
 * Javascript and HTML startup                                                         *
 **************************************************************************************/


</script>

    <title>Gossamer Links</title>

        </head>

        <script language="Javascript">
        document.writeln (
                                '<FRAMESET COLS="25%,74%,1" FRAMEBORDER="0" onLoad="frameset_onLoad()">' +
                '<FRAME NAME="left" scrolling="auto" src="about:blank"></FRAME>' +
                '<FRAME NAME="right" scrolling="auto" src="about:blank"></FRAME>' +
                '<FRAME NAME="code" scrolling="no" src="about:blank"></FRAME>' +
                '</FRAMESET>' );
        </script>
    <NOSCRIPT>
    <BODY BGCOLOR="#FFFFFF"><CENTER><H1>
        You will need to have JavaScript enabled in order to use the Category Browser.
    </H1></CENTER></BODY>
        </NOSCRIPT>
</HTML>


