javascriptvue.jscss-selectorsv-for

How can I change an individual item's style class in a v-for list?


I know there are a couple of similar questions but they don't quite hit what I need (or I'm not fully understanding them in which case, my apologies). This is one but it looks like it's Vue.JS 3 and I don't entirely understand how I would make it work for my code, even though the requirement is the same.

I want to change the colour background of an individual v-for item, based on the option selected from a drop-down box. At the moment all items change colour whenever a drop-down is changed.

Here is the codepen.io for the project: https://codepen.io/Salixa/pen/jOQPRNz You will need to input a name, press 'Add Player', and then 'Check Availability' will show you the v-for list. When you click on any one of the drop downs and it changes the class for each item in the list. Additionally you can add some availability so you can see what it ought to look like!

Current behaviour: screenshot from codepen.io illustrating current behaviour Ideal behaviour: screenshot from codepen.io illustrating ideal behaviour

The current state is that I have a v-for block that iterates over values in an object (which is itself in an array). Each value is a selection box. When the values are pre-defined from a separate function they render with the appropriate colour class, which is expected. When I change the selection the colour class applies to all items, this is also expected - but unwanted!

This is my current code: HTML

<div id="displayAvailability" v-for="(value, key, index) in player" :key="index">
      <div :id="'listItem'+index+1" :class="[selection != '' ? {available:selection==='Available',tentative:selection==='Tentative',busy:selection==='Busy',session:selection==='Session',unknown:selection==='Unknown'} : {available:value==='available', tentative:value==='tentative', busy:value==='busy', session:value==='session',unknown:value==='Unknown'}]" :key="index">
        <select id="availabilityOptions" @change="updateColour($event)" >
          <option>{{value}}</option>
          <option v-model="selection">Available</option>
          <option v-model="selection">Busy</option>
          <option v-model="selection">Tentative</option>
          <option v-model="selection">Session</option>
        </select>
      </div>
    </div>

And this is the JavaScript that causes the colour change:

updateColour(event){
      this.selection = event.target.value;
      console.log(this.selection);
      return this.selection;
    }

I understand that the issue here is that 'selection' is being changed to whatever the option is, and then based on selection the style is changed because I've put it on the div for each item. But is there a way to only change the individual item?

N.B.: I do not want to update the player object with the new selection - the expanded plan is that there will be 'default' availability and then each player can update an individual day. Also, I am using Vue.JS 2 and will need to do so because that is what I use for work and need to learn. Also also, the reason I have keys and indexes in places where they might seem random is because I've been trying to pick things up from other StackOverflow answers but, as mentioned, I'm quite new so not 100% I'm grasping things properly :(

EDIT I have had a go at implementing Matthew's suggestions (thank you Matthew!) but without success. The code I have changed it to is this:


    <div :id="'listItem'+index+1" :class="[selection != '' ? 
{available:selection==='Available',tentative:selection==='Tentative',busy:selection==='Busy',session:selection==='Session',unknown:selection==='Unknown'}
 : {available:value==='available', tentative:value==='tentative', busy:value==='busy', session:value==='session',unknown:value==='Unknown'}]" :key="index">
            <select id="availabilityOptions" v-model="value">
              <option>{{value}}</option>
              <option>Available</option>
              <option>Busy</option>
              <option>Tentative</option>
              <option>Session</option>
            </select>

Unfortunately, the result of this that a) no colours change at all now b) when I add a new player all the previous changes made are erased.


Solution

  • After fussing with this for a very long time I decided to follow a different route. Through various other helpful answers on this site which I will endeavour to link at the bottom I found a way to get the individual DOM element using a ref. $refs is an array so in order to get a specific element you need to access it with bracket [] notation. I passed the event and the index to the updateColour() function. (Worth noting that it needs to be ref not :ref).

        <div :id="'listItem'+index+1" ref="days" :class="[selection != '' ? {available:selection==='Available',tentative:selection==='Tentative',busy:selection==='Busy',session:selection==='Session',unknown:selection==='Unknown'} : {available:availability==='available', tentative:availability==='tentative', busy:availability==='busy', session:availability==='session',unknown:availability==='Unknown'}]" :key="index">
            <select id="availabilityOptions" @change="updateColour($event,index)" >
    

    Then I was able to access the specific element in the method and give it a class like so:

    this.$refs.days[index].classList.remove("unknown","busy","tentative","session")
    this.$refs.days[index].classList.add("available")
    

    I decided to remove all other classes to ensure that one wasn't superseding another.

    Here is a newer codepen showing what I've done, I've left the previous one as-is for future people to see what was happening, and I'll leave this one as-is as the answer.

    Obviously this still has some flaws and is a work in progress but it is a solution to "how do I change the style class of a specific element as generated in a v-for list".

    Helpful answers include:

    https://stackoverflow.com/a/70246468/22072704 which then linked me to the Mozilla Developer Network documentation for classList.

    https://stackoverflow.com/a/56687624/22072704 which helped me understand how to access the specific element although I ended up not using $el or .querySelector once I'd stumbled across classList.

    https://stackoverflow.com/a/59680932/22072704 for getting me to understand refs in the first place!

    Thanks again Matthew for your help :)