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

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>

Related

Vue 3 - V-Model Confusion

I am teaching myself vue 3. I have read article after article on v-model and each time I think I understand how it works I get confused again.
My goal: I built a custom dropdown component. I need the ability to control the value of this dropdown from the parent. When the dropdown changes I want to let the parent know the new value and the index.
Child component.vue
<div>
<select
:value="modelValue"
#input="$emit('update:modelValue', $event.target.value)"
>
<option v-for="option in options" :key="option">
{{ option }}
</option>
</select>
</div>
</template>
<script>
export default {
props: ["options", "modelValue"],
emits: ["update:modelValue"],
methods: {
selected() {
//??????
//want to emit this to the parent
let selectedIndex = this.$event.target.selectedIndex + 1
//this.$emit(value, selectedIndex)
},
},
};
</script>
parent.vue
<template>
<my-drop-down :options="options" v-model="selectedOption" />
</template>
<script>
import myDropDown from "./components/base_dropdown.vue";
export default {
name: "App",
data: () => ({
selectedOption: "2 Line",
selectedIndex: 0,
options: ["1 Line", "2 Line", "3 Line"],
}),
components: {
myDropDown,
},
methods: {
//How can I call this when the select value changes??
onSelectChange(selected, index) {
console.log(`Parent L3rd Change, name: ${selected}, index: ${index} `);
},
},
};
</script>
The two way binding is working correctly. I can control the value of the dropdown from either the child or the parent. But how do I call the onSelectChange method in my child component
Also, and this is may be a dumb question but...
v-model="selectedOption" is the same as writing :value="modelValue" #input="$emit('update:modelValue', $event.target.value)"
so why is the parent written like this <my-drop-down :v-model="selectedOption" />
and the child written like this <select :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
and not simply
<select :v-model="selectedOption />
If you want to call a method inside your parent component when the "select value changes", It is better to call it inside a Vue watch like the codes below:
Parent component:
<template>
<my-drop-down :options="options" v-model="selectedOption" />
</template>
<script>
import myDropDown from "../components/baseDropdown.vue";
export default {
name: "parentModel",
data: () => ({
selectedOption: "2 Line",
// selectedIndex: 0,
options: ["1 Line", "2 Line", "3 Line"],
}),
components: {
myDropDown,
},
computed: {
/* It is better to use computed property for "selectedIndex", because it is related to "selectedOption" and changes accordingly. */
selectedIndex: function () {
return this.options.indexOf(this.selectedOption)
}
},
watch: {
selectedOption(newSelect, oldSelect) {
this.onSelectChange(this.selectedOption, this.selectedIndex)
}
},
methods: {
//How can I call this when the select value changes??
onSelectChange(selected, index) {
console.log(`Parent L3rd Change, name: ${selected}, index: ${index} `);
},
},
}
</script>
<style scoped>
</style>
Child component:
<template>
<div>
<select
:value="modelValue"
#change="$emit('update:modelValue', $event.target.value)"
>
<!-- You can use v-model also here. But it only changes the value of "modelValue" and does not emit anything to parent component. -->
<!-- <select v-model="modelValue">-->
<option v-for="option in options" :key="option">
{{ option }}
</option>
</select>
</div>
</template>
<script>
export default {
name: "baseDropdown",
props: ["options", "modelValue"],
emits: ["update:modelValue"],
/* --------------------------------- */
/* You don't need this method, because "$emit('update:modelValue', $event.target.value)" that is used in "select" tag itself is enough to emit data to the parent component. */
/* --------------------------------- */
// methods: {
// selected() {
//
// //??????
// //want to emit this to the parent
// let selectedIndex = this.$event.target.selectedIndex + 1
// //this.$emit(value, selectedIndex)
// },
// },
}
</script>
<style scoped>
</style>
And about your second part of the question:
v-model="selectedOption" is the same as writing :value="modelValue" #input="$emit('update:modelValue', $event.target.value)"
In my opinion it is not a true statement for two reasons:
Reason one: according to Vue docs :
v-model="selectedOption" is the same as writing :value="selectedOption"
#input="event => selectedOption = event.target.value"
you can't see any $emit in the above statement. But in your case you want to emit data to the parent component.
Reason two: again according to Vue docs it is better to use change as an event for <select> tag.
You look to be needing a watcher in your parent component, one that watches for changes to the selectedOption property, and then uses the new value to get the index from the options array and adds one to it, and uses the new value to set the selectedIndex property.
Per the Vuejs API section on Watchers:
Computed properties allow us to declaratively compute derived values. However, there are cases where we need to perform "side effects" in reaction to state changes - for example, mutating the DOM, or changing another piece of state based on the result of an async operation.
With Options API, we can use the watch option to trigger a function whenever a reactive property changes.
So, for your code, it might look something like:
watch: {
selectedOption(newValue, oldValue) {
console.log('In watcher. New value: ' + newValue)
// get the index of the String in the array and use it to set
// the selectedIndex:
this.selectedIndex = this.options.findIndex(i => i === newValue) + 1;
console.log('selectedIndex: ' + this.selectedIndex)
}
},
As for your question's "second part",
why is the parent written like this <my-drop-down :v-model="selectedOption" />
and the child written like this <select :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
and not simply <select :v-model="selectedOption />
It is probably best to ask that as a separate question, in order to maintain question specificity as required by the site, but as I see it, selectedOption is not acting as a model for the select tag, and in fact selectedOption isn't even a property of the child component, nor should it be.

VueJS: Dynamic Class Binding

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>

Vue.js using a computed property to show or hide part of a component

I'm trying to show / hide part of a component based on the value in a drop down list. Before moving this part of my form, using a computed property worked just fine. However... I am using two way binding in my component and it seems that the computed value of the property I am using is updating too late. Here is the component js
Vue.component('system', {
template: '#system',
props: ['name', 'count'],
computed: {
issummit: function() {
return this.name === '5a37fda9f13db4987411afd8';
}
// audiovideo: function() {
// return this.system === params.id.easy3eeg || this.system === params.id.easy3psg || this.system === params.id.essentia;
// }
},
data () {
return {
systemname: this.name,
systemcount: this.count,
systemoptions: [
{ text: 'Select One', value: null },
{ text: 'Summit', value:'5a37fda9f13db4987411afd8'},
{ text: 'Essentia', value:'5a37fda1de9e84bb9c44a909'},
{ text: 'Alterna', value:'5a8caadc86dc269de9887b0f'},
{ text: 'Easy III PSG', value:'5a37fe27b1e43d5ca589aee3'},
{ text: 'Easy III EEG', value:'5a37fd9a08a387d4efcf9ddb'},
{ text: 'IOMAX', value:'5a8cab59a1353f170f6e92a4'},
{ text: 'Cascade Pro', value:'5a8cab696f6a77f774e8de7f'}
]
}
},
watch: {
name (name) {
this.systemname = name;
},
count (count) {
this.systemcount = count;
}
},
methods: {
updateName: function() {
this.$emit('update:name', this.systemname);
},
updateCount: function() {
this.$emit('update:count', this.systemcount);
}
}
});
Here is the component template
<script type="text/x-template" id="system">
<b-row>
<b-form-group class="col-sm" label="Count">
<b-form-input type="number" #change="updateCount" required v-model="systemcount" class="col-sm"></b-form-input>
</b-form-group>
<b-form-group class="col-sm" label="System">
<b-form-select #change="updateName" :options="systemoptions" required v-model="systemname"></b-form-select>
</b-form-group>
<!-- Summit Options -->
<template v-if="issummit">
<b-form-group class="col-sm" label="Channels">
<b-form-input type="number" required v-model="summitchannels"></b-form-input>
</b-form-group>
<b-form-group label="Accessories">
<b-form-checkbox-group v-model="summitaccessories" :options="summitoptions">
</b-form-checkbox-group>
</b-form-group>
</template>
</b-row>
</script>
<script src="scripts/system.js"></script>
And here is the template in use
<system v-for="system in systems"
:name.sync="system.name"
:count.sync="system.count">
</system>
The computed value does update... however the problem is that it seems to update after it is used to determine the rendering. If I select "Summit" in my drop down, I would expect the hidden part of my component to show, that its not until I select something else that it is then shown... the second time I make a selection the computed value from the previous attempt is used.
EDIT
Per some suggestions I edited the select to use a regular DOM object and this fixed the issue. However, this only became an issue when I moved this over to a template... everything worked peachy before... any ideas?
<div role="group" class="col-sm b-form-group form-group">
<label class="col-form-label pt-0">System</label>
<div>
<select #change="updateName" class="form-control custom-select" required v-model="systemname">
<option v-for="o in systemoptions" :value="o.value">{{o.text}}</option>
</select>
</div>
</div>
I have a minimal reproduction in https://jsfiddle.net/3vkqLnxq/1/
It works as intended. The change is all b-* tags are changed to dom.
So the most possible cause is that b-form-select has some issue.
You should use getters and setters for computed property data binded.
Something like this:
computed: {
issummit: {
// getter
get: function () {
return this.name === '5a37fda9f13db4987411afd8';
},
// setter
set: function (newValue) {
this.systemname = newValue;
}
}
}
More:
https://v1.vuejs.org/guide/computed.html#Computed-Setter
I solved a similar problem with changing the
#change="updateName"
to
#change.native="updateName"
(https://v2.vuejs.org/v2/guide/components.html#Binding-Native-Events-to-Components)
This let me use the "real" direct change event (and not the too late one) which solved my problem.

Input-fields as components with updating data on parent

I'm trying to make a set of components for repetitive use. The components I'm looking to create are various form fields like text, checkbox and so on.
I have all the data in data on my parent vue object, and want that to be the one truth also after the user changes values in those fields.
I know how to use props to pass the data to the component, and emits to pass them back up again. However I want to avoid having to write a new "method" in my parent object for every component I add.
<div class="vue-parent">
<vuefield-checkbox :vmodel="someObject.active" label="Some object active" #value-changed="valueChanged"></vuefield-checkbox>
</div>
My component is something like:
Vue.component('vuefield-checkbox',{
props: ['vmodel', 'label'],
data(){
return {
value: this.vmodel
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="value" #change="$emit('value-changed', value)">
{{label}}
</label>
</div>
</div>`
});
I have this Vue object:
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
},
methods: {
valueChange: function (newVal) {
this.carouselAd.autoOrder = newVal;
}
}
});
See this jsfiddle to see example: JsFiddle
The jsfiddle is a working example using a hard-coded method to set one specific value. I'd like to eighter write everything inline where i use the component, or write a generic method to update the parents data. Is this possible?
Minde
You can use v-model on your component.
When using v-model on a component, it will bind to the property value and it will update on input event.
HTML
<div class="vue-parent">
<vuefield-checkbox v-model="someObject.active" label="Some object active"></vuefield-checkbox>
<p>Parents someObject.active: {{someObject.active}}</p>
</div>
Javascript
Vue.component('vuefield-checkbox',{
props: ['value', 'label'],
data(){
return {
innerValue: this.value
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="innerValue" #change="$emit('input', innerValue)">
{{label}}
</label>
</div>
</div>`
});
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
}
});
Example fiddle: https://jsfiddle.net/hqb6ufwr/2/
As an addition to Gudradain answer - v-model field and event can be customized:
From here: https://v2.vuejs.org/v2/guide/components.html#Customizing-Component-v-model
By default, v-model on a component uses value as the prop and input as
the event, but some input types such as checkboxes and radio buttons
may want to use the value prop for a different purpose. Using the
model option can avoid the conflict in such cases:
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
// this allows using the `value` prop for a different purpose
value: String
},
// ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>
The above will be equivalent to:
<my-checkbox
:checked="foo"
#change="val => { foo = val }"
value="some value">
</my-checkbox>

Understanding 2-way data binding and scope using Leaflet with Vue

I'm used to approaching this problem the React way, with 1-way data binding and state, so I'm having trouble thinking about it with Vue.
I have a map that renders points based on the lat/lng of news stories. When a user changes the value of a select, the points on the map update. When the user clicks on a point, a popup opens with a link to the story. However, I cannot get these two functionalities to work together.
Here's my Vue template:
<div class="row">
<div id="map" class="map"></div>
</div>
<select v-model="selectedMonth">
<option disabled value="">Please select one</option>
<option>January 2018</option>
<option>December 2017</option>
<option>November 2017</option>
<option>October 2017</option>
</select>
<button v-on:click="reset">Reset all</button>
<div class="row">
<div class="col col-xs-12 col-sm-6 col-md-3 col-lg-3"
v-for="(story, index) in displayedStories">
<img v-bind:src="story.img_src" />
<br />
<a v-bind:href="story.url" target="_blank">{{ story.card_title }}</a>
<p>{{ story.published }}</p>
</div>
</div>
and the JS:
export default {
name: 'app',
data() {
return {
leafleftMap: null,
tileLayer: null,
markers: [],
allStories: [],
selectedMonth: null,
}
},
mounted() {
this.getNodes()
this.initMap()
},
computed: {
displayedStories() {
const displayedStories = this.selectedMonth
? dateFilter(this.selectedMonth, this.allStories)
: this.allStories
if (this.leafleftMap) {
/* remove old markers layer */
this.leafleftMap.removeLayer(this.markers)
/* create a new batch of markers */
const markers = displayedStories.map(story => L.marker(story.coords)
.bindPopup(mapLink(story))
)
const storyMarkers = L.layerGroup(markers)
/* add current markers to app state and add to map */
this.markers = storyMarkers
this.leafleftMap.addLayer(storyMarkers)
this.changedMonth = this.selectedMonth
}
return displayedStories
},
},
methods: {
getNodes() { /* make api request */ }
initMap () { /* initialize map with */ }
},
}
The problem is with the line this.leafleftMap.removeLayer(this.markers). When it's there, the markers render and change with the select button, but the popup doesn't work. When I remove that line, the popup works, but the map loses its ability to update when the select changes.
I tried adding a custom directive to the select:
<select v-model="selectedMonth" v-updateMonth>
in hopes to focus when the JavaScript is enacted:
directives: {
updateMonth: {
update: function(el, binding, vnode) {
console.log('select updated')
vnode.context.leafleftMap.removeLayer(vnode.context.markers)
}
}
},
but the directive is called whenever anything on the page changed, not just when I update the select.
I'm trying to call a function (to remove the markers) only when the select is changed, but can't seem to get that to work in Vue. It wants to call every function with every update.
I was able to solve this issue with an #change and I think I have a better understanding of when to use computed in Vue.
First, I moved a lot of the update logic out of computed:
computed: {
displayedStories() {
return this.selectedMonth
? dateFilter(this.selectedMonth, this.allStories)
: this.allStories
},
},
so that it's just returning an array. Then I added a listener to the select:
<select v-model="selectedMonth" #change="updateStories()">
and then created a new method for handling that change:
methods {
updateStories() {
const markers = displayedStories.map(story => L.marker(story.coords)
.bindPopup(mapLink(story)))
const storyMarkers = L.layerGroup(markers)
this.markers = storyMarkers
this.leafleftMap.addLayer(storyMarkers)
this.changedMonth = this.selectedMonth
},
},

Categories

Resources