组件从零之文件树
很久没有更新了,今天就来谈谈文件树这个组件的一些原理和做法。首先看看文件树的组件效果,如下图就是一个很基本的文件树。
原理
首先讲一下原理,其实也还是通过 display:block和display:none 的实现。在一个无序列表中,有的li标签下包裹着子树,而有的文件树下没有,那些有子树的文件我们前面给他一个加和减的符号用于辨识。 对于有子树的文件夹,默认情况下是加号的图标,子树通过display:none而隐藏。当点击对应的li时,把相应的子树通过 display:block显示出来即可。而对于子树的缩放,则可以直接用padding-left进行缩放就好了。假设缩放量一层为20px,那么只要对应小一级别就缩放20px对应倍数就好了。
html代码清单
<div>
<ul>
<li>
<div style="padding-left: 0px">
<i class="icon icon-control icon-plus"></i>
<i class="icon icon-file"></i>
<span>全部文件</span>
</li>
</ul>
</div>
这个就是最基础的结构了。其中style中的padding-left直接控制缩放量。icon直接导入背景图片,通过改变雪碧图的background-position来 控制对应图标。当然假如有子级的话只要在li标签内的span标签后面继续嵌套即可。
下图是用到的图
css代码清单
.icon {
display: inline-block;
background: url(../img/icon.png) no-repeat;
vertical-align: middle;
}
.icon-minus {
width: 15px;
height: 15px;
margin: 0 4px;
background-position: -30px -22px;
}
.icon-plus {
width: 15px;
height: 15px;
margin: 0 4px;
background-position: -30px 0;
}
.icon-file {
width: 22px;
height: 18px;
margin-right: 5px;
background-position: 0 -22px;
}
假设已经有了下图这样的结果了,下一步就是通过js来控制点击行为。
简单来说就是给有子代的li绑定事件监听,通过更改类名实现 display:block和display:none ,同时更换加减号图标。 通过这种方法,我们能实现方法,但是每次假如想添加子数,就只能通过臃长的代码来一步一步解决。 所以下面讲讲如何通过js(jq),来实现便捷一点的文件树。
通过JavaScript实现复杂一点的文件树
第一步首先是把用到的样式和结构完成好。
css代码清单
* { margin: 0; padding: 0; }
body{
font-size: 14px;
color: #333;
font-family: "microsoft yahei";
}
ul, li { list-style-type: none;}
#treeView {
width: 600px;
margin: 20px auto 0;
border: 1px solid #f2f2f2;
}
.treeNode {
height: 32px;
border: 1px solid #fff;
border-width: 1px 0;
cursor: pointer;
font-size: 0;
}
.treeNode:hover {
background: #f4f9fd;
border-color: #e5f0fb;
}
.treeNode-cur,
.treeNode-cur:hover {
background: #e5f0fb;
border-color: #BBD4EF #fff;
}
.icon {
display: inline-block;
background: url(../img/icon.png) no-repeat;
vertical-align: middle;
}
.icon-minus {
width: 15px;
height: 15px;
margin: 0 4px;
background-position: -30px -22px;
}
.icon-plus {
width: 15px;
height: 15px;
margin: 0 4px;
background-position: -30px 0;
}
.icon-file {
width: 22px;
height: 18px;
margin-right: 5px;
background-position: 0 -22px;
}
.title {
position: relative;
top: 6px;
font-size: 14px;
}
.treeNode-empty .icon-minus {
background-position: -999px -999px;
}
.treeNode-empty .icon-plus {
background-position: -999px -999px;
}
#treeView ul.none {
display: none;
}
结构上,我们只需要一行代码,因为后续的代码都是通过js渲染html结构。
html代码清单
<div id="treeView"></div>
第二步,新建一个js文件保存用到文件树数据
不同于用html上直接写结构,对于每一个数的节点,我们通过数组记录,每一个数组子项有三个属性, 分别是id,记录第几级的pid,还有这个子项的标题(文字)显示。
js代码清单
var data = {
files: [
{
id: 0,
pid: -1,
title: '全部文件'
},
{
id: 1,
pid: 0,
title: '我的图片'
},
{
id: 2,
pid: 0,
title: '我的音乐'
},
{
id: 3,
pid: 0,
title: '我的电影'
},
{
id: 4,
pid: 0,
title: '我的书籍'
},
{
id: 11,
pid: 1,
title: '风景'
},
{
id: 12,
pid: 1,
title: '人物'
},
{
id: 13,
pid: 1,
title: '动物'
},
{
id: 41,
pid: 4,
title: 'JavaScript'
},
{
id: 42,
pid: 4,
title: 'Java'
},
{
id: 43,
pid: 4,
title: 'PHP'
}
]
};
对应的最外层就是全部文件的就是-1层,然后点开显示的就是第0层。对应的pid为1即pid为0的那一层里面第一个子树的子树。 (好像有点绕,其实看图可以看得出来!!)
所以你想添加,删除或者修改文件树,直接通过在这个文件中修改即可,而不是去到html文件中添加一大堆的代码。
第三步,获取刚才js中保存的数据。
这里我另外新建了一个文件用来保存和获取文件树的数据。
js代码清单
function getLevelById(data,id) {
return getParents(data,id).length;
}
function hasChilds(data,id){
return getChildById(data,id).length !== 0;
}
function getChildById(arr,pid){
var newArr = [];
for( var i = 0; i < arr.length; i++ ){
if( arr[i].pid == pid ){
newArr.push(arr[i]);
}
};
return newArr;
}
function getParents(data,currentId){
var arr = [];
for( var i = 0; i < data.length; i++ ){
if( data[i].id == currentId ){
arr.push(data[i]);
arr = arr.concat(getParents(data,data[i].pid))
break;
}
}
return arr;
}
第四步,设置添加到html中的模板。
要设置html模板,我们首先就得要获取这个容器treeView和保存的数据,然后我们根据最简单实现的文件树中的一个节点为模板, 把可以修改的内容替换成保存的数据。还要获取对应的图标,当发生变化和默认时对应的样子。
第五步,对每个节点设置点击事件
这一步最主要就是获取节点和判断其有没有子树,有和无又是对应两个不同的处理。
下面是这两部最终的代码
js代码清单
function index(){
var treeView = document.getElementById("treeView");
var treeData=data.files;
treeView.innerHTML = treeHtml(treeData, -1); // 初始化
var fileItem=document.getElementsByClassName("treeNode");
var root_icon=fileItem[0].getElementsByClassName("icon").item(0); //获取加号图标
root_icon.className = 'icon icon-control icon-minus'; //把图标换成减图标
tools.each(fileItem, function (item) { //遍历每个子树节点
filesHandle(item); //每个节点设置操作
});
function treeHtml(fileData, fileId){ //html渲染
var _html='';
var children = getChildById(fileData, fileId); //获取子树数组
var hideChild = fileId > 0 ? 'none' : ''; //当id>0即非父树时隐藏所有子树
_html += '<ul class="'+hideChild+'">';
children.forEach(function (item, index) { //遍历每个子代渲染html
var hasChild = hasChilds(fileData, item.id); //判断是否有子树
var className = hasChild ? '' : 'treeNode-empty'; //无子树则为空子树样式
var treeRoot_cls = fileId === -1 ? 'treeNode-cur' : ''; //设置子树类名
var level=getLevelById(fileData, item.id); //获取子树在第几层级
var distance=(level-1) * 20 +"px"; //设置子树的缩放量
// html模板内容添加
_html += `
<li>
<div class="treeNode ${className} ${treeRoot_cls}" style="padding-left: ${distance}" data-file-id="${item.id}">
<i class="icon icon-control icon-plus"></i>
<i class="icon icon-file"></i>
<span class="title">${item.title}</span>
</div>
${treeHtml(fileData, item.id)}
</li>`;
});
_html += '</ul>'; //嵌套ul标签
return _html;
}
function filesHandle(item) {
tools.addEvent(item, 'click', function () { //每个节点添加点击事件
// var treeNode_cur=document.getElementsByClassName("treeNode-cur")[0];
var treeNode_cur = tools.$('.treeNode-cur')[0]; //获取节点
var fileId = item.dataset.fileId; //获取节点的id
var curElem = document.querySelector('.treeNode[data-file-id="'+fileId+'"]');
var hasChild = hasChilds(treeData, fileId); //是否有子数
var icon_control = tools.$('.icon', item)[0]; //符号
var openStatus = true; //状态
// treeNode_cur.classList.remove("treeNode-cur"); //移除样式类treeNode-cur
// curElem.classList.add("treeNode-cur"); //增加样式类treeNode-cur
tools.removeClass(treeNode_cur, 'treeNode-cur');
tools.addClass(curElem, 'treeNode-cur');
if (hasChild) {
openStatus = tools.toggleClass(item.nextElementSibling, 'none'); //如果没有子代时treeNode下个节点ul设置为none样式
if (openStatus) {
icon_control.className = 'icon icon-control icon-plus'; //默认状态true显示的是加号符号
} else {
icon_control.className = 'icon icon-control icon-minus'; //打开后状态已经是false了,这个时候显示减号
}
}
});
};
}
以上代码就实现了文件树效果了。原理其实很容易,但是需求不同所以做的方式也不同。 希望能对你有帮助,这个系列也当做我自己个人学习的方式继续更下去!!