最终效果图:
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,读取数据并建立整课树的数据模型,然后初始化整棵树的视图(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);
该组件已在IE6+,Firefox上测试通过。
有兴趣的朋友可以联系我,进行进一步的改进和扩展。