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