鄙人公司没有专门的前端,所以项目开发中都是前后端一起抡。最近用bootstrap用的比较频繁,发现bootstrap除了框架本身的样式组件外,还提供了多种插件供开发者选择。本篇博文讲的就是bootstrap的一个树形插件bootstrap-treeview。
最近项目权限模块中,需要将用户菜单做成可配置的。授权人员的授权操作是通过对树形菜单中的复选框进行勾选后保存来完成的,如下图所示:
bootstrap-treeview本身对勾选/取消的支持是没问题,问题在于复选框的业务逻辑上:
① 如果 勾选了父级节点,怎么让子节点全部变为勾选状态?
② 如果只选择了某个子节点,怎么让该节点所有的父节点全部变为选中状态?
博主开发的时候也是问了度娘,但很多网友的分享让自己这样前端薄弱的人看得头大,所以项目功能实现后,特意整理了自己的简洁实现,如博友有更好的方法,欢迎告知,共同进步。
首先,请求服务器后台获取的节点数据,通过树形插件的事件触发机制,在点击复选框做选中/取消操作的时候,去执行全选的代码:
function modify(id) { BASE.ajax("permission/pers", { id: id }, function (data) { $("#demoTree").treeview({ color: "#428bca", data: data, levels: 1, //显示时展开到几级 showIcon: false, showCheckbox: true, onNodeChecked: function (event, node) { //选中事件 checkAllNodes("checkNode", node); }, onNodeUnchecked: function (event, node) { //取消事件 checkAllNodes("uncheckNode", node); } }); $("#modal").modal("show"); }); }
然后,通过对选中的当前节点进行遍历,对遍历出的节点执行选中;如果子节点还有子节点,很简单,递归一下就能搞定:
function checkAllNodes(method, node) { var $tree = $('#modifyTree'); $(node.nodes).each(function (a, b) { //判断子节点存在,就执行相应的选中/取消事件。 //each回调函数中参量:a表示节点索引,b表示节点对象 $tree.treeview(method, [b.nodeId, { silent: true }]); if (b.nodes) //递归调用 checkAllNodes(method, b); }); }
自此,点击父节点 选中/取消 所有子节点的功能就算ok了。基于同样的思想,要想实现选中某一子节点后同时选中所有的父节点,那么只需要在代码中继续添加:① 通过子节点判断父节点的存在;② 选中父节点;③ 递归判断。
于是,上面的函数代码中就可以这样添加:
function checkAllNodes(method, node) { var $tree = $('#modifyTree'); if (node.parentId) {//如果父节点存在,选中/去除父节点 getParentNode(method, node, $tree); } $(node.nodes).each(function (a, b) { $tree.treeview(method, [b.nodeId, { silent: true }]); if (b.nodes) checkAllNodes(method, b); }); } function getParentNode(method, node, tree) { tree.treeview(method, [node.parentId, { silent: true }]); var pnode = tree.treeview('getNode', node.parentId); if (pnode.parentId) //递归判断父节点是否还有父节点 getParentNode(method, pnode, tree); }
自此,上面提到的两个问题就算是大功告成了。。。吗?稍等,如果这就算完成了,这篇博文记录对我实在没有多大意义。正当我喜滋滋的以为功能实现了的时候,突然发现了很大的bug,就是在通过子节点选中所有父节点的功能实现中,选中是没有问题,可是当取消某个子节点,无论兄弟节点是否有选中,父节点都一并被取消掉了。这肯定是有问题的。所以,自己又对取消事件单独做了判断,判断取消的时候,是否还有兄弟节点是处于选中状态,如果有,那么父节点就不执行取消了。代码如下:
function getParentNode(method, node, tree) { if (method == "uncheckNode") { //如果是取消事件,当判断兄弟节点是否存在 var arr = tree.treeview('getSiblings', node);//获取兄弟节点 for (var i = 0; i < arr.length; i++) { var brotherNode = arr[i]; if (brotherNode.state.checked) { //判断兄弟节点是否用选中状态 return; } } } tree.treeview(method, [node.parentId, { silent: true }]); var pnode = tree.treeview('getNode', node.parentId); if (pnode.parentId) getParentNode(method, pnode, tree); }
行文至此,上面的两个问题算是完美解决了。代码中的事件、属性,都是插件官网有详细说明的,插件使用过程中肯定需要根据业务需要去查询使用详情,再融合进自己的代码中的,不可生搬硬套。分享完结,希望能帮到一些人。如有疑惑或者更好的建议,留言讨论,不胜感激。。。