Reactive binding with VueJS - javascript

I'm trying to bind a class like so:
:class="{active: favs.medium_title.fontWeight === 'bold'}"
Except that fontWeight doesn't exist yet when the component is mounted.
Here's my object:
favs: {
...
medium_title: {},
...
}
So when I add the fontWeight property and it's value it doesn't set the active class.

You could use vm.$set (as you can read here) to add the new property:
this.$set( this.favs.medium_title , 'fontWeight' , 'bold' )
In this way it will be reactive and changes will be detected.

You need to add fontWeight to your data object and give it a default value so fontWeight exists on mounted.

Because the property fontWeight ain't declared it is not reactive.Hence you will need to declare it in the declaration of object.
In this the class is binding properly on change of value in the property on button click.
<div id="app">
<p :class="{active: favs.medium_title.fontWeight === 'bold'}">{{ message }}</p>
<button #click="favs.medium_title.fontWeight = 'bold'">
Click here
</button>
</div>
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
favs: {
medium_title: {
fontWeight:''
},
}
}
})
Refer the below fiddle for the solution.
https://jsfiddle.net/xgh63uLo/
If u don't want to declare the property in advance you may use Vue.$set to set the new property with making it reactive
Check below link
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

Related

Data is not updated when using as object, but changes normally when it's a variable

I'm writing a function to update a custom checkbox when clicked (and I don't want to use native checkbox for some reasons).
The code for checkbox is
<div class="tick-box" :class="{ tick: isTicked }" #click="() => isTicked = !isTicked"></div>
which works find.
However, there are so many checkboxes, so I use object to keep track for each item. It looks like this
<!-- (inside v-for) -->
<div class="tick-box" :class="{ tick: isTicked['lyr'+layer.lyr_id] }" #click="() => {
isTicked['lyr'+layer.lyr_id] = !isTicked['lyr'+layer.lyr_id]
}"></div>
Now nothing happens, no error at all.
When I want to see isTicked value with {{ isTicked }}, it's just shows {}.
This is what I define in the <script></script> part.
export default {
data() {
return {
isTicked: {},
...
};
},
...
}
Could you help me where I get it wrong?
Thanks!
Edit:
I know that declaring as isTicked: {}, the first few clicks won't do anything because its proerty is undefined. However, it should be defined by the first/second click not something like this.
Objects does not reflect the changes when updated like this.
You should use $set to set object properties in order to make them reactive.
Try as below
<div class="tick-box" :class="{ tick: isTicked['lyr'+layer.lyr_id] }" #click="onChecked"></div>
Add below method:
onChecked() {
this.$set(this.isTicked,'lyr'+this.layer.lyr_id, !this.isTicked['lyr'+this.layer.lyr_id])
}
VueJS watches data by reference so to update object in state you need create new one.
onChecked(lyr_id) {
const key = 'lyr'+lyr_id;
this.isTicked = {...this.isTicked, [key]: !this.isTicked[key]};
}

How to operate DOM in Vue.js

Here is the situation, I use v-for to display lots of data in JSON as seperate buttons. What I want to do is when i click single one of those buttons, the button's background color will change.
I want to use #click to bind function to each button, and in that function, do
as this
theButtonDOM.style.backgroundColor = 'black';
So, how can I get that DOM whose div element is generated by v-for?
OR any other solution to solve this 'background color change' problem?
Any time you trigger a click event in Vue template, you can access the event adding the special variable $event as argument to the function.
<button #click="my_method($event, other_argument)"></button>
and then access the target of the event inside the method:
methods: {
my_method(event, other_paramethers) {
let button = event.target
}
}
Even just with bind the method to the #click event without any argument, you can access the event as your first argument.
<button #click="my_method"></button>
...
methods: {
my_method($event) {
let button = $event.target
}
}
Then you can play with your button.
Look at this stackoverflow question and the Vue documentation for more details.
You can pass $event
<button #click="changeColor($event)"></button>
Then in your method
this.changeColor = function(evt){ evt.target.style.backgroundColor = 'red'; }
You can use #click and v-bind:class. When you click on an item, its index will be stored in selectedItem. Then v-bind:class automatically will add the class to the item that has an index equal to selectedItem.
new Vue({
el: '#app',
data: {
selectedItem: ""
},
computed:{},
methods: {}
}
)
.selectedItemClass {
background-color:green
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<div v-for="(item,index) in ['aa','bb','cc']"
v-bind:class="{ selectedItemClass : (selectedItem === index) }"
#click="selectedItem = index"
>{{item}}</div>
</div>
I am assuming you want the buttons to act as individual switches and not a radio group, as implemented in the answer by #eag845.
You could add a 'clicked' boolean attribute to your JSON Objects.
arrayOfJSONObjects.forEach(object => object.clicked = false); // Add 'clicked' attribute and init to false
Then use that attribute as a conditional to bind a CSS class using v-bind:class or :class, and then toggle the attribute within #click or whatever event handler function you put inside #click.
<button
v-for="btn in arrayOfJSONObjects"
:class="{ 'button--activated': btn.clicked }"
#click="btn.clicked = !btn.clicked"
>{{btn.foo}}</button>
Stylez
.button {
background-color: white;
}
.button--activated {
background-color: black;
}

Vue.js: Conditional class style binding

I have some data that is accessible via:
{{ content['term_goes_here'] }}
... and this evaluated to either true or false. I'd like to add a class depending on the truthiness of the expression like so:
<i class="fa" v-bind:class="[{{content['cravings']}} ? 'fa-checkbox-marked' : 'fa-checkbox-blank-outline']"></i>
where true gives me the class fa-checkbox-marked and false would give me fa-checkbox-blank-outline. The way I wrote it above gives me an error:
- invalid expression: v-bind:class="[{{content['cravings']}} ? 'fa-checkbox-marked' : 'fa-checkbox-blank-outline']"
How should I write it to be able to conditionally determine the class?
Use the object syntax.
v-bind:class="{'fa-checkbox-marked': content['cravings'], 'fa-checkbox-blank-outline': !content['cravings']}"
When the object gets more complicated, extract it into a method.
v-bind:class="getClass()"
methods:{
getClass(){
return {
'fa-checkbox-marked': this.content['cravings'],
'fa-checkbox-blank-outline': !this.content['cravings']}
}
}
Finally, you could make this work for any content property like this.
v-bind:class="getClass('cravings')"
methods:{
getClass(property){
return {
'fa-checkbox-marked': this.content[property],
'fa-checkbox-blank-outline': !this.content[property]
}
}
}
<i class="fa" v-bind:class="cravings"></i>
and add in computed :
computed: {
cravings: function() {
return this.content['cravings'] ? 'fa-checkbox-marked' : 'fa-checkbox-blank-outline';
}
}
Why not pass an object to v-bind:class to dynamically toggle the class:
<div v-bind:class="{ disabled: order.cancelled_at }"></div>
This is what is recommended by the Vue docs.
the problem is blade, try this
<i class="fa" v-bind:class="['{{content['cravings']}}' ? 'fa-checkbox-marked' : 'fa-checkbox-blank-outline']"></i>
You could use string template like with backticks `` :
:class="`${content['cravings'] ? 'fa-checkbox-marked' : 'fa-checkbox-blank-outline'}`"
if you want to apply separate css classes for same element with conditions in Vue.js
you can use the below given method.it worked in my scenario.
html
<div class="Main" v-bind:class="{ Sub: page}" >
in here, Main and Sub are two different class names for same div element.
v-bind:class directive is used to bind the sub class in here.
page is the property we use to update the classes when it's value changed.
js
data:{
page : true;
}
here we can apply a condition if we needed.
so, if the page property becomes true element will go with Main and Sub claases css styles. but if false only Main class css styles will be applied.

Vuejs dynamically toggle class is not working in Laravel Blade Template

I am novice in VueJs and As I am trying to implement the basic toggle class functionality using v-bind property of VueJs in my Laravel project. I am not getting the value of variable className while rendering of the page. Please guide me where I am doing wrong. The code is given below:
<div id="root">
<button type="button" v-bind:class="{'className':isLoading}" v-on:click="toggleClass">Toggle Me</button>
</div>
JavaScript is:
<script>
var app = new Vue({
el: '#root',
data: {
className:"color-red",
isLoading:false
},
methods:{
toggleClass(){
this.isLoading=true;
this.className="color-blue";
}
}
})
</script>
Style is:
<style>
.color-red{
background-color:red;
}
.color-blue{
background-color:blue;
}
</style>
You're mixing your approaches slightly. The main issue is in v-bind:class="{'className':isLoading}". This directive, the way you wrote it, toggles a class with the name "className" (literally that, not the value of the variable className) to your element if isLoading is true. If it's false, it doesn't assign any class.
Looking at your code, it seems you're actually trying to set two different classes depending on what the value of isLoading is. The easiest way to do this would be to use v-bind:class="isLoading ? 'color-red' : 'color-blue". Take a look at a working example here.
An even better solution that doesn't pollute your template with logic is to move that expression to a computed property, like this.
You can not have className as well as a variable name, as vue expects it as actual CSS class, documentation suggests one more way, have class object like following:
<script>
var app = new Vue({
el: '#root',
data: {
classObj:{ "color-red" : true } ,
isLoading:false
},
methods:{
toggleClass(){
this.isLoading=true;
this.classObj = { "color-blue" : true};
}
}
})
</script>
<div id="root">
<button type="button" v-bind:class="classObj" v-on:click="toggleClass">Toggle Me</button>
</div>

DOM element to corresponding vue.js component

How can I find the vue.js component corresponding to a DOM element?
If I have
element = document.getElementById(id);
Is there a vue method equivalent to the jQuery
$(element)
Just by this (in your method in "methods"):
element = this.$el;
:)
The proper way to do with would be to use the v-el directive to give it a reference. Then you can do this.$$[reference].
Update for vue 2
In Vue 2 refs are used for both elements and components: http://vuejs.org/guide/migration.html#v-el-and-v-ref-replaced
In Vue.js 2 Inside a Vue Instance or Component:
Use this.$el to get the HTMLElement the instance/component was mounted to
From an HTMLElement:
Use .__vue__ from the HTMLElement
E.g. var vueInstance = document.getElementById('app').__vue__;
Having a VNode in a variable called vnode you can:
use vnode.elm to get the element that VNode was rendered to
use vnode.context to get the VueComponent instance that VNode's component was declared (this usually returns the parent component, but may surprise you when using slots.
use vnode.componentInstance to get the Actual VueComponent instance that VNode is about
Source, literally: vue/flow/vnode.js.
Runnable Demo:
Vue.config.productionTip = false; // disable developer version warning
console.log('-------------------')
Vue.component('my-component', {
template: `<input>`,
mounted: function() {
console.log('[my-component] is mounted at element:', this.$el);
}
});
Vue.directive('customdirective', {
bind: function (el, binding, vnode) {
console.log('[DIRECTIVE] My Element is:', vnode.elm);
console.log('[DIRECTIVE] My componentInstance is:', vnode.componentInstance);
console.log('[DIRECTIVE] My context is:', vnode.context);
// some properties, such as $el, may take an extra tick to be set, thus you need to...
Vue.nextTick(() => console.log('[DIRECTIVE][AFTER TICK] My context is:', vnode.context.$el))
}
})
new Vue({
el: '#app',
mounted: function() {
console.log('[ROOT] This Vue instance is mounted at element:', this.$el);
console.log('[ROOT] From the element to the Vue instance:', document.getElementById('app').__vue__);
console.log('[ROOT] Vue component instance of my-component:', document.querySelector('input').__vue__);
}
})
<script src="https://unpkg.com/vue#2.5.15/dist/vue.min.js"></script>
<h1>Open the browser's console</h1>
<div id="app">
<my-component v-customdirective=""></my-component>
</div>
If you're starting with a DOM element, check for a __vue__ property on that element. Any Vue View Models (components, VMs created by v-repeat usage) will have this property.
You can use the "Inspect Element" feature in your browsers developer console (at least in Firefox and Chrome) to view the DOM properties.
Hope that helps!
this.$el - points to the root element of the component
this.$refs.<ref name> + <div ref="<ref name>" ... - points to nested element
💡 use $el/$refs only after mounted() step of vue lifecycle
<template>
<div>
root element
<div ref="childElement">child element</div>
</div>
</template>
<script>
export default {
mounted() {
let rootElement = this.$el;
let childElement = this.$refs.childElement;
console.log(rootElement);
console.log(childElement);
}
}
</script>
<style scoped>
</style>
So I figured $0.__vue__ doesn't work very well with HOCs (high order components).
// ListItem.vue
<template>
<vm-product-item/>
<template>
From the template above, if you have ListItem component, that has ProductItem as it's root, and you try $0.__vue__ in console the result unexpectedly would be the ListItem instance.
Here I got a solution to select the lowest level component (ProductItem in this case).
Plugin
// DomNodeToComponent.js
export default {
install: (Vue, options) => {
Vue.mixin({
mounted () {
this.$el.__vueComponent__ = this
},
})
},
}
Install
import DomNodeToComponent from'./plugins/DomNodeToComponent/DomNodeToComponent'
Vue.use(DomNodeToComponent)
Use
In browser console click on dom element.
Type $0.__vueComponent__.
Do whatever you want with component. Access data. Do changes. Run exposed methods from e2e.
Bonus feature
If you want more, you can just use $0.__vue__.$parent. Meaning if 3 components share the same dom node, you'll have to write $0.__vue__.$parent.$parent to get the main component. This approach is less laconic, but gives better control.
Since v-ref is no longer a directive, but a special attribute, it can also be dynamically defined. This is especially useful in combination with v-for.
For example:
<ul>
<li v-for="(item, key) in items" v-on:click="play(item,$event)">
<a v-bind:ref="'key' + item.id" v-bind:href="item.url">
<!-- content -->
</a>
</li>
</ul>
and in Vue component you can use
var recordingModel = new Vue({
el:'#rec-container',
data:{
items:[]
},
methods:{
play:function(item,e){
// it contains the bound reference
console.log(this.$refs['key'+item.id]);
}
}
});
I found this snippet here. The idea is to go up the DOM node hierarchy until a __vue__ property is found.
function getVueFromElement(el) {
while (el) {
if (el.__vue__) {
return el.__vue__
} else {
el = el.parentNode
}
}
}
In Chrome:
Solution for Vue 3
I needed to create a navbar and collapse the menu item when clicked outside. I created a click listener on windows in mounted life cycle hook as follows
mounted() {
window.addEventListener('click', (e)=>{
if(e.target !== this.$el)
this.showChild = false;
})
}
You can also check if the element is child of this.$el. However, in my case the children were all links and this didn't matter much.
If you want listen an event (i.e OnClick) on an input with "demo" id, you can use:
new Vue({
el: '#demo',
data: {
n: 0
},
methods: {
onClick: function (e) {
console.log(e.target.tagName) // "A"
console.log(e.targetVM === this) // true
}
}
})
Exactly what Kamil said,
element = this.$el
But make sure you don't have fragment instances.
Since in Vue 2.0, no solution seems available, a clean solution that I found is to create a vue-id attribute, and also set it on the template. Then on created and beforeDestroy lifecycle these instances are updated on the global object.
Basically:
created: function() {
this._id = generateUid();
globalRepo[this._id] = this;
},
beforeDestroy: function() {
delete globalRepo[this._id]
},
data: function() {
return {
vueId: this._id
}
}

Categories