做了一個樹形菜單,想實現如下兩個操作: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事件執行,否則仍然不生效。