Click here to Skip to main content
15,879,095 members
Articles / Programming Languages / Javascript
Article

JavaScript Horizontal Tree

Rate me:
Please Sign up or sign in to vote.
4.85/5 (52 votes)
1 Jul 20063 min read 181.1K   4.5K   104   39
Horizontal treeview for JavaScript.

Sample Image - Horizontal Tree.png

Introduction

Recently, I had a task to make a horizontal tree using JavaScript. I thought that I would find millions of examples for a tree on the internet, but I didn't find anything useful. We all know the vertical tree which is the most commonly available on the internet, as it's used for many scenarios like navigation menus etc., but what if we wanted to represent the info in a horizontal tree format, which will help the user to have a more intensive view for the data on the tree? I didn't want to waste time building something from scratch, so I've took the code here and developed another version from it to draw a horizontal tree.

Using the code

The main idea is that instead of building tree nodes as a DIV under DIV, we will use table and table cells to represent a level of node, so whenever I start a new level, I will draw a new table, and for every node, I will put it in a table cell, for example:

JavaScript
str += ' <td valign=top align=center > '; 
str += this.node(cn, n);
str += ' </td> ';

Now, I think I should explain what the biggest problem I had when I developed this script, it's the lines between the nodes. So I started with a trivial idea or solution which is putting a border-top style for the table cell that contains a child node, and then I had to go with this idea and split each table cell that contains a child node into a new table with two rows: one for the lines and the other for the node text, icon and plus/minus button if it exists. The first row in that table is split into three cells: the first cell should have a border top in any of these two cases:

  1. the child node is a rightmost node.
  2. the child node is a middle node.

The second cell will contain a vertical line, and this line should be displayed in any case. The third cell should have a border top in any of these two cases:

  1. The child node is a leftmost node.
  2. The child node is a middle node.

So, I had to add three new properties to the tree node object: one if this node is a leftmost node or not, the second if this node is a rightmost node or not, and the third if the node is the only child or not. If the three values are false, then this node is a middle node. I had to keep track of how much children every parent has, and the index of the node between the parent and the children, and based on these numbers, I could decide which node is at the very left, which is at the very right, and which is an only child. And, here's the most effective part of the code:

JavaScript
// Creates the tree structure 
dTree.prototype.addNode = function(pNode) 
{ 
    var str = ''; 
    str += '<table border=0 ' + 
           'cellpadding=0 cellspacing=0 >'; 
    str += ' <tr>'; 
    var n=0; 
    if (this.config.inOrder) 
        n = pNode._ai; 

    var childsIndex =0;
    
    for (n; n<this.aNodes.length; n++)
    // loop through child nodes of each parent
    { 
        if (this.aNodes[n].pid == pNode.id) 
        { 
        var cn = this.aNodes[n]; 
        cn._p = pNode; cn._ai = n; 
        this.setCS(cn); 
        // Checks if a node has any children
        // and if it is the last sibling 
        cn._childIndex = childsIndex++;
        // set the index of the node
        // among its parent childs

        if(cn._p._children <= 1)
        // if this is the only child
            cn._isOnly; 
        else 
        { 
            if(cn._childIndex == 0)
            // if it's the very left node 
                cn._isLeft=true; 
            if(cn._childIndex == cn._p._children-1)
            // if it's the very right node 
                cn._isRight=true; 
        } 
        if (!cn.target && this.config.target) 
            cn.target = this.config.target; 
        if (cn._hc && !cn._io && this.config.useCookies) 
            cn._io = this.isOpen(cn.id); 
        if (!this.config.folderLinks && cn._hc) 
            cn.url = null; 
        if (this.config.useSelection && cn.id == 
            this.selectedNode && !this.selectedFound) 
        { 
            cn._is = true; this.selectedNode = n; 
            this.selectedFound = true; 
        } 
        str += ' <td valign=top align=center > '; 
        str += this.node(cn, n); 
        str += ' </td> '; 
        
        if (cn._ls) 
            break; 
        } 
    } 
    str += ' </tr>';
    str += '</table>';

    return str; 
    }; 

// Creates the node icon, url and text 
dTree.prototype.node = function(node, nodeId) 
{ 

    if(node._p._children <= 1)
        node._isOnly=true;

    var str = '<div class="dTreeNode" style="white-space:nowrap">'; 
    str += '<table border="0" cellpadding="0" ' + 
           'cellspacing="0" width="100%" >';
    str += '<tr>'; // first level of lines
    str += ' <td align="center" width="52%" ';
    
    if (this.root.id != node.pid) // if it's not a root node 
    { 
        if(this.config.useLines) // if we using line
        { 
            if(node._isOnly) // if it's the only child 
            { 
                str += ''; 
            } 
            else if(node._isLeft) // if it's the very left node 
            { 
                str += ''; 
            } 
            else if(node._isRight) // if it's the very right node 
            { 
                str += ' style="border-top-width:1px;' + 
                       'border-top-style:dotted;' + 
                       'border-top-color:Gray;" '; 
            } 
            else // if it's the middle node 
            { 
                str += ' style="border-top-width:1px;border' + 
                       '-top-style:dotted;border-top-color:Gray;" '; 
            } 
        } 
    } 
    str += ' >&nbsp; '; 

    str += ' </td>';
    str += ' <td valign="top" style="padding-top:0px;" ' + 
           'align="center" width="1%" ';
    str += ' >'; 
    
    if (this.root.id != node.pid)
    /// if this node isn't a first level node 
    { 
        str += '<img src="'; 
        if(this.config.useLines) 
        { 
            str += this.icon.line;
        } 
        else 
        { 
            str += this.icon.empty; 
        } 
        str += '" alt="" />'; 
    }


    str += ' </td>'; 
    str += ' <td align="center" width="52%" '; 

    if (this.root.id != node.pid) 
    { 
        if(this.config.useLines) 
        { 
            if(node._isOnly) 
                str += ' '; 
            else if(node._isLeft) 
                str += ' style="border-top-width:1px;border' + 
                       '-top-style:dotted;border-top-color:Gray;" '; 
            else if(node._isRight) 
                str += ''; 
            else 
                str += ' style="border-top-width:1px;border' + 
                       '-top-style:dotted;border-top-color:Gray;" '; 
        } 
    } 
    str += ' >&nbsp; '; 
    str += ' </td>'; 
    str += '</tr>'; 

    str += '<tr>'; 
    str += ' <td align="center" colspan="3">'; 
    str += '<table border=0 cellpadding="0" cellspacing="0" >'; 
    str += '<tr><td valign=top align=center>' + 
           this.indent(node, nodeId); 
    
    if (this.config.useIcons) 
    { 
        if (!node.icon) 
            node.icon = (this.root.id == node.pid) ? 
                         this.icon.root : ((node._hc) ? 
                         this.icon.folder : this.icon.node); 
        if (!node.iconOpen) 
            node.iconOpen = (node._hc) ? 
                             this.icon.folderOpen : 
                             this.icon.node; 
        if (this.root.id == node.pid) 
        { 
            node.icon = this.icon.root; 
            node.iconOpen = this.icon.root; 
        } 
        
        str += '<img id="i' + this.obj + nodeId + '" src="' + 
               ((node._io) ? node.iconOpen : node.icon) + '" alt="" />'; 
    } 
    str+="</td></tr><tr><td valign=top" + 
         " align=center style='white-space:nowrap'>";

    if (node.url) 
    { 
        str += '<a id="s' + this.obj + nodeId + '" class="' + 
               ((this.config.useSelection) ? ((node._is ? 'nodeSel' : 
                 'node')) : 'node') + '" href="' + node.url + '"'; 
        if (node.title) 
            str += ' title="' + node.title + '"'; 
        if (node.target) 
            str += ' target="' + node.target + '"'; 
        if (this.config.useStatusText) 
            str += ' onmouseover="window.status=\'' + node.name + 
                   '\';return true;" ' + 
                   'onmouseout="window.status=\'\';return true;" '; 
        if (this.config.useSelection && ((node._hc && 
            this.config.folderLinks) || !node._hc)) 
            str += ' onclick="javascript: ' + this.obj + 
                   '.s(' + nodeId + ');"'; 
        str += '>'; 
    } 
    else if ((!this.config.folderLinks || !node.url) && 
               node._hc && node.pid != this.root.id) 
            str += '<a href="javascript: ' + this.obj + 
                   '.o(' + nodeId + ');" class="node">'; 

    str += node.name;
    if (node.url || ((!this.config.folderLinks || 
                      !node.url) && node._hc)) 
    str += '</a>'; 
    str += '</div>'; 
    str += '</td></tr></table>'; 
    str += '</td></tr></table>'; 

    if (node._hc) // if this node have children
    { 
        str += '<div id="d' + this.obj + nodeId + '" class="clip" ' + 
               'style="display:' + ((node._io) ? 'block' : 'none') + ';">'; 
        str += '<table border=0 cellpadding=0 cellspacing=0>';
        str += '<tr><td height="9" align="center"><img src="' + 
               this.icon.smallLine+'" alt="" border=0></td></tr>';
        str += '<tr><td>';

        str += this.addNode(node); 

        str += '</td></tr></table>';
        str += '</div>'; 
    } 
    this.aIndent.pop(); 
    return str; 
};

Points of interest

The most important thing I learned from this script is how much recursion can be effective in JavaScript. Also, it was a very good experiment to go with a trivial idea to build good lines in a tree. I think it's pretty stable now.

History

This is my first article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Egypt Egypt
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWant to select a node Pin
Member 147832379-Apr-20 2:58
Member 147832379-Apr-20 2:58 
QuestionWordpress Plugin Pin
Member 1089901621-Jun-14 23:43
Member 1089901621-Jun-14 23:43 
QuestionThanks for such a great work Pin
Member 1043622425-Mar-14 21:59
professionalMember 1043622425-Mar-14 21:59 
QuestionJavaScript Horizontal Tree Pin
Biswa_c23-Apr-12 2:33
Biswa_c23-Apr-12 2:33 
BugConnection line can not be aligned when nodes is too many Pin
maxmas22-Feb-12 21:14
maxmas22-Feb-12 21:14 
GeneralMy vote of 5 Pin
Member 786771220-Jun-11 0:55
Member 786771220-Jun-11 0:55 
Questionhorizontal tree with javascript Pin
Badari Narayana12-Jan-11 18:32
Badari Narayana12-Jan-11 18:32 
GeneralRe: horizontal tree with javascript Pin
Bassam Abdul-Baki18-Jan-11 3:04
professionalBassam Abdul-Baki18-Jan-11 3:04 
How will you know which child out of the 100 to display if you do not see them all first?

GeneralList items instead of tables Pin
anand sueman14-Dec-10 1:15
anand sueman14-Dec-10 1:15 
GeneralFamily Tree Pin
Bahraini015-Apr-10 23:44
Bahraini015-Apr-10 23:44 
QuestionHow to generate dynamic node from the database in horizontal tree view. please help me Pin
ritesh suhane1-Mar-10 19:20
ritesh suhane1-Mar-10 19:20 
QuestionHowto dynamicly add a node to the tree Pin
hacking_mike27-Aug-09 1:24
hacking_mike27-Aug-09 1:24 
AnswerRe: Howto dynamicly add a node to the tree Pin
hacking_mike28-Aug-09 4:48
hacking_mike28-Aug-09 4:48 
GeneralPlease Help Pin
luo_fen_fang21-May-09 19:16
luo_fen_fang21-May-09 19:16 
GeneralHuge Data Problem. Pin
hymns27-Apr-09 5:24
hymns27-Apr-09 5:24 
Generalthanks very much Pin
21cn_zy24-Jun-08 16:38
21cn_zy24-Jun-08 16:38 
GeneralGreat work, but look this Pin
zenhaust27-Apr-08 12:47
zenhaust27-Apr-08 12:47 
GeneralVertical Menu limit Pin
Boon8230-Dec-07 18:33
Boon8230-Dec-07 18:33 
GeneralRe: Vertical Menu limit Pin
LYY15-May-08 17:22
LYY15-May-08 17:22 
GeneralRe: Vertical Menu limit Pin
girishoak4-Jan-09 18:44
girishoak4-Jan-09 18:44 
GeneralRe: Vertical Menu limit Pin
superb12-May-11 23:14
superb12-May-11 23:14 
Generalgreat work Pin
adi_99924-Sep-07 1:33
adi_99924-Sep-07 1:33 
Questionfunctioning of the code Pin
seralan27-Jul-07 4:53
seralan27-Jul-07 4:53 
AnswerRe: functioning of the code Pin
adi_9994-Oct-07 21:25
adi_9994-Oct-07 21:25 
QuestionPrint Problem Pin
shelal16-Aug-06 22:45
shelal16-Aug-06 22:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.