v-if not updating after eventbus event - vue.js

Hi I'm having problems with a v-if that's not updating after receiving an eventbus event. This is my code :
<template>
<div class="main">
<button v-if="inCreationMode()">
Make a new snippet
</button>
<button v-if="mode ==='edit'">Push edits</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
mode: "creation",
};
},
created() {
this.emitter.on("editSnippet", snippet => function(snippet){
this.mode = "edit";
});
},
};
</script>
I tried replacing the v-if conditions by functions :
<template>
<div class="main">
<button v-if="inCreationMode()">
Make a new snippet
</button>
<button v-if="inEditMode()">Push edits</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
mode: "creation",
};
},
created() {
this.emitter.on("editSnippet", snippet => function(snippet){
this.mode = "edit";
});
},
methods:{
inCreationMode() {
return this.mode === "creation";
},
inEditMode(){
return this.mode ==="edit";
}
}
};
</script>
I tried using this.$forceUpdate() as well.
Does anyone know a solution to this problem.
Thanks in advance.

I see many problems here, that could all be part of the reason why it's not working. Given that you have defined your emitter to be new Vue(), and added that to the Vue prototype correctly and that the component that emits the event does it by this.emitter.emit('editSnippet'). You have to:
Replace
this.emitter.on("editSnippet", snippet => function(snippet){ this.mode = "edit"; });
with
this.emitter.on("editSnippet", () => { this.mode = "edit";});
Reasons:
You have to use an arrow function, otherwise this will not reference your component.
Your callback should be a function that "does something", not one that returns a function (that won't be called)
Further improvements (optional):
Use computed props instead of methods as described in the other answer
Cleanup the listener in beforeUnmount (or beforeDestroy) with this.emitter.off... . Actually not really optional, it is at least really bad code style to not do that

You should use computed properties instead of methods in this case :
<template>
<div class="main">
<button #click=" inCreationMode() " v-if="inCreationMode">
Make a new snippet
</button>
<button v-if="inEditMode">Push edits</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
mode: "creation",
};
},
created() {
this.emitter.on("editSnippet", snippet => function(snippet){
this.mode = "edit";
});
},
computed:{
inCreationMode() {
return this.mode === "creation";
},
inEditMode(){
return this.mode ==="edit";
}
}
};
</script>
Note : remove the () from the v-if value

Related

How to fire an event in mount in Vuejs

I have a sidebar that you can see below:
<template>
<section>
<div class="sidebar">
<router-link v-for="(element, index) in sidebar" :key="index" :to="{ name: routes[index] }" :class='{active : (index==currentIndex) }'>{{ element }}</router-link>
</div>
<div class="sidebar-content">
<div v-if="currentIndex === 0">
Profile
</div>
<div v-if="currentIndex === 1">
Meine Tickets
</div>
</div>
</section>
</template>
<script>
export default {
mounted() {
EventBus.$on(GENERAL_APP_CONSTANTS.Events.CheckAuthentication, () => {
this.authenticated = authHelper.validAuthentication();
});
console.log()
this.checkRouter();
},
data(){
return {
currentIndex:0,
isActive: false,
sidebar: ["Profile", "Meine Tickets"],
routes: ["profile", "my-tickets"],
authenticated: authHelper.validAuthentication(),
}
},
computed: {
getUser() {
return this.$store.state.user;
},
},
methods: {
changeSidebar(index) {
this.object = this.sidebar[index].products;
this.currentIndex=index;
},
checkRouter() {
let router = this.$router.currentRoute.name;
console.log(router);
if(router == 'profile') {
this.currentIndex = 0;
} else if(router == 'my-tickets') {
this.currentIndex = 1;
}
},
},
}
</script>
So when the link is clicked in the sidebar, the route is being changed to 'http://.../my-account/profile' or 'http://.../my-account/my-tickets'. But the problem is currentIndex doesn't change therefore, the content doesn't change and also I cannot add active class into the links. So how do you think I can change the currentIndex, according to the routes. Should I fire an event, could you help me with this also because I dont know how to do it in Vue. I tried to write a function like checkRouter() but it didn't work out. Why do you think it is happening? All solutions will be appreciated.
So if I understand correctly, you want currentIndex to be a value that's based on the current active route? You could create it as a computed property:
currentIndex: function(){
let route = this.$router.currentRoute.name;
if(router == 'profile') {
return 0;
} else if(router == 'my-tickets') {
return 1;
}
}
I think you could leverage Vue's reactivity a lot more than you are doing now, there's no need for multiple copies of the same element, you can just have the properties be reactive.
<div class="sidebar-content">
{{ sidebar[currentIndex] }}
</div>
Also, you might consider having object be a computed property, something like this:
computed: {
getUser() {
return this.$store.state.user;
},
object() {
return this.sidebar[currentIndex].products;
}
},
Just use this.$route inside of any component template. Docs .You can do it simple without your custom logic checkRouter() currentIndex. See simple example:
<div class="sidebar-content">
<div v-if="$route.name === 'profile'">
Profile
</div>
<div v-if="$route.name === 'my-tickets'">
Meine Tickets
</div>
</div>

Using v-model on a button?

When a button is clicked I wish to push it's name to an array. When the button is not clicked I want to remove it's name from an array.
I know how to do this with an #click that pushes/splices the array.
I would like to know if there's a simple way of binding the clicks of the button to the array, just like how a checkbox works with v-model. I understand you cannot use v-model on a button but if we were to make the button it's own component and use v-model on that...
<custom-button v-model="myArray"></custom-button>
Is there a way to make this work?
I would create the structure for the custom-button components like:
...,
props: {
originalArray: {
required: true
}
},
data(){
return {
modifiedArray: this.originalArray.map(x => ({...x}))
}
},
methods: {
yourMethod()
{
//do your logic on the modifiedArray
this.$emit('changed',this.modifiedArray);
}
}
then you could use it like:
<custom-button :original-array="this.myArray" #changed="newArray => this.myArray = newArray" />
I would do it like this:
const CBtn = {
template: '#c-btn',
props: ['array', 'label'],
data(){
return {
ncTimeout: -1
}
},
computed:{
arr_proxy: {
get(){
// shallow copy to not modify parent array indices
return this.array.slice()
}
}
},
methods: {
update(){
this.notClicked()
if(!this.arr_proxy.includes(this.label))
this.$emit('update:array', this.arr_proxy.concat(this.label))
},
notClicked(){
clearTimeout(this.ncTimeout)
this.ncTimeout = setTimeout(()=>{
let index = this.arr_proxy.findIndex(v => v === this.label)
if(index>=0){
this.arr_proxy.splice(index, 1)
this.$emit('update:array', this.arr_proxy)
}
}, 1000)
}
}
}
new Vue({
components: {
CBtn
},
template: '#main',
data(){
return {arr: []}
}
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="c-btn">
<button
#click="update"
v-on="$listeners"
v-bind="$attrs"
>
{{label}}
</button>
</template>
<template id="main">
<div>
<c-btn label="1" :array.sync="arr" ></c-btn>
<c-btn label="2" :array.sync="arr" ></c-btn>
<c-btn label="3" :array.sync="arr" ></c-btn>
{{arr}}
<div>
</template>
<div id="app"></div>
So yes you can use v-model with in model option defined value: [propName] and event: [eventName] or the .sync modifier with 'update:[propName]' event.

Vue Mounting Component removing div

I am trying to mount a component on a function, it works fine. However I've got it setup so that it destroys the div after X amount of seconds. Then when I try and add the compoent again its removed the base div. I'm not sure how to fix this though...
Component:
<template>
<div>
<b-alert show dismissible variant="danger">
<i class="mdi mdi-block-helper mr-2"></i>{{ text }}
</b-alert>
</div>
</template>
<script>
export default {
name: "alertDanager",
props: {
text: null
},
created() {
setTimeout(() => this.destoryEl(), 5000);
},
methods: {
destoryEl() {
this.$destroy();
this.$el.parentNode.removeChild(this.$el);
}
}
};
</script>
Spawning the component in
const DangerAlertExtended = Vue.extend(dangerAlert);
const error = new DangerAlertExtended({ propsData: { text: "Error message" } });
error.$mount("#error");
I'm not sure how to make it stop overwriting the #error div...
Why not let v-if exclude it from the DOM instead of removing the element? Alternatively, it could be hidden but remain in the DOM with v-show (that's probably what I would do, unless there's a specific reason you need for it to not be in the DOM). I think it's generally better to let Vue manage the DOM rather than manipulate yourself.
<template>
<div v-if="showAlert">
<b-alert show dismissible variant="danger">
<i class="mdi mdi-block-helper mr-2"></i>{{ text }}
</b-alert>
</div>
</template>
<script>
export default {
name: "alertDanager",
props: {
text: null,
showAlert: true
},
created() {
setTimeout(() => this.hideAlert(), 5000);
},
methods: {
hideAlert() {
this.showAlert = false
}
}
}
</script>

VueJS Trying to capture enter press on child component before parent component handles enter

I have an auto complete input that when I press enter in this input, it is to emit its value to the parent component and then the parent submit action should be handled. However it appears that the parent is first receiving the enter key submiting the form and then the child component will finally emit the value meaning the data doesnt get updated until after it is needed.
I have an example code pen I made up
codepen
Vue.component('child', {
data () {
return {
someData: ""
}
},
template: `
<div>
<input #keyup.enter.capture="enterPressed" v-model="someData" />
</div>
`,
methods: {
enterPressed(){
this.$emit('updateData',this.someData)
console.log('CHILD: enter pressed')
}
}
});
Vue.component('parent', {
data () {
return {
lastGo: null,
parentData: "init"
}
},
template: `
<form v-on:submit.prevent="go">
<child #updateData="updateData"></child>
<button #click="go">Go</button>
<p>Parent data: <b>{{parentData}}</b></p>
<p>Last go: <b>{{lastGo}}</b></p>
</form>
`,
methods: {
updateData(data){
this.parentData = data;
},
go(){
this.lastGo = this.parentData;
console.log("go: "+this.parentData)
}
}
});
new Vue({
el: '#app'
});
I'm not sure how to resolve this, I feel maybe that my pattern just isn't going to work, is there a better way?
There is a way to work around is using #input event in child component
Vue.component('child', {
data () {
return {
someData: ""
}
},
template: `
<div>
<input #input="onInput" v-model="someData" />
</div>
`,
methods: {
onInput(){
console.log('CHILD: enter pressed')
this.$emit('updateData',this.someData)
}
}
});
Demo

vue.js – get new data information

I'm building a chrome extension using vue.js. In one of my vue components I get tab informations of the current tab and wanna display this information in my template. This is my code:
<template>
<div>
<p>{{ tab.url }}</p>
</div>
</template>
<script>
export default {
data() {
return {
tab: {},
};
},
created: function() {
chrome.tabs.query({ active: true, windowId: chrome.windows.WINDOW_ID_CURRENT }, function(tabs) {
this.tab = tabs[0];
});
},
};
</script>
The Problem is, that the template gets the data before it's filled through the function. What is the best solution for this problem, when the tab data doesn't change after it is set once.
Do I have to use the watched property, although the data is only changed once?
// EDITED:
I've implemented the solution, but it still doesn't work. Here is my code:
<template>
<div>
<div v-if="tabInfo">
<p>set time limit for:</p>
<p>{{ tabInfo.url }}</p>
</div>
<div v-else> loading... </div>
</div>
</template>
<script>
export default {
data() {
return {
tabInfo: null,
};
},
mounted() {
this.getData();
},
methods: {
getData() {
chrome.tabs.query({ active: true, windowId: chrome.windows.WINDOW_ID_CURRENT }, function(tabs) {
console.log(tabs[0]);
this.tabInfo = tabs[0];
});
},
},
};
</script>
The console.log statement in my getData function writes the correct object in the console. But the template only shows the else case (loading...).
// EDIT EDIT
Found the error: I used 'this' in the callback function to reference my data but the context of this inside the callback function is an other one.
So the solution is to use
let self = this;
before the callback function and reference the data with
self.tab
You could initialize tab to null (instead of {}) and use v-if="tabs" in your template, similar to this:
// template
<template>
<div v-if="tab">
{{ tab.label }}
<p>{{ tab.body }}</p>
</div>
</template>
// script
data() {
return {
tab: null,
}
}
new Vue({
el: '#app',
data() {
return {
tab: null,
}
},
mounted() {
this.getData();
},
methods: {
getData() {
fetch('https://reqres.in/api/users/2?delay=1')
.then(resp => resp.json())
.then(user => this.tab = user.data)
.catch(err => console.error(err));
}
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<div v-if="tab">
<img :src="tab.avatar" width="200">
<p>{{tab.first_name}} {{tab.last_name}}</p>
</div>
<div v-else>Loading...</div>
</div>