'Tree view component with drag'n'drop leaks memory
I need to create a TreeView component in Vue.js 3, and I need it to be drag'n'drop editable. The component I came up with is working, but it leaks memory.
Here is the component code, and a usage example:
TreeView.js
var _draggingNode = null
, _draggingFrom = null
;
/**
* Check whether a node is a descendant of some other node
* @param {*} node - The node to test
* @param {*} tree - The ancestor / subtree
* @returns true if node is a descendant of tree, false otherwise
*/
function isDescendantOf(node, tree) {
if (node === tree) {
// Recursion reached the node itself so the node is a child of the original parent
return true;
} else if (!tree.children || tree.children.length < 1) {
// Tree is a leaf, recursion ends an the node is not a descendant
return false;
} else {
return null != tree.children.find(elem => {
return isDescendantOf(node, elem);
});
}
}
var TreeView = {
//
props: {
tree: {
type: Object,
required: true
}
},
data() {
return {
dragging: null,
isDraggingOver: false
}
},
computed: {
hasChildren() {
return this.tree.children && this.tree.children.length > 0
}
},
//
methods: {
dragStart: function (evt, item) {
_draggingNode = item;
_draggingFrom = this.tree;
item.dragging = true;
evt.stopPropagation();
item = null;
},
dragLeave: function () {
this.isDraggingOver = false;
},
dragOver: function (evt) {
evt.stopPropagation();
this.isDraggingOver = true;
if(_draggingNode === this.tree) {
//don't drop nodes on themselves
return true;
} else if (isDescendantOf(this.tree, _draggingNode)) {
//parent nodes can't be dropped onto descendants
return true;
}
evt.preventDefault();
return false;
},
dragEnd: function (evt) {
_draggingNode.dragging = false;
_draggingNode = null;
_draggingFrom = null;
evt.stopPropagation();
},
drop: function (evt) {
var id = _draggingFrom.children.indexOf(_draggingNode);
_draggingFrom.children.splice(id, 1);
this.tree.children.push(_draggingNode);
this.isDraggingOver = false;
evt.stopPropagation();
}
},
//
template:
`<div class="tree"
@drop="drop($event)"
@dragover="dragOver($event)"
@dragleave="dragLeave()"
@dragenter.prevent
:class="{ 'dragging-over': isDraggingOver }">
<div class="tree-name">
{{ tree.name }} - {{ hasChildren }}
</div>
<ul class="tree-nodes"
v-if="hasChildren">
<li draggable="true"
v-for="(child, index) in tree.children"
:key="index"
:class="{ dragging: child.dragging }"
@dragstart="dragStart($event, child)"
@dragend="dragEnd($event)"
@drop="drop($event, index)"
class="tree-children ms-3">
<v-tree-view :tree="child" />
</li>
</ul>
</div>`
};
export { TreeView }
vue-index.js
import { TreeView } from "TreeView.js"
var _tree = {
"name": "root",
"children": [
{
"name" : 1,
"children": [
{
"name" : 10,
"children": [
]
},
{
"name" : 11,
"children": [
]
}
]
},
{
"name" : 2,
"children": [
{
"name" : 20,
"children": [
]
},
{
"name" : 21,
"children": [
]
}
]
}
]
};
const app = Vue.createApp({
data() {
return {
user: Vue.ref(UserSession),
tree: _tree
}
}
});
app.component('v-tree-view', TreeView);
app.config.errorHandler = (err) => {
console.error("App error: ", err);
}
app.mount('#app');
index.html
<!DOCTYPE html>
<html>
<head>
<title>TreeView - test</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<h1>The Tree</h1>
<v-tree-view :tree="tree" />
</div>
<script src="js/vue-index.js" type="module"></script>
</body>
</html>
Here is the leak after a couple of drag'n'drops:

Looks like the "splice" where nodes get removed after drop leave some detached elements that retain other objects.
I'm relatively new to Vue.js so I might have missed something basic, but I have some experience with Knockout js where I managed to resolve this kind of things by cleaning event listeners or components manually when the default behaviour didn't meet my needs. Can I do this in Vuejs? (i.e. in the unMounted method?)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
