leandronn
leandronn

Reputation: 171

Show extra card from v-for list of cards

I'm trying to show a card with extra information for each card inside a v-for loop.

<template>
    <div v-for="card in cards">
        <card>
            <!-- card content with information -->
            <button @click="viewExtra" />
        </card>
        <card v-if="card.infoExtra && showExtra>
            <!-- card content with extra information -->
        </card>
    </div>
</template>
<script>
    export default {
        data: () => ({
            cards: [] // Array with cards and information
            ,showExtra: false 
        }),
        methods: {
            viewExtra() {
                this.showExtra = !this.showExtra;
            }
    }
</script>

It should only show extra card if principal card contains extra information (card.infoExtra) but it's showing for all cards that have infoExtra, not for the one on which I click the button.

Here's what I've done so far:

enter image description here

What am I doing wrong.

Thanks in advance.

Upvotes: 1

Views: 2181

Answers (4)

Shreeraj
Shreeraj

Reputation: 767

Here is a better approach to your question which doesn't require any methods and covers multiple scenarios.

<div v-for="(card, index) in cards" :key="index">
  <card>
    <!-- card content with information -->
    <button @click="showExtraIndex = showExtraIndex === index && showExtraIndex !== null ? null : index" />
  </card>
  <card v-if="card.infoExtra && showExtraIndex === index">
    <!-- card content with extra information -->
  </card>
</div>

<script>
    export default {
        data: () => ({
            cards: []   // Array with cards and information
            showExtraIndex: null
        }),
    }
</script>

Code explanation:

  1. [MOST IMPORTANT] You must use :key while using v-for which acts as a unique id for each item of the array.

  2. Assign the index of the for loop to the variable showExtraIndex on click of showExtra button if the value of showExtraIndex is null. Once the value is assigned, it will fulfill the condition added to the below card showing extrainfo, and on clicking the same button back, it will assign null value to the variable showExtraIndex eventually hiding the extrainfo card.

Upvotes: 1

DOUBLE P
DOUBLE P

Reputation: 1

showExtra should be in every card object,You can't share one

Upvotes: 0

Roh&#236;t J&#237;ndal
Roh&#236;t J&#237;ndal

Reputation: 27192

My understanding was wrong, No need to add a separate property in each object as each card component will contains their own scope and render separately. Hence, single variable showExtra will work perfectly.

Here is the live Demo :

Vue.component('card', {
    data() {
        return {
            showExtra: false
        }
    },
    template: `
        <div class="card">
            <p>{{ cardinfo }}</p>
            <button @click="viewExtra">View Extra</button>
            <p v-if="extrainfo && showExtra">
                {{ extrainfo }}
            </p>
        </div>
    `,
    props: ['cardinfo', 'extrainfo'],
    methods: {
        viewExtra() {
            this.showExtra = !this.showExtra;
        }
    }
});

new Vue({
    el: '#container',
    data: {
        cards: [{
            id: 1,
            cardInfo: 'This is the content for Card 1',
            infoExtra: 'Extra infor for Card 1'
        }, {
            id: 2,
            cardInfo: 'This is the content for Card 2',
            infoExtra: 'Extra infor for Card 2'
        }, {
            id: 3,
            cardInfo: 'This is the content for Card 3',
            infoExtra: 'Extra infor for Card 3'
        }, {
            id: 4,
            cardInfo: 'This is the content for Card 4',
            infoExtra: 'Extra infor for Card 4'
        }]
    }
});
#container {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    margin: auto;
    background: grey;
    width: 750px;
    opacity: .5;
    border-radius: 3px;
    margin-top: 50px;
    padding: 10px;
}

.card {
    width: 20%;
    height: 180px;
    align-self: center;
    background: whitesmoke;
    padding: 5px;
    border-radius: 3px;
    margin: 10px 10px;
    font-family: monospace;
    transition: all .15s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<main id="container">
    <card v-for="card in cards" :key="card.id"
                :cardinfo="card.cardInfo" :extrainfo="card.infoExtra">
    </card>
</main>

Upvotes: 1

nart
nart

Reputation: 1848

Since you have mutual showExtra state when you toggle it to true, all cards will show extra information.

You should have had showExtra state for individual for each card

async created() {
  try {
    const response = await this.$api.getCard()
    this.cards = [...response?.data?.map(i => ({
      showExtra: false,   // attach state for each card, initially to false
      ...i,
    }))]
  } catch (err) {
    // do something
  }
}
<template>
  <div v-for="card in cards">
    <card>
      <!-- card content with information -->
      <button @click="card.showExtra = true" />  <!-- toggle it -->
    </card>
    <card v-if="card.infoExtra && card.showExtra>
      <!-- card content with extra information -->
    </card>
  </div>
</template>

Upvotes: 0

Related Questions