I'm building a tree component where nodes can be moved around. The component and data structure are similar to the one used in the Tree View example in the Vue.js documentation.
The data structure looks like this (irrelevant properties removed):
[
{"id": 1, "children": []},
{"id": 2, "children": []},
{"id": 3, "children": [
{"id": 4, "children": [
{"id": 5, "children": []},
{"id": 6, "children": []}
]}
]}
]
The nodes represent "folders" that can be folded or expanded in the view. The FolderNode component looks like this:
export default {
name: 'FolderNode',
props: {
node: { type: Object, required: true },
},
data: () => ({
expanded: true,
}),
methods: {
toggleExpand() {
this.expanded = !this.expanded;
},
},
};
The template is:
<template>
<li>
<span class="node-icon">
<span @click="toggleExpand">[{{ expanded ? '-' : '+' }}]</span>
</span>
<span class="node-label">{{ node.id }}</span>
<ol
v-if="node.children && node.children.length"
v-show="expanded"
>
<FolderNode
v-for="child in node.children"
:key="child.id"
:node="child"
/>
</ol>
</li>
</template>
This part works fine. I added drag and drop functionality to move nodes around (not shown above for simplicity) in the tree. When nodes are removed and inserted elsewhere, Vue.js instantiates new FolderNode components automatically to reflect the changes. These new FolderNode instances are created (when a node is moved to a different parent) with the default expanded state to true. I was hoping the :key property would work across different parents, but it only reuses components (and keeps their expanded state) for children of the same parent FolderNode.
So, how can I keep the folders' expanded state (and other display state) when they are moved?
The tree object will be used by other parts of the application so I can't add an expanded property directly to its nodes which would be the easiest solution. Besides, expanded is strictly "display state" and has nothing to do with the tree data.
I've thought of 2 possible solutions which I don't find really appealing:
Any other ideas?
We faced a similar problem when we build a tree component. We had the exact same thought.
Initially, we created a parallel data structure and with the help of Ramda, we would merge the internal data model external tree structure. But this proved not very elegant. And, tree conciliation is very difficult to achieve.
But, if you take another perspective on this approach, then it makes sense to make expanded as part of the tree Node object. There are multiple use cases for this:
And there are many other cases where it proves useful. We are using TypeScript, we simply declare that property as optional like:
export interface TreeNode<T extends any> {
label: string;
expanded: boolean;
children: Array<TreeNode<T>>;
isDisabled?: boolean;
// Holds the state if tree/node selected or not
select?: boolean;
// Unique key which identifies each node
_id?: string;
// Any other data that needs to be stored
context?: T;
}
As far as API is concerned, they need not be bothered about these additional keys as they are optional. Thus, it becomes trivial to support drag-n-drop with expanded state
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With