From 2d23618c8ce36b9ecb1f883c7dd6ba3ba5d84e4b Mon Sep 17 00:00:00 2001 From: FuryBean Date: Wed, 23 Nov 2016 10:46:05 +0800 Subject: [PATCH] Tree: refactor code & add insert, remove api for tree-store. (#1283) --- packages/tree/src/model/node.js | 121 +++++++++++------- .../tree/src/model/{tree.js => tree-store.js} | 35 ++++- packages/tree/src/model/util.js | 16 +++ packages/tree/src/tree-node.vue | 29 +++-- packages/tree/src/tree.vue | 39 ++++-- 5 files changed, 168 insertions(+), 72 deletions(-) rename packages/tree/src/model/{tree.js => tree-store.js} (84%) create mode 100644 packages/tree/src/model/util.js diff --git a/packages/tree/src/model/node.js b/packages/tree/src/model/node.js index 6d3bca470..7163e468d 100644 --- a/packages/tree/src/model/node.js +++ b/packages/tree/src/model/node.js @@ -1,4 +1,5 @@ import objectAssign from 'element-ui/src/utils/merge'; +import { markNodeData, NODE_KEY } from './util'; const reInitChecked = function(node) { const siblings = node.childNodes; @@ -26,7 +27,7 @@ const reInitChecked = function(node) { }; const getPropertyFromData = function(node, prop) { - const props = node._tree.props; + const props = node.store.props; const data = node.data || {}; const config = props[prop]; @@ -68,27 +69,35 @@ export default class Node { this.level = this.parent.level + 1; } - const tree = this._tree; - if (!tree) { - throw new Error('[Node]_tree is required!'); + const store = this.store; + if (!store) { + throw new Error('[Node]store is required!'); } - tree.registerNode(this); + store.registerNode(this); - if (tree.lazy !== true && this.data) { + const props = store.props; + if (props && typeof props.isLeaf !== 'undefined') { + const isLeaf = getPropertyFromData(this, 'isLeaf'); + if (typeof isLeaf === 'boolean') { + this.isLeafByUser = isLeaf; + } + } + + if (store.lazy !== true && this.data) { this.setData(this.data); - if (tree.defaultExpandAll) { + if (store.defaultExpandAll) { this.expanded = true; } - } else if (this.level > 0 && tree.lazy && tree.defaultExpandAll) { + } else if (this.level > 0 && store.lazy && store.defaultExpandAll) { this.expand(); } if (!this.data) return; - const defaultExpandedKeys = tree.defaultExpandedKeys; - const key = tree.key; + const defaultExpandedKeys = store.defaultExpandedKeys; + const key = store.key; if (key && defaultExpandedKeys && defaultExpandedKeys.indexOf(this.key) !== -1) { - if (tree.autoExpandParent) { + if (store.autoExpandParent) { let parent = this.parent; while (parent.level > 0) { parent.expanded = true; @@ -99,19 +108,16 @@ export default class Node { this.expand(); } - if (tree.lazy) { - tree._initDefaultCheckedNode(this); + if (store.lazy) { + store._initDefaultCheckedNode(this); } + + this.updateLeafState(); } setData(data) { - if (!Array.isArray(data) && !data.$treeNodeId) { - Object.defineProperty(data, '$treeNodeId', { - value: this.id, - enumerable: false, - configurable: false, - writable: false - }); + if (!Array.isArray(data)) { + markNodeData(this, data); } this.data = data; @@ -138,7 +144,7 @@ export default class Node { } get key() { - const nodeKey = this._tree.key; + const nodeKey = this.store.key; if (this.data) return this.data[nodeKey]; return null; } @@ -149,28 +155,49 @@ export default class Node { if (!(child instanceof Node)) { objectAssign(child, { parent: this, - _tree: this._tree + store: this.store }); child = new Node(child); } child.level = this.level + 1; - if (typeof index === 'undefined') { + if (typeof index === 'undefined' || index < 0) { this.childNodes.push(child); } else { this.childNodes.splice(index, 0, child); } + + this.updateLeafState(); + } + + insertBefore(child, ref) { + let index; + if (ref) { + index = this.childNodes.indexOf(ref); + } + this.insertChild(child, index); + } + + insertAfter(child, ref) { + let index; + if (ref) { + index = this.childNodes.indexOf(ref); + if (index !== -1) index += 1; + } + this.insertChild(child, index); } removeChild(child) { const index = this.childNodes.indexOf(child); if (index > -1) { - this._tree && this._tree.deregisterNode(child); + this.store && this.store.deregisterNode(child); child.parent = null; this.childNodes.splice(index, 1); } + + this.updateLeafState(); } removeChildByData(data) { @@ -211,19 +238,20 @@ export default class Node { } shouldLoadData() { - return this._tree.lazy === true && this._tree.load && !this.loaded; + return this.store.lazy === true && this.store.load && !this.loaded; } - get isLeaf() { - return !this.hasChild(); - } - - hasChild() { - const childNodes = this.childNodes; - if (!this._tree.lazy || (this._tree.lazy === true && this.loaded === true)) { - return childNodes && childNodes.length > 0; + updateLeafState() { + if (this.store.lazy === true && this.loaded !== true && typeof this.isLeafByUser !== 'undefined') { + this.isLeaf = this.isLeafByUser; + return; } - return true; + const childNodes = this.childNodes; + if (!this.store.lazy || (this.store.lazy === true && this.loaded === true)) { + this.isLeaf = !childNodes || childNodes.length === 0; + return; + } + this.isLeaf = false; } setChecked(value, deep) { @@ -240,7 +268,7 @@ export default class Node { } }; - if (!this._tree.checkStrictly && this.shouldLoadData()) { + if (!this.store.checkStrictly && this.shouldLoadData()) { // Only work on lazy load data. this.loadData(() => { handleDescendants(); @@ -254,7 +282,7 @@ export default class Node { const parent = this.parent; if (!parent || parent.level === 0) return; - if (!this._tree.checkStrictly) { + if (!this.store.checkStrictly) { reInitChecked(parent); } } @@ -263,7 +291,7 @@ export default class Node { const data = this.data; if (!data) return null; - const props = this._tree.props; + const props = this.store.props; let children = 'children'; if (props) { children = props.children || 'children'; @@ -284,19 +312,26 @@ export default class Node { const newNodes = []; newData.forEach((item, index) => { - if (item.$treeNodeId) { - newDataMap[item.$treeNodeId] = { index, data: item }; + if (item[NODE_KEY]) { + newDataMap[item[NODE_KEY]] = { index, data: item }; } else { newNodes.push({ index, data: item }); } }); - oldData.forEach((item) => { if (!newDataMap[item.$treeNodeId]) this.removeChildByData(item); }); - newNodes.forEach(({ index, data }) => this.insertChild({ data }, index)); + oldData.forEach((item) => { + if (!newDataMap[item[NODE_KEY]]) this.removeChildByData(item); + }); + + newNodes.forEach(({ index, data }) => { + this.insertChild({ data }, index); + }); + + this.updateLeafState(); } loadData(callback, defaultProps = {}) { - if (this._tree.lazy === true && this._tree.load && !this.loaded && !this.loading) { + if (this.store.lazy === true && this.store.load && !this.loaded && !this.loading) { this.loading = true; const resolve = (children) => { @@ -311,7 +346,7 @@ export default class Node { } }; - this._tree.load(this, resolve); + this.store.load(this, resolve); } else { if (callback) { callback.call(this); diff --git a/packages/tree/src/model/tree.js b/packages/tree/src/model/tree-store.js similarity index 84% rename from packages/tree/src/model/tree.js rename to packages/tree/src/model/tree-store.js index 7a0010860..93108789a 100644 --- a/packages/tree/src/model/tree.js +++ b/packages/tree/src/model/tree-store.js @@ -1,6 +1,7 @@ import Node from './node'; +import { getNodeKey } from './util'; -export default class Tree { +export default class TreeStore { constructor(options) { for (let option in options) { if (options.hasOwnProperty(option)) { @@ -12,7 +13,7 @@ export default class Tree { this.root = new Node({ data: this.data, - _tree: this + store: this }); if (this.lazy && this.load) { @@ -65,6 +66,36 @@ export default class Tree { } } + getNodeByData(data) { + const key = typeof data === 'string' ? data : getNodeKey(this.key, data); + return this.nodesMap[key]; + } + + insertBefore(data, refData) { + const refNode = this.getNodeByData(refData); + refNode.parent.insertBefore({ data }, refNode); + } + + insertAfter(data, refData) { + const refNode = this.getNodeByData(refData); + refNode.parent.insertAfter({ data }, refNode); + } + + remove(data) { + const node = this.getNodeByData(data); + if (node) { + node.parent.removeChild(node); + } + } + + append(data, parentData) { + const parentNode = parentData ? this.getNodeByData(parentData) : this.root; + + if (parentNode) { + parentNode.insertChild({ data }); + } + } + _initDefaultCheckedNodes() { const defaultCheckedKeys = this.defaultCheckedKeys || []; const nodesMap = this.nodesMap; diff --git a/packages/tree/src/model/util.js b/packages/tree/src/model/util.js new file mode 100644 index 000000000..50cc8f942 --- /dev/null +++ b/packages/tree/src/model/util.js @@ -0,0 +1,16 @@ +export const NODE_KEY = '$treeNodeId'; + +export const markNodeData = function(node, data) { + if (data[NODE_KEY]) return; + Object.defineProperty(data, NODE_KEY, { + value: node.id, + enumerable: false, + configurable: false, + writable: false + }); +}; + +export const getNodeKey = function(key, data) { + if (!key) return data[NODE_KEY]; + return data[key]; +}; diff --git a/packages/tree/src/tree-node.vue b/packages/tree/src/tree-node.vue index 52874c6c0..a135bb2e0 100644 --- a/packages/tree/src/tree-node.vue +++ b/packages/tree/src/tree-node.vue @@ -2,7 +2,7 @@
+ :class="{ 'is-expanded': childNodeRendered && expanded, 'is-current': tree.currentNode === _self, 'is-hidden': !node.visible }">
@@ -64,9 +64,12 @@ }, render(h) { const parent = this.$parent; + const node = this.node; + const data = node.data; + const store = node.store; return ( parent.renderContent - ? parent.renderContent.call(parent._renderProxy, h, { _self: parent.$parent.$vnode.context, node: this.node }) + ? parent.renderContent.call(parent._renderProxy, h, { _self: parent.tree.$vnode.context, node, data, store }) : { this.node.label } ); } @@ -75,7 +78,7 @@ data() { return { - $tree: null, + tree: null, expanded: false, childNodeRendered: false, showCheckbox: false, @@ -103,7 +106,7 @@ methods: { getNodeKey(node, index) { - const nodeKey = this.$tree.nodeKey; + const nodeKey = this.tree.nodeKey; if (nodeKey && node) { return node.data[nodeKey]; } @@ -112,14 +115,14 @@ handleSelectChange(checked, indeterminate) { if (this.oldChecked !== checked && this.oldIndeterminate !== indeterminate) { - this.$tree.$emit('check-change', this.node.data, checked, indeterminate); + this.tree.$emit('check-change', this.node.data, checked, indeterminate); } this.oldChecked = checked; this.indeterminate = indeterminate; }, handleClick() { - this.$tree.currentNode = this; + this.tree.currentNode = this; }, handleExpandIconClick(event) { @@ -132,18 +135,18 @@ } else { this.node.expand(); } - this.$tree.$emit('node-click', this.node.data, this.node, this); + this.tree.$emit('node-click', this.node.data, this.node, this); }, handleUserClick() { if (this.node.indeterminate) { - this.node.setChecked(this.node.checked, !this.$tree.checkStrictly); + this.node.setChecked(this.node.checked, !this.tree.checkStrictly); } }, handleCheckChange(ev) { if (!this.node.indeterminate) { - this.node.setChecked(ev.target.checked, !this.$tree.checkStrictly); + this.node.setChecked(ev.target.checked, !this.tree.checkStrictly); } } }, @@ -151,13 +154,13 @@ created() { const parent = this.$parent; - if (parent.$isTree) { - this.$tree = parent; + if (parent.isTree) { + this.tree = parent; } else { - this.$tree = parent.$tree; + this.tree = parent.tree; } - const tree = this.$tree; + const tree = this.tree; const props = this.props || {}; const childrenKey = props['children'] || 'children'; diff --git a/packages/tree/src/tree.vue b/packages/tree/src/tree.vue index 514ed10af..f466eaa61 100644 --- a/packages/tree/src/tree.vue +++ b/packages/tree/src/tree.vue @@ -1,20 +1,21 @@