How to download base64 file from remote api by vue-router link? - vuejs2

I need to download files from remote api by vue-router link. Remote api returns files in base64.
I add route:
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/files/:id', name: 'file', component: Vue.component('vue-file'), props: true }
],
});
and add component for it:
<template>
<div>
</div>
</template>
<script>
export default {
name: 'file',
props: {
id: String
},
mounted: function() {
this.download();
},
methods: {
download: function() {
let $this = this;
axios
.get('apiurl' + encodeURIComponent(this.id))
.then(function (response) {
download(response.data.base64content)
});
}
}
}
</script>
It works but I don't want to show component template <template><div></div></template>. I even don't want to refresh the content on the screen. Is it possible?

You shouldn't. Vue Components were done for rendering. Your implementation would be nicer as a mixin or plugin, if you don't want to render anything.
Although, I think you can do something like:
render() {
return null;
},

Related

How to use Buefy's Dialog component with user provided content and be safe in terms of XSS

Buefy's Dialog component expects a message prop - string. According to a documentation, that string can contain HTML. I would like to use template values in the string, but of course is should be XSS safe.
Current unsafe example
This is unsafe, as this.name is unsafe. I could use a NPM package to html encode the name, but I really prefer to use Vue.
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
name: { type: String, required: false },
},
methods: {
showModal() {
this.$buefy.dialog.confirm({
title: 'myTitle',
message: `<p>Hello ${this.name}</p>`, // unsafe - possible XSS!
cancelText: 'Cancel',
confirmText: 'OK',
type: 'is-success',
onConfirm: async () => {
// something
},
});
},
},
});
</script>
This is an issue of the used Buefy component, as documented here:
Desired Setup
I've created a new component, in this example I call it ModalMessage.Vue
<template>
<p>Hello {{name}}</p>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
name: { type: String, required: true },
},
});
</script>
Then I like to render the ModalMessage.Vue to a string in Typescript:
<script lang="ts">
import Vue from 'vue';
import ModalMessage from 'ModalMessage.vue';
export default Vue.extend({
props: {
name: { type: String, required: false },
},
methods: {
showModal() {
this.$buefy.dialog.confirm({
title: 'myTitle',
message:, // todo render ModalMessage and pass name prop
cancelText: 'Cancel',
confirmText: 'OK',
type: 'is-success',
onConfirm: async () => {
// something
},
});
},
},
});
</script>
Question
How could I render the ModalMessage.Vue, and passing the name prop, to a string?
I'm pretty sure this is possible - I have seen it in the past. Unfortunately I cannot find it on the web or StackOverflow. I could only find questions with rendering a template from string, but I don't need that - it needs to be to string.
Imho your real question is "How to use Buefy's Dialog component with user provided content and be safe in terms of XSS"
So what you want is to create some HTML, include some user provided content (this.name) within that HTML content and display it in a Dialog. You are right that putting unfiltered user input into a message parameter of Dialog is not safe (as clearly noted in Buefy docs)
But your "Desired Setup" seems unnecessary complicated. Imho the easiest way is to use (poorly documented) fact that message parameter of Buefy Dialog configuration objects can be an Array of VNode's instead of a string. It is poorly documented but it is very clear from the source here and here that if you pass an array of VNode's, Buefy puts that content into Dialogs default slot instead of rendering it using v-html (which is the dangerous part)
And easiest way to get Array of VNode in Vue is to use slots...
So the component:
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
methods: {
showModal() {
this.$buefy.dialog.confirm({
title: 'myTitle',
message: this.$slots.default, // <- this is important part
cancelText: 'Cancel',
confirmText: 'OK',
type: 'is-success',
onConfirm: async () => {
// something
},
});
},
},
});
</script>
and it's usage:
<MyDialog>
<p>Hello {{name}}</p>
</MyDialog>
or
<MyDialog>
<ModalMessage :name="name" />
</MyDialog>
In both cases if the name contains any HTML, it will be encoded by Vue
Here is a simple demo of the technique described above (using plain JS - not TS)
Try this.
<script lang="ts">
import Vue from 'vue';
import ModalMessage from 'ModalMessage.vue';
export default Vue.extend({
props: {
name: { type: String, required: false },
},
methods: {
showModal() {
const message = new Vue({
components: { ModalMessage },
render: h => h('ModalMessage', { name: this.name })
})
message.$mount()
const dialog = this.$buefy.dialog.confirm({
title: 'myTitle',
message: [message._vnode],
cancelText: 'Cancel',
confirmText: 'OK',
type: 'is-success',
onConfirm: async () => {
// something
},
});
dialog.$on('hook:beforeDestroy', () => {
message.$destroy()
});
},
},
});
</script>
Source codeļ¼š
Demo:

Dependency not found - Vue Js

I have recently added axios to a file called services.js so it's better organised. This file is on my root folder.
#/services.js
import axios from "axios";
const axiosInstance = axios.create({
baseURL: " server url here",
});
export const api = {
get(endpoint) {
return axiosInstance.get(endpoint);
},
post(endpoint, body) {
return axiosInstance.post(endpoint, body);
},
};
Then I have a component called Post.vue in my view folder:
<template>
<section>
<div>
<ul></ul>
</div>
</section>
</template>
<script>
import { api } from "#/services.js";
export default {
name: "Post",
props: ["id"],
data() {
return {
post: null,
};
},
methods: {
getPost() {
api.get(`/post/${this.id}`).then(response => {
this.post = response.data;
console.log(this.post);
});
},
},
created() {
this.getPost();
},
};
</script>
<style></style>
I also have a router.ts file with all my routes:
import Vue from "vue";
import VueRouter, { RouteConfig } from "vue-router";
import Home from "../views/Home.vue";
import Podcasts from "../views/Podcasts.vue";
import Post from "../views/Post.vue";
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{
path: "/",
name: "home",
component: Home,
},
{
path: "/podcasts",
name: "podcasts",
component: Podcasts,
},
{
path: "/post/:id",
name: "post",
component: Post,
props: true,
},
],
});
export default router;
It's giving me a dependency error like #/services.js did not exist.
Unsure what's wrong at this stage.
Thanks a lot in advance for helping out
In a standard Vue CLI project, the # symbol resolves to /src
If your file is in the root of your project try
import { api } from '#/../services'
But personally, I'd move it into src
You can check the Webpack configuration using
vue inspect
Look for the resolve.alias rules.
Check your webpack configuration, depends on the version of webpack you have, there should be an alias # like this:
const path = require('path');
module.exports = {
//...
resolve: {
alias: {
"#": path.resolve(__dirname) // check the path here
}
}
};
Or if you are using vue.config.js
configureWebpack: {
name: name,
resolve: {
alias: {
'#': path.resolve(__dirname)// check the path here
}
}
},
Make sure the path is correctly set up. You mentioned you have another project working fine, which makes it a good reference.

Pass data through router to other component in vue

I am trying to pass data through router. My code is working but it shows data in url. I don't want that like as POST method.url should like /data-list . Also I want to catch passing value from component. Here I did not use vuex . Actually my job is to show message that task is done based on this data. I am using Laravel for backend. Thanks in advance
1st component
axios.post("/request/data", dataform).then(function (resp) {
app.$router.push({ path: "/data-list/" + resp.data.success });
});
routes
{
path: '/data-list/:message?',
name: 'dataList',
component: dataList,
meta: {
auth: true
}
},
Another component. Here I want to catch
mounted() {
var app = this;
app.message = app.$route.params.message;
}
So if I understand correctly, you are fetching data in some component and then you are doing a router-push to dataList component.
You want to access the data in dataList component.
Since you always want the route to be /dataList, do this in your routes file
{
path: '/data-list', //removing dynamic tag.
name: 'dataList',
component: dataList,
meta: {
auth: true
}
},
Then in the component where you do router push, add a handleClick like so.
handleClick() {
let data = {
id: 25,
description: "pass data through params"
};
this.$router.push({
name: "dataList", //use name for router push
params: { data }
});
}
}
Then in your dataList component you can access the data passed in the mounted like so :
mounted() {
let data = this.$route.params.data;
console.log("data is", data);
}
Working implementation attached below.
You can push router data in router like this.
this.$router.push({
name: "dataList",
params: { data: resp.data },
});
and in the routes you can define your route as
{
path: "/dataList",
name: "dataList",
props: true,
meta: { title: "Data list" },
component: () => import("path to datalist component")
},
and in the DataList.vue you can use props to get the data
export default {
props:['data'],
mounted(){
alert(this.data);
}
}

load routes from api and import views dynamically

The most of my routes are protected and require permissions to access them. When the user signed in successfully my Navbar component makes an API call and retrieves a bunch of routes the user is able to access.
After that I add all the view files matching to the routes to the navbar.
This is an example code showing the process
<template>
<div>
<router-link
v-for="navItem in navItems"
:key="navItem.title"
:to="navItem.url"
>{{ navItem.title }}</router-link>
</div>
</template>
<script>
export default {
data: function() {
return {
navItems: []
};
},
created: async function() { // Setup the router here
this.navItems = [
// !!! API CALL !!!
{
title: "Dashboard",
url: "/Dashboard"
},
{
title: "Users",
url: "/users/Users"
},
{
title: "Groups",
url: "/groups/Groups"
}
];
const routes = await this.navItems.map(async navItem => {
const { url } = navItem;
return {
path: url,
component: await import(`../views${url}.vue`)
};
});
this.$router.addRoutes(routes);
}
};
</script>
Unfortunately I get this error
Uncaught (in promise) Error: [vue-router] "path" is required in a
route configuration.
but as you can see in the example code this attribute is set. I created an sample project here
https://codesandbox.io/s/vue-routing-example-i2znt
If you call this route
https://i2znt.codesandbox.io/#/Dashboard
I would expect the router to render the Dashboard.vue file.
the routes array that you build doesn't contains your routes objects.
It's an array of promises.
you should do something like
Promise.all(routes).then(resolvedRoutes => {
this.$router.addRoutes(resolvedRoutes)
})

Passing props to Vue.js components instantiated by Vue-router

Suppose I have a Vue.js component like this:
var Bar = Vue.extend({
props: ['my-props'],
template: '<p>This is bar!</p>'
});
And I want to use it when some route in vue-router is matched like this:
router.map({
'/bar': {
component: Bar
}
});
Normally in order to pass 'myProps' to the component I would do something like this:
Vue.component('my-bar', Bar);
and in the html:
<my-bar my-props="hello!"></my-bar>
In this case, the router is drawing automatically the component in the router-view element when the route is matched.
My question is, in this case, how can I pass the the props to the component?
<router-view :some-value-to-pass="localValue"></router-view>
and in your components just add prop:
props: {
someValueToPass: String
},
vue-router will match prop in component
sadly non of the prev solutions actually answers the question so here is a one from quora
basically the part that docs doesn't explain well is
When props is set to true, the route.params will be set as the component props.
so what you actually need when sending the prop through the route is to assign it to the params key ex
this.$router.push({
name: 'Home',
params: {
theme: 'dark'
}
})
so the full example would be
// component
const User = {
props: ['test'],
template: '<div>User {{ test }}</div>'
}
// router
new VueRouter({
routes: [
{
path: '/user',
component: User,
name: 'user',
props: true
}
]
})
// usage
this.$router.push({
name: 'user',
params: {
test: 'hello there' // or anything you want
}
})
In the router,
const router = new VueRouter({
routes: [
{ path: 'YOUR__PATH', component: Bar, props: { authorName: 'Robert' } }
]
})
And inside the <Bar /> component,
var Bar = Vue.extend({
props: ['authorName'],
template: '<p>Hey, {{ authorName }}</p>'
});
This question is old, so I'm not sure if Function mode existed at the time the question was asked, but it can be used to pass only the correct props. It is only called on route changes, but all the Vue reactivity rules apply with whatever you pass if it is reactive data already.
// Router config:
components: {
default: Component0,
named1: Component1
},
props: {
default: (route) => {
// <router-view :prop1="$store.importantCollection"/>
return {
prop1: store.importantCollection
}
},
named1: function(route) {
// <router-view :anotherProp="$store.otherData"/>
return {
anotherProp: store.otherData
}
},
}
Note that this only works if your prop function is scoped so it can see the data you want to pass. The route argument provides no references to the Vue instance, Vuex, or VueRouter. Also, the named1 example demonstrates that this is not bound to any instance either. This appears to be by design, so the state is only defined by the URL. Because of these issues, it could be better to use named views that receive the correct props in the markup and let the router toggle them.
// Router config:
components:
{
default: Component0,
named1: Component1
}
<!-- Markup -->
<router-view name="default" :prop1="$store.importantCollection"/>
<router-view name="named1" :anotherProp="$store.otherData"/>
With this approach, your markup declares the intent of which views are possible and sets them up, but the router decides which ones to activate.
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true }
// for routes with named views, you have to define the props option for each named view:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
Object mode
const router = new VueRouter({
routes: [
{ path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }
]
})
That is the official answer.
link
Use:
this.$route.MY_PROP
to get a route prop