moses toh
moses toh

Reputation: 13162

How do I make a child component just run if click a link on the vue component?

I have two component

My first component(parent component) like this :

<template>
    <ul class="list-group">
        <li v-for="item in invoices" class="list-group-item">
            <div class="row">
                ...
                <div class="col-md-7">
                    ...
                    <a href="javascript:"
                       class="toggle-show"
                       aria-expanded="false"
                       data-toggle="collapse"
                       :data-target="'#' + item.id"
                       @click="show(item.id)">
                        Show <span class="caret"></span>
                    </a>
                </div>
            </div>
            <div class="collapse" :id="item.id">
                <order-collapse/>
            </div>
        </li>
    </ul>
</template>
<script>
    import orderCollapse from './orderCollapse.vue'
    export default {
        ...
        components: {orderCollapse},
        data() {
            return {
                invoices: [
                    {
                        id: 1,
                        ...
                    },
                    {
                        id: 2,
                        ...
                    },
                    {
                        id: 3,
                        ...
                    }
                ]
            }
        },
        methods: {
            show(id) {
                // orderCollapse executed if this method run
            }
        },
    }
</script>

My second component (child component) like this :

<template>
    <table class="table table-bordered table-collapse">
        <!-- this is used to display detail by id -->
    </table>
</template>
<script>
    export default {
        name: 'order-collapse',
        ...
    }
</script>

If the parent component executed, the child component automatically executed too

I want if the parent component executed, the child component not executed

I want the child component executed if user click show link

How can I do it?

Upvotes: 0

Views: 66

Answers (1)

acdcjunior
acdcjunior

Reputation: 135752

How about creating a property (displayedIds) to hold the display status of each order-collapse and then condition their display using v-if:

<div ... v-if="displayedIds[item.id]">
    <order-collapse :id="item.id"></order-collapse>
</div>

And, considering displayedIds is initially declared as {}, you would have the show() method as:

  methods: {
    show(id) {
        // orderCollapse executed if this method run
        this.$set(this.displayedIds, id, true); // use $set to be reactive
    }
  },

Demo:

Vue.component('order-collapse', {
  template: "#oc",
  name: 'order-collapse',
  props: ['id'],
  mounted() {
    console.log('order-collapsed mounted for id', this.id);
  }
});

new Vue({
  el: '#app',
  data() {
    return {
      displayedIds: {},
      invoices: [{id: 1},{id: 2},{id: 3}]
    }
  },
  methods: {
    show(id) {
      // orderCollapse executed if this method run
      this.$set(this.displayedIds, id, true);
    }
  },
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>

<div id="app">
    <ul class="list-group">
        <li v-for="item in invoices" class="list-group-item">
            <div class="row">
                ...
                <div class="col-md-7">
                    ...
                    <a href="javascript:"
                       class="toggle-show"
                       aria-expanded="false"
                       data-toggle="collapse"
                       :data-target="'#' + item.id"
                       @click="show(item.id)">
                        Show <span class="caret"></span>
                    </a>
                </div>
            </div>
            <div class="collapse" :id="item.id" v-if="displayedIds[item.id]">
                <order-collapse :id="item.id"></order-collapse>
            </div>
        </li>
    </ul>
</div>

<template id="oc">
    <table class="table table-bordered table-collapse">
        <!-- this is used to display detail by id -->
        <tr>
          <td>ID: {{ id }}</td>
        </tr>
    </table>
</template>


Another possibility is having displayedIds as an array instead of an object. This way you could not use $set, just a regular .push():

methods: {
  show(id) {
    // orderCollapse executed if this method run
    this.displayedIds.push(id);
  }
},

And condition the display using .includes():

<div ... v-if="displayedIds.includes(item.id)">
    <order-collapse :id="item.id"></order-collapse>
</div>

Demo:

Vue.component('order-collapse', {
  template: "#oc",
  name: 'order-collapse',
  props: ['id'],
  mounted() {
    console.log('order-collapsed mounted for id', this.id);
  }
});

new Vue({
  el: '#app',
  data() {
    return {
      displayedIds: [],
      invoices: [{id: 1},{id: 2},{id: 3}]
    }
  },
  methods: {
    show(id) {
      // orderCollapse executed if this method run
      this.displayedIds.push(id);
    }
  },
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>

<div id="app">
    <ul class="list-group">
        <li v-for="item in invoices" class="list-group-item">
            <div class="row">
                ...
                <div class="col-md-7">
                    ...
                    <a href="javascript:"
                       class="toggle-show"
                       aria-expanded="false"
                       data-toggle="collapse"
                       :data-target="'#' + item.id"
                       @click="show(item.id)">
                        Show <span class="caret"></span>
                    </a>
                </div>
            </div>
            <div class="collapse" :id="item.id" v-if="displayedIds.includes(item.id)">
                <order-collapse :id="item.id"></order-collapse>
            </div>
        </li>
    </ul>
</div>

<template id="oc">
    <table class="table table-bordered table-collapse">
        <!-- this is used to display detail by id -->
        <tr>
          <td>ID: {{ id }}</td>
        </tr>
    </table>
</template>


Finally, if you find easier to reason, the display flag could be in each item itself, as a property.

In this case, the show should receive the whole item (not just item.id):

<a ... @click="show(item)">

The display would use just item.displayed:

<div ... v-if="item.displayed">
    <order-collapse :id="item.id"></order-collapse>
</div>

And the method:

methods: {
  show(item) {
    // orderCollapse executed if this method run
    this.$set(item, 'displayed', true);
  }
},

Demo:

Vue.component('order-collapse', {
  template: "#oc",
  name: 'order-collapse',
  props: ['id'],
  mounted() {
    console.log('order-collapsed mounted for id', this.id);
  }
});

new Vue({
  el: '#app',
  data() {
    return {
      invoices: [{id: 1},{id: 2},{id: 3}]
    }
  },
  methods: {
    show(item) {
      // orderCollapse executed if this method run
      this.$set(item, 'displayed', true);
      // you could use `item.displayed = true` if you declared `displayed: false` in each item at data
    }
  },
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>

<div id="app">
    <ul class="list-group">
        <li v-for="item in invoices" class="list-group-item">
            <div class="row">
                ...
                <div class="col-md-7">
                    ...
                    <a href="javascript:"
                       class="toggle-show"
                       aria-expanded="false"
                       data-toggle="collapse"
                       :data-target="'#' + item.id"
                       @click="show(item)">
                        Show <span class="caret"></span>
                    </a>
                </div>
            </div>
            <div class="collapse" :id="item.id" v-if="item.displayed">
                <order-collapse :id="item.id"></order-collapse>
            </div>
        </li>
    </ul>
</div>

<template id="oc">
    <table class="table table-bordered table-collapse">
        <!-- this is used to display detail by id -->
        <tr>
          <td>ID: {{ id }}</td>
        </tr>
    </table>
</template>

Which approach is the recommended? It boils down to taste. If you can/don't mind appending the displayed prop to each item, I think it is the simplest solution. Otherwise I'd go with the first (displayedIds as an object), unless it gives you the yikes, in which case I'd pick the array solution.

Upvotes: 1

Related Questions