link3151 link3152 link3153 link3154 link3155 link3156 link3157 link3158 link3159 link3160 link3161 link3162 link3163 link3164 link3165 link3166 link3167 link3168 link3169 link3170 link3171 link3172 link3173 link3174 link3175 link3176 link3177 link3178 link3179 link3180 link3181 link3182 link3183 link3184 link3185 link3186 link3187 link3188 link3189 link3190 link3191 link3192 link3193 link3194 link3195 link3196 link3197 link3198 link3199 link3200 link3201 link3202 link3203 link3204 link3205 link3206 link3207 link3208 link3209 link3210 link3211 link3212 link3213 link3214 link3215 link3216 link3217 link3218 link3219 link3220 link3221 link3222 link3223 link3224 link3225 link3226 link3227 link3228 link3229 link3230 link3231 link3232 link3233 link3234 link3235 link3236 link3237 link3238 link3239 link3240 link3241 link3242 link3243 link3244 link3245 link3246 link3247 link3248 link3249 link3250 link3251 link3252 link3253 link3254 link3255 link3256 link3257 link3258 link3259 link3260 link3261 link3262 link3263 link3264 link3265 link3266 link3267 link3268 link3269 link3270 link3271 link3272 link3273 link3274 link3275 link3276 link3277 link3278 link3279 link3280 link3281 link3282 link3283 link3284

[Vue.js] add class to a specific parent div when click a component created with v-for

when new to vuejs, this is what to do:
there is a list of components, each in a div. Now if I do something with the component (i.e. click it). to add a class to the parent div. This is what I did so far, the code is simplified, just to show what to do with a simple case.

my app.vue:

<div class=”toggle-box” v-for=”(name, index) in names” :class=”classActive” :key=”index”>
<app-comp :myName=”name” :myIndex=”index” @someEvent=”doSomething”></app-counter>
</div>
data() {
classActive: ‘’,
names: [‘alpha’, ‘beta’, ‘gamma’]
},
methods: {
doSomething() {
this.classActive === ‘’ ? this.classActive = ‘is-active’: this.classActive=’’;
}
}

the component:

<div>
<button @click=”toggle”>{ myName } - { myIndex }</button>
</div>

props: [‘myName’, ‘myIndex’],
methods: {
toggle() {
this.$emit(‘someEvent’, index);
}
}

what this do: it creates 3 divs with “toggle-box”-class with a button in it which has the label “name - index”. When I click a button, it emits the “someEvent”-event with index attached. The parent listens to this event and toggle the class ‘is-active’ on the div with the ‘toggle-box’ class. The thing is, right now, when I click a button, it adds the class to all 3 divs. Probably because there is no different between the 3 divs for vuejs. I know I can append the index to the event and call this with $event in the parent, but how do I use this? Or is there perhaps a better way to achieve what I want?

thanks for helping.

Solution :

There are several different ways to approach this but I think the starting point is to think about how you want to represent this as data rather than how that appears in the UI. So model before view.

Presumably you’re going to want to do something with these active items after they’ve been selected. I’d focus on that rather than the problem of highlighting them. The highlighting would then fall out relatively painlessly.

For the sake of argument, let’s assume that an array of active items is a suitable model for what you’re trying to achieve. It might not be but it makes for a simple example.

So:

data() {
return {
activeNames: [],
names: [‘alpha’, ‘beta’, ‘gamma’]
}
},

No mention of classes here as we’re not worrying about the UI concern, we’re trying to model the underlying data.

For the toggle method I’d be more inclined to emit the name than the index, but you’re better placed to judge which represents the data better. For my example it’ll be name:

methods: {
toggle() {
this.$emit(‘someEvent’, this.myName);
}
}

Then in the parent component we’ll add/remove the name from the array when the event is emitted. Other data structures might be better for this, I’ll come back to that at the end.

methods: {
doSomething(name) {
if (this.activeNames.includes(name)) {
this.activeNames = this.activeNames.filter(item => item !== name);
} else {
this.activeNames.push(name);
}
}
}

Now we have an array containing the active names we can use that to derive the class for those wrapper divs.

<div
class=”toggle-box”
v-for=”(name, index) in names”
:class=”{‘is-active’: activeNames.includes(name)}”
:key=”index”
\>

Done.

As promised, I’ll now return to other data structures you could use.

Instead of an array we might use an object with boolean values:

data() {
return {
names: [‘alpha’, ‘beta’, ‘gamma’],
activeNames: {
alpha: false,
beta: false,
gamma: false
}
}
}

In many ways this is an easier structure to work with for this particular example but we do end up duplicating the names as property keys. If we don’t prepopulate it like this we can end up with reactivity problems (though those can be solved using $set).

A further alternative is to use objects to represent the names in the first place:

data() {
return {
names: [
{name: ‘alpha’, active: false},
{name: ‘beta’, active: false},
{name: ‘gamma’, active: false}
]
}
}

Whether this kind of data structure makes sense for the use case I can’t really judge.

Update:

Based on what you’ve said in the comments I’d be inclined to create another component to represent the toggle-box. Each of these can store its own active state rather than trying to hold them all on the parent component. the v-for would then create instances of this new component directly. Depending on the circumstances it may be that this new component could merged into the original component.

There are all sorts of other considerations here that make it really difficult to give a definitive answer. If the active state needs to be known outside the toggle-box component then it’s a very different scenario to if it’s just internal state. If only one toggle-box can be open at once (like an accordion) then that’s similarly tricky as internal state is not sufficient.