VueRouter and meta fields - vue.js

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();
}

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: '/' }
}
}

Vue Router Navigation Guards

I have a page that can´t be accessed without permission. The permission is loaded by axios request in an action in the store. After the request the permission is stored in a store module. In the Navigation Guard beforeEach I have a getter that gets the permissions data from the store module.
Because it did not work I wrote a console.log to log the permissions data. The permissions data is an Array and when it logs the length of the Array it logs 0. That doesn´t make sense, because when I see into the Vue DevTools the store says that the array length is 1.
Does anyone have a solution that the store is faster?
Navigation Guard:
router.beforeEach(async (to, from, next) => {
var hasPermission = await store.getters.availableAppPermissions
hasPermission.forEach(function(item) {
if (
to.path.includes(item.appUrl) &&
to.matched.some(record => record.meta.requiresPermission)
) {
next({ name: 'Home' })
}
})
next()
})
Store Module:
import axios from 'axios'
export default {
state: {
availableApps: []
},
mutations: {
SET_AVAILABLE_APPS(state, availableApps) {
state.availableApps = availableApps
state.permissions = true
}
},
actions: {
loadAppsAvailableForCurrentUser({ commit }) {
return axios.get('/v1/apps').then(data => {
// Filter out apps that have false set in show_in_menu
const filteredApps = data.data.filter(app => app.showInMenu)
commit('SET_AVAILABLE_APPS', filteredApps)
})
}
},
getters: {
availableApps(state) {
return state.availableApps
},
availableAppPermissions(state) {
return state.availableApps.filter(item => item.hasPermission == false)
}
}
}
Code where loadAppsAvailableForCurrentUser is called:
This created is in the NavBar Component it is called on every Site because this Component is in the App.vue
created() {
if (this.$store.getters.loggedIn) {
this.$store.dispatch('loadUserData')
this.$store.dispatch('loadUserImageBase64')
this.$store.dispatch('loadVisibleTabs')
this.$store.dispatch('loadAppsAvailableForCurrentUser')
}
}

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()
})
}
}

How to attach axios / axios interceptor to Nuxt globally ?

how would i go about attaching axios / axios interceptor globally to nuxt (so its available everywhere), same how i18n is attached ?
The idea is that i would like to have a global axios interceptor that every single request goes through that interceptor.
Thanks
you can create a plugin called axios (/plugins/axios.js)
import Vue from 'vue';
import axios from 'axios';
axios.interceptors.request.use((config) => {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
Vue.use(axios);
then define this in nuxt.config.js
module.exports = {
//....
plugins: [
'~/plugins/axios',
],
//....
};
thats all, your interceptor is now working globally
It's hidden in the documentation - https://nuxtjs.org/docs/2.x/directory-structure/plugins
See number 3 of the first photo:
// plugins/axios.js
export default function ({ $axios, redirect }) {
$axios.onError(error => {
if (error.response.status == 404) {
redirect('/sorry')
}
})
}
then define this in nuxt.config.js
module.exports = {
//....
plugins: [
'~/plugins/axios',
],
//....
};
Maybe will be helpful for someone.
It just sets the lang parameter for every request.
Сreate a plugin called axios (/plugins/axios.js). Put it there:
export default function ({ $axios, app, redirect }) {
$axios.onRequest(config => {
config.params = config.params || {}; // get existing parameters
config.params['lang'] = app.i18n.locale;
})
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
redirect('/400')
}
})
}
Add in nuxt.config.js:
module.exports = {
plugins: [
'~/plugins/axios'
]
};
Create a new module, call it request.js for example.
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://example.org' // if you have one
})
// Put all interceptors on this instance
instance.interceptors.response.use(r => r)
export default instance
Then simply import that instance whenever you need it and use it like it was a normal axios instance:
import request from './request'
await request.get('/endpoint')
// or use promises
request.get('/endpoint').then(data => data)
If you really need it globally you can use the following code in your entry point of the application:
import request from './request'
global.request = request
// use it:
await request.get('example.org')
Or you can add it to the vue protype
Vue.prototype.$request = request
// in your component:
this.$request.get()
I'd advice against it though.

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

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;