0
votes

I am new to vuejs, this is what I want to do: I have a list of components, each in a div. Now if I do something with the component (i.e. click it). I want to add a class to the parent div. This is what I did so far, the code is simplified, just to show what I want 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.

1
Not sure why do you make classActive a property of your application, and not a component. - raina77ow
what do you mean? I know it might not be the best way, feel free to change it :-) - yangsunny

1 Answers

1
votes

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 your 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. Your 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 your 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.