Merge pull request #2090 from SergioCrisostomo/refactor-tree
Refactor tree
This commit is contained in:
commit
c89e0e1791
3 changed files with 123 additions and 117 deletions
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<collapse-transition>
|
||||
<ul :class="classes" v-show="visible">
|
||||
<ul :class="classes">
|
||||
<li>
|
||||
<span :class="arrowClasses" @click="handleExpand">
|
||||
<Icon type="arrow-right-b"></Icon>
|
||||
|
@ -8,15 +8,15 @@
|
|||
<Checkbox
|
||||
v-if="showCheckbox"
|
||||
:value="data.checked"
|
||||
:indeterminate="indeterminate"
|
||||
:indeterminate="data.indeterminate"
|
||||
:disabled="data.disabled || data.disableCheckbox"
|
||||
@click.native.prevent="handleCheck"></Checkbox>
|
||||
<span :class="titleClasses" v-html="data.title" @click="handleSelect"></span>
|
||||
<Tree-node
|
||||
v-if="data.expand"
|
||||
v-for="item in data.children"
|
||||
:key="item.nodeKey"
|
||||
:data="item"
|
||||
:visible="data.expand"
|
||||
:multiple="multiple"
|
||||
:show-checkbox="showCheckbox">
|
||||
</Tree-node>
|
||||
|
@ -29,7 +29,6 @@
|
|||
import Icon from '../icon/icon.vue';
|
||||
import CollapseTransition from '../base/collapse-transition';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
import { findComponentsDownward } from '../../utils/assist';
|
||||
|
||||
const prefixCls = 'ivu-tree';
|
||||
|
||||
|
@ -51,16 +50,11 @@
|
|||
showCheckbox: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
indeterminate: false
|
||||
prefixCls: prefixCls
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -103,40 +97,16 @@
|
|||
},
|
||||
handleSelect () {
|
||||
if (this.data.disabled) return;
|
||||
if (this.data.selected) {
|
||||
this.data.selected = false;
|
||||
} else if (this.multiple) {
|
||||
this.$set(this.data, 'selected', !this.data.selected);
|
||||
} else {
|
||||
this.dispatch('Tree', 'selected', this.data);
|
||||
}
|
||||
this.dispatch('Tree', 'on-selected');
|
||||
this.dispatch('Tree', 'on-selected', this.data.nodeKey);
|
||||
},
|
||||
handleCheck () {
|
||||
if (this.disabled) return;
|
||||
const checked = !this.data.checked;
|
||||
if (!checked || this.indeterminate) {
|
||||
findComponentsDownward(this, 'TreeNode').forEach(node => node.data.checked = false);
|
||||
} else {
|
||||
findComponentsDownward(this, 'TreeNode').forEach(node => node.data.checked = true);
|
||||
}
|
||||
this.data.checked = checked;
|
||||
this.dispatch('Tree', 'checked');
|
||||
this.dispatch('Tree', 'on-checked');
|
||||
},
|
||||
setIndeterminate () {
|
||||
this.indeterminate = this.data.checked ? false : findComponentsDownward(this, 'TreeNode').some(node => node.data.checked);
|
||||
if (this.data.disabled) return;
|
||||
const changes = {
|
||||
checked: !this.data.checked && !this.data.indeterminate,
|
||||
nodeKey: this.data.nodeKey
|
||||
};
|
||||
this.dispatch('Tree', 'on-check', changes);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// created node.vue first, mounted tree.vue second
|
||||
if (!this.data.checked) this.$set(this.data, 'checked', false);
|
||||
},
|
||||
mounted () {
|
||||
this.$on('indeterminate', () => {
|
||||
this.broadcast('TreeNode', 'indeterminate');
|
||||
this.setIndeterminate();
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
<template>
|
||||
<div :class="prefixCls">
|
||||
<Tree-node
|
||||
v-for="item in data"
|
||||
v-for="item in stateTree"
|
||||
:key="item.nodeKey"
|
||||
:data="item"
|
||||
visible
|
||||
:multiple="multiple"
|
||||
:show-checkbox="showCheckbox">
|
||||
</Tree-node>
|
||||
<div :class="[prefixCls + '-empty']" v-if="!data.length">{{ localeEmptyText }}</div>
|
||||
<div :class="[prefixCls + '-empty']" v-if="!stateTree.length">{{ localeEmptyText }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import TreeNode from './node.vue';
|
||||
import { findComponentsDownward } from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
import Locale from '../../mixins/locale';
|
||||
|
||||
const prefixCls = 'ivu-tree';
|
||||
|
||||
let key = 1;
|
||||
|
||||
export default {
|
||||
name: 'Tree',
|
||||
mixins: [ Emitter, Locale ],
|
||||
|
@ -46,92 +43,128 @@
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls
|
||||
prefixCls: prefixCls,
|
||||
stateTree: this.data,
|
||||
flatState: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
data(){
|
||||
this.stateTree = this.data;
|
||||
this.flatState = this.compileFlatState();
|
||||
this.rebuildTree();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
localeEmptyText () {
|
||||
if (this.emptyText === undefined) {
|
||||
if (typeof this.emptyText === 'undefined') {
|
||||
return this.t('i.tree.emptyText');
|
||||
} else {
|
||||
return this.emptyText;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
compileFlatState () { // so we have always a relation parent/children of each node
|
||||
let keyCounter = 0;
|
||||
const flatTree = [];
|
||||
function flattenChildren(node, parent) {
|
||||
node.nodeKey = keyCounter++;
|
||||
flatTree[node.nodeKey] = { node: node, nodeKey: node.nodeKey };
|
||||
if (typeof parent != 'undefined') {
|
||||
flatTree[node.nodeKey].parent = parent.nodeKey;
|
||||
flatTree[parent.nodeKey].children.push(node.nodeKey);
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
flatTree[node.nodeKey].children = [];
|
||||
node.children.forEach(child => flattenChildren(child, node));
|
||||
}
|
||||
}
|
||||
this.stateTree.forEach(rootNode => {
|
||||
flattenChildren(rootNode);
|
||||
});
|
||||
return flatTree;
|
||||
},
|
||||
updateTreeUp(nodeKey){
|
||||
const parentKey = this.flatState[nodeKey].parent;
|
||||
if (typeof parentKey == 'undefined') return;
|
||||
|
||||
const node = this.flatState[nodeKey].node;
|
||||
const parent = this.flatState[parentKey].node;
|
||||
if (node.checked == parent.checked && node.indeterminate == parent.indeterminate) return; // no need to update upwards
|
||||
|
||||
if (node.checked == true) {
|
||||
this.$set(parent, 'checked', parent.children.every(node => node.checked));
|
||||
this.$set(parent, 'indeterminate', !parent.checked);
|
||||
} else {
|
||||
this.$set(parent, 'checked', false);
|
||||
this.$set(parent, 'indeterminate', parent.children.some(node => node.checked || node.indeterminate));
|
||||
}
|
||||
this.updateTreeUp(parentKey);
|
||||
},
|
||||
rebuildTree () { // only called when `data` prop changes
|
||||
const checkedNodes = this.getCheckedNodes();
|
||||
checkedNodes.forEach(node => {
|
||||
this.updateTreeDown(node, {checked: true});
|
||||
// propagate upwards
|
||||
const parentKey = this.flatState[node.nodeKey].parent;
|
||||
if (!parentKey && parentKey !== 0) return;
|
||||
const parent = this.flatState[parentKey].node;
|
||||
const childHasCheckSetter = typeof node.checked != 'undefined' && node.checked;
|
||||
if (childHasCheckSetter && parent.checked != node.checked) {
|
||||
this.updateTreeUp(node.nodeKey); // update tree upwards
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getSelectedNodes () {
|
||||
const nodes = findComponentsDownward(this, 'TreeNode');
|
||||
return nodes.filter(node => node.data.selected).map(node => node.data);
|
||||
/* public API */
|
||||
return this.flatState.filter(obj => obj.node.selected).map(obj => obj.node);
|
||||
},
|
||||
getCheckedNodes () {
|
||||
const nodes = findComponentsDownward(this, 'TreeNode');
|
||||
return nodes.filter(node => node.data.checked).map(node => node.data);
|
||||
/* public API */
|
||||
return this.flatState.filter(obj => obj.node.checked).map(obj => obj.node);
|
||||
},
|
||||
updateData (isInit = true) {
|
||||
// init checked status
|
||||
function reverseChecked(data) {
|
||||
if (!data.nodeKey) data.nodeKey = key++;
|
||||
if (data.children && data.children.length) {
|
||||
let checkedLength = 0;
|
||||
data.children.forEach(node => {
|
||||
if (node.children) node = reverseChecked(node);
|
||||
if (node.checked) checkedLength++;
|
||||
});
|
||||
if (isInit) {
|
||||
if (checkedLength >= data.children.length) data.checked = true;
|
||||
} else {
|
||||
data.checked = checkedLength >= data.children.length;
|
||||
}
|
||||
return data;
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
updateTreeDown(node, changes = {}) {
|
||||
for (let key in changes) {
|
||||
this.$set(node, key, changes[key]);
|
||||
}
|
||||
|
||||
function forwardChecked(data) {
|
||||
if (data.children) {
|
||||
data.children.forEach(node => {
|
||||
if (data.checked) node.checked = true;
|
||||
if (node.children) node = forwardChecked(node);
|
||||
});
|
||||
return data;
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
if (node.children) {
|
||||
node.children.forEach(child => {
|
||||
this.updateTreeDown(child, changes);
|
||||
});
|
||||
}
|
||||
this.data.map(node => reverseChecked(node)).map(node => forwardChecked(node));
|
||||
this.broadcast('TreeNode', 'indeterminate');
|
||||
},
|
||||
handleSelect (nodeKey) {
|
||||
const node = this.flatState[nodeKey].node;
|
||||
if (!this.multiple){ // reset selected
|
||||
const currentSelectedKey = this.flatState.findIndex(obj => obj.node.selected);
|
||||
if (currentSelectedKey >= 0) this.$set(this.flatState[currentSelectedKey].node, 'selected', false);
|
||||
}
|
||||
this.$set(node, 'selected', !node.selected);
|
||||
|
||||
this.$emit('on-select-change', this.getSelectedNodes());
|
||||
},
|
||||
handleCheck({ checked, nodeKey }) {
|
||||
const node = this.flatState[nodeKey].node;
|
||||
this.$set(node, 'checked', checked);
|
||||
this.$set(node, 'indeterminate', false);
|
||||
|
||||
this.updateTreeUp(nodeKey); // propagate up
|
||||
this.updateTreeDown(node, {checked, indeterminate: false}); // reset `indeterminate` when going down
|
||||
|
||||
this.$emit('on-check-change', this.getCheckedNodes());
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.flatState = this.compileFlatState();
|
||||
this.rebuildTree();
|
||||
},
|
||||
mounted () {
|
||||
this.updateData();
|
||||
this.$on('selected', ori => {
|
||||
const nodes = findComponentsDownward(this, 'TreeNode');
|
||||
nodes.forEach(node => {
|
||||
this.$set(node.data, 'selected', false);
|
||||
});
|
||||
this.$set(ori, 'selected', true);
|
||||
});
|
||||
this.$on('on-selected', () => {
|
||||
this.$emit('on-select-change', this.getSelectedNodes());
|
||||
});
|
||||
this.$on('checked', () => {
|
||||
this.updateData(false);
|
||||
});
|
||||
this.$on('on-checked', () => {
|
||||
this.$emit('on-check-change', this.getCheckedNodes());
|
||||
});
|
||||
this.$on('toggle-expand', (payload) => {
|
||||
this.$emit('on-toggle-expand', payload);
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
data () {
|
||||
this.$nextTick(() => {
|
||||
this.updateData();
|
||||
this.broadcast('TreeNode', 'indeterminate');
|
||||
});
|
||||
}
|
||||
this.$on('on-check', this.handleCheck);
|
||||
this.$on('on-selected', this.handleSelect);
|
||||
this.$on('toggle-expand', node => this.$emit('on-toggle-expand', node));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -38,7 +38,10 @@
|
|||
}
|
||||
&-arrow{
|
||||
cursor: pointer;
|
||||
i{
|
||||
width: 12px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
i {
|
||||
transition: all @transition-time @ease-in-out;
|
||||
}
|
||||
&-open{
|
||||
|
@ -56,4 +59,4 @@
|
|||
cursor: @cursor-disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue