Yung Silva
Yung Silva

Reputation: 1500

Vue.js pass a callback as prop to child component and execute on parent when it is clicked

I am trying to create a dropdown component, in which it receives various data and the list items are built dynamically, but I am having difficulty detecting the click on any item in the list

parent

<Dropdown
  :items="[
    {
      text: 'Edit',
      icon: 'fal fa-edit',
      click: editFunction(id)
    },
    {
      text: 'Delete',
      icon: 'fal fa-trash-alt',
      click: deleteFunction(id)
    }
  ]"
/>

child Dropdown.vue

<template>
  <div class="dropdown">
    <a class="trigger">
      <i class="far fa-ellipsis-h"></i>
    </a>

    <ul class="items">
      <li v-for="(item, index) in items" :key="index" class="item">
        <a>
          <i :class="item.icon"></i>
          {{ item.text }}
        </a>
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  props: {
    items: {
      type: Array,
      default: () => []
    }
  }
})
</script>

currently in this way, as soon as the parent component is created, the editFunction(id) and deleteFunction(id) methods are executed.

I know it's possible because vuetifyjs it does that way, but I tried to inspect the source code but I got nowhere.

Upvotes: 0

Views: 4158

Answers (2)

Raffobaffo
Raffobaffo

Reputation: 2856

What you want to achieve can be done by

parent.vue 

<child-componet :parent-method="thisIsTheMethod" />

...

methods: {
      thisIsTheMethod()
           {
              //here it does something
           } 
     }

note that the method passed inside the prop does not have the parenthesis () because you are passing a reference to it.

To use it inside the child component add the ()

@click="parentMethod()"

To go back to your example, change this:

<Dropdown
  :items="[
    {
      text: 'Edit',
      icon: 'fal fa-edit',
      click: editFunction(id)
    },
    {
      text: 'Delete',
      icon: 'fal fa-trash-alt',
      click: deleteFunction(id)
    }
  ]"
/>

to

<Dropdown
  :items="[
    {
      text: 'Edit',
      icon: 'fal fa-edit',
      click: () => editFunction(10)
    },
    {
      text: 'Delete',
      icon: 'fal fa-trash-alt',
      click: () => deleteFunction(20)
    }
  ]"
/>

and leave your editFunction(id) method declaration as is. The argument will be automatically injected.

Despite this solution would work, the best way to achieve this communication between parent and child would be to emit the value in the child and then listen for it

Upvotes: 2

Christian Carrillo
Christian Carrillo

Reputation: 2761

there have betters ways to do it, but about your idea should be as below:

Vue.component('dropdown', {
  template: '#dropdown-template',
  props: {
    items: {
      type: Array,
      default: () => []
    }
  }
})

new Vue({
  el: '#app',
  data() {
    return {
      users: [
        { id: 1, name: 'foo' },
        { id: 2, name: 'bar' },
      ]
    }
  },
  methods: {
    editFunction(id) {
      console.warn('edit item ' + id)
    },
    deleteFunction(id) {
      console.warn('delete item ' + id)
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
  <table border="1">
     <thead>
       <tr>
         <th>id</th>
         <th>name</th>
         <th>actions</tr>
       </tr>
    </thead>
    <tbody>
      <tr v-for="user in users" :key="user.id">
        <td>{{ user.id }}</td>
        <td>{{ user.name }}</td>
        <td>
            <dropdown
              :items="[
                {
                  text: 'Edit',
                  icon: 'fal fa-edit',
                  event: 'edit-function'
                },
                {
                  text: 'Delete',
                  icon: 'fal fa-trash-alt',
                  event: 'delete-function'
                }
              ]"
              @edit-function="editFunction(user.id)"
              @delete-function="deleteFunction(user.id)"     
            />
        </td>
      </tr>
    </tbody>
  </table>
</div>

<script type="text/x-template" id="dropdown-template">
<div class="dropdown">
  <a class="trigger">
    <i class="far fa-ellipsis-h"></i>
  </a>

  <ul class="items">
    <li v-for="(item, index) in items" :key="index" class="item">
      <a @click="$emit(item.event)">
        <i :class="item.icon"></i>
        {{ item.text }}
      </a>
    </li>
  </ul>
</div>
</script>

Upvotes: 0

Related Questions