javascript - 更改 Vue 组件的所有权
问题描述
我正在尝试制作一个布局创建者,可以将一个小部件从画廊拖到屏幕上。为此,我制作了两个组件 -LayoutWidgets
和LayoutCanvas
. 这两个组件都有vue-grid-layouts作为子级。我的想法是,当将项目从画廊拖到画布时,项目组件将被重新设置为画布布局(这是从原始 JS 角度接近它,.appendChild
可用于重新排列 DOM),同时仍然保持其拖动状态并保留事件侦听器等。Vue 的 VDom 有什么办法做到这一点吗?这是我当前的包装器组件:
<template>
<ResizablePanes class="resizer-root">
<LayoutCanvas ref="canvas"
style="flex-grow:2"
@dragging="dragEvent($event, 'canvas')"/>
<LayoutWidgets ref="widgets"
style="flex-grow:1"
@dragging="dragEvent($event, 'widgets')"/>
</ResizablePanes>
</template>
<script>
import LayoutCanvas from './LayoutCanvas.vue';
import LayoutWidgets from './LayoutWidgets.vue';
import ResizablePanes from './ResizablePanes.vue';
export default {
components: {
LayoutCanvas,
LayoutWidgets,
ResizablePanes,
},
data: () => ({
dragging: null,
dragParent: null,
dragReceiverRefs: ['canvas', 'widgets'],
}),
computed: {
dragReceivers() {
return this.dragReceiverRefs
.filter(x => x != this.dragParent)
.map(x => ({
grid:this.$refs[x].$refs.grid,
bBox:this.$refs[x].$refs.grid.$el.getBoundingClientRect()
}))
}
},
methods: {
dragEvent(item, ref) {
if (!item) {
this.dragging = null;
this.dragParent = null;
window.removeEventListener('mousemove', this.dragHover);
} else {
this.dragging = item;
this.dragParent = ref;
window.addEventListener('mousemove', this.dragHover);
}
},
dragHover(e) {
const receiver = this.dragReceivers.find(x =>
x.bBox.left <= e.clientX &&
x.bBox.top <= e.clientY &&
x.bBox.right >= e.clientX &&
x.bBox.bottom >= e.clientY
);
// need help here:
receiver.grid.appendChild(this.dragging);
//
}
}
}
</script>
解决方案
好吧,这是一个不必要的难题,但我已经设法创建了一个解决方案,如果将来有人发现这个问题,我会在这里发布。
请注意,在此代码中:
moveNode
是要移动的 Vue 组件oldGrandParent
是包含其prop
定义的此父级的祖先newGrandParent
是它被移动到的等效祖先newParent
是将持有它的直接父级 Vue 组件
// New data object
const newLayout = { x:0, y:0, w:moveNode.w * 2, h:moveNode.h, i:Date.now() };
// Remove VNode from old parent
const vNode = moveNode.$parent._vnode.children.splice(moveNode.$parent._vnode.children.indexOf(moveNode.$vnode), 1)[0];
// Rekey it
vNode.key = newLayout.i;
// Move HTMLElement
newParent._vnode.elm.appendChild(vNode.elm);
// Move VNode
newParent._vnode.children.push(vNode);
// Clean up old parent
moveNode.eventBus.$emit('dragEvent', 'dragend')
// Re-initialise Node for new parent (using copied source code - I don't think there's an easier way to do this)
this.rebindEventBus(moveNode, Object.getOwnPropertyDescriptor(newParent, "eventBus"));
// Continue cleaning up old parent
moveNode.$parent.$children.splice(moveNode.$parent.$children.indexOf(moveNode), 1)
oldGrandParent.layout.splice(oldGrandParent.layout.findIndex(x => x.i == moveNode.i), 1);
// Give Vue instance new parent
moveNode.$parent = newParent;
// Add new data object to new parent, triggering reactivity
newGrandParent.layout.push(newLayout);
// Call Nodes "mounted" hook to finish re-parenting
this.$nextTick(function() { moveNode.$options.mounted.slice().reverse()[0].call(moveNode); });
// Reparenting complete!