Search code examples
vue.jsvuejs2v-for

select/deselect single element in v-for


I'm trying to make an option list with v-for, where you can choose only one option at a time. It works great except I can't unselect the option.

<div id="main">
  <ul>
    <li 
        v-for="l in list" 
        id="l.key"
        @click="selectone(l.key, l.isSelected)"
        v-bind:class="{ selected : l.isSelected, notselected : !l.isSelected }"
        > {{ l.tec }} </li>
   <ul>
</div>

The JS

new Vue({
  el:"#main",
  data: {
    list: [
      {key:"0", tec:"html", isSelected:false},
      {key:"1", tec:"css", isSelected:false},
      {key:"2", tec:"JS", isSelected:false},
      {key:"3", tec:"Git", isSelected:false},
      {key:"4", tec:"NodeJS", isSelected:false},
      {key:"5", tec:"Postgres", isSelected:false}
    ]
  },
  methods: {
    selectone: function(k, o) {
      for( i = 0; i < this.list.length; i ++ ) {
        if(this.list[i].isSelected == true ) {
          this.list[i].isSelected = false
        }
      }
      this.list[k].isSelected = !this.list[k].isSelected;
    }
  }
})

CSS

.selected {
    background:lightpink;
}
.notselected {
    background:lightblue;
}

Shouldn't my loop deactivate all options every time I click an element?


Solution

  • In selectone(), you're setting isSelected=false for all list items, and then attempting to toggle the selected list item's isSelected, which was just previously set to false (i.e., the "toggle" would always set isSelected=true for the selected item).

    The loop should exclude the key of the selected item:

    selectone(key) {
      for (let i = 0; i < this.list.length; i++) {
        if (this.list[i].key !== key) {
           this.list[i].isSelected = false
        }
      }
    
      // this.toggleSelection(key)
    }
    

    But the toggling code itself needs a bug-fix to properly lookup the list item. The first argument to selectone() is the key property of the list item. In order to get the item by key from the list array, you have to search the list, e.g., using Array.prototype.find():

    toggleSelection(key) {
      const listItem = this.list.find(item => item.key === key)
      if (listItem) {
        listItem.isSelected = !listItem.isSelected
      }
    }
    

    new Vue({
      el: '#app',
      data: {
        list: [
          {key:"0", tec:"html", isSelected:false},
          {key:"1", tec:"css", isSelected:false},
          {key:"2", tec:"JS", isSelected:false},
          {key:"3", tec:"Git", isSelected:false},
          {key:"4", tec:"NodeJS", isSelected:false},
          {key:"5", tec:"Postgres", isSelected:false}
        ]
      },
      methods: {
        selectone(key) {
          for (let i = 0; i < this.list.length; i++) {
            if (this.list[i].key !== key) {
              this.list[i].isSelected = false
            }
          }
    
          this.toggleSelection(key)
        },
        toggleSelection(key) {
          const listItem = this.list.find(item => item.key === key)
          if (listItem) {
            listItem.isSelected = !listItem.isSelected
          }
        }
      }
    })
    .selected {
      background:lightpink;
    }
    .notselected {
      background:lightblue;
    }
    <script src="https://unpkg.com/vue@2.6.10"></script>
    
    <div id="app">
      <ul>
        <li 
            v-for="l in list" 
            id="l.key"
            @click="selectone(l.key, l.isSelected)"
            v-bind:class="{ selected : l.isSelected, notselected : !l.isSelected }"
            > {{ l.tec }} </li>
       <ul>
    </div>

    Alternatively, you could track the selected index, set it in the item's click-handler, and set the class binding based on the item's index matching the selected index:

    // template
    <li 
      v-for="(l, index) in list" 
      id="l.key"
      @click="selectedIndex = index"
      v-bind:class="{ selected: index === selectedIndex, notselected: index !== selectedIndex }"
      > {{ l.tec }} </li>
    
    // script
    export default {
      data() {
        return {
          selectedIndex: -1,
          ...
        }
      }
    }
    

    new Vue({
      el: '#app',
      data: {
        selectedIndex: -1,
        list: [
          {key:"0", tec:"html", isSelected:false},
          {key:"1", tec:"css", isSelected:false},
          {key:"2", tec:"JS", isSelected:false},
          {key:"3", tec:"Git", isSelected:false},
          {key:"4", tec:"NodeJS", isSelected:false},
          {key:"5", tec:"Postgres", isSelected:false}
        ]
      }
    })
    .selected {
      background:lightpink;
    }
    .notselected {
      background:lightblue;
    }
    <script src="https://unpkg.com/vue@2.6.10"></script>
    
    <div id="app">
      <ul>
        <li 
            v-for="(l, index) in list" 
            id="l.key"
            @click="selectedIndex = index"
            v-bind:class="{ selected : index === selectedIndex, notselected : index !== selectedIndex }"
            > {{ l.tec }} </li>
       <ul>
    </div>