VueJS: Dynamic Class Binding - javascript

Vue -V -- 3.0.5.
I have a component Cube.vue in which I'm trying to set a blue or green class to a child element dynamically.
I've created the component and have it imported into a specific page but I can't get the or to work correctly.
<template>
<div :class="$style.cubeInner">
<div class="cube" :class="{ 'cube--blue': isBlue ? 'cube--green': isGreen }">
<div v-for="side in cubeside" :class="side.class" :key="side.id"></div>
</div>
</figure>
</template>
And here is my export
export default {
data() {
return {
Cube: 'cube',
isBlue: Boolean,
isGreen: Boolean,
};
}
};
I import into another component and render it via <cube-hover></cube-hover>. My question is do I need to set a prop or a data() for isBlue to be true or false? I can't seem to target the child since the entire component is being imported.
Basically, I can't target that nested <div>, it just adds the class to the parent. And I want to add 'cube--blue' or 'cube--green' to specific pages.

Put the boolean into a data field, and then the condition check into a computed function.
...updated to add context
export default {
data: () => {
...
isBlue: Boolean,
isGreen: Boolean,
},
computed:
isBlue() {
if (is it blue?) return true;
return false;
},
isGreen() {
if (is it green?) return true;
return false;
}
}
<template>
...
<div class="cube" :class="{ isBlue ? 'cube--blue' : 'cube--green': isGreen }">
<!-- I think this is where you went wrong: "'cube--blue': isBlue ? 'cube--green': isGreen" see note -->
</template>
note
You have a "?" separating your classes which should either be a comma, or you are trying to do a ternary operation. Comma separation could possibly apply both at once and I suspect you don't want that. Or if you are trying to do conditional class assignment:
Fix your ternary syntax:
`condition ? value if true : value if false`
you are missing the
: value if false portion
What you probably want is:
`:class="isBlue ? 'cube--blue' : 'cube--green'"
Lastly
Now that I've written this out I sort of feel like I should recommend a different approach. Assuming that this cube is either green OR blue, but never both at the same time, you might want to combine the logic into a single step. Perhaps you want to use a conditional inside of a getColor function? This is particularly smart if you will ever have more than two colors. Then the fn just returns a color and you can interpolate that into your class name like:
<div :class="`cube--${color}`"></i>

I can't understand what do you mean by 'or'.
By looking at your data just type:
<div class="cube" :class="{ 'cube--blue': isBlue, 'cube--green': isGreen }">
Update:
Kraken meant to change you approach to:
<div class="cube" :class="`cube--${getColor}`">
and in your data just type:
data() {
return {
color: 'blue',
};
},
computed: {
getColor() {
return this.color;
},
},
With this approach you prepare yourself for maybe more colors in the future. By just updating this.color.

<li
v-for="item in items"
:key="item.id"
class="nav-item"
:class="{ dropdown: hasChildren(item.children) }"
>
methods: {
hasChildren(item) {
return item.length > 0 ? true : false;
},
}

I think this is the best way to solve this problem.
<div class="checkbox-wrapper">
<div :class="[isInsurancePictureRequired === 'yes' ? 'custom-checkbox-active' : '', 'custom-checkbox']">
<label class="pic-required-checkbox-label" for="yes">
<input type="radio" id="yes" name="picture-require" value="yes" #click="handleCheckBox" checked>
<span class="checkmark"></span>
Yes
</label>
</div>

Related

v-bind:style for dynamical value in Vue.js

When I use v-bind:style for using dynamical value, I came across the problem that v-bind:style doesn't work but I'm sure v-bind:style gets correct value(:style='{ color : red(any other value) }') in console and css in style section reflects successfully. why not v-bind ?? Any idea ?? Thank you so much.
<div class="l-modal" v-if="modalVisible1">
<div class="p-modal" #click="hide_modal" :style='{ color : titleColor1 }' ref="test">
<p>{{titleTxt1}}</p>
<p>{{contentTxt1}}</p>
<p>{{endTxt1}}</p>
<button class="p-modal__btn">{{buttonTxt1}}</button>
</div>
</div>
<div class="l-modal__bg" v-if="modalBgVisible1" #click="hide_modal"></div>
data () {
return {
selected_title_color1:'',
titleColor1:'',
colors:['紅色','藍色','黃色','粉色','土地色','水藍色','灰色','淺綠色','橙色'],
colors_dic:{紅色:'red',藍色:'blue',黃色:'yellow',粉色:'pink',土地色:'blawn',水藍色:'aqua',灰色:'gray',淺綠色:'palegreen',橙色:'orange'},
}
},
watch:{
selected_title_color1: function () {
this.titleColor1 = this.colors_dic[this.selected_title_color1];
}
},
As described you should use an computed property for the style.
This one will also auto reflect any changes of props.
If you have some conditions you can also modify the values depending on that in the computed callback function. I've added an example with darkmode to make this approach clear.
export default {
data(){ return {
selected_title_color1:'',
titleColor1:'',
colors:['紅色','藍色','黃色','粉色','土地色','水藍色','灰色','淺綠色','橙色'],
colors_dic:{紅色:'red',藍色:'blue',黃色:'yellow',粉色:'pink',土地色:'blawn',水藍色:'aqua',灰色:'gray',淺綠色:'palegreen',橙色:'orange'},
modalVisible1: true,
darkmode: false, // example value
}},
watch:{
selected_title_color1: function () {
this.titleColor1 = this.colors_dic[this.selected_title_color1];
}
},
computed: {
// Here comes your style magic.
// Return an Object that you will bind to the style prop.
// Changes to any reactive value will be reflected directly.
style(){
// create the base style here.
let newStyle = {}
// apply your titleColor1 which results in style="{color:titleColor1}"
if(this.titleColor1){
this.color = this.titleColor1
}
// if you would have more conditions you can add them here, too
// just an _example_ if you would have a darkmode value in data.
if(this.darkmode){
this.backgroundColor = '#222222'
} ​
​
​return newStyle
​}
​},
​methods: {
​// rest of methods if
​}
}
and then attach it to your div with :style="style".
<div class="l-modal" v-if="modalVisible1">
​<div class="p-modal" #click="hide_modal" :style="style" ref="test">
​<p>{{titleTxt1}}</p>
​<p>{{contentTxt1}}</p>
​<p>{{endTxt1}}</p>
​<button class="p-modal__btn">{{buttonTxt1}}</button>
​</div>
</div>
<div class="l-modal__bg" v-if="modalBgVisible1" #click="hide_modal"></div>
Tip from my side. I would outsource the code for setting the color and bind the method to an event that changes the color instead of using an watcher. This can make your app a bit more flexible and cleans it up. But the way you've done it works, too.
Check following snippet please, it look like everything works fine:
new Vue({
el: "#demo",
data () {
return {
selected_title_color1:'',
titleColor1:'',
colors:['紅色','藍色','黃色','粉色','土地色','水藍色','灰色','淺綠色','橙色'],
colors_dic:{紅色:'red',藍色:'blue',黃色:'yellow',粉色:'pink',土地色:'blawn',水藍色:'aqua',灰色:'gray',淺綠色:'palegreen',橙色:'orange'},
modalVisible1: true,
}
},
watch:{
selected_title_color1: function () {
this.titleColor1 = this.colors_dic[this.selected_title_color1];
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div class="l-modal" v-if="modalVisible1">
<div class="p-modal" :style='{ color : titleColor1 }' ref="test">
<p>{{ titleColor1 }} - {{ selected_title_color1 }}</p>
</div>
</div>
<select v-model="selected_title_color1">
<option value="" disabled>Select color</option>
<option v-for="option in colors" v-bind:value="option">
{{ option }}
</option>
</select>
</div>

Array change detection for an array of complex objects in Vue JS 2

Update
Vue JS 3 will properly handle this: https://blog.cloudboost.io/reactivity-in-vue-js-2-vs-vue-js-3-dcdd0728dcdf
Problem:
I have a vue component that looks like this:
sub-comp.vue
<template>
<div>
<input type="text" class="form-control" v-model="textA">
<input type="text" class="form-control" v-model="textB">
<input type="text" class="form-control" v-model="textC">
</div>
</template>
<script>
export default {
props: {
textA: {
type: Number,
required: false
},
textB: {
type: Number,
required: false
},
textC: {
type: Number,
required: false
}
}
}
</script>
I have a parent component that looks like this:
layout-comp.vue
<template>
<div>
<button #click="addItem">Add</button>
<ul>
<li v-for="listItem in listItems"
:key="listItem.id">
<sub-comp
:textA="listItem.item.textA"
:textB="listItem.item.textB"
:textC="listItem.item.textC"
/>
</li>
</ul>
</div>
</template>
import subComp from '../sub-comp.vue'
export default {
components: {
subComp
},
data() {
return {
listItems: []
}
},
methods: {
addItem: function () {
var item = {
textA: 5,
textB: 100,
textC: 200
}
if (!item) {
return
}
this.length += 1;
this.listItems.push({
id: length++,
item: item
});
}
}
</script>
The thing is, anything I do to edit the textboxes, the array doesn't get changed, even though the reactive data shows that it changed. For example, it will always be as
{
textA: 5,
textB: 100,
textC: 200
}
Even if I changed textB: 333, the listItems array still shows textB: 100. This is because of this:
https://v2.vuejs.org/v2/guide/list.html#Caveats
Due to limitations in JavaScript, Vue cannot detect the following changes to an array
Question:
I'm wondering how do I update the array? I also want the change to occur when leaving the textbox, using the #blur event. I'd like to see what ways this can be done.
I read these materials:
https://codingexplained.com/coding/front-end/vue-js/array-change-detection
https://v2.vuejs.org/v2/guide/list.html
But it seems my example is a bit more complex, as it has indexes associated, and the arrays have complex objects.
Update 4/12/2018
Found out that in my addItem() that I had:
item = this.conditionItems[this.conditionItems.length - 1].item);
to
item = JSON.parse(JSON.stringify(this.conditionItems[this.conditionItems.length - 1].item));
I was thinking the sync modifier in the answer below was causing problems because it duplicated all items. But that's not the case. I was copying a vue object (including the observable properties), which caused it to happen. The JSON parse and JSON stringify methods only copies the properties as a normal object, without the observable properties. This was discussed here:
https://github.com/vuejs/Discussion/issues/292
The problem is that props flow in one direction, from parent to child.
Setting the value using v-model in child won't affect parent's data.
Vue has a shortcut to update parent's data more easily. It's called .sync modifier.
Here's how.
In sub-comp.vue
<template>
<div>
<input type="text" class="form-control" :value="textA" #input="$emit('update:textA', $event.target.value)" >
<input type="text" class="form-control" :value="textB" #input="$emit('update:textB', $event.target.value)">
<input type="text" class="form-control" :value="textC" #input="$emit('update:textC', $event.target.value)">
</div>
</template>
<script>
export default {
// remains the same
}
</script>
add .sync when you add the props
<sub-comp
:textA.sync="listItem.item.textA" // this will have the same effect of v-on:update:textA="listItem.item.textA = $event"
:textB.sync="listItem.item.textB"
:textC.sync="listItem.item.textC"
/>
update:
if you have reactivity problem, don't use .sync, add a custom event and use $set
<sub-comp
:textA="listItem.item.textA" v-on:update:textA="$set('listItem.item','textA', $event)"
/>

How can I add Multi class binding in Vue2

I faced one problem when I put multi-class binding in Vue2.
<div class="list-item clearfix" v-on:click="selectItem(trail)" :class="popupMode ? 'popup' : ''">
Here popupMode is props and I'd like to add one more class binding when I select the Item(click function:selectItem()).
For example, similar to selected.
This class is defined. How can I manage this problem?
Update HTML class bind like:
v-bind:class="{ popup: popupMode, selected: isSelected }"
where isSelected is a new prop like popupMode. On element click when selectItem method is called, set the bool isSelected prop to true and you will be able to see selected class after that.
JS:
data: {
popupMode: true,
isSelected: false
},
methods: {
selectItem() {
this.isSelected= true;
}
}
For more info check this: Binding HTML Classes
You can try this method:
{ active: isActive, 'text-danger': hasError }
Don't forget place isActive and hasError in data (in this case).
I hope this help's.

v-for causing actions to be applied to all divs

Previously I asked a question about removing a custom truncate filter in Vue. Please see the question here:
Removing a Vue custom filter on mouseover
However, I neglected to mention that I am using a v-for loop and when I hover over one div, I am noticing that all the divs in the loop are having the same action applied to them. I'm not sure how to target only the div that is being hovered over. Here is my template:
<div id="tiles">
<button class="tile" v-for="(word, index) in shuffled" #click="clickWord(word, index)" :title="word.english">
<div class="pinyin">{{ word.pinyin }}</div>
<div class="eng" #mouseover="showAll = true" #mouseout="showAll = false">
<div v-if="showAll">{{ word.english }}</div>
<div v-else>{{ word.english | truncate }}</div>
</div>
</button>
</div>
And the data being returned:
data(){
return {
currentIndex: 0,
roundClear: false,
clickedWord: '',
matchFirstTry: true,
showAll: false,
}
},
If you know Vue, I would be grateful for advice. Thanks!
In your example, showAll is being used for each of the buttons generated by the v-for to determine whether or not to show the complete text of the word.english value. This means that whenever the mouseover event of any the .eng class divs fires, the same showAll property is being set to true for every button.
I would replace the showAll Boolean value with a showWordIndex property initially set to null:
data() {
showWordIndex: null,
},
And then in the template, set showWordIndex to the index of the word on the mouseover handler (and to null in the mouseleave handler):
<button v-for="(word, index) in shuffled" :key="index">
<div class="pinyin">{{ word.pinyin }}</div>
<div
class="eng"
#mouseover="showWordIndex = index"
#mouseout="showWordIndex = null"
>
<div v-if="showWordIndex === index">{{ word.english }}</div>
<div v-else>{{ word.english | truncate }}</div>
</div>
</button>
Here's a working fiddle.
Even better would be to make a new component to encapsulate the functionality and template of everything being rendered in the v-for, passing the properties of each word object to the child component as props.
This way, you would still use the showAll property like you are in your example, but you would define it in the child component's scope. So now the showAll property will only affect the instance of the component it's related to.
Below is an example of that:
Vue.component('tile', {
template: '#tile',
props: ['pinyin', 'english'],
data() {
return { showAll: false };
},
filters: {
truncate: function(value) {
let length = 50;
if (value.length <= length) {
return value;
} else {
return value.substring(0, length) + '...';
}
}
},
})
new Vue({
el: '#app',
data() {
return {
words: [
{pinyin: 1, english: "really long string that will be cut off by the truncate function"},
{pinyin: 2, english: "really long string that will be cut off by the truncate function"},
{pinyin: 3, english: "really long string that will be cut off by the truncate function"},
{pinyin: 4, english: "really long string that will be cut off by the truncate function"},
],
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<div id="app">
<tile v-for="word, i in words" v-bind="word" :key="word"></tile>
</div>
<script id="tile" type="x-template">
<button :title="english">
<div class="pinyin">{{ pinyin }}</div>
<div class="eng" #mouseover="showAll = true" #mouseout="showAll = false">
<div v-if="showAll">{{ english }}</div>
<div v-else>{{ english | truncate }}</div>
</div>
</button>
</script>
In order to do this, you can't use a computed property (as I originally suggested in the answer of mine that you linked), since you need to be aware of the context that you are in. That said, you CAN use a filter if you apply a showAll property to each individual instance. If you declare this up front in your data model, the property will be reactive and you can toggle each item individually on mouseover and mouseout.
template:
<div id="app">
<div id="tiles">
<div class="tile" v-for="(word, index) in shuffled" :title="word.english">
<div class="pinyin">{{ word.pinyin }}</div>
<div class="eng" #mouseover="word.showAll = true" #mouseout="word.showAll = false">
{{ word.english | truncate(word) }}
</div>
</div>
</div>
</div>
js:
new Vue({
el: '#app',
data() {
return {
shuffled: [
{ english: 'here', showAll: false},
{ english: 'are', showAll: false },
{ english: 'there', showAll: false },
{ english: 'words', showAll: false }
],
currentIndex: 0,
roundClear: false,
clickedWord: '',
matchFirstTry: true,
}
},
filters: {
truncate: function(value, word) {
console.log(word)
let length = 3;
if (word.showAll || value.length <= length) return value;
return value.substring(0, length) + '...';
}
},
})
See working JSFiddle
The key is to apply showAll to each word instance and to then pass that word instance back to the filter so that we can check the value of the showAll property. As long as you declare it up front, Vue's reactivity system handles the rest for you.
Note that in this example it isn't necessary to use two elements with a v-if/else. A single element with a filter works perfectly.

Vue.js how to delete component v-for values

I am learning Vue, so I created radio button component, but I am struggling with how can one delete these values. My current solution deletes actual values, but selection is still displayed.
This is the component
<template id="fradio">
<div>
<div class="field is-horizontal">
<div class="field-label" v-bind:class = "{ 'required' : required }">
<label
class = "label"
>{{label}}
</label>
</div>
<div class="field-body">
<div>
<div class="field is-narrow">
<p class="control" v-for="val in values">
<label class = "radio">
<input
type="radio"
v-bind:name = "name"
v-bind:id = "name"
#click = "updateValue(val)"
>
<span>{{val[valueLabel]}}</span>
<span v-if="!valueLabel">{{val}}</span>
</label>
<label class="radio">
<button class="delete is-small" #click="removeValue"></button>
</label>
<slot></slot>
</p>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
required: true,
},
inputclass: {
type: String,
},
required: {
type: Boolean,
default: false,
},
valueLabel:{
type: String,
},
returnValue:{
type: String,
},
values:{},
name:'',
},
data() {
return {
};
},
methods: {
updateValue: function (value) {
var selectedValue;
(!this.returnValue) ? selectedValue = value : selectedValue = value[this.returnValue];
this.$emit('input', selectedValue)
},
removeValue: function() {
this.$emit('input',null);
},
},
}
</script>
It should be easy, but I need someone to point out the obvious...
Update:
I just realized that you may be more focused on the data not dynamically updating, which means that your issue might be that the data in the parent component is not being updated. Most of your data is being passed down as props, so I'd need to see how the event is being fired in the parent component in order to help diagnose what's wrong. Based on the code you provided, it looks like your removeValue() function is emitting an event but I don't see any code that actually removes the value.
I would check the parent component to make sure that it is removing the child component and that should fix your problem!
Initial Answer:
Generally, when removing an item from a v-for list, you need to know the index of the item and use the Array.splice in order to modify the list to remove it.
Here's a generic example off the top of my head.
<template>
<ul>
<li v-for="(fruit, index) in fruits"
#click="removeItem(index)">
{{ fruit }}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
fruits: ['Apple', 'Banana', 'Clementines']
}
},
methods: {
removeItem(index) {
this.fruits.splice(index, 1)
}
}
}
</script>
Let me know if you have any questions!

Categories

Resources