Artifact Content
Not logged in

Artifact d0527f77fa102d004186c830c424df392eb1f9d6


/* This file is a part of CanDyDOC fileset.
   File is written by Victor Nakoryakov and placed into the public domain.

   This file is javascript with classes that represents native style tree control. */
   
var pmNone = 0;
var pmPlus = 1;
var pmMinus = 2;

var hlNone = 0;
var hlGrey = 1;
var hlSelected = 2;

function TreeView(hrefMode)
{
    this.domEntry = document.createElement("div");
    this.children = new Array();
    this.selection = null;
    this.hrefMode = hrefMode;
    
    this.createBranch = function(text, iconSrc)
    {
        var root = new TreeNode(text, iconSrc, this.hrefMode);
        root.owner = this;
        this.children[ this.children.length ] = root;
        this.domEntry.appendChild( root.domEntry );
        return root;
    }
    
    this.branch = function(text)
    {
        var ret = null;
        for (var i = 0; i < this.children.length; ++i)
            if (this.children[i].textElement.data == text)
            {
                ret = this.children[i];
                break;
            }
            
        return ret;
    }
    
    this.domEntry.style.fontSize = "10px";
    this.domEntry.style.cursor = "default";
    this.domEntry.style.whiteSpace = "nowrap";
}

var idCounter = 0;
function TreeNode(text, iconSrc, hrefMode)
{
    this.id             = idCounter++;
    this.parentNode     = null;
    this.children       = new Array();
    this.domEntry       = document.createElement("div");
    this.icon           = document.createElement("img");
    this.textElement    = document.createTextNode(text);
    this.textSpan       = document.createElement("span");
    this.lineDiv        = document.createElement("div");
    this.hierarchyImgs  = new Array();
    this.onclick        = null;
    
    function createIcon()
    {
        var img = document.createElement("img");
        img.style.verticalAlign = "middle";
        img.style.position = "relative";
        img.style.top = "-1px";
        img.width = 16;
        img.height = 16;
        return img;
    }
    
    function createHierarchyImage()
    {
        var img = createIcon();
        img.pointsTop = false;
        img.pointsBottom = false;
        img.pointsRight = false;
        img.pmState = pmNone;
        return img;
    }
    
    function genHierarchyImageSrc(hierarchyImg)
    {
        var name = "";
        if (hierarchyImg.pointsTop)
            name += "t";
            
        if (hierarchyImg.pointsBottom)
            name += "b";
            
        if (hierarchyImg.pointsRight)
            name += "r";
            
        if (hierarchyImg.pmState == pmPlus)
            name += "p";
        else if (hierarchyImg.pmState == pmMinus)
            name += "m";
        
        if (name == "")
            name = "shim";
        
        return "candydoc/img/tree/" + name + ".gif";
    }
    
    function setSrc(icon, src)
    {
        icon.src = src;
        // After src change width and height are reseted in IE.
        // Bug workaround:
        icon.width = 16;
        icon.height = 16;
    }
    
    this.createChild = function(text, iconSrc)
    {
        var child = new TreeNode(text, iconSrc, this.owner.hrefMode);
        this.children[ this.children.length ] = child;
        this.domEntry.appendChild( child.domEntry );
        child.parentNode = this;
        child.owner = this.owner;
        
        // insert hierarchy images according to deepness level
        // of created child.
        
        if (this.children.length > 1)
        {
            // there were already added child before. So copy `level-1`
            // hierarchy images from it.
            
            var prevAddedChild = this.children[ this.children.length - 2 ];
            
            for (var i = 0; i < prevAddedChild.hierarchyImgs.length - 1; ++i)
            {
                var prevAddedChildImg = prevAddedChild.hierarchyImgs[i];
                var img = createHierarchyImage();
                setSrc(img, prevAddedChildImg.src);
                img.pointsTop = prevAddedChildImg.pointsTop;
                img.pointsBottom = prevAddedChildImg.pointsBottom;
                img.pointsRight = prevAddedChildImg.pointsRight;
                img.pmState = prevAddedChildImg.pmState;
                
                child.hierarchyImgs[ child.hierarchyImgs.length ] = img;
                child.lineDiv.insertBefore(img, child.icon);
            }
            
            // change last hierarchy image of prevAddedChild from |_ to |-
            var lastHierarchyImg = prevAddedChild.hierarchyImgs[ prevAddedChild.hierarchyImgs.length - 1 ];
            lastHierarchyImg.pointsBottom = true;
            setSrc(lastHierarchyImg, genHierarchyImageSrc(lastHierarchyImg));
                        
            // change hierarchy images of prevAddedChild's children on it's last
            // level to |
            prevAddedChild.addHierarchyTBLine(prevAddedChild.hierarchyImgs.length - 1);
        }
        else
        {
            // this is a first child. So copy `level-2`
            // hierarchy images from parent, i.e. this.
            
            for (var i = 0; i < this.hierarchyImgs.length - 1; ++i)
            {
                var parentImg = this.hierarchyImgs[i];
                var img = createHierarchyImage();
                setSrc(img, parentImg.src);
                img.pointsTop = parentImg.pointsTop;
                img.pointsBottom = parentImg.pointsBottom;
                img.pointsRight = parentImg.pointsRight;
                img.pmState = parentImg.pmState;
                
                child.hierarchyImgs[ child.hierarchyImgs.length ] = img;
                child.lineDiv.insertBefore(img, child.icon);
            }
            
            if (this.hierarchyImgs.length > 0) // we are not root
            {
                // change last hierarchy image of parent (i.e. this): add minus to it
                var lastHierarchyImg = this.hierarchyImgs[ this.hierarchyImgs.length - 1];
                lastHierarchyImg.pmState = pmMinus;
                setSrc(lastHierarchyImg, genHierarchyImageSrc(lastHierarchyImg));
                lastHierarchyImg.owner = this;
                lastHierarchyImg.onclick = new Function("e", "this.owner.processPMClick(e);");
                
                // make decision on image on `level-1`. It depends on parent's (ie this)
                // image on same level.
                var parentL1HierarchyImg = lastHierarchyImg;
                var l1HierarchyImg = createHierarchyImage();
                if (parentL1HierarchyImg.pointsBottom)
                {
                    l1HierarchyImg.pointsTop = true;
                    l1HierarchyImg.pointsBottom = true;
                }
                setSrc(l1HierarchyImg, genHierarchyImageSrc(l1HierarchyImg));
                child.hierarchyImgs[ child.hierarchyImgs.length ] = l1HierarchyImg;
                child.lineDiv.insertBefore(l1HierarchyImg, child.icon);
            }
        }
        
        // in any case on last level our child will have icon |_
        var img = createHierarchyImage();
        img.pointsTop = true;
        img.pointsRight = true;
        setSrc(img, genHierarchyImageSrc(img));
        
        child.hierarchyImgs[ child.hierarchyImgs.length ] = img;
        child.lineDiv.insertBefore(img, child.icon);
        
        return child;
    }
    
    this.lastChild = function()
    {
        return this.children[ this.children.length - 1 ];
    }
    
    this.child = function(text)
    {
        var ret = null;
        for (var i = 0; i < this.children.length; ++i)
            if (this.children[i].textElement.data == text)
            {
                ret = this.children[i];
                break;
            }
            
        return ret;
    }
    
    this.addHierarchyTBLine = function(level)
    {
        for (var i = 0; i < this.children.length; ++i)
        {
            var img = this.children[i].hierarchyImgs[level];
            img.pointsTop = true;
            img.pointsBottom = true;
            setSrc(img, genHierarchyImageSrc(img));
            this.children[i].addHierarchyTBLine(level);
        }
    }
    
    this.expand = function()
    {
        var img = this.hierarchyImgs[ this.hierarchyImgs.length - 1 ];
        
        if (img.pmState == pmPlus)
        {
            img.pmState = pmMinus;
            setSrc(img, genHierarchyImageSrc(img));
            
            for (var i = 0; i < this.children.length; ++i)
                this.children[i].domEntry.style.display = "";
        }
    }
    
    this.collapse = function()
    {
        var img = this.hierarchyImgs[ this.hierarchyImgs.length - 1 ];
        
        if (img.pmState == pmMinus)
        {
            img.pmState = pmPlus;
            setSrc(img, genHierarchyImageSrc(img));
            
            for (var i = 0; i < this.children.length; ++i)
                this.children[i].domEntry.style.display = "none";
        }
    }
    
    this.toggle = function()
    {
        var img = this.hierarchyImgs[ this.hierarchyImgs.length - 1 ];
        if (img.pmState == pmMinus)
            this.collapse();
        else
            this.expand();
    }
    
    this.select = function()
    {
        if (this.owner.selection != this)
        {
            if (this.owner.selection)
                this.owner.selection.setHighlight(hlNone);
                
            this.owner.selection = this;
            this.setHighlight(hlSelected);
        }
    }
    
    this.setHighlight = function(mode)
    {
        if (mode == hlNone)
        {
            this.textSpan.style.backgroundColor = "";
            this.textSpan.style.color = "";
            this.textSpan.style.border = "";
        }
        else if (mode == hlGrey)
        {
            this.textSpan.style.backgroundColor = "#aaaaaa";
            this.textSpan.style.color = "";
            this.textSpan.style.border = "";
        }
        else if (mode == hlSelected)
        {
            this.textSpan.style.backgroundColor = "3399cc";
            this.textSpan.style.color = "white";
            this.textSpan.style.border = "dotted 1px red";
        }
    }
    
    this.setOnclick = function(proc)
    {
        this.onclick = proc;
    }
    
    this.setRef = function(url)
    {
        if (this.anchor)
            this.anchor.href = url;
    }
    
    this.processPMClick = function(e)
    {
        this.toggle();
        
        // prevent this line selection, stop bubbling
        if (e)
            e.stopPropagation(); // Mozilla way
        if (window.event)
            window.event.cancelBubble = true; // IE way
    }
    
    this.processOnclick = function()
    {
        this.select();
        if (this.onclick instanceof Function)
            this.onclick();
    }
    
    ///////////////////////////////////////////////////////////////////////////
    if (iconSrc)
        this.icon.src = iconSrc;
    else
    {
        this.icon.width = 0;
        this.icon.height = 0;
    }
    
    this.icon.style.verticalAlign = "middle";
    this.icon.style.position = "relative";
    this.icon.style.top = "-1px";
    this.icon.style.paddingRight = "2px";
    
    if (!hrefMode)
    {
        this.textSpan.appendChild( this.textElement );
    }
    else
    {
        this.anchor = document.createElement("a");
        this.anchor.appendChild( this.textElement );
        this.textSpan.appendChild( this.anchor );
    }
    
    this.lineDiv.appendChild( this.icon );
    this.lineDiv.appendChild( this.textSpan );
    this.domEntry.appendChild( this.lineDiv );
    
    this.lineDiv.owner = this;
    
    if (!hrefMode)
        this.lineDiv.onclick = new Function("this.owner.processOnclick();");
}