VueJs Tree recursive elements emits to parent
Asked Answered
P

4

6

How do you emit event inside recursive child components vuejs

Taking the tree example from vue site https://v2.vuejs.org/v2/examples/tree-view.html

How would you transmit on click to the parent each clicked elements id?

Psychodynamics answered 14/9, 2017 at 13:16 Comment(2)
Did you try anything?Philps
yes I was trying to manage the state with vuex, has lot of similarities with redux but did not got the pointPsychodynamics
G
3

In the case of recursive elements, you can create an event bus in the parent, pass it to the children as a prop, and have each prop pass it to any children they generate.

Each child emits events on the bus, and the parent handles them. I copied the tree-view exercise you linked and added the bus functionality.

// demo data
var data = {
  name: 'My Tree',
  children: [{
      name: 'hello'
    },
    {
      name: 'wat'
    },
    {
      name: 'child folder',
      children: [{
          name: 'child folder',
          children: [{
              name: 'hello'
            },
            {
              name: 'wat'
            }
          ]
        },
        {
          name: 'hello'
        },
        {
          name: 'wat'
        },
        {
          name: 'child folder',
          children: [{
              name: 'hello'
            },
            {
              name: 'wat'
            }
          ]
        }
      ]
    }
  ]
};

var itemId = 0;

// define the item component
Vue.component('item', {
  template: '#item-template',
  props: {
    model: Object,
    bus: Object
  },
  data: function() {
    return {
      open: false,
      id: ++itemId
    }
  },
  computed: {
    isFolder: function() {
      return this.model.children &&
        this.model.children.length
    }
  },
  methods: {
    toggle: function() {
      if (this.isFolder) {
        this.open = !this.open;
        this.bus.$emit('toggled', this.id);
      }
    },
    changeType: function() {
      if (!this.isFolder) {
        Vue.set(this.model, 'children', [])
        this.addChild()
        this.open = true
      }
    },
    addChild: function() {
      this.model.children.push({
        name: 'new stuff'
      })
    }
  }
})

// boot up the demo
var demo = new Vue({
  el: '#demo',
  data: {
    treeData: data,
    bus: new Vue()
  },
  created() {
    this.bus.$on('toggled', (who) => {
      console.log("Toggled", who);
    });
  }
})
body {
  font-family: Menlo, Consolas, monospace;
  color: #444;
}

.item {
  cursor: pointer;
}

.bold {
  font-weight: bold;
}

ul {
  padding-left: 1em;
  line-height: 1.5em;
  list-style-type: dot;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<!-- item template -->
<script type="text/x-template" id="item-template">
  <li>
    <div :class="{bold: isFolder}" @click="toggle" @dblclick="changeType">
      {{model.name}}
      <span v-if="isFolder">[{{open ? '-' : '+'}}]</span>
    </div>
    <ul v-show="open" v-if="isFolder">
      <item class="item" :bus="bus" v-for="model in model.children" :model="model">
      </item>
      <li class="add" @click="addChild">+</li>
    </ul>
  </li>
</script>

<p>(You can double click on an item to turn it into a folder.)</p>

<!-- the demo root element -->
<ul id="demo">
  <item class="item" :bus="bus" :model="treeData">
  </item>
</ul>
Gametogenesis answered 14/9, 2017 at 15:31 Comment(1)
Thank you very much for the snippet is working. What would be the best approach to manage this state with vuex?Psychodynamics
N
16

Here's another solution if you don't want to create multiple Vue instances. I use this in my single file recursive components.

It uses the v-on directive (I'm using the @ shorthand).

In your recursive component <template>:

<YourComponent @bus="bus"></YourComponent>

In the recursive component methods:

methods: {
    bus: function (data) {
        this.$emit('bus', data)
    }
}

To kick it off, you emit an event in a child:

this.$emit('bus', {data1: 'somedata', data2: 'somedata'})

That data will be transmitted all the way up the chain, and then you receive that event in the page that called your recursive component:

methods: {
    bus (data) {
        // do something with the data
    }
}

Here's a fiddle showing it in action on the Vue.JS tree example. Right-click on an element, and it will output that model in the console:

https://jsfiddle.net/AlanGrainger/r6kxxoa0/

Nymphet answered 16/11, 2017 at 1:31 Comment(4)
Doesn't every child/parent get this event, then? If you had a tree inside a tree inside a tree (3 deep), the 'bus' event from the grandchild would get triggered in both the parent and grandparent, yes?Skaw
@LucasMorgan it's passed upwards through the tree all the way to the top, no matter how deep you make the tree. Check the fiddle and you can see it in action.Nymphet
What is "contextmenu" event? I search about it but got nothing. :)Ileanaileane
You are the MANAccomplish
G
3

In the case of recursive elements, you can create an event bus in the parent, pass it to the children as a prop, and have each prop pass it to any children they generate.

Each child emits events on the bus, and the parent handles them. I copied the tree-view exercise you linked and added the bus functionality.

// demo data
var data = {
  name: 'My Tree',
  children: [{
      name: 'hello'
    },
    {
      name: 'wat'
    },
    {
      name: 'child folder',
      children: [{
          name: 'child folder',
          children: [{
              name: 'hello'
            },
            {
              name: 'wat'
            }
          ]
        },
        {
          name: 'hello'
        },
        {
          name: 'wat'
        },
        {
          name: 'child folder',
          children: [{
              name: 'hello'
            },
            {
              name: 'wat'
            }
          ]
        }
      ]
    }
  ]
};

var itemId = 0;

// define the item component
Vue.component('item', {
  template: '#item-template',
  props: {
    model: Object,
    bus: Object
  },
  data: function() {
    return {
      open: false,
      id: ++itemId
    }
  },
  computed: {
    isFolder: function() {
      return this.model.children &&
        this.model.children.length
    }
  },
  methods: {
    toggle: function() {
      if (this.isFolder) {
        this.open = !this.open;
        this.bus.$emit('toggled', this.id);
      }
    },
    changeType: function() {
      if (!this.isFolder) {
        Vue.set(this.model, 'children', [])
        this.addChild()
        this.open = true
      }
    },
    addChild: function() {
      this.model.children.push({
        name: 'new stuff'
      })
    }
  }
})

// boot up the demo
var demo = new Vue({
  el: '#demo',
  data: {
    treeData: data,
    bus: new Vue()
  },
  created() {
    this.bus.$on('toggled', (who) => {
      console.log("Toggled", who);
    });
  }
})
body {
  font-family: Menlo, Consolas, monospace;
  color: #444;
}

.item {
  cursor: pointer;
}

.bold {
  font-weight: bold;
}

ul {
  padding-left: 1em;
  line-height: 1.5em;
  list-style-type: dot;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<!-- item template -->
<script type="text/x-template" id="item-template">
  <li>
    <div :class="{bold: isFolder}" @click="toggle" @dblclick="changeType">
      {{model.name}}
      <span v-if="isFolder">[{{open ? '-' : '+'}}]</span>
    </div>
    <ul v-show="open" v-if="isFolder">
      <item class="item" :bus="bus" v-for="model in model.children" :model="model">
      </item>
      <li class="add" @click="addChild">+</li>
    </ul>
  </li>
</script>

<p>(You can double click on an item to turn it into a folder.)</p>

<!-- the demo root element -->
<ul id="demo">
  <item class="item" :bus="bus" :model="treeData">
  </item>
</ul>
Gametogenesis answered 14/9, 2017 at 15:31 Comment(1)
Thank you very much for the snippet is working. What would be the best approach to manage this state with vuex?Psychodynamics
M
2

Use v-on="$listeners"

I'll let you into a little secret. The Vue $listeners property (which is documented as being there to pass events down to children), also passes child events to parents!

https://v2.vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components

Here is a pseudo-code example (shorthand for illustrative purposes):

<ancestor-component @messageForAncestor="displayMessage">
  ...
  <parent-component v-on="$listeners">
    ...
    <child-component @click="$emit('messageForAncestor')">

In the display above the child component would pass up an event. The parent would typically be able to listen for the messageForAncestor event and that's where it would need to stop, but. Putting v-on="$listeners" on the parent actually says please pass it on.

Warn: That was probably a bad idea

This is probably a very bad idea though. A better idea would be to simply ask the middle component (the parent) to pass it on...

<!-- better idea (listen for message and pass on message) --> 
<parent-component @message="$emit('message', $event)">

Mystify answered 20/4, 2020 at 18:16 Comment(2)
Could you please explain why this is a bad idea?Rase
@Rase The propagation of events in Vue is pretty well controlled. Events up, properties down. And they only go up 1 level. The components tag has to "listen for events" inside it, then re-emit them again to pass it up the chain. When you use v-on="$listeners" it passes all events up and down. This means that any event in the child component will automatically be passed up to the ancestor and events are passed down too. This means that unexpected listeners could receive events. definitely don't use event names like 'click' as it's likely there are existing listeners listening for them.Mystify
A
0

I think the best and clear way is here:

https://www.digitalocean.com/community/tutorials/vuejs-communicating-recursive-components

So, You need pass function as parameter. It`s help to awoid limitation of Vue emits, when you are using one with recursive components

Antiproton answered 27/9, 2023 at 12:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.