/**
* Original Author of this file: Martin Mouritzen. (martin@nano.dk)
*
*
* (Lack of) Documentation:
*
*
* If a finishedLoading method exists, it will be called when the tree is loaded.
* (good to display a div, etc.).
*
*
* You have to set the variable rootNode (as a TreeNode).
*
* You have to set a container element, this is the element in which the tree will be.
*
*
* TODO:
* Save cookies better (only 1 cookie for each tree). Else the page will totally cookieclutter.
*
*
* //////////////////////////////////////
*
* Changes made to the original version
*  - tree and treenode related methods and fields made instance methods and fields (were global)
*  - now you can have many trees on the same page independently
*  - trees and nodes maps intruduced to find trees and nodes by id
*  - nodes get autogenerated ids, unique over all nodes on the page
*
*  temporarly changes
*  - tree visual state gets reset each time tree is rendered
*  - drag & drop does not work (was for IE only)
*/


/***********************************************************************
* Configuration variables.
************************************************************************/

// Should the rootNode be displayed.
//var showRootNode = true;

// Should the nodes be sorted? (You can either specify a number, then it will be sorted by that, else it will
// be sorted alphabetically (by name).
// var sortNodes = true;

// This is IMPORTANT... use an unique id for each document you use the tree in. (else they'll get mixed up).
//var documentID = window.location.href;

// being read from cookie.
var nodesOpen = new Array();
// RootNode of the tree.
//var rootNode;

// Container to display the Tree in.
//var container;

// Shows/Hides subnodes on startup
var showAllNodesOnStartup = false;

// Is the roots dragable?
var dragable = false;


/************************************************************************
* The following is just instancevariables.
************************************************************************/
//var href = '';

// rootNodeCallBack name (if null, it's not selectable).
//var rootNodeCallBack = null;

// selectedNode
//var selectedNode = null; // !!!!

//var states = '';
//var statearray = new Array();

var treeNodeEdited = null; // !!!!

var editaborted = false;

var floatDragElement = null;
var colouredElement = null;
var draggedNodeID = null;
var lastDraggedOnNodeID = null;

// tree map, use to find/store trees, ex: trees['myId']
// Tree constructor registers the tree automatically in here
var trees = {};
// node index map, helps to find any node by id
// TreeNode constructor registers the node automatically in here
var nodes = {};


/**
 * The Tree Object
 *
 * @param id tree id
 * @param root Root Node
 * @param providerId Data Provider id
 * @param resourcePath server path to images (tricky)
 * @param bShowRoot true if you want to show tree root node, false otherwise
 */
function Tree(id, root, providerId, resourcePath, bShowRoot, selectionSpansTreeWidth, showLines) {
    this.id = id;
    this.rootNode = root;
    if (this.rootNode) this.rootNode.treeId = id;
    this.providerId = providerId;
    this.resourcePath = resourcePath;
    this.bShowRoot = !!bShowRoot;
    this.statearray = new Array();

    // Should the dashed lines between nodes be shown?
    // right now this aslo flattens tree (removes indents)
    //in the future we will have a separate param for this
    this.showLines = showLines || (showLines == null); //default to true

    this.sortNodes = true;
    this.sorters = [Tree.sortByOrder, Tree.sortByName];

    this.multiSelectEnabled = false;
    this.selectedNodes = [];

    //does clicking anywhere within node container (node parent) div force selection
    this.selectionSpansTreeWidth = selectionSpansTreeWidth;

    this.resetSelected = function() {
        this.selectedNodes = [];
    }

    this.getSelectedNode = function() {
        return (this.selectedNodes.length == 0) ? null : this.selectedNodes[0];
    }

    this.addNodeToSelected = function(node) {
        this.selectedNodes.push(node);
    }

    this.removeNodeFromSelected = function(node) {
        for (var i = 0; i < this.selectedNodes.length; i++) {
            if (this.selectedNodes[i] == node) {
                this.selectedNodes.splice(i, 1);
                return;
            }
        }
    }

    this.isNodeSelected = function(node) {
        for (var i = 0; i < this.selectedNodes.length; i++) {
            if (this.selectedNodes[i] == node) {
                return true;
            }
        }
        return false;
    }

    this.resortSubtree = function (parentNode) {
        var chs = parentNode.childs;
        if (chs && chs.length) {
            chs.sort( function(tree) { return function(a1, a2) { return tree.comparer(a1, a2) } } (this) );
            for (var i = 0; i < chs.length; i++) {
                this.resortSubtree(chs[i]);
            }
        }
    }

    this.resortTree = function() {
        if (this.sortNodes) {
            this.resortSubtree(this.rootNode);
        }
    }

    // self-indexing
    trees[id] = this;
}

/**
 * global node id counter
 */
Tree.getNextId = function() {
    var nextId = 1; // private static var
    return function() {
        return nextId++;
    }
}();


/**
 * The TreeNode Object
 * @param name The title of this node
 * @param icon The icon if this node (Can also be an array with 2 elements, the first one will represent the closed state, and the next one the open state)
 * @param param A parameter, this can be pretty much anything. (eg. an array with information).
 * @param orderNumber an orderNumber If one is given the nodes will be sorted by this (else they`ll be sorted alphabetically (If sorting is on).
 */
function TreeNode(name,icon,param,orderNumber) {
    this.id = Tree.getNextId();
    this.treeId = null; // tree sets it
    this.childs = new Array();
    this.name = (name == null ? 'unset name' : name);
    this.icon = (icon == null ? '' : icon);
    this.icon2 = null;
    this.tooltip = null;
    this.iconTooltip = null;
    this.parent = null;
    this.handler = null;
    this.handlerWhenIconSelected = null;
    this.mouseDownHandler = null;
    this.mouseUpHandler = null;
    this.startEditHandler = null;
    this.endEditHandler = null;
    this.dragHandler = null;
    this.mouseOverHandler = null;
    this.mouseOutHandler = null;
    this.param = (param == null ? '' : param);
    this.orderNumber = (orderNumber == null ? null : orderNumber);
    this.isloaded = false;

    this.openeventlisteners = new Array();
    this.editeventlisteners = new Array();
    this.moveeventlisteners = new Array();
    this.haschilds = false;
    this.editable = false;
    this.linestring = '';

    this.nextSibling = null;
    this.prevSibling = null;

    //this.childsHasBeenFetched = false;

    this.getID = function() {
        return this.id;
    }
    this.getTreeId = function() {
        return this.treeId || (this.parent && this.parent.getTreeId());
    }
    this.setName = function(newname) {
        this.name = newname;
    }
    this.getName = function() {
        return this.name;
    }
    this.getParam = function() {
        return this.param;
    }
    this.setIcon = function(icon) {
        this.icon = (icon == null ? '' : icon);
    }
    this.getIcon = function() {
        if (typeof(this.icon) == 'object') {
            return this.icon[0];
        }
        return this.icon;
    }
    this.getOpenIcon = function() {
        if (typeof(this.icon) == 'object') {
            return this.icon[1];
        }
        return this.icon;
    }
    this.hasIcon = function () {
        return this.icon != '';
    }
    this.setIcon2 = function(icon) {
        this.icon2 = icon;
    }
    this.getIcon2 = function() {
        return this.icon2;
    }
    this.getTooltip = function () {
        return this.tooltip;
    }
    this.setTooltip = function (tooltip) {
        this.tooltip = tooltip;
    }
    this.getIconTooltip = function () {
        return this.iconTooltip;
    }
    this.setIconTooltip = function (iconTooltip) {
        this.iconTooltip = iconTooltip;
    }
    this.getOrderNumber = function() {
        return this.orderNumber;
    }
    this.addOpenEventListener = function(event) {
        this.openeventlisteners[this.openeventlisteners.length] = event;
    }
    this.gotOpenEventListeners = function() {
        return (this.openeventlisteners.length > 0);
    }
    this.addEditEventListener = function(event) {
        this.editeventlisteners[this.editeventlisteners.length] = event;
    }
    this.gotEditEventListeners = function() {
        return (this.editeventlisteners.length > 0);
    }
    this.addMoveEventListener = function(event) {
        this.moveeventlisteners[this.moveeventlisteners.length] = event;
    }
    this.gotMoveEventListeners = function() {
        return (this.moveeventlisteners.length > 0);
    }
    this.addChild = function(childNode) {
        var possiblePrevNode = this.childs[this.childs.length - 1]
        if (possiblePrevNode) {
            possiblePrevNode.nextSibling = childNode;
            childNode.prevSibling = possiblePrevNode;
            // alert(childNode.prevSibling);
        }

        this.childs[this.childs.length] = childNode;
        childNode.setParent(this);

        var treeId = this.getTreeId();
        if (treeId) {
            var tree = trees[treeId];
            if (tree.sortNodes) {
                this.childs.sort( function(tree) { return function(a1, a2) { return tree.comparer(a1, a2) } } (tree) );
            }
        }
    }
    this.removeChild = function(childNode) {
        var found = false;
        for (var i=0;i<this.childs.length;i++) {
            if (found) {
                this.childs[i] = this.childs[i + 1];
            }
            if (this.childs[i] == childNode) {
                if (i == (this.childs.length - 1)) {
                    this.childs[i] = null;
                }
                else {
                    this.childs[i] = this.childs[i + 1];
                }
                found = true;
            }
        }
        if (found) {
            this.childs.length = this.childs.length-1;
        }
    }
    this.resetChilds = function() {
        this.childs = new Array();
    }
    this.setHasChilds = function(hasChilds) {
        this.haschilds = hasChilds;
    }
    this.hasChilds = function() {
        if (this.haschilds == true) {
            return true;
        }
        return (this.childs.length > 0);
    }
    this.getChildCount = function() {
        return this.childs.length;
    }
    this.getFirstChild = function() {
        if (this.hasChilds()) {
            return this.childs[0];
        }
        return null;
    }
    this.gotHandler = function() {
        return this.handler != null;
    }
    this.setHandler = function(handler) {
        this.handler = handler;
    }
    this.getHandler = function() {
        return this.handler;
    }
    this.setHandlerWhenIconSelected = function(handler) {
        this.handlerWhenIconSelected = handler;
    }
    this.getHandlerWhenIconSelected = function() {
        //if no special handler for when icon part of node selected just use default handler
        if (this.handlerWhenIconSelected == null) {
            return this.handler;
        } else {
            return this.handlerWhenIconSelected;
        }
    }
    this.gotMouseDownHandler = function() {
        return this.mouseDownHandler != null;
    }
    this.setMouseDownHandler = function(mouseDownHandler) {
        this.mouseDownHandler = mouseDownHandler;
    }
    this.getMouseDownHandler = function() {
        return this.mouseDownHandler;
    }
    this.gotMouseUpHandler = function() {
        return this.mouseUpHandler != null;
    }
    this.setMouseUpHandler = function(mouseUpHandler) {
        this.mouseUpHandler = mouseUpHandler;
    }
    this.getMouseUpHandler = function() {
        return this.mouseUpHandler;
    }

    this.gotDragHandler = function() {
        return this.dragHandler != null;
    }
    this.getDragHandler = function() {
        return this.dragHandler;
    }
    this.setDragHandler = function(dragHandler) {
        this.dragHandler = dragHandler;
    }
    this.gotMouseOverHandler = function() {
        return this.mouseOverHandler != null;
    }
    this.setMouseOverHandler = function(mouseOverHandler) {
        this.mouseOverHandler = mouseOverHandler;
    }
    this.getMouseOverHandler = function() {
        return this.mouseOverHandler;
    }
    this.gotMouseOutHandler = function() {
        return this.mouseOutHandler != null;
    }
    this.setMouseOutHandler = function(mouseOutHandler) {
        this.mouseOutHandler = mouseOutHandler;
    }
    this.getMouseOutHandler = function() {
        return this.mouseOutHandler;
    }
    //
    this.gotStartEditHandler = function() {
        return this.startEditHandler != null;
    }
    this.setStartEditHandler = function(startEditHandler) {
        this.startEditHandler = startEditHandler;
    }
    this.getStartEditHandler = function() {
        return this.startEditHandler;
    }
    //
    this.gotEndEditHandler = function() {
        return this.endEditHandler != null;
    }
    this.setEndEditHandler = function(endEditHandler) {
        this.endEditHandler = endEditHandler;
    }
    this.getEndEditHandler = function() {
        return this.endEditHandler;
    }

    this.setParent = function(parent) {
        this.parent = parent;
    }
    this.getParent = function() {
        return this.parent;
    }
    this.getLineString = function() {
        return this.linestring;
    }
    this.setLineString = function(string) {
        this.linestring = string;
    }
    this.isEditable = function() {
        return this.editable;
    }
    this.setEditable = function(editable) {
        this.editable = editable;
    }
    this.isLoaded = function() {
        return !this.haschilds || this.isloaded;
    }
    this.setIsLoaded = function(b) {
        this.isloaded = b;
    }


    // self-indexing
    nodes[this.id] = this;
}

function getTreeNode(nodeID) {
    return nodes[nodeID];
}
// !!!!
//function readStates() {
Tree.prototype.readStates = function () {

    //setCookie('tree' + documentID,'');
    //var states = getCookie('tree' + documentID);
    var states = getCookie('tree' + this.id);
    if (states != null) {
        var array = states.split(';');
        for(var i=0;i<array.length;i++) {
            var singlestate = array[i].split('|');
            this.statearray[i] = new Array();
            this.statearray[i]["key"] = singlestate[0];
            this.statearray[i]["state"]  = singlestate[1];
        }
    }
}
// !!!!
//function getState(nodeID) {
Tree.prototype.getState = function (nodeID) {
    var state;
    for(var i=0;i<this.statearray.length;i++) {
        if (this.statearray[i]["key"] == nodeID) {
            state = this.statearray[i]["state"];
            if (state == null || state == '') {
                state = 'closed';
            }
            return state;
        }
    }
    return "closed";
}
// !!!!
//function writeStates(nodeID,newstate) {
Tree.prototype.writeStates = function (nodeID,newstate) {
    //alert(nodeID);
    var str = '';
    var found = false;
    for(var i=0;i<this.statearray.length;i++) {
        if (this.statearray[i]["key"] == nodeID) {
            this.statearray[i]["state"] = newstate;
            found = true;
        }
        if (this.statearray[i]["state"] != null) {
            str += this.statearray[i]["key"] + '|' + this.statearray[i]["state"] + ';';
        }
    }
    if (found == false) {
        this.statearray[this.statearray.length] = new Array();
        this.statearray[this.statearray.length - 1]["key"] = nodeID;
        this.statearray[this.statearray.length - 1]["state"] = newstate;
        if (newstate != null) {
            str += nodeID + '|' + newstate + ';';
        }
    }
    //setCookie('tree' + documentID,str);
    setCookie('tree' + this.id,str);
}
Tree.prototype.resetStates = function () {
    this.statearray = new Array();
    setCookie('tree' + this.id,'');
}

/**
 * Default handler for caption click event.
 * Node gets selected.any
 * @param {Object} treeNode node being clicked
 * @event {Object} event
 */
Tree.prototype.onNodeClick = function (treeNode, event) {
    treeNode.nodeAction(event);
}

/**
 * Default handler for caption double click event
 * Folder gets toggled, leaf ignores this event.
 * @param {Object} treeNode node being clicked
 * @event {Object} event
 */
Tree.prototype.onNodeDoubleClick = function (treeNode, event) {
    treeNode.handleNode();
}

/**
 * Comparer for sorting.
 * Calls sorters on order they appear in tree.sorters array until sorter returns non-zero value
 * @param {Object} node1 first node
 * @param {Object} node2 second node
 * @returns negative number if node1<node2, positive number if node1>node2, 0 otherwise
 */
Tree.prototype.comparer = function (node1, node2) {
    var i, k;
    if (this.sorters && this.sorters.length) {
        for (i = 0; i < this.sorters.length; i++) {
            k = this.sorters[i](node1, node2);
            if (k != 0) {
                return k;
            }
        }
    }
    return 0;
}

/**
 * Sorter by order value assigned to nodes.
 * Order has to be a number. Node that has some order is considered to be
 * LESS than node that does not have any order (order=null)
 * @param {Object} node1 first node
 * @param {Object} node2 second node
 * @returns negative number if node1<node2, positive number if node1>node2, 0 otherwise
 */
Tree.sortByOrder = function (node1, node2) {
    var order1 = node1.getOrderNumber();
    var order2 = node2.getOrderNumber();
    if (order1 == null && order2 == null) {
        return 0;
    }
    if (order1 == null) {
        return 1;
    }
    if (order2 == null) {
        return -1;
    }
    return order1 - order2;
}

/**
 * Sorter alphabetically by node names
 * @param {Object} node1 first node
 * @param {Object} node2 second node
 * @returns negative number if node1<node2, positive number if node1>node2, 0 otherwise
 */
Tree.sortByName = function (node1, node2) {
    var n1 = node1.getName().toLowerCase();
    var n2 = node2.getName().toLowerCase();
    return n1 > n2 ? 1 : (n1 < n2 ? -1 : 0);
}


//
// Templates for tree UI
//

// templates for root
TreeNode.TREE_HEADER_TEMPLATE =
  '<div id="" style="" onclick="" onmousedown="">' +
    '<nobr>' +
      '<img id="" style="width:19px;height:20px;vertical-align:middle;" onclick="">' +
      '<img id="" class="nodrag" style="vertical-align:middle;" onselectstart="return false;" onmousedown="" onmouseup="" onmouseover=""  onmouseout="" onclick="">' +
      '<img src="adhoc/images/px.gif" height="1" width="1">' +
      '<span unselectable="ON" style="vertical-align:middle;" class="treetitle" ID="" onmousedown="" onmouseup="" onmouseover=""  onmouseout="" onclick="" ondblclick="" ></span>' +
      '<span id="" style="vertical-align:middle;">&nbsp;</span>' +
    '</nobr>' +
  '</div>';

TreeNode.TREE_FOOTER_TEMPLATE =
  '<div id="" style="display:block;"></div>';

// templates for other nodes
TreeNode.NODE_HEADER_TEMPLATE =
  '<div id="" style="">' +
    '<nobr>' +
      '<img style="width:19px;height:20px;vertical-align:middle;display:none;">' +
      '<img id="" style="width:19px;height:20px;vertical-align:middle;" onclick="">' +
      '<img id="" class="nodrag" style="vertical-align:middle;" onselectstart="return false;" onmousedown="" onmouseup="" onmouseover=""  onmouseout="" onclick="">' +
      '<img src="adhoc/images/px.gif" height="1" width="1">' +
      '<span unselectable="ON" style="vertical-align:middle;" class="treetitle" ID="" onmousedown="" onmouseup="" onmouseover=""  onmouseout="" onclick="" ondblclick="" ></span>' +
    '</nobr>' +
  '</div>';

TreeNode.NODE_FOOTER_TEMPLATE =
  '<div id="" style="display:none;">' +
  '</div>';


TreeNode.getTemplateHolder = function() {
    // templates are stored on the page
    var templateHolder = document.getElementById('treeTemplateHolder');
    if (templateHolder == null) {
        var templateHolder = document.createElement('DIV');
        document.body.appendChild(templateHolder);
        templateHolder.id = 'treeTemplateHolder';
        templateHolder.style.display = 'none';
        templateHolder.innerHTML =
            TreeNode.TREE_HEADER_TEMPLATE + TreeNode.TREE_FOOTER_TEMPLATE +
            TreeNode.NODE_HEADER_TEMPLATE + TreeNode.NODE_FOOTER_TEMPLATE;
    }
    return templateHolder;
}

TreeNode.prototype.isOpen = function() {
    var submenu = document.getElementById('node' + this.getID() + 'sub');
    return submenu.style.display != "none";
}

Tree.prototype.renderTree = function() {
    this.readStates();

    var href = this.resourcePath;
    var state = 'open';
    var rootId = this.rootNode.getID();
    var iconStartImage = this.rootNode.getIcon();
    this.writeStates(rootId,'open');

    var templH = TreeNode.getTemplateHolder().childNodes[0].cloneNode(true);
    var templF = TreeNode.getTemplateHolder().childNodes[1].cloneNode(true);
    var img1 = templH.childNodes[0].childNodes[0];
    var img2 = templH.childNodes[0].childNodes[1];
    var span1 = templH.childNodes[0].childNodes[3];
    var span2 = templH.childNodes[0].childNodes[4];

    templH.id = 'node' + rootId;
    templH.style.display = (this.bShowRoot == true ? 'block' : 'none');
    templH.onclick = function(rootId) { return function(event) { nodes[rootId].nodeAction(event) } } (rootId);

    img1.id = 'handler' + rootId;
    img1.src = href + 'images/' + (state == 'open' ? 'minus_last' : 'plus_last') + '_no_root.gif';
    img1.onclick = function(rootId) { return function(event) { nodes[rootId].handleNode(); } } (rootId);

    if (nodes[rootId].getIconTooltip() != null) {
        img1.title = nodes[rootId].getIconTooltip();
    }

    img2.id = 'iconimage' + rootId;
    if (iconStartImage && iconStartImage != '') {
        img2.src = iconStartImage;
        img2.onmousedown = function(rootId) { return function(event) { nodes[rootId].mouseDownOnNode(event);return false; } } (rootId);
        img2.onmouseup = function(rootId) { return function(event) { nodes[rootId].mouseUpOnNode(event);return false; } } (rootId);
        img2.onmouseover = function(rootId) { return function(event) { nodes[rootId].mouseOverNode(event) } } (rootId);
        img2.onmouseout = function(rootId) { return function(event) { nodes[rootId].mouseOutNode(event) } } (rootId);
        img2.onclick = function(rootId) { return function(event) { nodes[rootId].nodeAction(event) } } (rootId);
    } else {
        img2.parentNode.removeChild(img2);
        img2 = null;
    }

    span1.id = 'title' + rootId;
    if (nodes[rootId].param.fontStyle) {
        span1.style.fontStyle = nodes[rootId].param.fontStyle;
    }
    if (nodes[rootId].param.fontWeight) {
        span1.style.fontWeight = nodes[rootId].param.fontWeight;
    }
    if (nodes[rootId].param.fontColor) {
        span1.style.color = nodes[rootId].param.fontColor;
    }
    span1.onmousedown = function(rootId) { return function(event) { nodes[rootId].mouseDownOnNode(event);return false; } } (rootId);
    span1.onmouseup = function(rootId) { return function(event) { nodes[rootId].mouseUpOnNode(event);return false; } } (rootId);
    span1.onmouseover = function(rootId) { return function(event) { nodes[rootId].mouseOverNode(event) } } (rootId);
    span1.onmouseout = function(rootId) { return function(event) { nodes[rootId].mouseOutNode(event) } } (rootId);

    span1.onclick = function(rootId) {
        return function(event) {
            var n = nodes[rootId];
            trees[n.getTreeId()].onNodeClick(n,event);
        }
    } (rootId);

    span1.ondblclick = function(rootId) {
        return function(event) {
            var n = nodes[rootId];
            trees[n.getTreeId()].onNodeDoubleClick(n,event);
        }
    } (rootId);

    span1.appendChild(document.createTextNode(this.rootNode.getName()));
    if (this.rootNode.getTooltip()) {
        span1.title = this.rootNode.getTooltip();
    }

    span2.id = 'title' + rootId;

    templF.id = 'node' + rootId + 'sub';
    var chnodes;
    var ls = (this.bShowRoot) ? 'B' : '';
    if (this.rootNode.hasChilds()) {
        var chs = this.rootNode.childs;
        var len = chs.length;
        var cnt = this.rootNode.getChildCount();
        for(i = 0;i < len; i++) {
            chs[i].linestring = ls;
            chnodes = chs[i].showNode((i == (cnt - 1)));
            templF.appendChild(chnodes[0]);
            templF.appendChild(chnodes[1]);
        }
    }

    return [templH, templF];
}
/**
* Shows the given node, and subnodes.
*/
TreeNode.prototype.showNode = function(lastNode) {
    var tree = trees[this.getTreeId()];
    var href = tree.resourcePath;
    var id = this.getID();

    var templH = TreeNode.getTemplateHolder().childNodes[2].cloneNode(true);
    var templF = TreeNode.getTemplateHolder().childNodes[3].cloneNode(true);

    var spacerImgTempl = templH.childNodes[0].childNodes[0];
    var toggleImg = templH.childNodes[0].childNodes[1];
    var iconImg = templH.childNodes[0].childNodes[2];
    var span = templH.childNodes[0].childNodes[4];

    var iconImg2 = null;
    if (this.getIcon2() != null) {
        iconImg2 = iconImg.cloneNode(true);
    }

    linestring = this.getLineString();
    var state = tree.getState(id);

    templH.id='node' + id;

    var llen = linestring.length;
    var lineStr = (tree.showLines ? 'line' : 'white')
    for (var y = 0; y < llen; y++) {
        var sp = spacerImgTempl.cloneNode(true);
        sp.style.display = '';
        if (linestring.charAt(y) == 'I') {
            sp.src = href + 'images/' + lineStr + '.gif';
        }
        else if (linestring.charAt(y) == 'B') {
            sp.src = href + 'images/white.gif';
        }
        templH.childNodes[0].insertBefore(sp, toggleImg);
    }

    toggleImg.id = 'handler' + id;
    if (this.hasChilds()) {
        toggleImg.onclick = function(id) { return function(event) { nodes[id].handleNode(); } } (id);
        // If this is the first child of the rootNode, and showRootNode is false, we want to display a different icon.
        if (!tree.bShowRoot && (this.getParent() == tree.rootNode) && (this.getParent().getFirstChild() == this)) {
            if (!lastNode) {
                toggleImg.src = href + 'images/' + (state == 'open' ? (tree.showLines ? 'minus_no_root' : 'minus_nolines') : (tree.showLines ? 'plus_no_root' : 'plus_nolines')) + '.gif';
            }
            else {
                toggleImg.src = href + 'images/' + (state == 'open' ? 'minus_last' : 'plus_last') + '_no_root.gif';
            }
        }
        else {
            if (!lastNode) {
                toggleImg.src = href + 'images/' + (state == 'open' ? (tree.showLines ? 'minus' : 'minus_nolines') : (tree.showLines ? 'plus' : 'plus_nolines')) + '.gif';
            }
            else {
                toggleImg.src = href + 'images/' + (state == 'open' ? (tree.showLines ? 'minus_last' : 'minus_nolines') : (tree.showLines ? 'plus_last' : 'plus_nolines')) + '.gif';
            }
        }
    }
    else {
        // If this is the first child of the rootNode, and showRootNode is false, we want to display a different icon.
        if (!tree.bShowRoot && (this.getParent() == tree.rootNode) && (this.getParent().getFirstChild() == this)) {
            if (!lastNode) {
                toggleImg.src = href + 'images/' + (tree.showLines ? 't_no_root' : 'white') + '.gif';
            }
            else {
                toggleImg.src = href + 'images/white.gif';
            }
        }
        else {
            if (!lastNode) {
                toggleImg.src = href + 'images/' + (tree.showLines ? 't' : 'white') + '.gif';
            }
            else {
                toggleImg.src = href + 'images/' + (tree.showLines ? 'lastnode' : 'white') + '.gif';
            }
        }
    }
    iconStartImage = this.getIcon();
    if (state != 'closed') {
        if (this.hasChilds()) {
            iconStartImage = this.getOpenIcon();
        }
    }

    iconImg.id = 'iconimage' + id;
    if (iconStartImage && iconStartImage != '') {
        iconImg.src = iconStartImage;
        iconImg.onmousedown = function(id) { return function(event) { nodes[id].mouseDownOnNode(event);return false; } } (id);
        iconImg.onmouseup = function(id) { return function(event) { nodes[id].mouseUpOnNode(event);return false; } } (id);
        iconImg.onmouseover = function(id) { return function(event) { nodes[id].mouseOverNode(event) } } (id);
        iconImg.onmouseout = function(id) { return function(event) { nodes[id].mouseOutNode(event) } } (id);
        iconImg.onclick = function(id) { return function(event) { nodes[id].nodeIconAction(event) } } (id);
    } else {
        iconImg.parentNode.removeChild(iconImg);
        iconImg = null;
    }

    if (iconImg2 != null) {
        iconImg2.src = this.getIcon2();
        //templH.childNodes[0].insertBefore(iconImg2, span);
        span.appendChild(iconImg2);
        span.appendChild(document.createTextNode(' '));
    }

    if (this.getIconTooltip() != null) {
        iconImg.title = this.getIconTooltip();
    }

    span.id = 'title' + id;
    if (nodes[id].param.fontStyle) {
        span.style.fontStyle = nodes[id].param.fontStyle;
    }
    if (nodes[id].param.fontWeight) {
        span.style.fontWeight = nodes[id].param.fontWeight;
    }
    if (nodes[id].param.fontColor) {
        span.style.color = nodes[id].param.fontColor;
    }
    span.onmousedown = function(id) { return function(event) { nodes[id].mouseDownOnNode(event) } } (id)
    span.onmouseup = function(id) { return function(event) { nodes[id].mouseUpOnNode(event) } } (id);
    span.onmouseover = function(id) { return function(event) { nodes[id].mouseOverNode(event) } } (id)
    span.onmouseout = function(id) { return function(event) { nodes[id].mouseOutNode(event) } } (id)

    //if not showing lines make handler inamges very narrow so they don`t take up space
    //so in effect nos howing lines makes tree flat
    //if we want (in teh future) to show trees with indentation but no lines we will need a new var
    if (!tree.showLines) {
        //var handlerImage = document.getElementById('handler' + id);
        toggleImg.style.width = "1px";
    }

    span.onclick = function(id) {
        return function(event) {
            var n = nodes[id];
            trees[n.getTreeId()].onNodeClick(n,event);
        }
    } (id);

    span.ondblclick = function(id) {
        return function(event) {
            var n = nodes[id];
            trees[n.getTreeId()].onNodeDoubleClick(n,event);
        }
    } (id);

    span.appendChild(document.createTextNode(this.getName()));
    if (this.getTooltip()) {
        span.title = this.getTooltip();
    }

    if (tree.selectionSpansTreeWidth) {
        templH.onclick = handleNodeContainerEvent(span.onclick);
        templH.ondblclick = handleNodeContainerEvent(span.ondblclick);
        templH.onmousedown = handleNodeContainerEvent(span.onmousedown);
        templH.onmouseup = handleNodeContainerEvent(span.onmouseup);
        templH.onmouseover = handleNodeContainerEvent(span.onmouseover);
        templH.onmouseout = handleNodeContainerEvent(span.onmouseout);
    }

    templF.id = 'node' + id + 'sub';
    if (this.hasChilds()) {
        var bHidden = true;
        if (state == 'open') {
            templF.style.display = 'block';
            fireOpenEvent(this);
            bHidden = false;
        }
        else {
            templF.style.display = (showAllNodesOnStartup == true ? 'block' : 'none');
            bHidden = !showAllNodesOnStartup;
        }

        var subgroups;
        var newChar = '';

        if (!lastNode) {
            newChar = 'I';
        }
        else {
            newChar = 'B';
        }
        for(var z=0;z<this.getChildCount();z++) {
            this.childs[z].setLineString(linestring + newChar);
        }
        if (!bHidden) {
            for(var z=0;z<this.getChildCount();z++) {
                subgroups = this.childs[z].showNode((z == (this.getChildCount() -1)));
                templF.appendChild(subgroups[0]);
                templF.appendChild(subgroups[1]);
            }
        } else {
            templF.delayedRendering = true;
        }
    }

    return [templH, templF];
}

var handleNodeContainerEvent = function(theAction) {
    return function(event) {
        var evt = event ? event : window.event;
        var target = evt.target ? evt.target : evt.srcElement;
        var targetId = target.getAttribute('id');
        if (!targetId || (!startsWith(targetId,'node'))) {
            //this is another way of doing event bubble cancelling for events on container`s children
            return;
        }
        theAction(event);
    }
}

function findSpanChild(element) {
    if (element.tagName == 'SPAN') {
        return element;
    }
    else {
        if (element.childNodes) {
            for(var i=0;i<element.childNodes.length;i++) {
                var value = findSpanChild(element.childNodes[i]);
                if (value != false) {
                    return value;
                }
            }
            return false;
        }
    }
}

function doEndEdit(evt, node) {
    editEnded(node);
    if (node.gotEndEditHandler()) {
        node.getEndEditHandler()(evt, node);
    }
}

function editEnded(node) {
    var tree = trees[node.getTreeId()];
    if (treeNodeEdited != null) {
        var editTitle = document.getElementById('title' + treeNodeEdited.getID());
        var input = editTitle.childNodes[0];
        if (!tree.selectionSpansTreeWidth) {
            editTitle.className = 'treetitleselectedfocused';
        }

        var newValue = input.value;

        if (newValue == treeNodeEdited.getName()) {
            editTitle.innerHTML = newValue;
            treeNodeEdited = null;
            return;
        }

        fireEditEvent(treeNodeEdited,newValue);

        if (!editaborted) {
            treeNodeEdited.setName(newValue);
            editTitle.innerHTML = newValue;
        }

        treeNodeEdited = null;
    }
}

//function mouseDownOnNode(evt,nodeID) {
TreeNode.prototype.mouseDownOnNode = function(evt) {

    var evt = evt ? evt : window.event;
    var ctrl = isCtrlHeld(evt);

    this.addNodeToSelected(ctrl, evt);

    if (this.gotDragHandler()) {
        this.getDragHandler()(evt, this)
    }

    if (this.gotMouseDownHandler()) {
        this.getMouseDownHandler()(evt, this);
    }
}

TreeNode.prototype.mouseUpOnNode = function(evt) {

    var evt = evt ? evt : window.event;
    var ctrl = isCtrlHeld(evt);

    this.addRemoveNodeToFromMultiSelected(ctrl);

    if (this.gotMouseUpHandler()) {
        this.getMouseUpHandler()(evt, this);
    }
}

//function mouseOverNode(evt,nodeID) {
TreeNode.prototype.mouseOverNode = function(evt) {

    var evt = evt ? evt : window.event;

    if (this.getMouseOverHandler()) {
        this.getMouseOverHandler()(evt, this);
    }
}


//function mouseOutNode(evt,nodeID) {
TreeNode.prototype.mouseOutNode = function(evt) {

    var evt = evt ? evt : window.event;

    if (this.getMouseOutHandler()) {
        this.getMouseOutHandler()(evt, this);
    }
}

//function selectNode(evt,nodeID,iconSelected) {
TreeNode.prototype.addNodeToSelected = function(ctrlHeld, evt) {
    var tree = trees[this.getTreeId()];

    if (!tree.multiSelectEnabled && tree.getSelectedNode() != null) {
        if (tree.getSelectedNode() == this) {
            if (this.isEditable()) {
                if (treeNodeEdited == this) {
                    return;
                }
                treeNodeEdited = this;
                var obj = this;
                var oldName = this.getName();
                var editTitle = document.getElementById('title' + this.id);
                editTitle.className = 'editednode';

                editTitle.innerHTML = '<input type="text" name="editednode" class="editednodeinput">';
                var input = editTitle.childNodes[0];
                input.value = this.getName();
                input.focus();
                input.select();
                input.onclick = function(e) {cancelEventBubbling(e)};
                input.ondblclick = function(e) {cancelEventBubbling(e)};
                input.onmousedown = function(e) {cancelEventBubbling(e)};
                input.onmouseup = function(e) {cancelEventBubbling(e)};
                input.onkeypress = function(evt) {
                    var e = (window.event) ? window.event : evt;
                    if (e.keyCode == 13) {
                        input.onblur = null;
                        doEndEdit(e, obj);
                    } else if(e.keyCode == 27) {
                        input.onblur = null;
                        input.value = oldName;
                        doEndEdit(e, obj);
                    }
                };
                input.onblur = function (event) { doEndEdit(event, obj); }
                if (this.gotStartEditHandler()) {
                    this.getStartEditHandler()(evt, this, input);
                }
            }
            return;
        }
        if (treeNodeEdited != null) {
            doEndEdit(evt, treeNodeEdited);
        }
    }

    if (!tree.multiSelectEnabled || (!ctrlHeld && (tree.selectedNodes.length == 1 || !tree.isNodeSelected(this))) || tree.selectedNodes.length == 0) {

        if (tree.getSelectedNode() != null) {
            for (var i = 0; i < tree.selectedNodes.length; i++) {
                var oldNodeTitle = document.getElementById('title' + tree.selectedNodes[i].id);
                if (oldNodeTitle) {
                    oldNodeTitle.className = 'treetitle';
                }
            }
            tree.resetSelected();
        }

        tree.addNodeToSelected(this);
        var nodetitle = document.getElementById('title' + this.id);

        if (!tree.selectionSpansTreeWidth) {
            nodetitle.className = 'treetitleselectedfocused';
        }
    }

}
TreeNode.prototype.addRemoveNodeToFromMultiSelected = function(ctrlHeld) {

    var tree = trees[this.getTreeId()];

    if (ctrlHeld) {

        if (this.isSelected()) {
            if (tree.selectedNodes.length > 1) {
                tree.removeNodeFromSelected(this);
                var nodetitle = document.getElementById('title' + this.id);
                nodetitle.className = 'treetitle';
            }
        } else {
            tree.addNodeToSelected(this);
            var nodetitle = document.getElementById('title' + this.id);
            if (!tree.selectionSpansTreeWidth) {
                nodetitle.className = 'treetitleselectedfocused';
            }
        }

    } else {

        if (tree.multiSelectEnabled && tree.selectedNodes.length > 1) {
            for (var i = 0; i < tree.selectedNodes.length; i++) {
                var oldNodeTitle = document.getElementById('title' + tree.selectedNodes[i].id);
                oldNodeTitle.className = 'treetitle';
            }
            tree.resetSelected();

            tree.addNodeToSelected(this);
            var nodetitle = document.getElementById('title' + this.id);
            if (!tree.selectionSpansTreeWidth) {
                nodetitle.className = 'treetitleselectedfocused';
            }
        }

    }

}

TreeNode.prototype.deselect = function() {
    if (this.isSelected()) {
        var tree = trees[this.getTreeId()];
        tree.removeNodeFromSelected(this);
        var nodetitle = document.getElementById('title' + this.id);
        nodetitle.className = 'treetitle';
    }
}

TreeNode.prototype.select = function() {
    if (!this.isSelected()) {
        var tree = trees[this.getTreeId()];

        if (!tree.multiSelectEnabled && tree.selectedNodes.length > 0) {
            for (var i = 0; i < tree.selectedNodes.length; i++) {
                var oldNodeTitle = document.getElementById('title' + tree.selectedNodes[i].id);
                oldNodeTitle.className = 'treetitle';
            }
            tree.resetSelected();
        }

        tree.addNodeToSelected(this);
        var nodetitle = document.getElementById('title' + this.id);
        if (!tree.selectionSpansTreeWidth) {
            nodetitle.className = 'treetitleselectedfocused';
        }
    }
}

TreeNode.prototype.isSelected = function() {
    var tree = trees[this.getTreeId()];
    var len = tree.selectedNodes && tree.selectedNodes.length;
    if (len) {
        for (var i = 0; i < len; i++) {
            if (tree.selectedNodes[i] == this) {
                return true;
            }
        }
    }
    return false;
}

TreeNode.prototype.nodeAction = function(evt) {

    if (this.gotHandler()) {
        this.getHandler()(evt, this);
    }
}

TreeNode.prototype.nodeIconAction = function(evt) {

    if (this.gotHandler()) {
        this.getHandlerWhenIconSelected()(evt, this);
    }
}

//function refreshNode(treeNode) {
TreeNode.prototype.refreshNode = function() {
    var tree = trees[this.getTreeId()];
    var href = tree.resourcePath;

    var submenu = document.getElementById('node' + this.getID() + 'sub');
    submenu.innerHTML = '';
    for(var i=0;i<this.getChildCount();i++) {
        var parent = this.getParent();
        if (!parent) {
            var ls = (this == tree.rootNode && !tree.bShowRoot) ? '' : 'B';
            this.childs[i].setLineString(this.getLineString() + ls);
        }
        else {
            if (parent.childs[parent.childs.length - 1] == this) {
                this.childs[i].setLineString(this.getLineString() + 'B');
            }
            else {
                this.childs[i].setLineString(this.getLineString() + 'I');
            }
        }
        var ary = this.childs[i].showNode(i == (this.getChildCount() - 1));
        submenu.appendChild(ary[0]);
        submenu.appendChild(ary[1]);
    }
    var actionimage = document.getElementById('handler' + this.getID());
    if (this.getChildCount() == 0) {
        // TreeNode haven`t got any children, make sure the right image is displayed.
        if (actionimage.src.indexOf('last') == -1) {
            actionimage.src = href + 'images/' + (tree.showLines ? 't' : 'white') + '.gif';
        }
        else {
            actionimage.src = href + 'images/' + (tree.showLines ? 'lastnode' : 'white') + '.gif';
        }
        actionimage.onclick = null;

        // Close the submenu
        if (submenu) {
            submenu.style.display = 'none';
        }
    }
    else {
        // We have children, make sure to display the + and - icon.
        if (actionimage.src.indexOf('plus') != -1) {
            // The TreeNode have already got children, and displays them.
        }
        else if (actionimage.src.indexOf('minus') != -1) {
            // The TreeNode have already got children, and displays them.
        }
        else {
            actionimage.onclick = function(id) { return function(event) { nodes[id].handleNode(); } } (this.getID());
            if (actionimage.src.indexOf('last') == -1) {
                actionimage.src = href + 'images/' + (tree.showLines ? 'plus' : 'plus_nolines') + '.gif';
            }
            else {
                actionimage.src = href + 'images/plus_last.gif';
            }
        }
    }
}

//function handleNode(nodeID) {
TreeNode.prototype.handleNode = function () {
    //var treeNode = getTreeNode(nodeID);
    var tree = trees[this.getTreeId()];
    var href = tree.resourcePath;

    if (!this.hasChilds()) { // No reason to handle a node without childs.
        return;
    }

    var submenu = document.getElementById('node' + this.getID() + 'sub');


    if (submenu.delayedRendering) {
        for (var z = 0; z < this.getChildCount(); z++) {
            subgroups = this.childs[z].showNode((z == (this.getChildCount() - 1)));
            submenu.appendChild(subgroups[0]);
            submenu.appendChild(subgroups[1]);
        }
        submenu.delayedRendering = false;
    }


    var iconimageholder = document.getElementById('iconimage' + this.getID());
    var actionimage = document.getElementById('handler' + this.getID());

    // This will be used if showRootNode is set to false.
    var firstChildOfRoot = false;
    if (actionimage.src.indexOf('_no_root') != -1) {
        firstChildOfRoot = true;
    }

    if (submenu.style.display == 'none') {
        tree.writeStates(this.getID(),'open');
        fireOpenEvent(this);
        submenu.style.display = 'block';

        iconimageholder.src = this.getOpenIcon();

        if (actionimage.src.indexOf('last') == -1) {
            actionimage.src = href + 'images/' + ((firstChildOfRoot) ? 'minus_no_root' : (tree.showLines ? 'minus' : 'minus_nolines')) + '.gif';
        }
        else {
            actionimage.src = href + 'images/' + ((firstChildOfRoot) ? 'minus_last_no_root' : (tree.showLines ? 'minus_last' : 'minus_nolines')) + '.gif';
        }
    }
    else {
        tree.writeStates(this.getID(),'closed');
        submenu.style.display = 'none';

        iconimageholder.src = this.getIcon();

        if (actionimage.src.indexOf('last') == -1) {
            actionimage.src = href + 'images/' + ((firstChildOfRoot) ? 'plus_no_root' : (tree.showLines ? 'plus' : 'plus_nolines')) + '.gif';
        }
        else {
            actionimage.src = href + 'images/' + ((firstChildOfRoot) ? 'plus_last_no_root' : (tree.showLines ? 'plus_last' : 'plus_nolines')) + '.gif';
        }
    }
}

//dynamically changes the image of the node to the src provided
//warning - use for folders at your peril - since opening and closing folder will reset image!
TreeNode.prototype.swapIconImage = function (newSrc) {
    var iconimageholder = document.getElementById('iconimage' + this.getID());
    iconimageholder.src = newSrc;
}

TreeNode.prototype.changeName = function(newName) {
    this.name = newName;
    var titleholder = document.getElementById('title' + this.getID());
    titleholder.innerHTML = this.name;
}

Tree.prototype.addNode = function(newNode, parentNode) {
    if (!parentNode) {
        parentNode = this.rootNode;
    }

    var hasChildsProperty = parentNode.childs && parentNode.childs.length;
    if (!hasChildsProperty) {
        parentNode.childs = [];
    }

    parentNode.addChild(newNode);
    
    var parentNodeChildsDiv = document.getElementById('node' + parentNode.getID() + 'sub');
    if (!parentNodeChildsDiv) {
        return;    
    }

    var ch = parentNode.childs;
    var len = ch.length;
    var index = len - 1;

    if (index > 0) {
        index = this.binarySearchOfNode(ch, newNode);
    }

    var isLastNode = (index == len - 1);
    var isParentNodeRoot = (parentNode == this.rootNode);
    var parentOfParent = (!isParentNodeRoot && parentNode.parent != null) ? parentNode.parent : null;
    var isLastParent = (parentOfParent && parentOfParent.childs[parentOfParent.childs.length - 1] == parentNode);
    var newChar = (!isParentNodeRoot && isLastParent) ? 'B' : 'I';

    var ls = (!isParentNodeRoot || (isParentNodeRoot && this.bShowRoot)) ? newChar : '';
    newNode.setLineString(parentNode.getLineString() + ls);

    if (parentNodeChildsDiv.delayedRendering) {
        return;
    }

    var temps = newNode.showNode(isLastNode);

    if (isLastNode) {
        if (index > 0) {
            var prevNode = ch[index - 1];
//            prevNode.refreshNode();
            var prevNodeDiv = document.getElementById('node' + prevNode.getID());
            var prevNodeChildsDiv = document.getElementById('node' + prevNode.getID() + 'sub');

            parentNodeChildsDiv.removeChild(prevNodeDiv);
            parentNodeChildsDiv.removeChild(prevNodeChildsDiv);

//            prevNode.setLineString(parentNode.getLineString() + 'I');
            var prevNodeTemps = prevNode.showNode(false);

            parentNodeChildsDiv.appendChild(prevNodeTemps[0]);
            parentNodeChildsDiv.appendChild(prevNodeTemps[1]);
        }

        parentNodeChildsDiv.appendChild(temps[0]);
        parentNodeChildsDiv.appendChild(temps[1]);
    } else {
        var nextNode = ch[index + 1];
        var nextNodeDiv = document.getElementById('node' + nextNode.getID());
        var nextNodeHandler = document.getElementById('handler' + nextNode.getID());

        parentNodeChildsDiv.insertBefore(temps[0], nextNodeDiv);
        parentNodeChildsDiv.insertBefore(temps[1], nextNodeDiv);

        if (nextNodeHandler) {
            var href = this.resourcePath;

            if (nextNodeHandler.src.indexOf("minus_no_root") != -1) {
                nextNodeHandler.src = href + 'images/minus.gif';
            } else if (nextNodeHandler.src.indexOf("plus_no_root") != -1) {
                nextNodeHandler.src = href + 'images/plus.gif';
            } else if (nextNodeHandler.src.indexOf("minus_last_no_root") != -1) {
                nextNodeHandler.src = href + 'images/minus_last.gif';
            } else if (nextNodeHandler.src.indexOf("plus_last_no_root") != -1) {
                nextNodeHandler.src = href + 'images/plus_last.gif';
            } else if (nextNodeHandler.src.indexOf("t_no_root") != -1) {
                nextNodeHandler.src = href + 'images/t.gif';
            }
        }
    }
}

Tree.prototype.removeNode = function(node) {
    var parent = node.parent;

    var parentDiv = document.getElementById('node' + parent.getID() + 'sub');
    var nodeDiv = document.getElementById('node' + node.getID());
    var nodeChildsDiv = document.getElementById('node' + node.getID() + 'sub');

    if (nodeDiv) {
        parentDiv.removeChild(nodeDiv);
        parentDiv.removeChild(nodeChildsDiv);

        var chs = parent.childs;
        var len = chs.length;
        var isLastNode = chs[len - 1] == node;
        var isFirstNode = chs[0] == node;

        parent.removeChild(node);
        len = chs.length;

        if (isFirstNode && len > 0) {
            var nextNode = chs[0];
            var nextNodeHandler = document.getElementById('handler' + nextNode.getID());

            if (nextNodeHandler) {
                var href = this.resourcePath;

                if (nextNodeHandler.src.indexOf("minus.gif") != -1) {
                    nextNodeHandler.src = href + 'images/minus_no_root.gif';
                } else if (nextNodeHandler.src.indexOf("plus.gif") != -1) {
                    nextNodeHandler.src = href + 'images/plus_no_root.gif';
                } else if (nextNodeHandler.src.indexOf("minus_last.gif") != -1) {
                    nextNodeHandler.src = href + 'images/minus_last_no_root.gif';
                } else if (nextNodeHandler.src.indexOf("plus_last.gif") != -1) {
                    nextNodeHandler.src = href + 'images/plus_last_no_root.gif';
//                } else if (nextNodeHandler.src.indexOf("t.gif") != -1) {
//                    nextNodeHandler.src = href + 'images/t_no_root.gif';
                }
            }
        } else if (isLastNode) {
            chs = parent.childs;

            if (len > 0) {
                var n = chs[len - 1];
                var nDiv = document.getElementById('node' + n.getID());
                var nSubDiv = document.getElementById('node' + n.getID() + 'sub');
                parentDiv.removeChild(nDiv);
                parentDiv.removeChild(nSubDiv);

                var chnodes = n.showNode(true);
                parentDiv.appendChild(chnodes[0]);
                parentDiv.appendChild(chnodes[1]);
            }
        }
    }
}

Tree.prototype.binarySearchOfNode = function(nodes, node) {
    var low = 0;
    var high = nodes.length - 1;

    while (low <= high) {
        var mid = Math.round((low + high) / 2);
        var midNode = nodes[mid];

        if (this.comparer(midNode, node) < 0) {
            low = mid + 1;
        } else if (this.comparer(midNode, node) > 0) {
            high = mid - 1;
        } else {
            return mid; // key found
        }
    }

    return -(low + 1);  // key not found.
}

function fireOpenEvent(treeNode) {
    if (treeNode.gotOpenEventListeners()) {
        for(var i=0;i<treeNode.openeventlisteners.length;i++) {
            treeNode.openeventlisteners[i](treeNode.getID());
        }
    }
}
function fireEditEvent(treeNode,newVal) {
    if (treeNode.gotEditEventListeners()) {
        for(var i=0;i<treeNode.editeventlisteners.length;i++) {
            treeNode.editeventlisteners[i](treeNode.getID(), escape(newVal));
        }
    }
}
function fireMoveEvent(treeNode,draggedNodeID,droppedOnNodeID) {
    if (treeNode.gotMoveEventListeners()) {
        for(var i=0;i<treeNode.moveeventlisteners.length;i++) {
            treeNode.moveeventlisteners[i](draggedNodeID, droppedOnNodeID);
        }
    }
}

function getCookieVal (offset) {
    var endstr = document.cookie.indexOf (";",offset);
    if (endstr == -1) {
        endstr = document.cookie.length;
    }
    return unescape(document.cookie.substring(offset,endstr));
}
function getCookie (name) {
    var arg = name + "=";
    var alen = arg.length;
    var clen = document.cookie.length;
    var i = 0;
    while (i < clen) {
        var j = i + alen;
        if (document.cookie.substring(i, j) == arg) {
            return getCookieVal(j);
        }
        i = document.cookie.indexOf(" ", i) + 1;
        if (i == 0) {
            break;
        }
    }
    return null;
}
function setCookie (name, value) {
    var argv = setCookie.arguments;
    var argc = setCookie.arguments.length;
    var expires = (argc > 2) ? argv[2] : null;
    var path = (argc > 3) ? argv[3] : null;
    var domain = (argc > 4) ? argv[4] : null;
    var secure = (argc > 5) ? argv[5] : false;
    document.cookie = name + "=" + escape (value) + ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) + ((path == null) ? "" : ("; path=" + path)) + ((domain == null) ? "" : ("; domain=" + domain)) + ((secure == true) ? "; secure" : "");
}
