Run method before route - vue.js

I have a login modal that I activate by setting .is-active to it. For this, I have a method like this:
methods: {
toggleModal: function (event) {
this.isActive = !this.isActive
}
}
that I run onclick. Depending on the boolean value of isActive, my modal gets the class .is-active.
Thing is, in my modal I have a button that takes the user to a new view which means it's rendering a new component, with this code:
<router-link class="control" #click="toggleModal()" to="/register">
As you can see, it's routing to /register. Before doing this, I need to run toggleModal() so that the modal gets closed. Right now it's routing without running the method which means that the new view has my modal overlay which is... not optimal.
Is there any good way to do this in Vue? Could I maybe create a method, that first calls toggleModal(), and then routes from the method?
Thanks.

I would define a method that calls toggleModal first, then navigates. Like so:
methods: {
navigateAway () {
this.isActive = !this.isActive
this.$router.push('/register')
}
}
You don't need the event argument unless you intend on capturing more data from the event or event target. You could also wrap the router push in a setTimeout if you so desire, for perhaps cleaner looking view changes.
methods: {
navigateAway () {
let vm = this
vm.isActive = !vm.isActive
setTimeout(function () {
vm.$router.push('/register')
}, 50)
}
}
Of course, there are hooks that you can use from vue-router that make this easy. Example (assuming you're using single file components and Vue.js 2.x):
export default {
data () {
return {
isActive: false
}
},
beforeRouteLeave (to, from, next) {
this.isActive = false // I assume that you would not want to leave it open upon navigating away
next()
}
}
Link to vue router hooks: https://router.vuejs.org/en/advanced/navigation-guards.html

Related

Echarts OnClick Methods not reaching external methods in Vue

So I have implemented Echarts with a Vue application, on one of the charts, I am trying to get the item clicked and pass it back to the parent component that way I can do specific calculations to it.
The 'on click' method works and I can console.log('params') easily, however, trying to reach any other functions outside of it is not possible for some reason...
here is my code...
data() {
return {
myChart: null,
selectedState: {}
}
}.
mounted() {
this.myChart = echarts.init(document.getElementById("geoMap"))
this.myChart.on('click', function(params){
// It will run the console.log with correct info, but the
// method is not reachable...
console.log(params)
this.setSelectedState(params)
})
},
// Inside my vue script this is just a method to set the data for now...
methods: {
setSelectedState(params){
this.selectedState = params
},
}
any help would be nice!! thanks!
You're not in the Vue component context when listening to the chart event, so you have to change your callback function to an arrow one to access the component's this :
this.myChart.on('click', params => {
this.setSelectedState(params)
});
methods: {
setSelectedState(params) {
console.log(params);
this.selectedState = params
}
}
By the way, you should use ref instead of getting your div with document.getElementById to attach your chart :
<div ref="geoMap"></div>
this.myChart = echarts.init(this.$refs.geoMap);

Open modal dialog on event bus event

I've created a backend and am now trying to build a frontend, using it. I'm very new to Vue.js and am having a hard time telling it to do what I want; probably because of missing some basic concepts. Hopefully someone can point me in the right direction.
The App.vue groups following components: Header, main section (routed), footer and a modal login dialog.
The issue I'm trying to solve is to display the modal login dialog when clicking the Login button (which lives in the header component); currently, nothing besides the messages being logged happens.
For this I've created an event bus and am firing an event:
export default {
name: 'SppdTeamTunerHeader',
methods: {
emitShowLoginDialogEvent () {
EventBus.$emit('ShowLoginDialog', true)
}
}
}
Emitting the event works as I can see in the Vue DevTools for Chrome.
Here's the complete code of App.vue:
<template>
<div id="app">
<SppdTeamTunerHeader/>
<router-view></router-view>
<SppdTeamTunerFooter/>
<LoginDialogModal
v-show="isLoginDialogVisible"
/>
</div>
</template>
<script>
import SppdTeamTunerHeader from '#/components/TheHeader'
import SppdTeamTunerFooter from '#/components/TheFooter'
import LoginDialogModal from '#/components/LoginDialogModal'
import { EventBus } from '#/common/EventBus'
export default {
name: 'App',
components: {
SppdTeamTunerHeader,
SppdTeamTunerFooter,
LoginDialogModal
},
data: function () {
return {
isLoginDialogVisible: false
}
},
mounted () {
EventBus.$on('ShowLoginDialog', function (isVisible) {
console.log('Setting ShowLoginDialog isVisible=' + isVisible + '. isLoginDialogVisible=' + this.isLoginDialogVisible)
if (isVisible) {
this.isLoginDialogVisible = true
} else {
this.isLoginDialogVisible = false
}
console.log('Finished setting isLoginDialogVisible=' + this.isLoginDialogVisible)
})
},
destroyed () {
EventBus.$off('ShowLoginDialog')
}
}
</script>
When checking the console, following is being printed when clicking the login button:
Setting ShowLoginDialog isVisible=true. isLoginDialogVisible=undefined
Finished setting isLoginDialogVisible=true
The value logged for isLoginDialogVisible can't come from the variable defined in the data function as it prints undefined, whereas it has been defined as false (I guess that's my main problem).
I've read quite a few articles about the subject, e.g:
https://codingexplained.com/coding/front-end/vue-js/why-components-data-properties-must-be-functions
https://v2.vuejs.org/v2/guide/instance.html#Data-and-Methods
The modal dialog example I've based the implementation comes from here: https://alligator.io/vuejs/vue-modal-component/
This is happening because you are not using an Arrow function. Instead of a plain function, use arrow function like this:
mounted () {
// Note the use of arrow function.
EventBus.$on('ShowLoginDialog', (isVisible) => {
// .. All your code
})
}
If you use plain function function () {}, then this pointer is not accessible within inner function. Arrow function will lexically bind this pointer to mounted() function's this context. So use an arrow function i.e. () => {};
Note: If you insist on using plain old function syntax then use closure variable to keep track of this pointer:
mounted () {
// Assign this pointer to some closure variable
const vm = this;
EventBus.$on('ShowLoginDialog', function (isVisible) {
console.log('Setting ShowLoginDialog isVisible=' + isVisible + '. isLoginDialogVisible=' + vm.isLoginDialogVisible)
if (isVisible) {
vm.isLoginDialogVisible = true
} else {
vm.isLoginDialogVisible = false
}
console.log('Finished setting isLoginDialogVisible=' + vm.isLoginDialogVisible)
})
}
This has nothing to do with Vue.js. It is a typical JavaScript behavior.
I believe your listener for the EventBus events needs to be accessible to App. Right now EventBus and App are two separate instances. You could mount the event handler inside App like this:
mounted () {
EventBus.$on('ShowLoginDialog', function (isVisible) {
...
});

VueJS Adding to lifecycle hooks on every component

So I have a loader screen in my app, and the idea is to show the loader screen on the beforeCreate hook so the user can't see the stuff being rendered, and then on the mounted hook remove the loader screen.
This is fun and nice for when you have two or three view/components, but currently my app has a lot more than that, and adding it to each component/view doesn't make much sense for me.
So I was wondering, is there any way to add something to the beforeCreate and mounted hooks on a global scope. Something like this:
main.js
Vue.beforeCreate(() => {
//Show the loader screen
});
Vue.mounted(() => {
//Hide the loader screen
});
That way it would be applied to every component and view
You can use mixins for this purposes, and import in components.
//mixins.js
export default {
beforeCreate() {},
mounted() {}
}
And in component add mixins: [importedMixins]
You will have access to 'this'.
Actualy you can use and vuex to (mapGetters, mapActions etc.)
If you don't want include mixins in every component, try to use vue plugins system (https://v2.vuejs.org/v2/guide/plugins.html):
MyPlugin.install = function (Vue, options) {
// 1. add global method or property
Vue.myGlobalMethod = function () {
// something logic ...
}
// 2. add a global asset
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// something logic ...
}
...
})
// 3. inject some component options
Vue.mixin({
created: function () {
// something logic ...
}
...
})
// 4. add an instance method
Vue.prototype.$myMethod = function (methodOptions) {
// something logic ...
}
}
And use your plugin like this Vue.use(MyPlugin, { someOption: true })
There is something very silimar to your request in vue-router. I've never used afterEach but beforeEach works perfectly.
router.beforeEach((to, from, next) => {
/* must call `next` */
})
router.beforeResolve((to, from, next) => {
/* must call `next` */
})
router.afterEach((to, from) => {})
Here is a documentation
There is also a hook called 'beforeRouteEnter'.
Link to beforeRouteEnter docs

Can I handle back button within methods in vuejs 2?

I need some help in vuejs 2. I want to detect back button pressed event. I did some research and found this,
document.addEventListener("backbutton", yourCallBackFunction, false");
I think it is global event. I need something local, within a method. where i can use some logic.
methods: {
backButtonPressed() {
}
}
Or can i bind the global one to local function? Can anyone help me with that? TIA
Add the event on your mounted method on your root Vue component (the one the Vue instance is tied to.
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
methods: {
yourCallBackFunction () {
// Your logic
}
}
mounted () {
document.addEventListener("backbutton", this.yourCallBackFunction, false);
},
beforeDestroy () {
document.removeEventListener("backbutton", this.yourCallBackFunction);
}
})
We also remove it on beforeDestroy to keep things tidy.
Note: I've not personally used the back button event so have added it to this example only because you say it's working but need a way to globally handle it. This code will do just that.
Note: As per #Luke's comment - we add the listener in the mounted event so it doesn't execute for in the SSR context. If SSR isn't a factor / consideration then we can use the created lifecycle hook.
If still someone come across this issue.
A solution for an event listener for browser-back is https://developer.mozilla.org/de/docs/Web/API/WindowEventHandlers/onpopstate
window.onpopstate = function() {
alert('browser-back');
};
Is easy, if you need to catch that behavior only your component, you can use beforeRouteLeave function in the root of your component.
Example:
beforeRouteLeave (to, from, next) {
const answer = window.confirm('Do you really want to leave?)
if (answer) {
next()
} else {
next(false)
}
}
But if you need to add this behavior globally, you need catch with beforeEnter in the routes.
If you are using vue-router(no idea if you don't, why...) a good solution is to use in your component:
beforeRouteLeave(to, from, next) {
if (from.name === 'nameOfFromRoute' && to.name === 'nameOfToRoute' ) {
next(false);
} else {
next();
}
console.log({ to, from });
},
This was one variation I found to work as well, a little less verbose and uses router.push in the beforeDestroy lifecycle method
Listen for popstate
Push the desired name/path to redirect
The code below would be a better understanding.
beforeDestroy() {
window.addEventListener("popstate", (event) => {
this.$router.push({ path: <your path> });
});
},
This implementation was on Nuxt 2.14.6 and works just as well with all versions of Vue.
I have a similar problem and solved using #click="backFunction"
and created the function on methods like this:
methods: {
backFunction(){
//yourlogic
},

Vue $emit in beforeDestroy

Vue - I want to track user changes on a page and send those updates if they navigate away. The basic idea is
//child
beforeDestroy: function() {
var that = this;
axios.post('gate/cart.php', userUpdates)
.then(function(res) {
if (res.data.success) {
that.$emit('updateCart', res.data.cart);
//parent (App.vue)
<router-view
#updateCart="updateCart"
...
methods: {
updateCart: function(newCart) {
alert('caught');
this.cart = newCart;
The dev tools show me the emit is emitted and the correct payload (res.data.cart) is sent, but the parent method isn't called. (That alert doesn't trigger.) I know the updateCart method in the parent is working, as another component uses it fine like this with a regular method:
addToCart: function() {
var that = this;
axios.post('gate/cart.php', this.dataToSend)
.then(function(res) {
if(res.data.success === true) {
that.$emit('updateCart', res.data.cart)
that.$router.push({ path: '/product/' + that.product.id})
}
If the ajax is working, I get a correct $emit, and the target method is ok, what lifecycle hook caveat is stopping me from executing the parent method? Do you know a better way to do this? (I want to check the success of the ajax call before updating the parent data.)