mirror of https://github.com/ElemeFE/element
Improve Tree:
1. Add props: renderContent, highlightCurrent 2. Fix Bug: tree do not change when data changedpull/470/head
parent
baf3d192d8
commit
785bed20df
|
@ -235,6 +235,8 @@
|
|||
| props | 配置选项,具体看下表 | object | — | — |
|
||||
| load | 加载子树数据的方法 | function(node, resolve) | — | — |
|
||||
| show-checkbox | 节点是否可被选择 | boolean | — | false |
|
||||
| render-content | 树节点的内容区的渲染 Function,会传入两个参数,h 与 { node: node }。 | Function | - | - |
|
||||
| highlight-current | 是否高亮当前选中节点,默认值是 false。| boolean | - | false |
|
||||
|
||||
### props
|
||||
|
||||
|
@ -253,5 +255,5 @@
|
|||
### Events
|
||||
| 事件名称 | 说明 | 回调参数 |
|
||||
|---------- |-------- |---------- |
|
||||
| node-click | 节点被点击时的回调 | 传递给 `data` 属性的数组中该节点所对应的对象 |
|
||||
| node-click | 节点被点击时的回调 | 共三个参数,依次为:传递给 `data` 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 |
|
||||
| check-change | 节点选中状态发生变化时的回调 | 共三个参数,依次为:传递给 `data` 属性的数组中该节点所对应的对象、<br>节点本身是否被选中、节点的子树中是否有被选中的节点 |
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
@component-namespace el {
|
||||
@b tree {
|
||||
overflow: auto;
|
||||
cursor: default;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d3dce6;
|
||||
|
@ -82,6 +81,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
|
||||
background-color: #eff7ff;
|
||||
}
|
||||
|
||||
.collapse-transition {
|
||||
transition: 0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
let idSeed = 0;
|
||||
let nodeIdSeed = 0;
|
||||
import objectAssign from 'object-assign';
|
||||
|
||||
const reInitChecked = function(node) {
|
||||
const siblings = node.children;
|
||||
const siblings = node.childNodes;
|
||||
|
||||
let all = true;
|
||||
let none = true;
|
||||
|
@ -42,7 +42,7 @@ const getPropertyFromData = function(node, prop) {
|
|||
|
||||
export default class Node {
|
||||
constructor(options) {
|
||||
this.id = idSeed++;
|
||||
this.id = nodeIdSeed++;
|
||||
this.text = null;
|
||||
this.checked = false;
|
||||
this.indeterminate = false;
|
||||
|
@ -61,7 +61,7 @@ export default class Node {
|
|||
// internal
|
||||
this.level = -1;
|
||||
this.loaded = false;
|
||||
this.children = [];
|
||||
this.childNodes = [];
|
||||
this.loading = false;
|
||||
|
||||
if (this.parent) {
|
||||
|
@ -74,8 +74,17 @@ export default class Node {
|
|||
}
|
||||
|
||||
setData(data) {
|
||||
if (!Array.isArray(data) && !data.$treeNodeId) {
|
||||
Object.defineProperty(data, '$treeNodeId', {
|
||||
value: this.id,
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: false
|
||||
});
|
||||
}
|
||||
|
||||
this.data = data;
|
||||
this.children = [];
|
||||
this.childNodes = [];
|
||||
|
||||
let children;
|
||||
if (this.level === -1 && this.data instanceof Array) {
|
||||
|
@ -85,14 +94,7 @@ export default class Node {
|
|||
}
|
||||
|
||||
for (let i = 0, j = children.length; i < j; i++) {
|
||||
const child = children[i];
|
||||
this.insertChild(new Node({
|
||||
data: child,
|
||||
parent: this,
|
||||
lazy: this.lazy,
|
||||
load: this.load,
|
||||
props: this.props
|
||||
}));
|
||||
this.insertChild({ data: children[i] });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,26 +109,47 @@ export default class Node {
|
|||
insertChild(child, index) {
|
||||
if (!child) throw new Error('insertChild error: child is required.');
|
||||
|
||||
if (!child instanceof Node) {
|
||||
throw new Error('insertChild error: child should an instance of Node.');
|
||||
if (!(child instanceof Node)) {
|
||||
objectAssign(child, {
|
||||
parent: this,
|
||||
lazy: this.lazy,
|
||||
load: this.load,
|
||||
props: this.props
|
||||
});
|
||||
child = new Node(child);
|
||||
}
|
||||
|
||||
child.parent = this;
|
||||
child.level = this.level + 1;
|
||||
|
||||
if (typeof index === 'undefined') {
|
||||
this.children.push(child);
|
||||
this.childNodes.push(child);
|
||||
} else {
|
||||
this.children.splice(index, 0, child);
|
||||
this.childNodes.splice(index, 0, child);
|
||||
}
|
||||
}
|
||||
|
||||
removeChild(child) {
|
||||
const index = this.children.indexOf(child);
|
||||
const index = this.childNodes.indexOf(child);
|
||||
|
||||
if (index > -1) {
|
||||
child.parent = null;
|
||||
this.children.splice(child, index);
|
||||
this.childNodes.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
removeChildByData(data) {
|
||||
let nodeIndex = -1;
|
||||
let targetNode = null;
|
||||
this.childNodes.forEach((node, index) => {
|
||||
if (node.data === data) {
|
||||
nodeIndex = index;
|
||||
targetNode = node;
|
||||
}
|
||||
});
|
||||
|
||||
if (nodeIndex > -1) {
|
||||
targetNode.parent = null;
|
||||
this.childNodes.splice(nodeIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,13 +170,7 @@ export default class Node {
|
|||
|
||||
doCreateChildren(array, defaultProps = {}) {
|
||||
array.forEach((item) => {
|
||||
const node = new Node(objectAssign({
|
||||
data: item,
|
||||
lazy: this.lazy,
|
||||
load: this.load,
|
||||
props: this.props
|
||||
}, defaultProps));
|
||||
this.insertChild(node);
|
||||
this.insertChild(objectAssign({ data: item }, defaultProps));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -170,9 +187,9 @@ export default class Node {
|
|||
}
|
||||
|
||||
hasChild() {
|
||||
const children = this.children;
|
||||
const childNodes = this.childNodes;
|
||||
if (!this.lazy || (this.lazy === true && this.loaded === true)) {
|
||||
return children && children.length > 0;
|
||||
return childNodes && childNodes.length > 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -183,9 +200,9 @@ export default class Node {
|
|||
|
||||
const handleDeep = () => {
|
||||
if (deep) {
|
||||
const children = this.children;
|
||||
for (let i = 0, j = children.length; i < j; i++) {
|
||||
const child = children[i];
|
||||
const childNodes = this.childNodes;
|
||||
for (let i = 0, j = childNodes.length; i < j; i++) {
|
||||
const child = childNodes[i];
|
||||
child.setChecked(value !== false, deep);
|
||||
}
|
||||
}
|
||||
|
@ -225,15 +242,33 @@ export default class Node {
|
|||
return data[children];
|
||||
}
|
||||
|
||||
updateChildren() {
|
||||
const newData = this.getChildren() || [];
|
||||
const oldData = this.childNodes.map((node) => node.data);
|
||||
|
||||
const newDataMap = {};
|
||||
const newNodes = [];
|
||||
|
||||
newData.forEach((item, index) => {
|
||||
if (item.$treeNodeId) {
|
||||
newDataMap[item.$treeNodeId] = { 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));
|
||||
}
|
||||
|
||||
loadData(callback, defaultProps = {}) {
|
||||
if (this.lazy === true && this.load && !this.loaded) {
|
||||
this.loading = true;
|
||||
|
||||
const loadFn = this.load;
|
||||
const resolve = (children) => {
|
||||
this.loaded = true;
|
||||
this.loading = false;
|
||||
this.children = [];
|
||||
this.childNodes = [];
|
||||
|
||||
this.doCreateChildren(children, defaultProps);
|
||||
|
||||
|
@ -242,7 +277,7 @@ export default class Node {
|
|||
}
|
||||
};
|
||||
|
||||
loadFn(this, resolve);
|
||||
this.load(this, resolve);
|
||||
} else {
|
||||
if (callback) {
|
||||
callback.call(this);
|
||||
|
|
|
@ -8,8 +8,6 @@ export default class Tree {
|
|||
}
|
||||
}
|
||||
|
||||
this._isTree = true;
|
||||
|
||||
this.root = new Node({
|
||||
data: this.data,
|
||||
lazy: this.lazy,
|
||||
|
@ -28,9 +26,9 @@ export default class Tree {
|
|||
getCheckedNodes(leafOnly) {
|
||||
const checkedNodes = [];
|
||||
const walk = function(node) {
|
||||
const children = node.root ? node.root.children : node.children;
|
||||
const childNodes = node.root ? node.root.childNodes : node.childNodes;
|
||||
|
||||
children.forEach(function(child) {
|
||||
childNodes.forEach(function(child) {
|
||||
if ((!leafOnly && child.checked) || (leafOnly && child.isLeaf && child.checked)) {
|
||||
checkedNodes.push(child.data);
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ class Transition {
|
|||
el.style.paddingTop = el.dataset.oldPaddingTop;
|
||||
el.style.paddingBottom = el.dataset.oldPaddingBottom;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
functional: true,
|
||||
|
|
|
@ -1,29 +1,42 @@
|
|||
<template>
|
||||
<div class="el-tree-node"
|
||||
:class="{ expanded: childrenRendered && expanded }">
|
||||
<div class="el-tree-node__content" :style="{ 'padding-left': node.level * 16 + 'px' }"
|
||||
@click="handleExpandIconClick">
|
||||
<span class="el-tree-node__expand-icon"
|
||||
:class="{ 'is-leaf': node.isLeaf, expanded: !node.isLeaf && expanded }"
|
||||
></span>
|
||||
<el-checkbox v-if="showCheckbox" :indeterminate="node.indeterminate" v-model="node.checked" @change="handleCheckChange" @click.native="handleUserClick"></el-checkbox>
|
||||
@click.stop="handleClick"
|
||||
:class="{ expanded: childNodeRendered && expanded, 'is-current': $tree.currentNode === _self }">
|
||||
<div class="el-tree-node__content"
|
||||
:style="{ 'padding-left': node.level * 16 + 'px' }"
|
||||
@click="handleExpandIconClick">
|
||||
<span
|
||||
class="el-tree-node__expand-icon"
|
||||
:class="{ 'is-leaf': node.isLeaf, expanded: !node.isLeaf && expanded }">
|
||||
</span>
|
||||
<el-checkbox
|
||||
v-if="showCheckbox"
|
||||
v-model="node.checked"
|
||||
:indeterminate="node.indeterminate"
|
||||
@change="handleCheckChange"
|
||||
@click.native="handleUserClick">
|
||||
</el-checkbox>
|
||||
<span
|
||||
v-if="node.loading"
|
||||
class="el-tree-node__icon el-icon-loading"
|
||||
>
|
||||
class="el-tree-node__icon el-icon-loading">
|
||||
</span>
|
||||
<span class="el-tree-node__label" v-html="node.label"></span>
|
||||
<node-content :node="node"></node-content>
|
||||
</div>
|
||||
<collapse-transition>
|
||||
<div class="el-tree-node__children"
|
||||
<div
|
||||
class="el-tree-node__children"
|
||||
v-show="expanded">
|
||||
<el-tree-node v-for="child in node.children" :node="child"></el-tree-node>
|
||||
<el-tree-node
|
||||
:render-content="renderContent"
|
||||
v-for="child in node.childNodes"
|
||||
:node="child">
|
||||
</el-tree-node>
|
||||
</div>
|
||||
</collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/ecmascript-6">
|
||||
<script type="text/jsx">
|
||||
import CollapseTransition from './transition';
|
||||
|
||||
export default {
|
||||
|
@ -34,18 +47,35 @@
|
|||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {},
|
||||
renderContent: Function
|
||||
},
|
||||
|
||||
components: {
|
||||
CollapseTransition
|
||||
CollapseTransition,
|
||||
NodeContent: {
|
||||
props: {
|
||||
node: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
render(h) {
|
||||
const parent = this.$parent;
|
||||
return (
|
||||
parent.renderContent
|
||||
? parent.renderContent.call(parent._renderProxy, h, { _self: parent.$parent.$vnode.context, node: this.node })
|
||||
: <span class="el-tree-node__label">{ this.node.label }</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
$tree: null,
|
||||
expanded: false,
|
||||
childrenRendered: false,
|
||||
childNodeRendered: false,
|
||||
showCheckbox: false,
|
||||
oldChecked: null,
|
||||
oldIndeterminate: null
|
||||
|
@ -71,21 +101,25 @@
|
|||
this.indeterminate = indeterminate;
|
||||
},
|
||||
|
||||
handleClick() {
|
||||
this.$tree.currentNode = this;
|
||||
},
|
||||
|
||||
handleExpandIconClick(event) {
|
||||
let target = event.target;
|
||||
if (target.tagName.toUpperCase() !== 'DIV' &&
|
||||
target.parentNode.nodeName.toUpperCase() !== 'DIV' ||
|
||||
target.nodeName.toUpperCase() === 'LABLE') return;
|
||||
target.parentNode.nodeName.toUpperCase() !== 'DIV' ||
|
||||
target.nodeName.toUpperCase() === 'LABEL') return;
|
||||
if (this.expanded) {
|
||||
this.node.collapse();
|
||||
this.expanded = false;
|
||||
} else {
|
||||
this.node.expand(() => {
|
||||
this.expanded = true;
|
||||
this.childrenRendered = true;
|
||||
this.childNodeRendered = true;
|
||||
});
|
||||
}
|
||||
this.$tree.$emit('node-click', this.node.data);
|
||||
this.$tree.$emit('node-click', this.node.data, this.node, this);
|
||||
},
|
||||
|
||||
handleUserClick() {
|
||||
|
@ -111,6 +145,12 @@
|
|||
}
|
||||
|
||||
const tree = this.$tree;
|
||||
const props = this.props || {};
|
||||
const childrenKey = props['children'] || 'children';
|
||||
|
||||
this.$watch(`node.data.${childrenKey}`, () => {
|
||||
this.node.updateChildren();
|
||||
});
|
||||
|
||||
if (!tree) {
|
||||
console.warn('Can not find node\'s tree.');
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
<template>
|
||||
<div class="el-tree">
|
||||
<el-tree-node v-for="child in tree.root.children" :node="child"></el-tree-node>
|
||||
<div class="el-tree" :class="{ 'el-tree--highlight-current': highlightCurrent }">
|
||||
<el-tree-node
|
||||
v-for="child in tree.root.childNodes"
|
||||
:node="child"
|
||||
:props="props"
|
||||
:render-content="renderContent">
|
||||
</el-tree-node>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -14,6 +19,7 @@
|
|||
data: {
|
||||
type: Array
|
||||
},
|
||||
renderContent: Function,
|
||||
showCheckbox: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
@ -31,6 +37,7 @@
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
highlightCurrent: Boolean,
|
||||
load: {
|
||||
type: Function
|
||||
}
|
||||
|
@ -49,7 +56,8 @@
|
|||
|
||||
data() {
|
||||
return {
|
||||
tree: {}
|
||||
tree: {},
|
||||
currentNode: null
|
||||
};
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue