最终效果图:
UI说明:针对table本身进行增强的tree table组件。
tree的数据来源是单元格内a元素的自定义属性:level和type。具体代码如下:
- <table id="treeGrid" border="0" cellpadding="0" cellspacing="0">
- <THEAD>
- <tr><th>Department</th><th>EmployeeID</th><th>position</th></tr>
- </THEAD>
- <TBODY>
- <tr><td width="250px"><a level="0" type="node" href="javascript:void(0);">Dept4</a></td><td>-</td><td>-</td></tr>
- <tr><td><a level="1" type="leaf" href="javascript:void(0);">fanggw</a></td><td>c3025</td><td>MASTER</td></tr>
- <tr><td><a level="1" type="node" href="javascript:void(0);">team1</a></td><td>-</td><td>-</td></tr>
- <tr><td><a level="2" type="leaf" href="javascript:void(0);">zhanghy</a></td><td>c3268</td><td>SE</td></tr>
- <tr><td><a level="2" type="leaf" href="javascript:void(0);">chenf</a></td><td>c3401</td><td>SE</td></tr>
- </TBODY>
- </table>
<table id="treeGrid" border="0" cellpadding="0" cellspacing="0">
<THEAD>
<tr><th>Department</th><th>EmployeeID</th><th>position</th></tr>
</THEAD>
<TBODY>
<tr><td width="250px"><a level="0" type="node" href="javascript:void(0);">Dept4</a></td><td>-</td><td>-</td></tr>
<tr><td><a level="1" type="leaf" href="javascript:void(0);">fanggw</a></td><td>c3025</td><td>MASTER</td></tr>
<tr><td><a level="1" type="node" href="javascript:void(0);">team1</a></td><td>-</td><td>-</td></tr>
<tr><td><a level="2" type="leaf" href="javascript:void(0);">zhanghy</a></td><td>c3268</td><td>SE</td></tr>
<tr><td><a level="2" type="leaf" href="javascript:void(0);">chenf</a></td><td>c3401</td><td>SE</td></tr>
</TBODY>
</table>
根据上述数据源结构,先遍历该table,读取数据并建立整课树的数据模型,然后初始化整棵树的视图(node和leaf的显示由css样式定义,方便修改,如果需要可以进一步在初始化的时候由外部代码指定),并关联节点的click处理程序。
下面是主要的实现代码:
- var Class = {
- create: function() {
- return function() {
- this.initialize.apply(this, arguments);
- }
- }
- }
- var Node = Class.create();
- Node.prototype = {
- initialize: function(link, level, type) {
- this.name = link.innerText;
- this.id;
- this.link = link;
- this.type = type;
- this.level = level;
- this.isOpen = true;
- this.isClicked = false;
- this.root;
- this.img; //clicked img's path
- this.parent;
- this.children = new Array();
- this.getChildren();
- },
- getChildren: function() {
- if (this.type == "node") {
- //alert(this.link.innerText);
- var dataRows = document.getElementById("treeGrid").getElementsByTagName("TBODY")[0].getElementsByTagName("TR");
- var pushFlag = false;
- for(var j=0; j<dataRows.length; j++) {
- var linkTag = dataRows[j].firstChild.firstChild;
- var level = linkTag.getAttribute("level");
- var type = linkTag.getAttribute("type");
- if (!pushFlag) {
- if (linkTag == this.link) {
- pushFlag = true;
- }
- continue;
- }
- //alert("cur lvl:"+level+"; type:"+type +" ;parentLvl:" +(parseInt(this.level)+1));
- if (level == (parseInt(this.level)+1)) {
- //alert("push node's lvl:"+level+"; type:"+type);
- var leaf = new Node(linkTag, level, type);
- leaf.parent = this;
- leaf.id = level+"_"+j;
- this.children.push(leaf);
- } else if (level == this.level) {
- break;
- } else {
- continue;
- }
- }
- }
- //for (var i=0; i<this.children.length; i++) {
- //this.children[i].parent = this;
- //}
- //alert("childs:"+this.children.length);
- },
- getNext: function() {
- var next = null;
- //alert(this.name);
- if (this.parent) {
- for (var i=0; i<this.parent.children.length; i++) {
- if (this.parent.children[i] == this && i < (this.parent.children.length-1)) {
- next = this.parent.children[i+1];
- break;
- }
- }
- }
- /*
- if (next)
- alert("next:"+next.name);
- else
- alert("current is last");
- */
- return next;
- },
- getCurrentRow: function() {
- return this.link.parentNode.parentNode.parentNode;
- },
- changeClickImg: function() {
- if (this.isOpen) {
- this.img.src = this.img.src.replace("minus", "plus");
- } else {
- this.img.src = this.img.src.replace("plus","minus");
- }
- this.isOpen = this.isOpen?false:true;
- },
- getInnerHTML: function() {
- var oFragment = document.createDocumentFragment();
- //make the indent img by level
- for (var lvl = this.level-1; lvl>0; lvl--) {
- var indentImg = document.createElement("img");
- //get parent node by level
- var parentNode = this.parent;
- for (var i=1; i<lvl; i++) {
- parentNode = parentNode.parent;
- }
- //alert(this.name+":"+parentNode.name);
- //alert(parentNode.getNext()?parentNode.getNext().name:"null");
- /* parent node has nextSibling insert vertical line img */
- if (parentNode.getNext()) {
- indentImg.src = "./images/I.gif";
- } else {
- /* parent node has nextSibling insert blank img */
- indentImg.src = "./images/blank.gif";
- }
- indentImg.align = "absbottom";
- oFragment.appendChild(indentImg);
- }
- //make the plus or minus img
- var img = document.createElement('img');
- var path;
- if (this.type == "node") {
- if (this.level == 0) {
- path = "./images/minus.gif";
- } else {
- if (this.children.length > 0) {
- if (this.getNext()) {
- path = "./images/Tminus.gif";
- } else {
- path = "./images/Lminus.gif";
- }
- }
- else {
- if (this.getNext()) {
- path = "./images/T.gif";
- } else {
- path = "./images/L.gif";
- }
- }
- }
- } else {
- if (this.getNext()) {
- path = "./images/T.gif";
- }
- else {
- path = "./images/L.gif";
- }
- }
- img.src = path;
- img.align = "absbottom";
- //set cursor pointer style to the minus/plus img
- img.style.cursor = "pointer"
- this.img = img;
- img.onclick = expand;
- oFragment.appendChild(img);
- oFragment.appendChild(this.link);
- var div = document.createElement("div");
- div.setAttribute("id", this.id);
- /* div css class set by type */
- div.className = (this.type=="node")?"node":"leaf";
- /* all node is margin to left by 10 pixel except root */
- if (this.level > 0) {
- div.style.marginLeft = "10px";
- }
- div.appendChild(oFragment);
- return div;
- }
- }
- /* global variable */
- //tree root
- var root;
- //all nodes of the tree
- var nodes = new Array();
- /* initialize the whole tree grid */
- function initTreeGrid() {
- //dataRows is the datasource of the tree
- var dataRows = document.getElementById("treeGrid").getElementsByTagName("TBODY")[0].getElementsByTagName("TR");
- //find the root of the tree
- for (var i=0; i<dataRows.length; i++) {
- var linkTag = dataRows[i].firstChild.firstChild;
- var level = linkTag.getAttribute("level");
- var type = linkTag.getAttribute("type");
- if (level == 0 && type == "node") {
- var root = new Node(linkTag, 0, "node");
- root.parent = null;
- root.id = "0_0";
- break;
- }
- }
- //put all node into 1-index array
- nodes.push(root);
- initNodes(root);
- //display the table tree
- for (var j = 0; j<nodes.length; j++) {
- dataRows[j].firstChild.appendChild(nodes[j].getInnerHTML());
- }
- }
- function initNodes(node) {
- for (var j=0; j<node.children.length; j++) {
- nodes.push(node.children[j]);
- if (node.children[j].children.length > 0) {
- initNodes(node.children[j]);
- }
- }
- }
- /* expand row elements by isOpen flag */
- function expand() {
- var currentDivId = event.srcElement.parentNode.id;
- var currentNode;
- //get the clicked node
- for(var i=0; i<nodes.length; i++) {
- if (currentDivId == nodes[i].id) {
- currentNode = nodes[i];
- break;
- }
- }
- //expand the clicked node
- expandChild(currentNode);
- //set the isClicked flag when the row minus img is clicked
- currentNode.isClicked = currentNode.isClicked?false:true;
- }
- function expandChild(currentNode) {
- //alert(currentNode.name);
- for (var i=0; i<currentNode.children.length; i++) {
- var child = currentNode.children[i];
- if (child.type == "node" && !child.isClicked) {
- expandChild(child);
- }
- child.getCurrentRow().style.display = currentNode.isOpen?"none":"block";
- }
- currentNode.changeClickImg();
- }
- /* utility function */
- function addEvent(obj, evType, fn) {
- /* adds an eventListener for browsers which support it
- Written by Scott Andrew: nice one, Scott */
- if (obj.addEventListener) {
- obj.addEventListener(evType, fn, true);
- return true;
- }
- else if (obj.attachEvent) {
- var r = obj.attachEvent("on"+evType, fn);
- return r;
- }
- else {
- return false;
- }
- }
- /* add load event to body element*/
- addEvent(window, "load", initTreeGrid);