From 476f76875cb47a23c4f3f6aebe98e20957d73e90 Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Wed, 19 Jul 2017 18:36:26 +0800 Subject: [PATCH] Tree: checkbox can be disabled --- examples/docs/en-US/tree.md | 87 +++++++++++++++++++++++++++ examples/docs/zh-CN/tree.md | 86 ++++++++++++++++++++++++++ packages/tree/src/model/node.js | 63 ++++++++++++++----- packages/tree/src/model/tree-store.js | 84 +++++++++++--------------- packages/tree/src/tree-node.vue | 14 +---- packages/tree/src/tree.vue | 3 +- test/unit/specs/tree.spec.js | 65 ++++++++++++++++++++ 7 files changed, 327 insertions(+), 75 deletions(-) diff --git a/examples/docs/en-US/tree.md b/examples/docs/en-US/tree.md index b5cdb9fc1..a08b1bfea 100644 --- a/examples/docs/en-US/tree.md +++ b/examples/docs/en-US/tree.md @@ -93,6 +93,35 @@ }] }]; + const data3 = [{ + id: 1, + label: 'Level one 1', + children: [{ + id: 3, + label: 'Level two 2-1', + children: [{ + id: 4, + label: 'Level three 3-1-1' + }, { + id: 5, + label: 'Level three 3-1-2', + disabled: true + }] + }, { + id: 2, + label: 'Level two 2-2', + disabled: true, + children: [{ + id: 6, + label: 'Level three 3-2-1' + }, { + id: 7, + label: 'Level three 3-2-2', + disabled: true + }] + }] + }]; + let id = 1000; const regions = [{ @@ -211,6 +240,7 @@ return { data, data2, + data3, regions, defaultProps, props, @@ -363,6 +393,63 @@ Used for node selection. In the following example, data for each layer is acquir ``` ::: +### Can disable checkbox + +The checkbox of a node can be set as disabled. In the example, 'disabled' property is declared in defaultProps, and some nodes are set as 'disabled:true'. The corresponding checkbox is disabled and can't be clicked. + +::: demo +```html + + + + +``` +::: + ### Default expanded and default checked Tree nodes can be initially expanded or checked diff --git a/examples/docs/zh-CN/tree.md b/examples/docs/zh-CN/tree.md index 850d6bd8b..76d5a5b7e 100644 --- a/examples/docs/zh-CN/tree.md +++ b/examples/docs/zh-CN/tree.md @@ -93,6 +93,35 @@ }] }]; + const data3 = [{ + id: 1, + label: '一级 2', + children: [{ + id: 3, + label: '二级 2-1', + children: [{ + id: 4, + label: '三级 3-1-1' + }, { + id: 5, + label: '三级 3-1-2', + disabled: true + }] + }, { + id: 2, + label: '二级 2-2', + disabled: true, + children: [{ + id: 6, + label: '三级 3-2-1' + }, { + id: 7, + label: '三级 3-2-2', + disabled: true + }] + }] + }]; + let id = 1000; const regions = [{ @@ -211,6 +240,7 @@ return { data, data2, + data3, regions, defaultProps, props, @@ -427,6 +457,62 @@ ``` ::: +### 禁用状态 +可将 Tree 的某些节点设置为禁用状态 + +::: demo 通过`disabled`设置禁用状态。 +```html + + + + +``` +::: + ### 树节点的选择 ::: demo 本例展示如何获取和设置选中节点。获取和设置各有两种方式:通过 node 或通过 key。如果需要通过 key 来获取或设置,则必须设置`node-key`。 diff --git a/packages/tree/src/model/node.js b/packages/tree/src/model/node.js index 161e986cd..c4bf5481b 100644 --- a/packages/tree/src/model/node.js +++ b/packages/tree/src/model/node.js @@ -1,28 +1,45 @@ import objectAssign from 'element-ui/src/utils/merge'; import { markNodeData, NODE_KEY } from './util'; -const reInitChecked = function(node) { - const siblings = node.childNodes; - +export const getChildState = node => { let all = true; let none = true; + let allWithoutDisable = true; - for (let i = 0, j = siblings.length; i < j; i++) { - const sibling = siblings[i]; - if (sibling.checked !== true || sibling.indeterminate) { + for (let n of node) { + if (n.checked !== true || n.indeterminate) { all = false; + if (!n.disabled) { + allWithoutDisable = false; + } } - if (sibling.checked !== false || sibling.indeterminate) { + if (n.checked !== false || n.indeterminate) { none = false; } } + return { all, none, allWithoutDisable, half: !all && !none }; +}; + +const reInitChecked = function(node) { + const {all, none, half} = getChildState(node.childNodes); + if (all) { - node.setChecked(true); - } else if (!all && !none) { - node.setChecked('half'); + node.checked = true; + node.indeterminate = false; + } else if (half) { + node.checked = false; + node.indeterminate = true; } else if (none) { - node.setChecked(false); + node.checked = false; + node.indeterminate = false; + } + + const parent = node.parent; + if (!parent || parent.level === 0) return; + + if (!node.store.checkStrictly) { + reInitChecked(parent); } }; @@ -145,6 +162,10 @@ export default class Node { return null; } + get disabled() { + return getPropertyFromData(this, 'disabled'); + } + insertChild(child, index) { if (!child) throw new Error('insertChild error: child is required.'); @@ -260,16 +281,30 @@ export default class Node { this.isLeaf = false; } - setChecked(value, deep) { + setChecked(value, deep, recursion, passValue) { this.indeterminate = value === 'half'; this.checked = value === true; + let { allWithoutDisable } = getChildState(this.childNodes); + + if (this.childNodes.length && allWithoutDisable) { + this.checked = false; + value = false; + } const handleDescendants = () => { if (deep) { const childNodes = this.childNodes; for (let i = 0, j = childNodes.length; i < j; i++) { const child = childNodes[i]; - child.setChecked(value !== false, deep); + passValue = passValue || value !== false; + const isCheck = child.disabled ? child.checked : passValue; + child.setChecked(isCheck, deep, true, passValue); + } + const { half, all } = getChildState(childNodes); + console.log(this.data.label, all); + if (!all) { + this.checked = all; + this.indeterminate = half; } } }; @@ -288,7 +323,7 @@ export default class Node { const parent = this.parent; if (!parent || parent.level === 0) return; - if (!this.store.checkStrictly) { + if (!this.store.checkStrictly && !recursion) { reInitChecked(parent); } } diff --git a/packages/tree/src/model/tree-store.js b/packages/tree/src/model/tree-store.js index 7e60e5be9..f019bac81 100644 --- a/packages/tree/src/model/tree-store.js +++ b/packages/tree/src/model/tree-store.js @@ -1,4 +1,4 @@ -import Node from './node'; +import Node, { getChildState } from './node'; import { getNodeKey } from './util'; export default class TreeStore { @@ -188,61 +188,47 @@ export default class TreeStore { } _setCheckedKeys(key, leafOnly = false, checkedKeys) { - const allNodes = this._getAllNodes(); - allNodes.sort((a, b) => b.level - a.level); + let allNodes = this._getAllNodes().sort((a, b) => a.level - b.level); const keys = Object.keys(checkedKeys); - allNodes.forEach((node) => { - let checked = keys.indexOf(node.data[key] + '') > -1; + for (let node of allNodes) { + let checked = keys.indexOf(node.data[key].toString()) > -1; + if (!checked) { + node.setChecked(false, false); + continue; + } - if (!node.isLeaf) { - if (!this.checkStrictly) { + if (node.isLeaf || this.checkStrictly) { + node.setChecked(checked, false); + continue; + } + + const { all, none, half } = getChildState(node.childNodes); + + if (all) { + node.setChecked(true, !this.checkStrictly); + } else if (half) { + checked = checked ? true : 'half'; + node.setChecked(checked, !this.checkStrictly && checked === true); + } else if (none) { + node.setChecked(checked, !this.checkStrictly); + } + + if (leafOnly) { + node.setChecked(false, false); + const traverse = function(node) { const childNodes = node.childNodes; - let all = true; - let none = true; - - for (let i = 0, j = childNodes.length; i < j; i++) { - const child = childNodes[i]; - if (child.checked !== true || child.indeterminate) { - all = false; + childNodes.forEach((child) => { + if (!child.isLeaf) { + child.setChecked(false, false); } - if (child.checked !== false || child.indeterminate) { - none = false; - } - } - - if (all) { - node.setChecked(true, !this.checkStrictly); - } else if (!all && !none) { - checked = checked ? true : 'half'; - node.setChecked(checked, !this.checkStrictly && checked === true); - } else if (none) { - node.setChecked(checked, !this.checkStrictly); - } - } else { - node.setChecked(checked, false); - } - - if (leafOnly) { - node.setChecked(false, false); - const traverse = function(node) { - const childNodes = node.childNodes; - - childNodes.forEach((child) => { - if (!child.isLeaf) { - child.setChecked(false, false); - } - traverse(child); - }); - }; - - traverse(node); - } - } else { - node.setChecked(checked, false); + traverse(child); + }); + }; + traverse(node); } - }); + } } setCheckedNodes(array, leafOnly = false) { diff --git a/packages/tree/src/tree-node.vue b/packages/tree/src/tree-node.vue index 908ff973b..5f16b84f9 100644 --- a/packages/tree/src/tree-node.vue +++ b/packages/tree/src/tree-node.vue @@ -18,8 +18,8 @@ v-if="showCheckbox" v-model="node.checked" :indeterminate="node.indeterminate" - @change="handleCheckChange" - @click.native.stop="handleUserClick"> + :disabled="!!node.disabled" + @change="handleCheckChange"> { }, options), true); }; + const getDisableTreeVm = (props, options) => { + return createVue(Object.assign({ + template: ` + + `, + + data() { + return { + defaultExpandedKeys: [], + defaultCheckedKeys: [], + clickedNode: null, + count: 1, + data: [{ + id: 1, + label: '一级 1', + children: [{ + id: 11, + label: '二级 1-1', + children: [{ + id: 111, + label: '三级 1-1', + disabled: true + }] + }] + }, { + id: 2, + label: '一级 2', + children: [{ + id: 21, + label: '二级 2-1' + }, { + id: 22, + label: '二级 2-2' + }] + }, { + id: 3, + label: '一级 3', + children: [{ + id: 31, + label: '二级 3-1' + }, { + id: 32, + label: '二级 3-2' + }] + }], + defaultProps: { + children: 'children', + label: 'label', + disabled: 'disabled' + } + }; + } + }, options), true); + }; + const ALL_NODE_COUNT = 9; it('create', () => { @@ -344,6 +399,16 @@ describe('Tree', () => { }, 0); }); + it('set disabled checkbox', done => { + vm = getDisableTreeVm(':props="defaultProps" show-checkbox node-key="id"'); + const node = document.querySelectorAll('.el-tree-node__content')[2]; + const nodeCheckbox = node.querySelector('.el-checkbox input'); + vm.$nextTick(() => { + expect(nodeCheckbox.disabled).to.equal(true); + done(); + }); + }); + it('check strictly', (done) => { vm = getTreeVm(':props="defaultProps" show-checkbox check-strictly'); const tree = vm.$children[0];