Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding view-only properties to a tree data object

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:

  1. Create a parallel tree structure mirroring the data tree that holds the display state. Pass this to the folder components and have them query that to know their state.
  2. Use a map to map nodes of the data tree to a state object. The problem is that maps are not reactive in Vue.js so I'd have to resort to ugly hacks to make it work.

Any other ideas?

like image 712
bernie Avatar asked Sep 13 '26 03:09

bernie


1 Answers

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:

  • Sometimes you might want to show the first Node as expanded
  • During the search operation, all the Nodes with specific name must expand the way it happens in most of the code editors.
  • When used as a file explorer, nodes containing specific files must be expanded by default.

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

like image 124
Harshal Patil Avatar answered Sep 15 '26 18:09

Harshal Patil



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!