无限级tree的实现

无限级,无级限,你有多少层我不管。

在web开发中有时候会出现“无限层级”这样的需求,那什么是无限层级呢?什么时候需要考虑无限层级呢?最常见的是网页中的树菜单tree了。一般网页中的树菜单长这样。(UI本人写的,比较简陋,看客勿怪)

无限级tree的实现

之所以称之为“无限”,那主要是因为我们无法确定数据的层级深度到底有多少层。而我们前端拿到数据之后需要渲染页面,如果确定只有一两层的话,那直接使用一两层for循环就搞定了。但是无法确定有多少层的话,那我们就需要考虑多层级的情况了。

例如:我们有如下的tree数据结构,需要将它渲染到页面中去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var treeData = [{
name: '操作菜单',
children: [{
name: '用户管理'
}, {
name: '权限管理',
children: [{
name: '学生'
}, {
name: '老师'
}, {
name: '家长'
}]
}, {
name: '系统管理'
}]
}, {
name: '个人中心',
children: [{
name: '修改密码'
}, {
name: '修改头像'
}, {
name: '修改信息'
}]
}, {
name: '菜单 1',
children: [{
name: '菜单 1-1'
}, {
name: '菜单 1-2',
children: [{
name: '菜单 1-2-1',
children: [{
name: '菜单 1-2-1-1',
children: [{
name: '菜单 1-2-1-1-1'
}]
},{
name: '菜单 1-2-1-2'
}]
},{
name: '菜单 1-2-2'
}]
}, {
name: '菜单 1-3'
}, {
name: '菜单 1-4'
}]
}];

数据里面一层套一层,层层相扣,大多数真实的场景中我们无法确知具体的层级到底有多深。这里目前演示为例。

字符串拼接



通过上面的结构我们可以通过JavaScript字符串拼接的方式,将生成好的所有html字符串插入到dom节点中。

HTML

1
2
3
4
5
<div class="tree">
<ul class="tree-root" id="treeRoot">

ul>
div>

JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var getTree = function(node) {
var i = 0;
var len = node.length;
var html = '';

for(; i < len; i++){
if (node[i].children && node[i].children.length > 0) {
html += ['
  • ',
    '' + node[i].name + '',
    '
      ',
      getTree(node[i].children),
      '
    '
    ,

    '
  • '

    ].join('');
    } else {
    html += ['
  • ',
    '' + node[i].name + '',
    '
  • '

    ].join('');
    }
    }

    return html;
    };

    var tpl = getTree(treeData);

    $('#treeRoot').html(tpl);

    上面的代码中重点在getTree函数,getTree函数直接返回拼接好的dom字符串。而函数内部做了逻辑判断,如果有子级children的话,再嵌套一层,继续执行getTree函数。这一步很重要:内部再次调用getTree函数时的参数为node[i].children。这样做就简单地实现无限级嵌套的树菜单了。

    上面的代码虽然可行,但有一个很大的缺点就是不利于修改和维护。这还是简单的dom结构,如果结构比较复杂的话,那拼接起来可要仔仔细细的了。反正挺费劲儿,而且别人一看一大坨的字符串拼接肯定会头疼。这个问题很多前辈们也想到了,并且提出了解决方案,那就是前端模板引擎。

    模板引擎



    前端模板引擎实在是太多了,每个模板引擎的语法都很相似,这里我就以Simplite模板引擎来将上面的代码进行优化。关于Simplite模板引擎的源码,大家可以参考github

    html结构和上面一样,不一样的是多了一个script标签模板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <script type="text/html" id="treeTemplate">
    <% for(var i = 0; i < _this.length; i++){ %>
    <% var node = _this[i]; %>
    <% if(node.children && node.children.length > 0) { %>
  • class="hide-node">
    class="tree-collapse" href="javascript:void(0)"><%= node.name %>a>
  • 注意:标签的type属性是text/html,这段代码浏览器在解析的时候是不会渲染,也不会报错,只是一段普通的文本。在里面<%%>这样的符号就是Simplite模板引擎所特有的模板语法了,语法很简单。直接使用for循环,输出html结构,然后如果有子模板的话就直接通过include嵌套子模板,include这个也是该模板引擎所自带的嵌套语法。其它的模板引擎大多数都带有这个功能。

    那么使用了模板引擎之后,我们的JavaScript代码就变得简单多了。

    1
    2
    3
    4
    5
    6
    var tpl = new Simplite({
    target: 'treeRoot',
    template: 'treeTemplate'
    });

    tpl.render(treeData);

    然后页面结构就会渲染出来。(这里的代码我只展示了一部分,需要demo源码的参考我的 github )。

    Angular的方式



    下面做一个简单的扩展,通过AngularJS的方式来展示上面这个例子。大家会发现语法更加简洁了。

    HTML结构

    1
    2
    3
    4
    5
    <div class="tree">
    <ul class="tree-root" id="treeRoot" ng-include="'treeTemplate'" ng-init="node = treeData">

    ul>
    div>

    在这里的HTML结构有所更改,标签上直接使用的是angular的指令ng-include,来实现模板的嵌套。而在angular中的模板使用的是ng-template

    下面是它的模板

    1
    2
    3
    4
    5
    6
    7
    8
    <script type="text/ng-template" id="treeTemplate">
  • "nd in node" ng-class="{'hide-node': !nd._hide_}">
    "nd._hide_ = !nd._hide_" ng-class="{'tree-collapse': nd.children && nd.children.length > 0}" href="javascript:void(0)">{{ nd.name }}</a>
      if="nd.children && nd.children.length > 0" class="tree-group" ng-init="node = nd.children" ng-include="'treeTemplate'">

      ul>
      li>
      script>
  • 模板里面包含了一些指令,ng-repeatng-ifng-init等。ng-repeat是用来遍历输出html结构的,相当于for循环输出。ng-if是做逻辑判断的,例如:ng-if="nd.children && nd.children.length > 0"的意思是如果有子模板的话,就显示此标签。而模板里面的ng-init是用来做数据初始化的,当再次调用当前模板的时候应该遍历的是子模板的数据(children),于是指令就初始化重新遍历渲染。

    使用Angular的方式只当做扩展,有兴趣的可以自己研究下Angular这个框架。无限层级还会在那些地方使用到呢?欢迎大家讨论。最后附上本篇文章的demo源码