树形结构

项目中需要对发送信息的对象做一个树形的管理,主要功能是全选和取消选中checkbox(比如选中所有子对象则父元素也自动选中,当子对象中的任何一个取消选中那么父元素也会自动取消选中,这里涉及到递归寻找父对象)。

这里是html结构,对应后述代码

代码
<ul id="root">
<li>
<label><a href="javascript:;">校讯通</a></label>
<ul class="two">
<li>
<label><a href="javascript:;">沈阳市</a></label>
<ul class="two">
<li>
<label><input type="checkbox" value="123456" /><a href="javascript:;">二小</a></label>
<ul class="two">
<li><label><input type="checkbox" value="123456"/><a href="javascript:;">二年级</a></label></li>
<li>
<label><input type="checkbox" value="123456"/><a href="javascript:;">三年级</a></label>
<ul class="two">
<li>
<label><input type="checkbox" value="123456"/><a href="javascript:;">一班</a></label>
<ul class="two">
<li><label><input type="checkbox" value="123456"/><a href="javascript:;">张三</a></label></li>
<li><label><input type="checkbox" value="123456"/><a href="javascript:;">王五</a></label></li>
</ul>
</li>
<li><label><input type="checkbox" value="123456"/><a href="javascript:;">实验班</a></label></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<label><a href="javascript:;">抚顺市</a></label>
<ul class="two">
<li><label><input type="checkbox" value="123456"/><a href="javascript:;">二小</a></label></li>
<li><label><input type="checkbox" value="123456"/><a href="javascript:;">一中</a></label></li>
</ul>
</li>
</ul>
</li>
</ul>

下面是用到的样式和标识图片

代码
body {padding:0; margin:0; font: 12px/1.5 Tahoma, Helvetica, Arial, sans-serif;}
body, h1, h2, h3, h4, h5, h6,dl, dt, dd, ul, ol, li,pre,form, fieldset, legend, button, input, textarea,th, td
{margin: 0;padding: 0;}
html
{ overflow:-moz-scrollbars-vertical; }
a
{ text-decoration: none;}
a:hover
{ text-decoration: underline;}

#root
{margin:10px;width:200px;overflow:hidden;}
#root li
{line-height:25px;}
#root .rem
{padding-left:16px;}
#root .add
{background:url(add.gif) -4px -31px no-repeat;}
#root .ren
{background:url(add.gif) -4px -7px no-repeat;}
#root li a
{color:#666666;padding-left:5px;outline:none;blr:expression(this.onFocus=this.blur()); }
#root li input
{vertical-align:middle;margin-left:5px;}
#root .two
{padding-left:20px;display:none;}

js代码里首先定义了一些工具方法,里面有部分注释,代码写的也比较简单,主要是讲下思路。

代码
function addEvent(el,name,fn){ //绑定事件
if(el.addEventListener) return el.addEventListener(name,fn,false);
return el.attachEvent('on'+name,fn);
}
function nextnode(node){ //寻找下一个兄弟并剔除空的文本节点
if(!node)return ;
if(node.nodeType == 1)
return node;
if(node.nextSibling)
return nextnode(node.nextSibling);
}
function prevnode(node){ //寻找上一个兄弟并剔除空的文本节点
if(!node)return ;
if(node.nodeType == 1)
return node;
if(node.previousSibling)
return prevnode(node.previousSibling);
}
function parcheck(self,checked){ //递归寻找父亲元素,并找到input元素进行操作
var par = prevnode(self.parentNode.parentNode.parentNode.previousSibling),parspar;
if(par&&par.getElementsByTagName('input')[0]){
par.getElementsByTagName(
'input')[0].checked = checked;
parcheck(par.getElementsByTagName(
'input')[0],sibcheck(par.getElementsByTagName('input')[0]));
}
}
function sibcheck(self){ //判断兄弟节点是否已经全部选中
var sbi = self.parentNode.parentNode.parentNode.childNodes,n=0;
for(var i=0;i<sbi.length;i++){
if(sbi[i].nodeType != 1) //由于孩子结点中包括空的文本节点,所以这里累计长度的时候也要算上去
n++;
else if(sbi[i].getElementsByTagName('input')[0].checked)
n
++;
}
return n==sbi.length?true:false;
}

第一个方法addEvent()用来绑定事件,这个方法如果严格来说还要考虑到特殊浏览器既不支持addEventListener,也不支持attachEvent绑定,那就会出现问题,目前只考虑到主流浏览器,没做到那么细。

nextnode()方法和prevnode()方法功能基本类似,只不过nextnode是寻找元素的前一个非空白字符节点,prevnode是寻找后一个非空白字符节点,如果使用到firstChild,lastChild也同样需要剔除空白字符节点,否则在处理的时候会相当的头疼。

parcheck()方法递归按照特定的方式去寻找父元素,如果找的到则对checkbox进行操作,然后再递归寻找上一级的父元素然后再操作checkbox,如此类推便可以实现多级的树形结构。

sibcheck()用来判断兄弟节点是否全部选中并返回布尔类型值。

接下来是主体执行函数

代码
addEvent(document.getElementById('root'),'click',function(e){//绑定input点击事件,使用root根元素代理
e = e||window.event;
var target = e.target||e.srcElement;
var tp = nextnode(target.parentNode.nextSibling);
switch(target.nodeName){
case 'A': //点击A标签展开和收缩树形目录,并改变其样式会选中checkbox
if(tp&&tp.nodeName == 'UL'){
if(tp.style.display != 'block' ){
tp.style.display
= 'block';
prevnode(target.parentNode.previousSibling).className
= 'ren'
}
else{
tp.style.display
= 'none';
prevnode(target.parentNode.previousSibling).className
= 'add'
}
}
break;
case 'SPAN': //点击图标只展开或者收缩
var ap = nextnode(nextnode(target.nextSibling).nextSibling);
if(ap.style.display != 'block' ){
ap.style.display
= 'block';
target.className
= 'ren'
}
else{
ap.style.display
= 'none';
target.className
= 'add'
}
break;
case 'INPUT'://点击checkbox,父亲元素选中,则孩子节点中的checkbox也同时选中,孩子结点取消父元素随之取消
if(target.checked){
if(tp){
var checkbox = tp.getElementsByTagName('input');
for(var i=0;i<checkbox.length;i++)
checkbox[i].checked
= true;
}
}
else{
if(tp){
var checkbox = tp.getElementsByTagName('input');
for(var i=0;i<checkbox.length;i++)
checkbox[i].checked
= false;
}
}
parcheck(target,sibcheck(target));
//当孩子结点取消选中的时候调用该方法递归其父节点的checkbox逐一取消选中
break;
}
});
window.onload
= function(){//页面加载时给有孩子结点的元素动态添加图标
var labels = document.getElementById('root').getElementsByTagName('label');
for(var i=0;i<labels.length;i++){
var span = document.createElement('span');
span.style.cssText
='display:inline-block;height:18px;vertical-align:middle;width:16px;cursor:pointer;';
span.innerHTML
= '&nbsp;'
span.className
= 'add';
if(nextnode(labels[i].nextSibling)&&nextnode(labels[i].nextSibling).nodeName == 'UL')
labels[i].parentNode.insertBefore(span,labels[i]);
else
labels[i].className
= 'rem'
}
}

 

addEvent方法把点击事件绑定在根节点上,然后再判断点击对象,如果是a标签则有展开或收缩子节点,因为用了label标签会自动把checkbox选中;如果是span标签(该标签用来表示当前状态是否为展开或者收缩)则只会展开或者收缩子节点;如果是checkbox并且有孩子结点的则把其下的checkbox全部选中或取消,如果有父亲节点并有checkbox的话则调用sibcheck()方法检查兄弟节点是否都被选中,如果是那么父亲节点也自动选中否则就取消,并递归继续寻找父亲节点。

最后在页面onload的时候每个label节点是否有下属节点如果有则在前面加上span标签用来标识。

posted @ 2010-10-12 16:33  冰封e族  阅读(561)  评论(0编辑  收藏  举报