How to access current route meta fields in common javascript module - VueJs - vue.js

I have following route in my application:
const router = new Router({
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
routes: [
{ path: '/', component: HomePage, meta: { pageType: 'home'} },
],
});
and have on common js module:
const trackEvent = {
getFields: () => {
//here need to access meta fields(pageType) of current route. how is it possible ?
}
}
export default trackEvent;
i want to access meta field in common module. how is it possible ?

The meta property is accessible via this.$route.meta on a Vue instance. Just pass that to the getFields method.
export default {
created() {
let meta = getFields(this.$route.meta);
}
}
getFields: (meta) => {
console.log(meta);
return meta.fields.pageType; // not sure what you're trying to return exactly
}
If you can't pass in the current route, you'll need to import the router object and get the current route from that:
import router from 'path/to/your/router'
const trackEvent = {
getFields: () => {
let meta = router.currentRoute.meta;
console.log(meta);
return meta.fields.pageType; // not sure what you're trying to return exactly
}
}
export default trackEvent;

Related

How to perform actions before providing downloadable files in Vue

I want to be able to keep track of file downloads in a Vue project. The goal is to provide a url like mysite.com/some/path/file-name.txt/tracking-source, perform an action like send the path to tracking api, then serve the file at mysite.com/some/path/file-name.txt
I tried using a redirect but it doesn't seem to provide a file download, it just updates the path in the browser.
use a route that captures the "tracking-source" parameter and performs the necessary tracking action, and then serves the file using the sendFile method from the express library.
Here is an example of how to set up a route in a Vue project using the vue-router library:
import Vue from 'vue'
import Router from 'vue-router'
import path from 'path'
import express from 'express'
Vue.use(Router)
const router = new Router({
routes: [
{
path: '/some/path/:fileName/:trackingSource',
name: 'download-file',
component: {
beforeRouteEnter (to, from, next) {
const { params } = to
// Perform tracking action using the trackingSource parameter
// ...
// Serve the file
const filePath = path.join(__dirname, 'path/to/files', `${params.fileName}.txt`)
express.sendFile(filePath, (err) => {
if (err) next(err)
})
}
}
}
]
})
here the route captures the "fileName" nd "trackingSource" parameters from the URL, and uses the beforeRouteEnter navigation guard to perform the tracking action and serve the file.
without express you can do something like this
<template>
<div>
<a ref="downloadLink" :href="fileUrl" download>Download</a>
<button #click="downloadFile">Download</button>
</div>
</template>
<script>
export default {
data() {
return {
fileUrl: ''
}
},
methods: {
async downloadFile() {
const { params } = this.$route
const fileName = `${params.fileName}.txt`
const filePath = `/path/to/files/${fileName}`
const response = await fetch(filePath)
if (!response.ok) {
throw new Error(`Failed to fetch file: ${response.status}`)
}
const blob = await response.blob()
this.fileUrl = window.URL.createObjectURL(blob)
this.$refs.downloadLink.click()
}
}
}
</script>
Since I also store my files in the public/files directory of the vue project, I opted to not fetch it.
{
path: '/files/:fileName/:source',
redirect: to => {
const fileName = to.params.fileName
logEvent(analytics, fileName, {source: to.params.source});
const a = document.createElement('a');
document.body.appendChild(a);
a.href = `/files/${fileName}`;
a.download = fileName;
a.click();
setTimeout(() => {
window.URL.revokeObjectURL(a.href);
document.body.removeChild(a);
}, 0)
return {path: '/' }
}
}

Cannot use Vue-Router to get the parameters in the URL

Today, when trying to use Vue-Router (in Vue-CLI) to get URL parameters, I encountered difficulties ($route.query is empty), the code is as follows.
Code purpose: Get the parameters carried after the URL (such as client_id in "http://localhost:8080/#/?client_id=00000000000077")
Project file structure:
router/index.js:
App.vue(Get part of the code for URL parameters):
The running result of this part of the code:
I'm not sure why $router.currentRoute and $route aren't matching up, but you could simply use $router.currentRoute.query.client_id if you need it in mounted().
Another workaround is to use a $watch on $route.query.client_id:
export default {
mounted() {
const unwatch = this.$watch('$route.query.client_id', clientId => {
console.log({ clientId })
// no need to continue watching
unwatch()
})
}
}
Or watch in the Composition API:
import { watch } from 'vue'
import { useRoute } from 'vue-router'
export default {
mounted() {
console.log({
route: this.$route,
router: this.$router,
})
},
setup() {
const route = useRoute()
const unwatch = watch(() => route.query.client_id, clientId => {
console.log({ clientId })
// no need to continue watching
unwatch()
})
}
}

Is there any way to pass mixin to component loaded through Vue Router

I have a mixin which contains beforeCreate lifecycle event.
I would like to import that mixin only into certain components, which are directly loaded through router. I don't want to go into each one of them and manually import the mixin, and I would also want to avoid loading it globally.
I believe that the proper way to do it is in route options, possibly overriding the component method, or by adding mixin option for the route (alongside props, meta...).
I requested this new feature, but I guess I was misunderstood, or I didn't understand the proposed solution.
I tried to create main Vue instance and extend it in my components, but the method only executed from the main component.
Is there any way to make this work?
Example of project code is here
Perhaps I've misunderstood what you're asking but I'd have thought you could achieve this by extending the component:
import Vue from 'vue'
import Router from 'vue-router'
import MyMixin from './mixins/MyMixin'
import MyList from './components/MyList'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/list',
name: 'list',
component: {
extends: MyList,
mixins: [MyMixin]
}
}
// ...
]
})
So rather than using MyList directly it's being extended and the mixin added in.
Or if you've got a lot of them and want to avoid duplication you could do something like this:
export default new Router({
routes: [
{
path: '/list',
name: 'list',
doMagic: true,
component: MyList
}
// ...
].map(route => {
if (route.doMagic) {
route.component = {
extends: route.component,
mixins: [MyMixin]
}
}
return route
})
})
Here I've used a flag called doMagic to determine which components to modify but if you just wanted to change all of them then you wouldn't need such a flag.
That doesn't take nested routes into account but it could be adapted if required.
Likewise if you're using async components then you'll have to fiddle around with the promises but the core principle should be exactly the same.
Update:
Based on the example code provided, the following seems to work with lazily loaded components:
const routes = [
// ... routes defined as usual
];
const newRoutes = routes.map(route => {
const originalComponent = route.component;
let component = null;
if (typeof originalComponent === 'object') {
// Components that aren't lazily loaded
component = wrap(originalComponent);
} else {
// Components that are lazily loaded
component = async () => {
const module = await originalComponent();
return wrap(module.default || module);
}
}
return {
...route,
component
};
function wrap (cmp) {
return {
extends: cmp,
mixins: [MyMixin]
}
}
});
export default new Router({
routes: newRoutes
});

vuejs - cached data used with different url

I have built a profile vue page but I realized that when I change URL parameter it does not load new user but displays the first user. I load data in created which is probably the cause. How can I tell the page that it has a different parameter and it shall reload?
router.js
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/profile/:id',
name: 'user-profile',
component: () => import('./views/Profile.vue'),
props: true,
},
Profile.vue
async created() {
const response = await this.$store.dispatch('GET_USER_PROFILE_BY_ID', { id: this.id });
this.userProfile = response.data.data;
URLs:
http://localhost:8080/profile/1dvklq9cnz
http://localhost:8080/profile/1e0tcb2kn2
The created hook only gets executed when the component is actually created. changing the url to load the same route, but with a different ID is a routeUpdate instead.
Remember that params or query changes won't trigger enter/leave navigation guards. You can either watch the $route object to react to those changes, or use the beforeRouteUpdate in-component guard.
https://router.vuejs.org/guide/advanced/navigation-guards.html
Abstract out the fetch & set that you have in your created hook. Then, call it in both created() and beforeRouteUpdate().
{
methods: {
async getProfile(id) {
const response = await this.$store.dispatch('GET_USER_PROFILE_BY_ID', { id});
this.userProfile = response.data.data;
}
created() { this.getProfile(this.id); },
beforeRouteUpdate(to, from, next) {
const { params: {id} } = to;
this.getProfile(id);
next();
}
}

VueRouter and meta fields

According to the VueRouter documentation, it is possible to add meta fields and globally restrict routes based on their values.
After attempting an implementation as outlined, I get an error:
ReferenceError: record is not defined (line 46)
Which corresponds to this line:
if (!hasCookies(record.meta.cookies)) {
Here is the file that has the router-guard logic:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const routes = [
{
path : '/authenticate/:id',
component : require ('./components/authenticate.vue'),
redirect: '/here',
},
// can only get here if the passcode has been filled out
{
path : '/client/create',
component : require('./components/createClientForm.vue'),
meta : {
cookies: ['passcode_cookie'], // need passcode to be able to create the client
redirect: '/authenticate/1' // dummy value here for now
}
},
// can only get here if passcode and client form have been completed
{
path : '/test/:id',
component : require('./components/runTest.vue'),
meta : {
cookies : ['passcode_cookie', 'client_cookie'],
redirect : '/client/create'
}
}
];
const router = new VueRouter ({
routes,
mode: 'history',
});
function hasCookies (cookies) {
return cookies.every(cookie => document.cookie.indexOf(cookie) !== -1);
}
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.cookies)) {
// this route requires cookies, check if cookies exist
if (!hasCookies(record.meta.cookies)) {
next({
path : record.meta.redirect || '/',
})
} else {
next();
}
} else {
next(); // make sure to always call next()!
}
});
const app = new Vue ({
router
}).$mount('#app');
Any idea on what I may be doing wrong?
The error is self-explanatory here. The variable record is not in scope, it's not a function parameter and it's not a global variable. record is defined only within the callback function you passed to some. It's out of scope at the point where the error occurs.
Try this instead:
const record = to.matched.find(record => record.meta.cookies && !hasCookies(record.meta.cookies));
if (record) {
next({ path: record.meta.redirect || '/' });
} else {
next();
}