(vue router) disabling browser back and forward arrows, with warning - vue.js

I'm creating a multipage quizz, so I need the user not to be able to go back to the previous page. So I wrote:
const router = createRouter({
history: createMemoryHistory(),
routes
});
It's working (navigation is disabled), but it the user hits back a few times, it ends up leaving the page without warning.
Is there a way to add a warning for the user?
Thanks in advance
Regards

You could use a global navigation guard to check if the user is navigating to a recognised route or not, and prompt for confirmation before navigating away.
Something like:
router.beforeEach((to, from) => {
const route = this.$router.resolve(to)
if (route.resolved.matched.length) {
// the route exists, return true to proceed with the navigation
return true
}else{
//the route does not exists, prompt for confirmation
return confirm("Are you sure you want to leave this site?")
}
})

In turns out the solution is outside Vue/VueRouter:
window.addEventListener('beforeunload', function (e) {
e.preventDefault();
e.returnValue = '';
});
Now the Vue-specific navigation is not recorded by the browser, and clicking the Back arrow displays the browser's built-in message.

Related

Vue router beforeEach async/await page scroll issue

Pages are showing strange scroll behavior when clicking back/front. When triggering those events, the current page scrolls to the position of the back or front page. In other words, the current page scrolls to the targeted position first (savedPosition) and then the page changes.
I am using the scrollBehavior method in vue-router, but the issue seems to be irrelevant to that (I have deleted the scrollBehavior, yet same problem happens).
While tracking down the problem, I found where the problem might be caused by, the router navigation.
In vue-router beforeEach, I'm fetching data using async/await as below.
router.beforeEach((to, from, next) => routerHandler(to, from, next))
routerHandler code is below.
export const routerHandler = async (to, from, next) => {
if (to.path !== '/error') {
console.log('window scrollY before await : ', window.scrollY)
console.log('window location before await : ', window.location.hash)
GetData().then(response => {
console.log('window scrollY after await : ', window.scrollY)
console.log('window location after await : ', window.location.hash)
// setting data in vue store
// auth checking process
if (condition) {
next()
} else {
next(route)
}
}).catch(e => {
console.log('Error:', e)
})
return
}
next()
}
What I found out so far is that it seems while waiting for the data to be fetched, the current page scroll moves. If I remove the "await" and "return", then the issue doesn't occur. However, it is just because next() which is at the bottom is being called and the inner logic is processed after it.
The console.log result is something like the following
window scrollY before await : 0
window location before await : #/location/one
window scrollY after await : 813
window location after await : #/location/one
This log shows that before the next() is called and page transition happens, scroll changes within the same page...
So, my question is does using async/await inside vue-router beforeeach cause scroll issues in vue.js? And since I need to fetch data to create auth checking logic before each routing, what can I do to prevent the weird scroll behavior when clicking back/front. (not the button that I created, but the one the browser has... called popstate, I guess?)
I would be very grateful if anyone could help me out with this!

Why does the browser display cached Vue.js view on route/url change?

I have a homepage with <router-link> tags to views. It is a simple master/detail relationship where the Homepage is a catalogue of products and the Product detail page/view shows information on each item.
When I first launch the website and click on an item on the Homepage view (e.g. URL: http://localhost:8080/100-sql-server-2019-licence), the Product view gets loaded and the product detail loads fine.
If I then press the back button in the browser to return to the Homepage and then click on a different Product (e.g. URL: http://localhost:8080/101-oracle-12c-licence), the URL in the browser address bar changes but I get the previous product's information. Its lightning quick and no new network calls are done which means its just showing a cached page of the previous product. If I then hit the refresh button while on that page, the network call is made and the correct product information is displayed.
I did a search online but couldn't find this problem described on the search results. Could anyone point me in the right direction of how to cause a refresh/re-render of a route when the route changes?
What is happening
vue-router will cache your components by default.
So when you navigate to the second product (that probably renders the same component as the first product), the component will not be instantiated again for performance reasons.
From the vue-router documentation:
For example, for a route with dynamic params /foo/:id, when we
navigate between /foo/1 and /foo/2, the same Foo component instance
will be reused.
The easy (but dirty) fix
The easy -but hacky and not recommended - way to solve this is to give your <router-view /> a key property, e.g.:
<router-view :key="$route.fullPath" />
This will force vue-router to re-instantiate the view component every time the url changes.
However you will loose all performance benefits you would normally get from the caching.
Clean fix: properly handling route changes
The clean way to solve this problem is to react to the route-change in your component (mostly this boils down to moving ajax calls from mounted into a $route watcher), e.g.:
<script>
export default {
data() {
return {
productDetails: null,
loading: false
};
},
watch: {
'$route': {
// with immediate handler gets called on first mount aswell
immediate: true,
// handler will be called every time the route changes.
// reset your local component state and fetch the new data you need here.
async handler(route) {
this.loading = true;
this.productDetails = null;
try {
// example for fetching your product data
const res = await fetch("http://give.me.product.data/" + encodeURIComponent(route.params.id));
this.productDetails = await res.json();
} finally {
this.loading = false;
}
}
}
}
};
</script>
Alternative: Navigation Guards
Alternatively you could also use vue-routers In-Component Navigation Guards to react to route changes:
<script>
export default {
async beforeRouteUpdate (to, from, next) {
// TODO: The route has changed.
// The old route is in `from`, the new route in `to`.
this.productData = await getProductDataFromSomewhere();
// route will not change before you haven't called `next()`
next();
}
};
</script>
The downside of the navigation guards is that you can only use them directly in the component that the route renders.
So you can't use navigation guards in components deeper within the hierarchy.
The upside is that the browser will not view your site before you call next(), which gives you time to load the data necessary before your route is displayed.
Some helpful ressources
Vue Router Navigation Guards Documentation
vue-router github issue
Similar Question about vue-router component reuse on stackoverflow

Nuxt - How to find the previous route?

I am able to use this.$router.go(-1) to redirect a user to the previous route. However, I am not able to understand how I get get the information about the previous route before redirecting.
Basically, by first reading what the previous route was, I want to make sure that the previous route was from the same domain and only then redirect to it, otherwise do something else.
in nuxt static methods where you have access to nuxt context ex: asyncData or middlewares :
asyncData({from}){
// do something with from
}
but if you want to get the prev route in vue instance you can use
this.$nuxt.context.from
also, remember that from can be undefined if there wasn't any prev page in the browser history
In your page you can add asyncData hook which have access to context object with from property:
asyncData({ from }) {
console.log(from);
}
You can achieve this by implementing the Vue Navigation guards, on the component/page.
<script>
export default {
beforeRouteEnter(to, from, next) {
console.log(from)
next()
},
data() {
return {
...
prevRoute: null,
}
}
}
</script>
Or there is this guy https://gist.github.com/zolotyx/b4e7fda234b3572fb7adaf11391f8eef#file-auth-js, he has made a script to help in redirecting
There is no out of the box way to get this information.
What you can do is attach a beforeRouteEnter as a global guard and save the route before navigating to a new route.
Then you can check to see if there is a previous route saved and execute this.$router.go(-1)
If you are using the router in history mode you could be tempted to use the History api that vue-router is using to get this information. But HistoryApi doesn't allow for this as this would be a huge privacy problem. You could see the entire history of the user in the current tab.
In the [middleware] directory you can put this script [routing.js]:
/* eslint-disable no-undef */
/* eslint-disable no-console */
export default function (context) {
// current route
console.log('route=', context.route.name)
// previous route
if (process.client) {
const from = context.from
console.log('from=', from)
}
}
In [nuxt.config.js]:
router: {
...
middleware: 'routing'
},
Whenever you change the current page in the client you should see a log showing the previous page. Maybe it can help you.

Vue Router: does this.$router.push navigate to a new URL?

I read the documentation of vue-router (https://router.vuejs.org/guide/essentials/navigation.html)
This is the method called internally when you click a ,
so clicking is the equivalent of calling
router.push(...)
As far as I know clicking router-link element navigates to the URL placed in "to" attribute. However, according to History API
(https://developer.mozilla.org/en-US/docs/Web/API/History_API#Examples), history.pushState(...) only changes the history and does not navigate to a new URL.
So... how can we explain this contradiction?
I think you need to define exactly what you mean by "navigate to a new URL"; to me it can mean either reloading the page at a new URL, or simply changing the URL in the address bar without reloading the page.
history.pushState() does change the URL, but it doesn't cause the browser to perform a full page reload as is typical when you click a link. This is how "single page apps" work – they intercept <a> clicks and use history.pushState() to prevent the page from reloading.
history.pushState(...) only changes the history and does not navigate to a new URL.
Here I think "and does not navigate to a new URL" is wrong – it does, except the page doesn't reload.
There is no contradiction here. There is no reason why the Vue Router could not do a change to the url with the history api and change the component as rendered in various router-view components.
When you include a router-link in your code, this is a component like any other. Vue will render this component. The interesting part is this:
const router = this.$router
// And later
const handler = e => {
if (guardEvent(e)) {
if (this.replace) {
router.replace(location)
} else {
router.push(location)
}
}
}
const on = { click: guardEvent }
if (Array.isArray(this.event)) {
this.event.forEach(e => { on[e] = handler })
} else {
on[this.event] = handler
}
For the history api, you can see in the source that for a this.$router.push(..) we transition, and we push the state with this pushState function. The transition itself can be found in history/base.js.

vue-router - how to abort route change in beforeRouteEnter

I'm seeing some behaviour I don't understand in the beforeRouteEnter navigation guard with vue.js/vue-router. I understand from the docs that this guard "does NOT have access to this component instance", but that if you need to get access to the component instance you can do so by means of a callback. I've done this because I want to abort the route change if one of the props hasn't been defined (normally because of a user clicking a forward button). So this is what I have:
beforeRouteEnter(to, from, next) {
console.log("ProductDetail: routing from = "+from.path+" to "+to.path);
next(vm => {
if (!vm.product) {
console.log("Product empty: routing back one page");
vm.$router.go(-1);
}
});
},
The idea is that I test for the existence of the prop and if it's not valid, go back (or otherwise abort the route change). From the console log, I can see that what is happening, though, is that the component instance is in fact getting created, presumably as a result of the callback being called, and throwing a bunch of errors, before the vm.$router.go(-1) kicks in and takes the user back to the previous screen.
So what, if anything, can I do to actually prevent the route change from completing if one of the requisite conditions isn't present, if it's too late by the time I can test for it?
You can try this code
beforeRouteEnter(to, from, next) {
// Your code
next(vm => {
if (!vm.product) {
console.log("Product empty: routing back one page");
next(from)
}
});
}
You can read more about this guard in https://router.vuejs.org/en/advanced/navigation-guards.html
Have you tried: next(false)?
next(false): abort the current navigation. If the browser URL was changed (either manually by the user or via back button), it will be reset to that of the from route.
Reference