0

I currently have a TreeView component that contains a tree. This tree contains Children that have each have a label, perm, and its own children.

Example of the tree:

App.vue

let tree = ref({
  label: 'org',
  perm: {
    POST: "auth",
    GET: "null"
  },
  children: [
    {
      label: '*',
      perm: {
        POST: "auth",
        GET: "null"
      },
      children: [{
        label: 'repo',
        perm: {
          POST: "null",
          GET: "null"
        },
        children: []
      }]
    },
    {
      label: 'test',
      perm: {
        POST: "auth",
        GET: "auth"
      },
      children: []
    }
  ]
})

I'm currently displaying this in a simple Tree View using this:

TreeView.vue

 <div @click="toggleHideShow()"> 
     {{ label }}
 </div>
 <div class="child" v-if="hideShow">
     <TreeView v-for="child in children" :label="child.label" :perm="child.perm" :children="child.children" />
 </div>

I intend to be able to add children dynamically through this TreeView, so is there a way for me to get the exact Children object onclick?

Right now what I've tried is passing the attributes through the toggleHideShow method:

 <div @click="toggleHideShow(label, perm, children)"> 

and then creating a new Children object, looping through the tree and then comparing each one to find the matching object.

const toggleHideShow = (label: string, perm: Perm, children?: Array<Children>)=> {
    hideShow.value = !hideShow.value
    const newChild: Children = {
        label: label,
        perm: perm,
        children: children
    }

    //do for loop here
}

The problem here is that my tree variable is declared in the App.vue of my project, and I am passing this tree object into my TreeView.

<TreeView :label="tree.label" :perm="tree.perm" :children="tree.children" />

So I have 3 questions:

1: How can I access this tree variable from my TreeView component, and would modifying it also modify the tree variable that is inside my App.vue, and hence dynamically updating the TreeView?

2: Would it be better for me to declare a method inside App.vue that took in the newly created newChild variable as a parameter and then adding it to the tree from there? If so how would I go about creating this method from my TreeView component?

3: Is there any other way for me to know which Children object in the tree I have clicked on? And then how would I access it from the tree variable since it isn't an array?

2 Answers 2

1
+50

With recursion, any change made to a child has to be made at parent level. If you're 2 levels deep, you emit an event updating the parent => emits an event upgrading the grand parent => updates the parent => updates the child.

This happens even when you're 10 levels deep.
You'd think the biggest problem with this approach is performance. It's not. Vue is quite efficient at change detection and rendering.
The biggest problem will be debugging. If you start getting errors at some level, it will be difficult to figure out where and what's wrong.

I suggest a simpler approach:

  • put all your items into a flat array and give them unique identifiers
  • instead of keeping items inside children, only keep their ids

Now, whenever you update an item, neither its ancestors nor its descendants need to know about the change.

You can have an item editor which gets the currently selected item and can modify it. Because you don't need to worry about what happens to its children or about broadcasting the change to the top level, everything is more straight forward and testable.

Working demo: https://codesandbox.io/s/editable-tree-bo6msx?file=/src/App.vue

To keep things organised, I've extracted the tree related logic into a store, which exports methods to add/remove/edit tree items.

Another benefit of this approach is it could be easily modified to allow multiple parents for the same item, should you ever need it (the only thing that needs to change is removeChild method, which needs to look for all parents, not for only one).

Sign up to request clarification or add additional context in comments.

Comments

1

It's always best practice to never mutate prop values passed down to children. You should emit from the children back up to the root node (App.vue) and make changes to your tree structure there. It's not too complicated to do when you consider the fact that arrays and objects are passed by reference, so you can emit the actual children array of the current TreeView node and then App.vue can push to that given array and it will actually update the appropriate array within tree without you having to search for it.

In the snippet below I keep the hideShow toggling on the {{ label }} but add a new span that can be clicked to add a child node when clicked. It simply emits the current children array. All TreeView nodes catch this emit and pass it up again until it reaches App.vue which handles the actual adding of a new child.

TreeView.vue

<template>
  <li>
    <div>
      <span @click="hideShow = !hideShow">{{ label }}</span>
      <span @click="$emit('addChild', children)">(add child)</span>
    </div>

    <div v-if="isOpen" class="child">
      <ul>
        <TreeView
          v-for="(child, i) in children"
          :key="i"
          :label="child.label"
          :perm="child.perm"
          :children="child.children"
          @add-child="$emit('addChild', $event)"
        />
      </ul>
    </div>
  </li>

App.vue

<template>
  <ul>
    <TreeView
      :label="tree.label"
      :perm="tree.perm"
      :children="tree.children"
      @add-child="addChild($event)"
    />
  </ul>
</template>
const addChild = item => {
  item.push({
    label: 'new item',
    perm: {},
    children: []
  });
};

You should find that even though App is receiving an array passed to it from an emit, updating the array updates the same array in tree.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.