Browse Source

Tree: refactor code & add insert, remove api for tree-store. (#1283)

FuryBean 8 years ago
parent
commit
2d23618c8c

+ 77 - 42
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;
-  }
-
-  get isLeaf() {
-    return !this.hasChild();
+    return this.store.lazy === true && this.store.load && !this.loaded;
   }
 
-  hasChild() {
+  updateLeafState() {
+    if (this.store.lazy === true && this.loaded !== true && typeof this.isLeafByUser !== 'undefined') {
+      this.isLeaf = this.isLeafByUser;
+      return;
+    }
     const childNodes = this.childNodes;
-    if (!this._tree.lazy || (this._tree.lazy === true && this.loaded === true)) {
-      return childNodes && childNodes.length > 0;
+    if (!this.store.lazy || (this.store.lazy === true && this.loaded === true)) {
+      this.isLeaf = !childNodes || childNodes.length === 0;
+      return;
     }
-    return true;
+    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);

+ 33 - 2
packages/tree/src/model/tree.js → 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;

+ 16 - 0
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];
+};

+ 16 - 13
packages/tree/src/tree-node.vue

@@ -2,7 +2,7 @@
   <div class="el-tree-node"
     @click.stop="handleClick"
     v-show="node.visible"
-    :class="{ 'is-expanded': childNodeRendered && expanded, 'is-current': $tree.currentNode === _self, 'is-hidden': !node.visible }">
+    :class="{ 'is-expanded': childNodeRendered && expanded, 'is-current': tree.currentNode === _self, 'is-hidden': !node.visible }">
     <div class="el-tree-node__content"
       :style="{ 'padding-left': (node.level - 1) * 16 + 'px' }"
       @click="handleExpandIconClick">
@@ -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 })
               : <span class="el-tree-node__label">{ this.node.label }</span>
           );
         }
@@ -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';
 

+ 25 - 14
packages/tree/src/tree.vue

@@ -1,20 +1,21 @@
 <template>
   <div class="el-tree" :class="{ 'el-tree--highlight-current': highlightCurrent }">
     <el-tree-node
-      v-for="child in tree.root.childNodes"
+      v-for="child in root.childNodes"
       :node="child"
       :props="props"
+      :key="getNodeKey(child)"
       :render-content="renderContent">
     </el-tree-node>
-    <div class="el-tree__empty-block" v-if="!tree.root.childNodes || tree.root.childNodes.length === 0">
+    <div class="el-tree__empty-block" v-if="!root.childNodes || root.childNodes.length === 0">
       <span class="el-tree__empty-text">{{ emptyText }}</span>
     </div>
   </div>
 </template>
 
 <script type="text/ecmascript-6">
-  import Tree from './model/tree';
-  import { t } from 'element-ui/src/locale';
+  import TreeStore from './model/tree-store';
+  import {t} from 'element-ui/src/locale';
 
   export default {
     name: 'el-tree',
@@ -62,9 +63,9 @@
     },
 
     created() {
-      this.$isTree = true;
+      this.isTree = true;
 
-      this.tree = new Tree({
+      this.store = new TreeStore({
         key: this.nodeKey,
         data: this.data,
         lazy: this.lazy,
@@ -77,11 +78,14 @@
         defaultExpandAll: this.defaultExpandAll,
         filterNodeMethod: this.filterNodeMethod
       });
+
+      this.root = this.store.root;
     },
 
     data() {
       return {
-        tree: {},
+        store: null,
+        root: null,
         currentNode: null
       };
     },
@@ -103,31 +107,38 @@
 
     watch: {
       data(newVal) {
-        this.tree.setData(newVal);
+        this.store.setData(newVal);
       },
       defaultCheckedKeys(newVal) {
-        this.tree.setDefaultCheckedKey(newVal);
+        this.store.setDefaultCheckedKey(newVal);
       }
     },
 
     methods: {
       filter(value) {
         if (!this.filterNodeMethod) throw new Error('[Tree] filterNodeMethod is required when filter');
-        this.tree.filter(value);
+        this.store.filter(value);
+      },
+      getNodeKey(node, index) {
+        const nodeKey = this.nodeKey;
+        if (nodeKey && node) {
+          return node.data[nodeKey];
+        }
+        return index;
       },
       getCheckedNodes(leafOnly) {
-        return this.tree.getCheckedNodes(leafOnly);
+        return this.store.getCheckedNodes(leafOnly);
       },
       getCheckedKeys(leafOnly) {
-        return this.tree.getCheckedKeys(leafOnly);
+        return this.store.getCheckedKeys(leafOnly);
       },
       setCheckedNodes(nodes, leafOnly) {
         if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedNodes');
-        this.tree.setCheckedNodes(nodes, leafOnly);
+        this.store.setCheckedNodes(nodes, leafOnly);
       },
       setCheckedKeys(keys, leafOnly) {
         if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedNodes');
-        this.tree.setCheckedKeys(keys, leafOnly);
+        this.store.setCheckedKeys(keys, leafOnly);
       }
     }
   };