Conditionally adding a CSS class in Vue - vue.js

Just started with Vue so I can't get this simple thing working. All I'm trying to do is toggle a class based on a condition.
<button type="button"
class="btn dropdown-toggle"
v-bind:class="{ btn-default: (search.category != 'all') }">
{{ filterCategoryText || 'Category' }}
</button>

Firstly, as you discovered, you should probably remove the duplicate class definition. You can mix static and dynamic classes in the bound class definition. (If you leave the duplicate there it still works, though)
Then, you have the choice...
Object syntax
// property names will be in the class list if their values are truthy
:class="{
'btn-default': search.category != "all",
'btn' : true,
'dropdown-toggle' : true
}"
Array syntax
// an item in the array becomes a class in the class list
:class="[
search.category != 'all' ? 'btn-default':'',
'btn',
'dropdown-toggle'
]"
Simple expression
// if you only have one item, just use a simple expression
:class="search.category != 'all' ? 'btn-default':''"
Docs are here

You still could have used Object syntax for the class binding. I guess the code in you example didn't work because you didn't wrap object property name with quotes. Moreover, you have at least three options here. In such case I would always stick with objects if possible as it's usually more readable. Take a look:
new Vue({
el: '#app',
data: {
red: 'nope',
},
methods: {
toggle() {
this.red = this.red === 'yes' ? 'nope' : 'yes';
}
},
})
.is-bold {
font-weight: 900;
}
.is-red {
color: red;
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p class="is-bold" :class="{'is-red': red === 'yes'}">
Option
</p>
<p class="is-bold" :class="red === 'yes' ? 'is-red' : ''">
Option 1
</p>
<p class="is-bold" :class="[red === 'yes' ? 'is-red' : '']">
Option 2
</p>
<button #click="toggle">Toggle class</button>
</div>
jsfiddle: https://jsfiddle.net/oniondomes/06bg516h/

Figured it:
<button type="button"
:class="[(search.category) ? '' : 'btn-default', 'btn dropdown-toggle']"
{{ filterCategoryText || 'Category' }}
</button>

try this instead of v-bind:class="{ 'btn-default': search.category != 'all' }"

Related

How to make single property in array reactive when using `ref` instead of `reactive`?

I have a component that displays rows of data which I want to toggle to show or hide details. This is how this should look:
This is done by making the mapping the data to a new array and adding a opened property. Full working code:
<script setup>
import { defineProps, reactive } from 'vue';
const props = defineProps({
data: {
type: Array,
required: true,
},
dataKey: {
type: String,
required: true,
},
});
const rows = reactive(props.data.map(value => {
return {
value,
opened: false,
};
}));
function toggleDetails(row) {
row.opened = !row.opened;
}
</script>
<template>
<div>
<template v-for="row in rows" :key="row.value[dataKey]">
<div>
<!-- Toggle Details -->
<a #click.prevent="() => toggleDetails(row)">
{{ row.value.key }}: {{ row.opened ? 'Hide' : 'Show' }} details
</a>
<!-- Details -->
<div v-if="row.opened" style="border: 1px solid #ccc">
<div>opened: <pre>{{ row.opened }}</pre></div>
<div>value: </div>
<pre>{{ row.value }}</pre>
</div>
</div>
</template>
</div>
</template>
However, I do not want to make the Array deeply reactive, so i tried working with ref to only make opened reactive:
const rows = props.data.map(value => {
return {
value,
opened: ref(false),
};
});
function toggleDetails(row) {
row.opened.value = !row.opened.value;
}
The property opened is now fully reactive, but the toggle doesn't work anymore:
How can I make this toggle work without making the entire value reactive?
The problem seems to come from Vue replacing the ref with its value.
When row.opened is a ref initialized as ref(false), a template expression like this:
{{ row.opened ? 'Hide' : 'Show' }}
seems to be interpreted as (literally)
{{ false ? 'Hide' : 'Show' }}
and not as expected as (figuratively):
{{ row.opened.value ? 'Hide' : 'Show' }}
But if I write it as above (with the .value), it works.
Same with the if, it works if I do:
<div v-if="row.opened.value">
It is interesting that the behavior occurs in v-if and ternaries, but not on direct access, i.e. {{ rows[0].opened }} is reactive but {{ rows[0].opened ? "true" : "false" }} is not. This seems to be an issue with Vue's expression parser. There is a similar problem here.

how to change styles depending of true/false in vuejs

I'm trying to change the style if one of the classes returns true.
My template-
<div
class=""
:class="{
' green': visibleComponent != 'orders',
' opacity-25 ': visibleComponent == 'orders',
}"
#click="orders"
>
<div class="">
Orders
</div>
</div>
Any help on how do I go about this?
Do this;
<div
:class="`${visibleComponent !== 'orders' ? 'green' : 'opacity-25'}`"
#click="orders"
>
I assume you talking about Conditional Rendering
this offical sample code should be clear about how to use v-if
<script>
export default {
data() {
return {
awesome: true
}
}
}
</script>
<template>
<button #click="awesome = !awesome">toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
</template>
this is how u do it:
<div class="anyclass" :class="dynamicClass" #click="orders">
<div class="">
Orders
</div>
</div>
<script setup> // Composition API
const dynamicClass = computed(() => {
return {
'green': visibleComponent != 'orders',
'opacity-25 ': visibleComponent == 'orders',
}
})
// Options API
export default {
computed: {
dynamicClass() {
return {
'green': visibleComponent != 'orders',
'opacity-25 ': visibleComponent == 'orders',
}
}
}
}
</script>
Assuming you have already defined visibleComponent in your data object or as a ref() with Vue3, you could use a ternary operator inline with a dynamic class binding. This is most similar to what you are showing in your code already.
If you are unfamiliar with ternary operators, it is basically an if/else statement using shorthand. condition ? ifConditionTrue : ifConditionFalse This format works very nicely with Vue dynamic class bindings.
<div
:class="
visibleComponent
? visibleComponent != 'orders' && 'green'
: visibleComponent == 'orders' && 'opacity-25'
">
</div>
This first checks if the visibleComponent property has been assigned a value. Then if that value is not 'orders', the green class will bind to the div. Else if the value is 'orders', the opacity-25 class will bind.

Stencil component not rendering the updated tabs

import { Component, h, State } from '#stencil/core';
// import '#webcomponents/custom-elements';
import '#clr/core/icon/register';
import { ClarityIcons, plusIcon } from '#clr/core/icon';
ClarityIcons.addIcons(plusIcon);
#Component({
tag: 'tabs-component',
styleUrl: 'tabs-component.css',
shadow: false,
})
export class TabsComponent {
#State() tabs: Array<object> = [
(
<li role="presentation" class="nav-item">
<button id="tab3" class="btn btn-link nav-link" aria-controls="panel3"
aria-selected="false" type="button">Cloud</button>
</li>
)
];
addTab(onHead = true) {
// debugger
const tab = (
<li role="presentation" class="nav-item">
<button id="tab3" class="btn btn-link nav-link" aria-controls="panel3"
aria-selected="false" type="button">Dashboard</button>
</li>
);
if (onHead) {
this.tabs.unshift(tab);
} else {
this.tabs.push(tab);
}
console.log(this.tabs);
}
render() {
return (
<div>
<ul id="demoTabs" class="nav" role="tablist">
<li role="presentation" class="nav-item" onClick={() => this.addTab()}>
<cds-icon shape="plus" class="cursor"></cds-icon>
</li>
{this.tabs}
</ul>
<section id="panel1" role="tabpanel" aria-labelledby="tab1">
tab1
</section>
<section id="panel2" role="tabpanel" aria-labelledby="tab2" aria-hidden="true">
tab2
</section>
<section id="panel3" role="tabpanel" aria-labelledby="tab3" aria-hidden="true">
tab3
</section>
</div>
);
}
}
This is a matter of referential equality. Objects are always passed by reference not by value and therefore two objects are never equal (reference-wise, even if they contain the same values).
The array is a special kind of object and therefore is also passed by reference. Modifying an array's value does not change its reference.
Some examples to illustrate this:
const foo = ['a', 'b'];
console.log(foo === ['a', 'b', 'c']); // false
foo.push('c');
console.log(foo === ['a', 'b', 'c']); // still false
console.log(['a', 'b', 'c'] === ['a', 'b', 'c']); // actually always false
console.log(foo === foo); // always true because it is the same reference
Stencil compares #State() decorated class members using the same strict equality operator === (same goes for #Prop()). If the value is the same, then the component is not re-rendered.
In the case of your tabs state, the value of this.tabs is a reference to the array that you assign to it. Modifying the array (e. g. this.tabs.push(...)) only changes the value of the array referenced by this.tabs but not the actual reference that is stored in this.tabs.
Therefore you need to re-assign this.tabs in order to let Stencil know that this member has changed. The easiest way to do that is
this.tabs = [...this.tabs];
which spreads the values of the array into a new array (which returns a new reference). Alternatively something like this.tabs = this.tabs.slice() would also do the trick (anything that returns a new array works).
In your case it's easiest to change your addTab method to
addTab(onHead = true) {
const tab = (
<li role="presentation" class="nav-item">
<button id="tab3" class="btn btn-link nav-link" aria-controls="panel3"
aria-selected="false" type="button">Dashboard</button>
</li>
);
this.tabs = onHead ? [tab, ...this.tabs] : [...this.tabs, tab];
}
(i. e. either spread the original value before or after the new item).
Stencil performs strict equality checks (===) to determine whether a Prop/State variable has changed which is why it doesn't detect push and unshift as changes. You have to make sure to replace the array with a new one. The quickest way to do this in your example is to manually replace the array with a copy after the manipulation:
if (onHead) {
this.tabs.unshift(tab);
} else {
this.tabs.push(tab);
}
this.tabs = [...this.tabs];
See the Stencil docs for updating arrays.
Look like temp variable works the trick, strange.
const tempTabs = [...this.tabs];
if (onHead) {
tempTabs.unshift(tab);
} else {
tempTabs.push(tab);
}
this.tabs = tempTabs;

How to binding only single class when using several v-on:click?

I'm newbie and studying Vue.js
I create v-on:click function and element toggle class when I click the button.
I am not good at English, I think it will be fast to show the code.
<button #click="bindA = !bindA">A</button>
<button #click="bindB = !bindB">B</button>
<span :class="[{ classA:bindA }, { classB:bindB }]"></span>
data: function() {
return {
bindA: true, // default
bindB: false
}
it's now. clicked bindA and B.
// browser
<span class="classA classB"></span>
but I want
// bindA click , remove classB
<span class="classA"></span>
// bindB click , remove classA
<span class="classB"></span>
It's simple in jquery, but difficult in vue.
It's very simple in vue as well.
Bind the class according to the conditions you want to see the data
:class="{'classA': (bindA== true), 'classB':(bindA== false)}"
Try adding a method to the #click - then you can build up a more complex logic, than simple "toggle".
new Vue({
el: "#app",
data: {
bindA: true,
bindB: false
},
methods: {
bind(btn) {
if ((btn === 'A' && !this.bindA) || (btn === 'B' && !this.bindB)) {
this.bindA = !this.bindA
this.bindB = !this.bindB
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="bind('A')">A</button>
<button #click="bind('B')">B</button>
<span :class="{ classA:bindA, classB:bindB }"></span>
</div>

conditional class binding in list using props in vue js

progressbar component receives props from parent component name activeStep that its value is active index of progressbar. my challenge is while rendering list of progressbar i want to set active class to li s that less than value of active step property or equal to value
how can i do that?
<template>
<div>
<ul class="progressbar">
<li v-for="(progressbarCase,index) in progressbarCases" :key="progressbarCase.id">
{{progressbarCase.title}}
</li>
</ul>
</div>
</template>
<style lang="css">
#import "../assets/stylesheet/progressbar.css";
</style>
<script>
export default {
props: ['activeStep'],
data () {
return {
progressbarCases: [
{title: 'first'},
{title: 'second'},
{title: 'third'}
]
}
}
}
</script>
If you want add class conditional, you can use :class="". For example: :class="true ? 'active' : 'disable'". with this,if condition is true: class="active" else class="disable"
Quite simple, if you have more than one conditionnal class, you need to define the :class attribute as an object like this:
<li :class="{
'a-conditional-class': true,
'an-other-conditional-class-but-not-displayed': false,
}">
</li>
For your information, you can also couple conditional and unconditional classes !
And in one :class attribute, like this:
<li :class="[
'an-unconditional-class',
{
'a-conditional-class': true,
'an-other-conditional-class-but-not-displayed': false,
}
]">
</li>
Have a look on the official documentation for more information about Class an style binding: https://v2.vuejs.org/v2/guide/class-and-style.html#ad