让前端飞

AnguarJS directive调用外部函数

2017-03-21  本文已影响0人  外大街

需求

angular-ui bootstrap提供了很多重写过bootstrap的组件,利用的就是AngularJS自定义指令directive。但是其实,很多组件都已经有别的框架写好了,比如jQuery的jquery-ui等等。所以,我有个想法,能不能利用这些已经存在组件,组装一个适配AngularJS的控件呢。因此做如下尝试,将bootstrap-treeview做一个适配AngularJS的,达到预期效果。

利用到的框架

  1. requirejs
  2. jquery
  3. bootstrap
  4. bootstrap-treeview

bootstrap-treeview简介

bootstrap-treeview是一个jquery的插件组件,是个树状结构数据展示组件,树结构的样式依赖bootstrap,官方的截图如下:

bootstrap-treeview

bootstrap-treeview 参数配置

{
    enableLinks: true, // 树节点是否可以链接,即生成一个a标签
    showTags: true,    // 树节点最右边是否显示badge
    data: []           // 数据源,必须为一个数组
}

bootstrap-treeview 数据源格式

data: [{
    text: 'node',
    href: 'javascript:void(0)',  // enableLinks === false 不起作用
    tags: [10],                  // tags 可以不需要,showTags === false 不起作用
    nodes: [
        // 子节点,与父节点格式一致,如果没有子节点,可以为空值
        {
            text: 'subnode',
            href: '#/nodes.json',
        }
        // ......
    ]
}]

bootstrap-treeview基本用法

  1. 页面添加标签
<div id="treeView"></div>
  1. 创建treeview组件
$(function () {
    var data = [{
        text: 'parent',
        href: 'javascript:void(0);',
        tags: [2],
        nodes: [{
            text: 'child1',
            href: '#/children/1.json',
        }, {
            text: 'child2',
            href: '#/children/2.json',
        }]

    }]
    var options = {
        enableLinks: true,
        showTags: true,
        data: data
    };

    $('div#treeView').treeview(options);
})

显示效果如下图所示:

treeview示例

适配方法添加

因为 bootstrap-treeview 只能在初始化的时候,设置数据源,因此给bootstrap-treeview加入一个方法 setData,具体实现如下:

Tree.prototype.setData = function(data) {
    this.options.data = data;
    this.init(this.options);
}

再在Tree函数返回值中增加一个属性:

setData: $.proxy(this.setData, this)

这样,便给 bootstrap-treeview 的 Tree 增加了一个新方法,其实很简单。

AngularJS directive指令适配代码实现

有了对bootstrap-treeview基本的用法了解,现在就可以写我们适配的指令了。这里,我们实现一个适配如下树形结构的数据源,多级代理商结构:

[{
    "id": 1,
    "name": "一级代理商",
    "agents": [{
        "id": 2,
        "name": "二级代理商",
        "agents": [{
            "id": 5,
            "name": "三级代理商"
        }]
    }]
}, {
    "id": 3,
    "name": "二级代理商",
    "agents": [{
            "id": 4,
            "name": "三级代理商"
        }, {
            "id": 6,
            "name": "三级代理商"
        },
        {
            "id": 7,
            "name": "终端代理商"
        }
    ]
}]

代理商数据源中并没有href属性,可以在我们适配的过程中,动态生成。

主模块 app.moduel.js

定义一个模块 app

define([
    'require',
    'angular'
], function(require, angular) {
    'use strict';
    return angular.module('app', []);
});

定义指令 treeview.directive.js

define([
    'require',
    'app.module'
], function(require, app) {
    'use strict';

    app.directive('treeView', TreeView);

    function TreeView() {
        return {
            template: '<div class="alert alert-warning"></div>',
            scope: {
                items: '=', // 节点数据源,可以是自己的原始数据结构,有 adpater 函数进行适配
                adapter: '&' // 适配函数,这是实现适配的关键
            },
            replace: true,
            link: linkFn
        };
    }
    // 链接函数
    function linkFn(scope, element, attrs) {
        // 从scope当中取出已经被 AngularJS 帮我们解析过的适配函数
        // 注意不是 scope.adapter,因为经过AngularJS解析过后,
        // scope的adapter属性只是,我们真正传入的函数表达式的一个代理,
        // 通过这个代理函数的调用,才能得到真正的函数本身
        var adapterFn = scope.adapter();
        // 创建树
        element.treeview({
            enableLinks: true,
            showTags: true,
            data: []
        });
        // 用于缓存数据源,数据未变化,不要去渲染
        var cachedItems = [];

        // 监听 items 属性的变化,如果变化了,就将数据源更新到 bootstrap-treeview 中
        scope.$watch('items', function(items) {
            // 如果数据源为变化,直接返回
            if (angular.equals(cachedItems, items)) return;
            // 缓存变化数据
            cachedItems = items;
            // 存放适配之后的数据源
            var nodes = [];
            // 将数据源中的数据,逐条进行适配
            angular.forEach(items, function(item) {
                // 调用外部适配函数,将适配只收的节点存放近 nodes 数组中
                adapterFn.call(scope, nodes, item);
            });
            // bootstrap-treeview 本身没有提供 setData 函数,为能够动态设置数据源,我加入的
            element.treeview(true).setData(nodes);
        });
    }
    // 这里无意义,因为 requirejs 要求 define 函数返回一个对象,也可以不返回
    return {};
});

定义控制器 agents.controller.js

define([
    'require',
    'app.module',
    'treeview'
], function(require, app) {
    'use strict';

    app.controller('AgentCtrl', ['$scope', '$http', AgentCtrl]);

    function AgentCtrl($scope, $http) {
        $scope.list = list;
        $scope.agentAdapter = agentAdapter;

        // 查询代理商
        // list();

        function list() {
            $http.get('/raws/agents.json').then(function(response) {
                var _root = {
                    id: 0,
                    name: '根节点',
                    agents: response.data
                };

                var agents = [_root];
                $scope.agents = agents;
            }, function(err) {
                console.error(err);
            })
        }

        // 多级代理商适配函数
        function agentAdapter(nodes, agent) {
            var node = {};
            node.text = agent.name;
            // 根据代理商的属性,动态生成 href 属性
            // 如果是根节点,不能点击
            if (agent.id === 0) {
                node.href = 'javascript:void(0);';
            } else {
                node.href = '/agents/' + agent.id + '.json';
            }

            nodes.push(node);

            if (agent.agents && agent.agents.length > 0) {
                node.tags = [agent.agents.length];
                // 生成子节点
                node.nodes = [];

                // 遍历下级代理商适配子节点
                angular.forEach(agent.agents, function(agent) {
                    agentAdapter(node.nodes, agent);
                });
            }
        }
    }

    return {};
});

页面 index.html

<!DOCTYPE html>
<html lang="zh" ng-cloak>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>TreeView Directive Demo</title>

    <base href="/" target="_self" />

    <link rel="stylesheet" href="stylesheets/bootstrap.css" />
    <link rel="stylesheet" href="stylesheets/angular-csp.css" />

    <script data-main="javascripts/main.js" src="javascripts/require.js" type="text/javascript"></script>
</head>

<body>
    <div class="container" ng-controller="AgentCtrl">
        <div>
            <button class="btn btn-warning" ng-click="list()"><i class="glyphicon glyphicon-refresh">Refresh</i></button>
        </div>
        <div id="treeView" tree-view items="agents" adapter="agentAdapter" class="alert" style="width: 250px;"></div>
        <!--<tree-view tree-view items="agents" adapter="agentAdapter" class="alert" style="width: 250px;"> </tree-view>-->
    </div>
</body>

</html>

运行截图

  1. 初始页面
页面初始状态
  1. 点击Refresh
加载数据源之后

项目完整代码

http://git.oschina.net/dzhang/treeview

上一篇下一篇

猜你喜欢

热点阅读