ScrollBehavior doesn't work properly in nuxt js - vue.js

I have created a file app/router.scrollBehavior.js for scroll position, but when I press browser back button, savedPosition doesn't call because fetch api has not completed. Can any one help me for this. How to know when api's completion done after my savePosition trigger otherwise every time scroll got on bottom position. is ther any way to get store, commit fetch hooks in same functions.
export default function (to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { x: 0, y: 0 } } }

You can use async scrolling, that resolves to the desired position descriptor.
It's possible to hook this up with events from a page-level transition component to make the scroll behavior play nicely with your page transitions.
scrollBehavior (to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ x: 0, y: 0 })
}, 500)
})
}

Related

Prevent route change on refresh with Vue navigation guards

I have navigation guards working perfectly based on a userTp value, however whenever the user reloads a page it redirects them to name: 'login'.
I would like to prevent a route change on refresh. Is there a simple way to do this within navigation guards?
router.beforeEach(async (to, from, next) => {
if (to.meta.requireAuth) {
if (!store.state.auth.user) {
await store.dispatch(AUTH_REFRESH)
}
if (to.meta.userTp === store.state.auth.user.userTp) {
next()
window.scrollTo(0, 0)
} else {
next({ name: 'login' })
window.scrollTo(0, 0)
}
} else {
next()
window.scrollTo(0, 0)
}
})
EDIT:
I found a semi-workaround for this, but it's not perfect. It allows users to access pages if they know the correct page url path, but since my setup has a server-side userTp check before sending data it just shows the page template but without any data in it.
My temp solution:
(Added as the first if statement)
router.beforeEach(async (to, from, next) => {
if (to = from) {
if (!store.state.auth.user) {
await store.dispatch(AUTH_REFRESH)
to()
}
}

Vue Nuxt scroll-to different page

I am trying to implement a menu with menu items scrolling to sections ids.
It works fine if I scroll on a section in the page that is on the page where I am currently.
To achieve it I installed vue-scrollto.
The problem is that the same menu is on other pages too, meaning that in that case it should load the page where the section is and scroll to it.
To give you an idea, it's a website previously done in jQuery the I am redoing in Nuxt. In jQuery I wass solving it with:
$('html,body').animate({
scrollTop: $(window.location.hash).offset().top -60
}, 1000);
So far the closest I got is:
<li class="nav-item"><nuxt-link to="/" v-scroll-to="{ element: '#intro', duration: 1000, offset: -100 }" >Welcome</nuxt-link></li>
But if for instance, I am in the about page, first it loads the homepage and if I click again it scrolls to the section. How can I make work without having to click the menu link twice?
I had a similar use case in Nuxt. I achieved using the following (although I didn't use the plugin you referred to):
~/app/router.scrollBehavior.js
export default async function (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
}
const findEl = (hash, x) => {
return document.querySelector(hash) ||
new Promise((resolve, reject) => {
if (x > 50) {
return resolve()
}
setTimeout(() => { resolve(findEl(hash, ++x || 1)) }, 100)
})
}
if (to.hash) {
const el = await findEl(to.hash)
if ('scrollBehavior' in document.documentElement.style) {
return window.scrollTo({ top: el.offsetTop, behavior: 'smooth' })
} else {
return window.scrollTo(0, el.offsetTop)
}
}
return { x: 0, y: 0 }
}
You can find more information about configuring Nuxt router and its scroll behaviour here.

How to handle anchors (bookmarks) with Vue Router?

I'm looking for a smart way to handle in-page anchors with Vue Router. Consider the following:
<router-link to="#app">Apply Now</router-link>
<!-- some HTML markup in between... -->
<div id="app">...</div>
The "scroll to anchor" behavior described in the docs works fine except:
When you click on the anchor, it brings you down to the div id="app". Now scroll away from the div back to the anchor and try clicking it again -- this time you will not jump down to the div. In fact, the anchor will retain the class router-link-active and the URL will still contain the hash /#app;
With the steps above, if you refresh the page (the URL will still contain the hash) and click on the anchor, nothing will happen either.
This is very unfortunate from the UX perspective because a potential customer has to manually scroll all the way down again to reach the application section.
I was wondering if Vue Router covers this situation. For reference, here's my router:
export default new VueRouter({
routes,
mode: 'history',
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return { selector: to.hash }
} else if (savedPosition) {
return savedPosition;
} else {
return { x: 0, y: 0 }
}
}
})
I haven't found anything in the resources to solve your issue but you could utitlize the $route.hash in your mounted hook of the component that holds your <router-view></router-view> to solve the refresh issue.
<script>
export default {
name: 'app',
mounted: function()
{
// From testing, without a brief timeout, it won't work.
setTimeout(() => this.scrollFix(this.$route.hash), 1);
},
methods: {
scrollFix: function(hashbang)
{
location.hash = hashbang;
}
}
}
</script>
Then to solve the issue of second clicks you could use the native modifier and bind to your <router-link></router-link>. It's a fairly manual process but will work.
<router-link to="#scroll" #click.native="scrollFix('#scroll')">Scroll</router-link>
There may also be something you could do with the router's afterEach method but haven't figured that out yet.
If you're already on the route with the hash, you can just set it to scroll to the target.
(also note scrollBehavior() method in your router won't get called if you're already on the route you're trying to go to).
export default {
methods: {
anchorHashCheck() {
if (window.location.hash === this.$route.hash) {
const el = document.getElementById(this.$route.hash.slice(1))
if (el) {
window.scrollTo(0, el.offsetTop)
}
}
},
},
mounted() {
this.anchorHashCheck()
},
}
Then add a #click.native to listen to events on the anchor in your <router-link>,
<router-link :to="{hash: '#some-link'}" #click.native="anchorHashCheck">
Some link
</router-link>
Possible solution which is more resusable IMO:
this.$router.push({ name: 'home' }, undefined, () => { location.href = this.$route.hash })
As the 3rd argument is the abort() function, it may have unwanted side effects though..
If you want to use it globally, add a function to your Router:
pushWithAnchor: function (routeName, toHash) {
const fromHash = Router.history.current.hash
fromHash !== toHash || !fromHash
? Router.push({ name: routeName, hash: toHash })
: Router.push({ name: routeName, hash: fromHash }, undefined, () => { window.location.href = toHash })
}
And use it in components with:
this.$router.options.pushWithAnchor('home', '#fee-calculator-section')
Within a template you could do something like:
<a #click="this.$router.options.pushWithAnchor('home', '#fee-calculator-section')"></a>
Sadly you cant use a scroll offset though
I used this solution:
<router-link to="/about" #click.native="scrollFix('#team')" >The team</router-link>
And this:
methods: {
scrollFix: function(hash) {
setTimeout(() => $('html, body').animate({
scrollTop: $(hash).offset().top
}, 1000), 1)
}
}
I realize you asked about anchors and you have an answer already. However, the below function should work for both anchors and regular links. It allows you to scroll to the position of the first instance of the Component a Route has been matched to. I wrote it to see if I could by-pass using a hash while retaining anchor-style scrolling.
scrollBehavior(to, from, savedPosition) {
if (to.matched) {
const children = to.matched[1].parent.instances.default.$children;
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (child.$options._componentTag === to.matched[1].components.default.name) {
return {
x: child.$el.offsetLeft,
y: child.$el.offsetTop
};
}
}
}
return {
x: 0,
y: 0
};
}
The reason I'm using parent.instances is because the to.matched[1].instances value is empty. It's not the most elegant solution, though it might help someone else out there.
Note: This only works when you want to scroll the first instance of a Component.

How to simulate the "scroll to anchor" on page load in Vue Router?

I have a small Vue.js SPA with the following router configuration, taken from the docs:
export default new VueRouter({
routes, // defined above...
mode: 'history',
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return { selector: to.hash }
} else if (savedPosition) {
return savedPosition;
} else {
return { x: 0, y: 0 }
}
}
})
Consider a link on the homepage:
<router-link to="#services">Services</router-link>
It jumps to the anchor element <div id="services">...</div> as expected. However, when you activate the link, then scroll away from #services, and refresh the page, you will not be brought back to #services. You will stay in the same position where you left off, even though the URL would still have the hash in it (e.g. in the form of app.dev/#services).
How can I configure the router so that on page load, it bring the user to the anchor element, given that the URL contains its hash (and, well, that hash corresponds to a valid existing element)?
I had the same problem but also wanted to have an animated scroll to the hash. I was able to check off both features with vue-scrollto.
https://github.com/rigor789/vue-scrollto
Something like this should work.
// import
import VueScrollTo from 'vue-scrollto';
//...
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
VueScrollTo.scrollTo(to.hash, 700);
return { selector: to.hash }
} else if (savedPosition) {
return savedPosition;
} else {
return { x: 0, y: 0 }
}
}
That way, it always animates to the hash.
If you don't want to animate, use 0 as the time instead of 700.
If you don't like using that module, you can manually jump to the anchor via regular javascript using the techniques described here

Click on link changes the url but not the content/data on page

the story:
I am on product page #/product/7 and on the same page I have 4 more products that are similar to the one that is being viewed. All these products have links to their pages:
router-link(:to="{ name: 'product', params: { id: product.id }}" v-text='product.title').
the problem:
When I click on any of the product links, the url changes but the content remains the same. So, if I am on #/product/7 and click on #/product/8 the url only will change. If I navigate from /product/:id page and click on a product it takes me to the right page with proper content.
As you can see on screenshot, current product id is 15, but the content is the one from the id 7, as shown in url at the bottom while I was hovering over the Sleek Silk Shirt product in cart.
Any ideas how to fix this?
You have to update the data of products variable when you change the route as vue optimises page reloads and does not reload in your case if you are on same route.
You can adapt the approach: Fetching Before Navigation described in vue-router docs:
With this approach we fetch the data before actually navigating to the new route. We can perform the data fetching in the beforeRouteEnter guard in the incoming component, and only call next when the fetch is complete:
export default {
data () {
return {
product: {},
error: null
}
},
beforeRouteEnter (to, from, next) {
getProduct(to.params.id, (err, product) => {
if (err) {
// display some global error message
next(false)
} else {
next(vm => {
vm.product = product
})
}
})
},
// when route changes and this component is already rendered,
// the logic will be slightly different.
watch: {
$route () {
this.product = {}
getProduct(this.$route.params.id, (err, product) => {
if (err) {
this.error = err.toString()
} else {
this.product = product
}
})
}
}
}
I couldnt really internalise the above answer with 'getProduct', so to be put simply.
I am using a Store and I needed to watch the $route and when it changes I called my store to dispatch the api call.
watch: {
$route () {
this.$store.dispatch('fetchStudyStandards',
this.$route.params.standardID);
}
}
So basically watch the route and if it changes, re do your api call.