一直想弄清楚windows注册表里面的那个树用js怎么搞出来,昨天倒腾了一天,终于小有成就,实现了一棵拥有基本功能的js树。
以前看树感觉最复杂的部分就是那些各种线条,各种加号减号到底怎么组织出来的,用css又怎么画出一棵树来。昨天看了司徒正妹的博客,有了一些灵感,然后就试着自己摆弄了一下。
司徒用的是纯div的形式,而我用的dl列表的形式。用dl有个好处,就是即使没有样式,也能将就看,而且在语义化上也更说得过去。
好了,废话到此,下面就看怎么样一步步实现的吧
首先我们来试着画一棵静态树,我们把树分为文件夹和文件,文件夹的图标和名字放到dt里,文件夹的内容放到dd里,如果文件夹里还有子文件夹,那么子文件也是一个dl,以此类推
然后是加号减号、文件左边的虚线、左边的细线:
加号减号就是图片,但是要注意的是不同位置的加号减号四周的虚线是不一样的,一共有四种情况:
第一种:在一棵树的跟节点上,且这个根节点没有兄弟节点
第二种:在一棵树的根节点上,且这个根节点还有兄弟节点
第三种:在一棵树的分支节点上,前面后面均有兄弟节点
第四种:在一棵树的末尾分支上,只有前面有兄弟节点
文件左边的虚线只有两种情况:
第一种:非最后一个文件
第二种:最后一个文件
最后是列表左边的那天长长的竖线,这条竖线的实现其实很简单,给dl设置一个背景,然后repeat:repeat-y即可,不过只有非末尾节点才有这条线(因为这条线就是连接各个节点的线):
有了这些基本图标,我们就可以试着画一颗树了:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>实现一棵简单的树BY libmw</title> <meta name="Generator" content="EditPlus"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <base href="http://images.cnblogs.com/cnblogs_com/libmw/399586/"> <style type="text/css"> body{ font-size:12px; } dl{ margin:0; padding:0; } dl .line{ background:url('o_vertLine.gif') repeat-y; margin:0; padding:0; } dl.open dd{ display:block; } dl.close dd{ display:none; } dt{ line-height:22px; height:22px; } dd{ margin-left: 16px; } img{ vertical-align: middle; background-color: white; } </style> </head> <body> <dl class="open" tree-position="root"> <dt><span><img src="o_folderNodeOpenLastFirst.gif" icon-type="folderHandle" /><img src="o_folder.gif" /></span>北京</dt> <dd> <dl class="line open" tree-position="branch"> <dt><span><img src="o_folderNodeOpen.gif" icon-type="folderHandle" /><img src="o_folder.gif" /></span>海淀</dt> <dd><span><img src="o_docNode.gif" /><img src="o_doc.gif" /></span>五道口</dd> <dd><span><img src="o_docNodeLast.gif" /><img src="o_doc.gif" /></span>知春路</dd> </dl> </dd> <dd> <dl class="open" tree-position="leaf"> <dt><span><img src="o_folderNodeOpenLast.gif" icon-type="folderHandle" /><img src="o_folder.gif" /></span>朝阳</dt> <dd><span><img src="o_docNode.gif" /><img src="o_doc.gif" /></span>大望路</dd> <dd><span><img src="o_docNodeLast.gif" /><img src="o_doc.gif" /></span>望京</dd> </dl> </dd> <dl> </body> </html>
注意我给dl元素添加的open和close的样式,这两个样式可以用来控制文件夹的展开和收起。
随后的工作就是想办法用js来生成这些html代码了,为了使用方便,我们数据源的格式使用json,json里的对象或者数组代表文件夹,其他代表文件。这样递归循环这个json,最后把整棵树的html代码生成好,然后添加到容器里面。
在递归过程中,我们需要检测节点的位置,从而得到相应的节点图标,这个其实不难,最后我们把这个位置信息放到dl节点的自定义属性tree-position里,方便在展开和收起树的时候通过这个属性得到相应的图标。
最后我们给树的外层容器添加click方法,click后根据父节点的className来判断是收起还是展开树。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title> New Document </title> <meta name="Generator" content="EditPlus"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <style type="text/css"> body{ font-size:12px; } dl{ margin:0; padding:0; } dl .line{ background:url('http://images.cnblogs.com/cnblogs_com/libmw/399586/o_vertLine.gif') repeat-y; margin:0; padding:0; } dl.open dd{ display:block; } dl.close dd{ display:none; } dt{ line-height:22px; height:22px; } dd{ margin-left: 16px; } img{ vertical-align: middle; background-color: white; } </style> <base href="http://images.cnblogs.com/cnblogs_com/libmw/399586/"> </head> <body> <dl id="test"></dl> </body> <script> config = { icon: { folder: { root: ['o_folderNodeOpenLastFirst.gif', 'o_folderNodeLastFirst.gif'] ,branchroot: ['o_folderNodeOpenFirst.gif', 'o_folderNodeFirst.gif'] ,branch: ['o_folderNodeOpen.gif', 'o_folderNode.gif'] ,branchlast: ['o_folderNodeOpenLast.gif', 'o_folderNodeLast.gif'] } ,file: { branchleaf: 'o_docNode.gif' ,leaf: 'o_docNodeLast.gif' } } } function addClass(node,className){ node.className += ' ' + className; } function replaceClass(node,oldClassName,newClassName){ node.className = node.className.replace(oldClassName,newClassName); } function hasClass(node,className){ var reg = new RegExp('\\b' +className+ '\\b'); return reg.test(node.className); } createFolder = function(json, status, isRoot){ //{'海淀':['五道口','知春路'],'朝阳':['大望路','望京']}的第一次循环,item为['五道口','知春路'],key为海淀,cur为1,num为2 //['五道口','知春路','上地']的第一次循环,item为五道口,key为0,cur为1,num为3 function getNode(item, key, cur, num){//item待生成的节点(对象或者字符串), key当前对象的对象名,cur:当前第几次循环,num:总共几次循环 var htm = ''; if(typeof item == 'object'){//生成文件夹形式的dd节点 position = isRoot && cur == 1?(num>1?'branchroot':'root'):(cur==num?'branchlast':'branch');//根文件夹判断是独根还是多个分支的根,分支文件夹判断是否为最后一个分支 htm += '<dd><dl class="'+status + (position=='branch'?' line':(position=='branchroot'?' line':''))+'" tree-position="'+position+'">'; htm += '<dt><span><img src="'+config.icon.folder[position][status=='open'?0:1]+'" icon-type="folderHandle" /><img src="o_folder.gif" /></span>'+key+'</dt>'; htm += createFolder(item, status); htm += '</dl></dd>'; }else{//生成文件形式的dd节点 htm += '<dd><span><img src="'+config.icon.file[cur==num?'leaf':'branchleaf']+'" /><img src="o_doc.gif" /></span>'+item+'</dd>'; } return htm; } var html = ''; if(Object.prototype.toString.apply(json) == '[object Array]'){//数组 for(i=0,len=json.length;i<len;i++){ if(typeof json[i] == 'object') html += createFolder(json[i], status); else html += getNode(json[i], i, i+1, len); } }else if(typeof json == 'object'){//对象 var len = 0,cur=1; for(var key in json){len++;} for(var key in json){ html += getNode(json[key], key, cur++, len); } }else{ return ''; } return html; } create = function(node, json, status){ var html = '<dl>'; html += createFolder(json, status, true); html += '</dl>'; node.innerHTML = html; node.onclick = function(e){ var evt = e || window.event, target = evt.target || evt.srcElement; if(target.nodeName == 'IMG' && target.getAttribute('icon-type') == 'folderHandle'){ var ctn = target.parentNode.parentNode.parentNode, isOpen = hasClass(ctn, 'open'); replaceClass(ctn, isOpen?'open':'close', isOpen?'close':'open'); target.src = config.icon.folder[ctn.getAttribute('tree-position')][isOpen?1:0]; } } } var json = { '北京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门'] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] } ,'北的京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] } ,'北的3京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] } ,'北2的京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] } ,'北g的京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'北的京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] } ,'朝阳':['大望路','望京'] ,'北的京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] ,'北的京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] } } } ,'北的f京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] ,'北的京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] } } ,'北e的京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] ,'北的京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] } } ,'北的w京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] ,'北的京': { '海淀': { '13号': ['五道口','知春路'] ,'4号': ['西直门',{'望京':['望京东','望京西']}] ,'90号': ['未开通'] } ,'朝阳':['大望路','望京'] } } } create(document.getElementById('test'),json,'open') </script> </html>
这样,一棵完整的树就生成了。如果需要链接,直接把json的属性值改为html字符串就行。