
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:
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:
- the child node is a rightmost node.
- 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:
- The child node is a leftmost node.
- 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:
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++)
{
if (this.aNodes[n].pid == pNode.id)
{
var cn = this.aNodes[n];
cn._p = pNode; cn._ai = n;
this.setCS(cn);
cn._childIndex = childsIndex++;
if(cn._p._children <= 1)
cn._isOnly;
else
{
if(cn._childIndex == 0)
cn._isLeft=true;
if(cn._childIndex == cn._p._children-1)
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;
};
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>';
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 += '';
}
else if(node._isRight)
{
str += ' style="border-top-width:1px;' +
'border-top-style:dotted;' +
'border-top-color:Gray;" ';
}
else
{
str += ' style="border-top-width:1px;border' +
'-top-style:dotted;border-top-color:Gray;" ';
}
}
}
str += ' > ';
str += ' </td>';
str += ' <td valign="top" style="padding-top:0px;" ' +
'align="center" width="1%" ';
str += ' >';
if (this.root.id != node.pid)
{
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 += ' > ';
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)
{
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.