做了一个树形菜单,想实现如下两个操作:1.勾选父节点全选子节点 2.勾选子节点自动勾选父节点。本以为实现起来应该很简单,但实际做的时候遇到了还是碰到很多问题,现在来记录一下。
直接上代码:
let clickFlag = 0; // clickFlag用来记录节点是被手动点击而使state变为cheked的,而不是因为级联选择导致节点被checked // 初始化树形菜单 function initTreeView(model) { $('#treeview-checkable').treeview({ data: getMenus(model), // 加载的数据源 showIcon: false, showCheckbox: true, // 展示复选框 levels: 2, onNodeChecked: function(event, node) { //选中时触发 checkNode(node); }, onNodeUnchecked: function (event, node) { //取消选中时触发 unCheckNode(node); }, onNodeClicked: function (event, node) { // click事件早于check事件 if (node.nodes != undefined && !node.state.checked) { if (node.parentId == undefined) { // 顶层节点 clickFlag = 1; } else { // 中间节点 clickFlag = 2; } } else { clickFlag = 0; } }, }); }; // 选择父菜单节点后,选中所有子菜单;当父菜单下所有子菜单被选中之后,自动勾选上父菜单节点 function checkNode(data) { if(data.nodes != undefined && clickFlag != 0) { for(var i = 0; i < data.nodes.length; i++) { $('#treeview-checkable').treeview("checkNode", [data.nodes[i].nodeId, {slient: true}]); } if (clickFlag == 2) { // 点击中间节点,选中了节点下所有子节点后,改变标识,防止污染后续操作 clickFlag = 0; } } if (data.parentId != undefined) { var parentNode = $('#treeview-checkable').treeview('getParent', data.nodeId); let checkCount = 0; for (x in parentNode.nodes) { if (parentNode.nodes[x].state.checked) { checkCount++; } } if(checkCount > 0) { // 选中当前节点的父节点 $('#treeview-checkable').treeview("checkNode", [parentNode.nodeId, {slient: true}]); } } } // 取消选择父菜单节点后,自动取消菜单下全部人员 function unCheckNode(data) { if(data.nodes != undefined) { var flag = false; for(var i = 0; i < data.nodes.length; i++) { if (data.nodes[i].state.checked) { flag = true; break; } } if(flag) { for(var i = 0; i < data.nodes.length; i++) { $('#treeview-checkable').treeview("uncheckNode", [data.nodes[i].nodeId, {slient: true}]); } } } if (data.parentId != undefined) { var parentNode = $('#treeview-checkable').treeview('getParent', data.nodeId); var checkCount = 0; for (x in parentNode.nodes) { if (parentNode.nodes[x].state.checked) { checkCount++; } } if(checkCount == 0) { $('#treeview-checkable').treeview("uncheckNode", [parentNode.nodeId, {slient: true}]); } } }
其中clickFlag很重要,如果没有这个属性,会出现以下情况:点击了子节点后自动勾选了父节点,因为定义了父节点被选中后就全选子节点,所以这时父节点下所有子节点都会被选中。因此,我就想到需要用一个flag来标注节点是被人手动点击的,而不是因为级联操作被选中的,这样才能避免出现上面说的问题。
节点的选中状态判断是通过onNodeChecked和onNodeUnchecked来判断的,还需要一个点击事件来记录节点是否被点击,bootstrap-treeview默认并没有提供这个事件,需要我们另外添加,添加方式如下:
;(function ($, window, document, undefined) { /*global jQuery, console*/ 'use strict'; var pluginName = 'treeview'; var _default = {}; _default.settings = { injectStyle: true, levels: 2, expandIcon: 'glyphicon glyphicon-plus', collapseIcon: 'glyphicon glyphicon-minus', emptyIcon: 'glyphicon', nodeIcon: '', selectedIcon: '', checkedIcon: 'glyphicon glyphicon-check', uncheckedIcon: 'glyphicon glyphicon-unchecked', color: undefined, // '#000000', backColor: undefined, // '#FFFFFF', borderColor: undefined, // '#dddddd', onhoverColor: '#F5F5F5', selectedColor: '#FFFFFF', selectedBackColor: '#428bca', searchResultColor: '#D9534F', searchResultBackColor: undefined, //'#FFFFFF', enableLinks: false, highlightSelected: true, highlightSearchResults: true, showBorder: true, showIcon: true, showCheckbox: false, showTags: false, multiSelect: false, // Event handlers onNodeChecked: undefined, onNodeCollapsed: undefined, onNodeDisabled: undefined, onNodeEnabled: undefined, onNodeExpanded: undefined, onNodeSelected: undefined, onNodeUnchecked: undefined, onNodeUnselected: undefined, onSearchComplete: undefined, onSearchCleared: undefined, /** * 给 bootstrap treeview 添加 点击事件 定义 */ onNodeClicked: undefined }; Tree.prototype.unsubscribeEvents = function () { this.$element.off('click'); this.$element.off('nodeChecked'); this.$element.off('nodeCollapsed'); this.$element.off('nodeDisabled'); this.$element.off('nodeEnabled'); this.$element.off('nodeExpanded'); this.$element.off('nodeSelected'); this.$element.off('nodeUnchecked'); this.$element.off('nodeUnselected'); this.$element.off('searchComplete'); this.$element.off('searchCleared'); /** * 给 bootstrap treeview 添加 点击事件 元素 */ this.$element.off('nodeClicked'); }; Tree.prototype.subscribeEvents = function () { this.unsubscribeEvents(); this.$element.on('click', $.proxy(this.clickHandler, this)); if (typeof (this.options.onNodeChecked) === 'function') { this.$element.on('nodeChecked', this.options.onNodeChecked); } if (typeof (this.options.onNodeCollapsed) === 'function') { this.$element.on('nodeCollapsed', this.options.onNodeCollapsed); } if (typeof (this.options.onNodeDisabled) === 'function') { this.$element.on('nodeDisabled', this.options.onNodeDisabled); } if (typeof (this.options.onNodeEnabled) === 'function') { this.$element.on('nodeEnabled', this.options.onNodeEnabled); } if (typeof (this.options.onNodeExpanded) === 'function') { this.$element.on('nodeExpanded', this.options.onNodeExpanded); } if (typeof (this.options.onNodeSelected) === 'function') { this.$element.on('nodeSelected', this.options.onNodeSelected); } if (typeof (this.options.onNodeUnchecked) === 'function') { this.$element.on('nodeUnchecked', this.options.onNodeUnchecked); } if (typeof (this.options.onNodeUnselected) === 'function') { this.$element.on('nodeUnselected', this.options.onNodeUnselected); } if (typeof (this.options.onSearchComplete) === 'function') { this.$element.on('searchComplete', this.options.onSearchComplete); } if (typeof (this.options.onSearchCleared) === 'function') { this.$element.on('searchCleared', this.options.onSearchCleared); } /** * 给 bootstrap treeview 添加 点击事件 赋值 */ if (typeof (this.options.onNodeClicked) === 'function') { this.$element.on('nodeClicked', this.options.onNodeClicked); } }; Tree.prototype.clickHandler = function (event) { if (!this.options.enableLinks) event.preventDefault(); var target = $(event.target); var node = this.findNode(target); if (!node || node.state.disabled) return; /** * @update by sxh: 先执行点击事件 */ this.onClicked(node, _default.options); var classList = target.attr('class') ? target.attr('class').split(' ') : []; if ((classList.indexOf('expand-icon') !== -1)) { this.toggleExpandedState(node, _default.options); this.render(); } else if ((classList.indexOf('check-icon') !== -1)) { this.toggleCheckedState(node, _default.options); this.render(); } else { if (node.selectable) { this.toggleSelectedState(node, _default.options); } else { this.toggleExpandedState(node, _default.options); } this.render(); } /** * clickHandler -- 最后执行点击事件 */ // this.onClicked(node, _default.options); }; /** * 给 bootstrap treeview 添加 点击事件 * 依赖于clickHandler 方法。最后执行 */ Tree.prototype.onClicked = function (node, options) { if (!node) return; if (!options.silent) { this.$element.trigger('nodeClicked', $.extend(true, {}, node)); } };
另外一点需要注意的是,添加的click事件一定要先于checked事件执行,否则仍然不生效。