Home > Net >  In Vue JS, how do I add a class to an element inside my v-for loop when clicked on, without it being
In Vue JS, how do I add a class to an element inside my v-for loop when clicked on, without it being

Time:12-02

Hope the question made some sense. I'm using Vue JS and I have few elements being looped using v-for. Inside each block of elements being looped, is a button along with a div. In this case my loop runs three times. That means I get three buttons in each looped block of code, along with those three div's.

When I click on one of the three buttons I would like for the div in that block to receive a class dynamically. However, as it stands, when I click on one of the three buttons, the class is being added, but it is being added to all three looped div elements inside the loop.

How can I get the current looped div to only receive the specific class when the related button is clicked on, without the other two div's from the loop receiving it also?

Here is my code so far with what I have tried myself:

<template>
    <div v-for="(todo, index) in todos" :key="index" class="relative">
        <button @click="showDiv">
            show div
        </button>
        <div :class="appear" class="absolute bg-red-500">
            <p>{{ todo.text }}</p>
        </div>
    </div>
</template>

<script>
    import {defineComponent} from 'vue'
    export default defineComponent({
        data() {
            return {
                appear: 'left-0',
                todos: [
                    { text: 'Learn Laravel' },
                    { text: 'Learn Vue JS' },
                    { text: 'Learn Tailwind CSS' },
                ]
            }
        },
        methods: {
            showDiv() {
                this.appear = 'left-2/4';
            }
        }
    })
</script>

CodePudding user response:

This is happening because you have all of your code running at the same "level" (or scope to be more technical), thus the DOM "thinks" all of the elements are supposed to get the class added to it. You can solve this problem easily by simply creating a new component for repetitive bits of code (like when you need a for loop) and by making use of Vue's slots (https://vuejs.org/v2/guide/components-slots.html) feature.

// corresponding example JS file or section (depending on your setup) for TodoListItem
export default {
  name: 'TodoListItem',
  props: {
    todo: { type: Object, requried: true } 
  },
  data() {
    return { 
      isClicked: false 
    }
  },
  methods: {
    toggleIsClicked() {
      this.isClicked = !this.isClicked;
    },
  },
};
<!-- index.html -->
<todo-list>
  <todo-list-item
    v-for="(todo, index) in todos"
    :key="index"
    :todo="todo"
  ></todo-list-item>
</todo-list>

<!-- TodoList.vue -->
<template>
  <div class="todo-container">
    <slot></slot>
  </div>
</template>

<!-- TodoListItem.vue -->
<!-- Don't forget to set your props in the corresponding component JS file or else your TodoListItem.vue won't know about the todo object you are passing down to it. -->
<!-- You will also need to move your @click handler here too in the methods section -->
<template>
  <div class="relative">
    <button @click="showDiv">show div</button>
    <div :class="appear" class="absolute bg-red-500">
      <p>{{ todo.text }}</p>
    </div>
  </div>
</template>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

I hope this helps my friend!

CodePudding user response:

The reason is that you are using one variable appear to manage the state of all the buttons, instead you can maintain seperate booleans called visible for each of the items

Check the below working example

var app = new Vue({
        el: "#app",
        data() {
            return {
                appear: 'left-0',
                
                /*** Change added to below array ***/
                
                todos: [
                    { text: 'Learn Laravel', visible: false },
                    { text: 'Learn Vue JS', visible: false },
                    { text: 'Learn Tailwind CSS', visible: false },
                ]
            }
        },
        /** Changes added below **/
        computed: {
         getButtonText() {
          return ind => {
           return this.todos[ind].visible ? 'hide div': 'show div';
          }
         },
         getClass() {
           return ind => {
             return this.todos[ind].visible ? 'left-2/4': 'absolute bg-red-500';
           }
         }
        },
        methods: {
            showDiv(index) {
                this.todos[index].visible = !this.todos[index].visible; // Change added
            }
        }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
    <div v-for="(todo, index) in todos" :key="index" class="relative">
        <button @click="showDiv(index)">
           {{getButtonText(index)}} <!-- Change added -->
        </button>
        <div :class="getClass(index)"> <!-- Change added -->
            <p>{{ todo.text }}</p>
        </div>
    </div>
</div>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

add a boolean field in your array and use that field as a condition for the dynamic class e.g

                { text: 'Learn Laravel', isLeft: false },
                { text: 'Learn Vue JS', isLeft: false },
                { text: 'Learn Tailwind CSS', isLeft: false },

Now on click, just toggle the isLeft field and use it as a condition to dynamic class

<div v-for="(todo, index) in todos" :key="index" class="relative">
    <button @click="todo.isLeft = !todo.isLeft">
        show div
    </button>
    <div :class="{'left-2/4': todo.isLeft}" class="absolute bg-red-500">
        <p>{{ todo.text }}</p>
    </div>
</div>                
  • Related