Vue.js add multiple active class in v-for cicle

Viewed 567

I have some li elements in a v-for cicle. I need to select more then one at the same time and, if already selected, remove active class.

My script works if I select the items in order: from first to last (to select items), from last to first (to deselect items). Please help me.

var example1 = new Vue({
  el: '#example-1',
  data: {
    tags: ['tag-1', 'tag-2', 'tag-3', 'tag-4'],
    activeTag: [],
  },
  methods: {
    onTagClick: function(i) {
      if ( this.activeTag.includes(i) ) {
        console.log('Delete');
        const index = this.activeTag.indexOf(i);
        if ( index > -1 ) {
            this.activeTag.splice(index, 1);
        }
      } else {          
        console.log('Add');          
        this.activeTag.push(i);
      }
      console.log(`activeTag[i]: ${this.activeTag}`);
    }
  }
})
li {
  display: inline-block;
  padding: 8px 10px;
  margin-right: 0.5rem;
}

a {
color: #000;
  text-decoration: none;
}

li.active>a{
  color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.js"></script>
<div id="example-1">
  <ul v-if="tags">
    <li v-for="(tag, i) in tags" :key="i" :class="{active: activeTag[i] === i}">
        <a @click="onTagClick(i)"  href="javascript:void(0)">{{ tag }}</a>
    </li>
</ul>
</div>

3 Answers

What do you think about this solution? It's the shortest way to achive the same result.

var example1 = new Vue({
  el: '#example-1',
  data: {
    tags: [{tag:'tag-1', active: false},{tag:'tag-2', active: false}, {tag:'tag-3', active: false}, {tag:'tag-4', active: false} ],
    
  },
  methods: {
    onTagClick: function(i) {
       this.tags[i].active = !this.tags[i].active ;
    }
  }
})
li {
  display: inline-block;
  padding: 8px 10px;
  margin-right: 0.5rem;
}

a {
color: #000;
  text-decoration: none;
}

li.active>a{
  color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.js"></script>
<div id="example-1">
  <ul v-if="tags">
    <li v-for="(obj, i) in tags" :key="i" :class="{active: obj.active}" >
        <a @click="onTagClick(i)"  href="javascript:void(0)">{{ obj.tag }}</a>
    </li>
</ul>

template

<li v-for="(tag, index) in tags" :key="index" :class="{active: tag.active}">
    <a href="#" @click.prevent="tag.active = !tag.active">{{ tag.name }}</a>
</li>

Script

  computed: {
    checked(){ return this.tags.filter(tag => tag.active).map(tag => tag.name)}
  },
  data() {
    return {
      tags: [
        {name:'tag-1', active: false},
        {name:'tag-2', active: false},
        {name:'tag-3', active: false},
        {name:'tag-4', active: false}
      ]
    }
  }

Complete sample. Check this out

<template>
  <ul v-if="tags">
    <li v-for="(tag, index) in tags" :key="index" :class="{active: tag.active}">
      <a href="#" @click.prevent="tag.active = !tag.active">{{ tag.name }}</a>
    </li>
  </ul>
  <div>
    {{checked}}
  </div>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
  computed: {
    checked(){ return this.tags.filter(tag => tag.active).map(tag => tag.name)}
  },
  data() {
    return {
      tags: [
        {name:'tag-1', active: false},
        {name:'tag-2', active: false},
        {name:'tag-3', active: false},
        {name:'tag-4', active: false}
      ]
    }
  }
});
</script>
<style>
li {
  display: inline-block;
  padding: 8px 10px;
  margin-right: 0.5rem;
}

a {
color: #000;
  text-decoration: none;
}
li.active>a{
  color: red;
}
</style>

Here is my solution

<template>
  <div>
    <span
      v-for="element in array"
      :key="element.id"
      :class="{ red: element.active }"
      @click="toggleSelection(element)"
    >
      {{ element.label }}
    </span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      array: [
        { id: 1, label: 'tag1', active: false },
        { id: 2, label: 'tag2', active: false },
        { id: 3, label: 'tag3', active: false },
      ],
    }
  },
  methods: {
    toggleSelection(element) {
      const selectedElementIndex = this.array.findIndex((item) => item.id === element.id)
      this.array[selectedElementIndex].active = !this.array[selectedElementIndex].active
    },
  },
}
</script>

<style scoped>
.red {
  color: red;
}
</style>

I still highly recommend updating your array and giving it a real unique ID, rather than passing index to the :key, which is actually doing the opposite of what it's supposed to do.

To achieve a friendly array with nice keys, you can use this on your current tags:

this.array = tags.map((tag, index) => ({id: index, label: tag, active: false}))

You could call this method once you have pulled the data from an API (in an async created() hook), change it then insert it into your template, hence modify the structure to a more friendly format.

Related