I once built a nested message board (using ASP & VBScript, bah). In order to print all messages, nested, I created a recursive function that queries the DB for all messages of a certain parent, prints the messages and calls the same function itself with parent ID of the current message. That was a disaster to the server, as a single page could execute about 100 queries.

I decided to move the rendering logic to the client side, since SEO wasn’t such a big of a deal for this, and the load time was critical. So at first I queried the messages I wanted (I could do this in one query thanks to the DB structure), I pushed the data to JavaScript, as a flat, unordered array of objects, using JSON:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var treeData=[
  { "id":1,    "parentId":null,  "title":"1"            },
  { "id":2,    "parentId":1,     "title":"1.1"          },
  { "id":3,    "parentId":null,  "title":"2"            },
  { "id":4,    "parentId":1,     "title":"1.2"          },
  { "id":5,    "parentId":1,     "title":"1.3"          },
  { "id":6,    "parentId":4,     "title":"1.2.1"        },
  { "id":7,    "parentId":4,     "title":"1.2.2"        },
  { "id":8,    "parentId":4,     "title":"1.2.3"        },
  { "id":9,    "parentId":null,  "title":"3"            },
  { "id":10,   "parentId":9,     "title":"3.1"          },
  { "id":11,   "parentId":10,    "title":"3.1.1"        },
  { "id":12,   "parentId":11,    "title":"3.1.1.1"      },
  { "id":13,   "parentId":11,    "title":"3.1.1.2"      },
  { "id":14,   "parentId":11,    "title":"3.1.1.3"      },
  { "id":15,   "parentId":13,    "title":"3.1.1.2.1"    }
];

The first approach to print this, nested, is using a recursive function in JavaScript, that, on every call needs to find the child messages based on their parentId, and actually mimic the server behavior (loop on root messages, query child messages, print, recursively).

But a way cooler approach, is to utilize the DOM itself for nesting, and to utilize a hash table indexer to store and find messages based on their id.

After creating all elements for all messages, a hash table (associative array, or just a JavaScript “object”) can be used to store a node based on its id, so when we want to find it, we can just use nodesById[someNodeId]:

1
2
3
4
var nodesById={};
for (var i=0;i<nodeCollection.length;i++) {
    nodesById[nodeCollection[i].data.id]=nodeCollection[i];
}

Then, the magic of having all elements nested happens when looping on all nodes. Each node has a parent_id except root nodes. We need to find the parent of current node in the nodesById object, by using nodesById[nodeParentId], and append the current node to its parent we found. Root nodes appended to a root container.

1
2
3
4
5
for (var i=0;i<nodeCollection.length;i++) {
    var node=nodeCollection[i];
    var parentElement=node.data.parentId ? nodesById[node.data.parentId].getChildrenElement() : rootNodeItemsContainer;
    parentElement.appendChild(node.element);
}

That way, we utilize the DOM for native nesting, without a recursion, and a simple single loop. The array doesn’t even have to be ordered, and no worries about requesting a node when its parent isn’t exist, as all of the elements are already created before.

The Entire Code

[iframe: style=“width: 100%; height: 860px” src=“http://jsfiddle.net/elado/yxmKZ/embedded/js,css,result”]

“`js var treeData=[ {“id”:1,”parentId”:null,”title”:”1”}, {“id”:2,”parentId”:1,”title”:”1.1”}, {“id”:3,”parentId”:null,”title”:”2”}, {“id”:4,”parentId”:1,”title”:”1.2”}, {“id”:5,”parentId”:1,”title”:”1.3”}, {“id”:6,”parentId”:4,”title”:”1.2.1”}, {“id”:7,”parentId”:4,”title”:”1.2.2”}, {“id”:8,”parentId”:4,”title”:”1.2.3”}, {“id”:9,”parentId”:null,”title”:”3”}, {“id”:10,”parentId”:9,”title”:”3.1”}, {“id”:11,”parentId”:10,”title”:”3.1.1”}, {“id”:12,”parentId”:11,”title”:”3.1.1.1”}, {“id”:13,”parentId”:11,”title”:”3.1.1.2”}, {“id”:14,”parentId”:11,”title”:”3.1.1.3”}, {“id”:15,”parentId”:13,”title”:”3.1.1.2.1”} ]; function NodeItem(data) { this.data=data; var container=document.createElement(“li”), title=container.appendChild(document.createElement(“div”)); title.innerHTML=this.data.title; this.element=container; } NodeItem.prototype={ // lazy children element creation - not all nodes have children getChildrenElement:function () { return this._childElement = this._childElement || this.element.appendChild(document.createElement(“ul”)); } }; // convert all nodes to NodeItem instance var nodeCollection=treeData.map(function (node) { return new NodeItem(node); }); // for faster lookup, store all nodes by their id var nodesById={}; for (var i=0;i

Comments