From 1902e394fc141b6a55c593ca475f5bc32499dc5a Mon Sep 17 00:00:00 2001 From: 07akioni <07akioni2@gmail.com> Date: Tue, 25 Feb 2020 18:47:41 +0800 Subject: [PATCH] refactor(tree): wip --- src/Tree/src/ChildNodesExpandTransition.js | 51 ----- src/Tree/src/Tree.js | 255 ++++++++++++--------- src/Tree/src/TreeNode.js | 60 ++++- src/Tree/src/TreeNodeCheckbox.vue | 4 +- src/Tree/src/TreeNodeContent.vue | 10 +- src/Tree/src/TreeNodeSwitcher.vue | 26 ++- src/Tree/src/utils.js | 8 + styles/Tree.scss | 22 +- styles/themes/dark/components/Tree.scss | 7 +- styles/themes/light/components/Tree.scss | 7 +- 10 files changed, 254 insertions(+), 196 deletions(-) delete mode 100644 src/Tree/src/ChildNodesExpandTransition.js create mode 100644 src/Tree/src/utils.js diff --git a/src/Tree/src/ChildNodesExpandTransition.js b/src/Tree/src/ChildNodesExpandTransition.js deleted file mode 100644 index 5e252c946..000000000 --- a/src/Tree/src/ChildNodesExpandTransition.js +++ /dev/null @@ -1,51 +0,0 @@ -export default { - props: { - transitionDisabled: { - type: Boolean, - default: false - } - }, - methods: { - handleBeforeLeave () { - this.$el.style.maxHeight = this.$el.offsetHeight + 'px' - this.$el.style.height = this.$el.offsetHeight + 'px' - this.$el.getBoundingClientRect() - }, - handleLeave () { - // debugger - this.$el.style.maxHeight = 0 - this.$el.getBoundingClientRect() - }, - handleEnter () { - this.$nextTick().then(() => { - this.$el.style.height = this.$el.offsetHeight + 'px' - this.$el.style.maxHeight = 0 - this.$el.getBoundingClientRect() - this.$el.style.maxHeight = this.$el.style.height - }) - }, - handleAfterEnter () { - this.$el.style.height = null - this.$el.style.maxHeight = null - } - }, - beforeDestroy () { - if (this.transitionDisabled) { - const parent = this.$el.parentElement - if (parent) parent.removeChild(this.$el) - } - }, - render (h) { - return h('transition', { - props: { - name: 'n-fade-in-height-expand-transition' - }, - on: { - beforeLeave: this.handleBeforeLeave, - leave: this.handleLeave, - enter: this.handleEnter, - afterEnter: this.handleAfterEnter - } - }, this.$slots.default) - } -} diff --git a/src/Tree/src/Tree.js b/src/Tree/src/Tree.js index 151ea9b5c..deed01d5c 100644 --- a/src/Tree/src/Tree.js +++ b/src/Tree/src/Tree.js @@ -1,53 +1,46 @@ -import { - treedOptions, - dropIsValid, - applyDrop, - linkedCascaderOptions, - menuOptions -} from '../../_utils/data/menuModel' - import withapp from '../../_mixins/withapp' import themeable from '../../_mixins/themeable' import NTreeNode from './TreeNode' -import NTreeChildNodesExpandTransition from './ChildNodesExpandTransition' +import NFadeInHeightExpandTransition from '../../_transition/FadeInHeightExpandTransition' +import { isLeaf, isLoaded } from './utils' -function createNode (node, h, self) { +function createNode (node, h, treeInstance) { const listeners = { - 'switcher-click': self.handleSwitcherClick, - select: self.handleSelect, - dragenter: self.handleDragEnter, - dragstart: self.handleDragStart, - dragleave: self.handleDragLeave, - drop: self.handleDrop, - check: self.handleCheck + 'switcher-click': treeInstance.handleSwitcherClick, + select: treeInstance.handleSelect, + dragenter: treeInstance.handleDragEnter, + dragstart: treeInstance.handleDragStart, + dragleave: treeInstance.handleDragLeave, + drop: treeInstance.handleDrop, + check: treeInstance.handleCheck } - const expanded = self.syntheticExpandedKeys.includes(node.key) + const expanded = treeInstance.syntheticExpandedKeys.includes(node.key) const props = { data: node, expanded, - selected: self.syntheticSelectedKeys.includes(node.key), - draggable: self.draggable, - checkable: self.checkable, - drop: self.drop, - blockNode: self.blockNode, - checked: self.syntheticCheckedKeys.includes(node.key) + selected: treeInstance.syntheticSelectedKeys.includes(node.key), + draggable: treeInstance.draggable, + checkable: treeInstance.checkable, + drop: treeInstance.drop, + blockNode: treeInstance.blockNode, + checked: treeInstance.syntheticCheckedKeys.includes(node.key) } return h(NTreeNode, { props, on: listeners, key: node.key }, - [!node.isLeaf - ? h(NTreeChildNodesExpandTransition, { + [!isLeaf(node) + ? h(NFadeInHeightExpandTransition, { props: { - transitionDisabled: self.transitionDisabled + transitionDisabled: treeInstance.transitionDisabled } }, [ - expanded + expanded && node.children ? h('ul', { staticClass: 'n-tree-children-wrapper' - }, node.children.map(child => createNode(child, h, self))) + }, node.children.map(child => createNode(child, h, treeInstance))) : null ] ) @@ -55,25 +48,36 @@ function createNode (node, h, self) { ) } -function convertRootedOptionsToVNodeTree (root, h, self) { - return root.children.map(child => createNode(child, h, self)) +function convertOptionsToVNodeTree (options, h, treeInstance) { + return options.map(child => createNode(child, h, treeInstance)) } export default { name: 'NTree', mixins: [ withapp, themeable ], + model: { + prop: 'selected-keys', + event: 'selected-keys-change' + }, + provide () { + return { NTree: this } + }, props: { data: { type: Array, default: null }, + autoExpandParent: { + type: Boolean, + default: true + }, checkable: { type: Boolean, default: false }, draggable: { type: Boolean, - default: true + default: false }, blockNode: { type: Boolean, @@ -83,6 +87,10 @@ export default { type: Array, default: null }, + disabled: { + type: Boolean, + default: false + }, defaultCheckedKeys: { type: Array, default: null @@ -99,18 +107,10 @@ export default { type: Array, default: null }, - lazy: { + remote: { type: Boolean, default: false }, - allowDrop: { - type: Function, - default: () => true - }, - allowDrag: { - type: Function, - default: () => true - }, multiple: { type: Boolean, default: false @@ -119,36 +119,15 @@ export default { type: String, default: '' }, - onExpand: { + onLoad: { type: Function, - default: () => { - return (node, next) => { - next() - } - } - }, - onSelect: { - type: Function, - default: () => { - return (node, next) => { - next() - } - } - }, - onDrop: { - type: Function, - default: () => { - return (node, next) => { - next() - } - } + default: null } }, created () { - this.internalCheckedKeys = this.defaultCheckedKeys || this.internalCheckedKeys - this.internalExpandedKeys = this.defaultExpandedKeys || this.internalExpandedKeys - this.internalSelectedKeys = this.defaultSelectedKeys || this.internalSelectedKeys - this.treeData = treedOptions(this.data) + this.internalCheckedKeys = this.defaultCheckedKeys || [] + this.internalExpandedKeys = this.defaultExpandedKeys || [] + this.internalSelectedKeys = this.defaultSelectedKeys || [] }, data () { return { @@ -160,18 +139,16 @@ export default { draggingNode: null, droppingNodeKey: null, expandTimerId: null, - transitionDisabled: false + transitionDisabled: false, + loadingKeys: [] } }, watch: { - data (newData) { - this.treeData = treedOptions(newData) + data () { this.internalExpandedKeys = [] this.internalCheckedKeys = [] this.internalSelectedKeys = [] - this.draggingNodeKey = null - this.draggingNode = null - this.droppingNodeKey = null + this.loadingKeys = [] this.expandTimerId = null } }, @@ -208,25 +185,33 @@ export default { } }, methods: { - getSelectedKeys () { - return this.syntheticSelectedKeys - }, - getCheckedKeys () { - return this.syntheticCheckedKeys - }, - getExpandedKeys () { - return this.syntheticExpandedKeys - }, disableTransition () { this.transitionDisabled = true }, enableTransition () { this.transitionDisabled = false }, + resetDragStatus () { + this.draggingNodeKey = null + this.draggingNode = null + this.droppingNodeKey = null + }, handleCheck (node, checked) { - if (!this.hasCheckedKeys) { - if (checked) { + if (this.disabled || node.disabled) return + if (checked) { + if (this.hasCheckedKeys) { + this.$emit('checked-keys-change', this.syntheticCheckedKeys.concat([node.key])) + } else { this.internalCheckedKeys.push(node.key) + } + } else { + if (this.hasCheckedKeys) { + const checkedKeysAfterChange = this.syntheticCheckedKeys + checkedKeysAfterChange.splice( + checkedKeysAfterChange.findIndex(key => key === node.key), + 1 + ) + this.$emit('checked-keys-change', checkedKeysAfterChange) } else { this.internalCheckedKeys.splice( this.internalCheckedKeys.findIndex(key => key === node.key), @@ -235,79 +220,121 @@ export default { } } }, - handleDrop (node, dropType) { - const drop = [this.draggingNode, node, dropType] - if (dropIsValid(drop)) { - this.disableTransition() - this.$nextTick().then(() => { - applyDrop(drop) - return this.$nextTick() - }).then(() => { - this.enableTransition() - }) - } - this.draggingNodeKey = null - this.draggingNode = null - }, toggleExpand (node) { - const index = this.syntheticExpandedKeys.findIndex(expandNodeId => expandNodeId === node.key) + if (this.disabled) return + const index = this.syntheticExpandedKeys + .findIndex(expandNodeId => expandNodeId === node.key) if (~index) { - this.$emit('collapse', node) if (!this.hasExpandedKeys) { this.internalExpandedKeys.splice(index, 1) + this.$emit('expanded-keys-change', this.internalExpandedKeys) + } else { + const expandedKeysAfterChange = Array.from(this.syntheticExpandedKeys) + expandedKeysAfterChange.splice(index, 1) + this.$emit( + 'expanded-keys-change', + expandedKeysAfterChange + ) } } else { - if (!node.isLeaf) { + if (!isLeaf(node)) { this.$emit('expand', node) if (!this.hasExpandedKeys) { this.internalExpandedKeys.push(node.key) + this.$emit('expanded-keys-change', this.internalExpandedKeys) + } else { + this.$emit( + 'expanded-keys-change', + this.syntheticExpandedKeys.concat(node.key) + ) } } } }, handleSwitcherClick (node) { + if (this.disabled || node.disabled) return this.toggleExpand(node) }, handleSelect (node) { + if (this.disabled || node.disabled) return this.$emit('select', node) if (this.internalSelectedKeys.includes(node.key)) this.internalSelectedKeys = [] else this.internalSelectedKeys = [node.key] }, - handleDragEnter (node) { - this.$emit('dragenter', node) + handleDragEnter ({ event, node }) { + if (!this.draggable || this.disabled || node.disabled) return + this.$emit('dragenter', { event, node }) + if (!this.autoExpandParent) return this.droppingNodeKey = node.key if (node.key === this.draggingNodeKey) return - if (!this.syntheticExpandedKeys.includes(node.key) && !node.isLeaf) { + if ( + !this.syntheticExpandedKeys.includes(node.key) && + !isLeaf(node) + ) { window.clearTimeout(this.expandTimerId) - this.expandTimerId = window.setTimeout(() => { - if (this.droppingNodeKey === node.key && !this.syntheticExpandedKeys.includes(node.key)) { + const expand = () => { + if ( + this.droppingNodeKey === node.key && + !this.syntheticExpandedKeys.includes(node.key) + ) { if (!this.hasExpandedKeys) { this.internalExpandedKeys.push(node.key) + this.$emit('expanded-keys-change', this.internalExpandedKeys) + } else { + this.$emit('expanded-keys-change', this.syntheticExpandedKeys.concat(node.key)) } - this.$emit('expand', node.key) } + } + if (!isLoaded(node)) { + if (!this.loadingKeys.includes(node.key)) { + this.loadingKeys.push(node.key) + } + this + .onLoad(node) + .then(() => { + this.loadingKeys.splice( + this.loadingKeys.find(key => key === node.key), + 1 + ) + expand() + }) + return + } + this.expandTimerId = window.setTimeout(() => { + expand() this.expandTimerId = null }, 800) } }, - handleDragLeave (node) { + handleDragLeave ({ event, node }) { + if (!this.draggable || this.disabled || node.disabled) return this.droppingNodeKey = null - this.$emit('dragleave', node) + this.$emit('dragleave', { event, node }) }, - handleDragStart (node) { + handleDragStart ({ event, node }) { + if (!this.draggable || this.disabled || node.disabled) return this.draggingNodeKey = node.key this.draggingNode = node - this.$emit('dragstart', node) + this.$emit('dragstart', { event, node }) + }, + handleDrop ({ event, node, dropPosition }) { + if (!this.draggable || this.disabled || node.disabled) return + const drop = { + event, + node, + dragNode: this.draggingNode, + dropPosition + } + this.$emit('drop', drop) + this.resetDragStatus() } }, render (h) { - const lOptions = linkedCascaderOptions(this.treeData, 'multiple-all-options') - const mOptions = menuOptions(lOptions)[0] return h('div', { staticClass: 'n-tree', class: { [`n-${this.syntheticTheme}-theme`]: this.syntheticTheme } - }, convertRootedOptionsToVNodeTree(mOptions, h, this)) + }, convertOptionsToVNodeTree(this.data, h, this)) } } diff --git a/src/Tree/src/TreeNode.js b/src/Tree/src/TreeNode.js index 727d20c2f..e4ac27a0c 100644 --- a/src/Tree/src/TreeNode.js +++ b/src/Tree/src/TreeNode.js @@ -1,9 +1,15 @@ import NTreeNodeSwitcher from './TreeNodeSwitcher.vue' import NTreeNodeCheckbox from './TreeNodeCheckbox.vue' import NTreeNodeContent from './TreeNodeContent.vue' +import { isLeaf, isLoaded } from './utils' export default { name: 'NTreeNode', + inject: { + NTree: { + default: null + } + }, props: { data: { type: Object, @@ -38,27 +44,58 @@ export default { default: null } }, + computed: { + loading () { + return this.NTree.loadingKeys.includes(this.data.key) + } + }, methods: { handleSwitcherClick () { - this.$emit('switcher-click', this.data) + const node = this.data + const NTree = this.NTree + if (NTree.remote && !isLeaf(node) && !isLoaded(node)) { + if (!NTree.loadingKeys.includes(node.key)) { + NTree.loadingKeys.push(node.key) + } + NTree.onLoad && + NTree.onLoad(node) + .then(() => { + NTree.loadingKeys.splice( + NTree.loadingKeys.find(key => key === node.key), + 1 + ) + this.$emit('switcher-click', node) + }) + } else { + this.$emit('switcher-click', node) + } }, handleContentClick () { this.$emit('select', this.data) }, - handleDragOver () { - this.$emit('dragover', this.data) + handleDragOver (e) { + this.$emit('dragover', { event: e, node: this.data }) }, - handleDragEnter () { - this.$emit('dragenter', this.data) + handleDragEnter (e) { + this.$emit('dragenter', { event: e, node: this.data }) }, - handleDragStart () { - this.$emit('dragstart', this.data) + handleDragStart (e) { + this.$emit('dragstart', { event: e, node: this.data }) }, - handleDragLeave () { - this.$emit('dragleave', this.data) + handleDragLeave (e) { + this.$emit('dragleave', { event: e, node: this.data }) + }, + handleDragEnd (e) { + this.$emit('dragend', { event: e, node: this.data }) + this.resetDragStatus() }, handleDrop (e, dropPosition) { - this.$emit('drop', this.data, dropPosition) + this.$emit('drop', { + event: e, + node: this.data, + dropPosition + }) + this.NTree.resetDragStatus() }, handleCheck (checked) { this.$emit('check', this.data, checked) @@ -71,7 +108,8 @@ export default { h(NTreeNodeSwitcher, { props: { expanded: this.expanded, - hide: this.data.isLeaf + loading: this.loading, + hide: isLeaf(this.data) }, on: { click: this.handleSwitcherClick diff --git a/src/Tree/src/TreeNodeCheckbox.vue b/src/Tree/src/TreeNodeCheckbox.vue index cbb2a2047..d6a09e1f1 100644 --- a/src/Tree/src/TreeNodeCheckbox.vue +++ b/src/Tree/src/TreeNodeCheckbox.vue @@ -2,7 +2,7 @@ @@ -22,7 +22,7 @@ export default { } }, methods: { - handleInput (value) { + handleChange (value) { this.$emit('check', value) } } diff --git a/src/Tree/src/TreeNodeContent.vue b/src/Tree/src/TreeNodeContent.vue index 1a22df105..b95233617 100644 --- a/src/Tree/src/TreeNodeContent.vue +++ b/src/Tree/src/TreeNodeContent.vue @@ -88,12 +88,12 @@ export default { handleContentDrop (e) { e.preventDefault() this.pending = false - const actionType = ({ - top: 'insertBefore', - bottom: 'insertAfter', - body: 'append' + const dropPosition = ({ + top: 'top', + bottom: 'bottom', + body: 'center' })[this.pendingPosition] - this.$emit('drop', e, actionType) + this.$emit('drop', e, dropPosition) } } } diff --git a/src/Tree/src/TreeNodeSwitcher.vue b/src/Tree/src/TreeNodeSwitcher.vue index d53d02de5..236708ae7 100644 --- a/src/Tree/src/TreeNodeSwitcher.vue +++ b/src/Tree/src/TreeNodeSwitcher.vue @@ -7,19 +7,33 @@ }" @click="handleClick" > - - - +
+ + + + + + +