vue router beforeRouteLeave fired twice - vuejs2

I am trying to create a global "unsaved changes confirm" functionality.
So whenever some form is edited, I set a global vuex state saying there's unsaved changed, and then I want the user to confirm before the route changes.
I have created a global mixin - and it works fine, except when theres nested router views - then the beforeRouteLeave is fired twice, and then the next() function doesn't work?
This is how it looks:
Vue.mixin({
computed: {
unsavedChanges() {
return this.$store.getters["helpers/unsavedChanges"]
}
},
beforeRouteLeave (to, from, next) {
if (this.unsavedChanges) {
this.$Modal.confirm({
title: 'Unsaved changes',
content: '<p>Are you sure you wish to leave the page?</p>',
okText: 'Continue',
cancelText: 'Cancel',
onOk: () => {
next()
},
onCancel: () => {
next(false)
}
});
}
}
})
It works fine whenever there's no sub router views, but if there are, then the router doesnt change, even if you click Continue and the next() function is called.
How do I solve this? Thanks.

Related

onBeforeEnter does not exist in vue-router#next

I am trying to switch over to vuejs3 and the new vue-router.
Now I see that beforeRouteEnter is not exposed:
// same as beforeRouteUpdate option with no access to `this`
onBeforeRouteUpdate((to, from, next) => {
// your logic
console.log("Hello world") //this is only triggered if the id changes
next()
})
So my question is: How can I trigger the initial axios-requests on a specific route like /dashboard as I did before?
It's not possible to execute code in setup before the route enters because at the time of setup the navigation has already been confirmed.
Another option #1
You can still use the options api, so you could still use beforeRouteEnter:
setup() {
...
},
beforeRouteEnter(to, from, next) {
console.log(to);
}
Another option #2
Use beforeEnter in the router:
{
path: '/foo',
component: Foo,
beforeEnter(to) {
console.log(to)
}
}

Vue. How to route on current page

I have page '/users'.
export default {
name: 'Users',
created () {
const queryParams = this.$route.query
this[GET_USERS_FROM_SERVER](queryParams)
},
methods: {
...mapActions([GET_USERS_FROM_SERVER]),
onBtnFilterClick () {
this.$route.push('/users?minAge=20&name=alex&withPhoto=true')
}
}
}
When page started, it checks params and gets users from server. But it doesnt work and i think it is because router think that '/users' and '/users?params' is the same path.
If I add this.$router.go() after this.$router.go() it will reload current page and it works. But I want to do it in another way. How can I do this?
Don't reload the page if you do not have to.
this.$route.query can be just as reactive as your other data, so use this fact.
export default {
name: 'Users',
watch: {
'$route.query': {
immediate: true,
deep: true,
handler (queryParams) {
this[GET_USERS_FROM_SERVER](queryParams)
}
}
},
methods: {
...mapActions([GET_USERS_FROM_SERVER]),
onBtnFilterClick () {
this.$route.push('/users?minAge=20&name=alex&withPhoto=true')
}
}
}
When you watch for changes on $route.query, you call this[GET_USERS_FROM_SERVER] whenever it changes. I suspect that this changes the data in your component. I've set the immediate flag to run it when the component is created. I've set the deep flag, because this is an object, and I am not entirely sure if the query object gets replaced with every route change, or just modified. The deep flag will make sure that it will always trigger the handler.

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
},

In vue-router, how to use beforeLeave

I want to finish some information verify before one component leave.
I've scanned the vue-router document: https://router.vuejs.org
But I use vue-cli, in my file: router1.vue,
console.log(this.$router.beforeLeave) -> undefined
How can I use it?
Add this to your router1.vue:
export default {
//...
beforeRouteLeave (to, from, next) {
// called when the route that renders this component is about to
// be navigated away from.
// has access to `this` component instance.
},
//...
}
for example:
beforeRouteLeave (to, from , next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
And it will be called before this route leave.
ref: https://router.vuejs.org/en/advanced/navigation-guards.html

Run method before route

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