Am trying to create a simple menu using vue router , id like to iterate all routes and display in my menu , currently am using below instance method in my component but i just get a function , how would i iterate to get individual routes ?
methods : {
getMenuLinks: function() {
var t = this.$router.map() ;
//t returns a vue object instance
return t._children ;
// did not know how to iterate this
}
}
I want to iterate all maped routes to get something like below of each mapped route :
<a v-link="{ path: 'home' }">Home</a>
In Nuxt, the routes are generated automatically so I couldn't do what #zxzak suggested.
Here's what you can do in that case.
<template v-for="item in items">
<b-nav-item :to="item.path">
{{item.name}}
</b-nav-item>
</template>
export default {
created() {
this.$router.options.routes.forEach(route => {
this.items.push({
name: route.name
, path: route.path
})
})
}
, data() {
return {
items: []
}
}
}
You can simply iterate over $router.options.routes in your template:
<nav>
<router-link v-for="route in $router.options.routes" :key="route.path" :to="route.path">{{ route.name }}</router-link>
</nav>
Maybe add styling for the selected route:
:class="{ active: route.path === $router.currentRoute.path }"
edit: for active class, use https://router.vuejs.org/api/#active-class instead
Since vue-router 3.5, Router instance has now a getRoutes() method.
So an up to date answer could be
<router-link
for="r in routes"
:key="r.path"
:to="r.path"
>
{{ r.name }}
</router-link>
computed: {
routes() { return this.$router.getRoutes() }
}
Instead of relaying on Vue's internals, put routes inside the data of your starting component.
var map = {
'/foo': {
component: Foo
},
'/bar': {
component: Bar
}
}
var routes = Object.keys(map)
var App = Vue.extend({
data: function() {
return {
routes: routes
}
}
})
router.map(map)
router.start(App, '#app')
http://jsfiddle.net/xyu276sa/380/
Another solution is using Webpack's require.context
// search for src/pages/**/index.vue
function routesGen () {
const pages = require.context('./pages/', true, /index\.vue$/)
const filePaths = pages.keys()
const getRoutePath = filePath => filePath.match(/\.(\/\S+)\/index\.vue/)[1]
return filePaths.map(filePath => ({
path: getRoutePath(filePath),
component: pages(filePath).default
}))
}
As VueRouter is simply a JavaScript class as other classes, you can extend it and add any custom functionality including the questionable one:
// TypeScript
import Vue from 'vue';
import VueRouter, { RouteConfig } from 'vue-router';
class VueRouterEx extends VueRouter {
matcher: any;
public routes: RouteConfig[] = [];
constructor(options) {
super(options);
const { addRoutes } = this.matcher;
const { routes } = options;
this.routes = routes;
this.matcher.addRoutes = (newRoutes) => {
this.routes.push(...newRoutes);
addRoutes(newRoutes);
};
}
}
Vue.use(VueRouterEx);
const router = new VueRouterEx({
mode: 'history',
base: process.env.BASE_URL,
routes: [],
});
export default router;
So, from any component, you can get the routes using this.$router.routes
Related
I created my project by
vue init webpack project
#vue/cli 4.0.5
Here is my App.vue.
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
Router file
let router = new Router({
routes: [
{
path: '/videos',
name: 'Videos',
component: Videos
}
]
})
Files under Videos folder
index.js
import Videos from './Videos'
export default Videos
Videos.vue
<template>
<div>
<ul>
<li v-for="video in videos" :key="video.index">
{{ video.index }} - {{ video.value }}
</li>
</ul>
<div class="button">
<cv-button #click="submit">Submit</cv-button>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
created: () => {
const _this = this
const url = process.env.API_URL
axios.get(url + 'api/hello', {mode: 'no-cors'})
.then(response => {
const resource = response.data
const videos = resource.videos
_this.videos = Object.keys(videos).map((key) => {
return {
index: key,
value: videos[key]
}
})
})
},
data: () => {
return {
videos: []
}
},
methods: {
submit: function () {
const url = process.env.API_URL
axios.get(url + 'api/videos')
.then(response => {
console.log(response)
const resource = response.data
const videos = resource.videos
this.videos = Object.keys(videos).map((key) => {
return {
index: key,
value: videos[key]
}
})
})
}
}
}
</script>
Basically, I want to get a list of videos inside created function but neither this.videos nor _this.videos worked. When I tried to log this inside the created function, I was seeing a {} JSON object, not VueComponent.
{
a: {computed: {}, data: f, ...},
videos: [{...},{...}]
}
When I tried to get the list by click on the button, which triggers the submit function, it worked as expected, and this was a VueComponent.
VueComponent {_uid: 23, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
I don't understand what happened here? Why I worked with the submit function but not inside the created function?
Thanks
With created: () => {} notation created function executes in global scope. Try created() {}
I'm using prerender-spa-plugin in order to prerender certain pages so I get better SEO from my Vue app.
My goal is to transform the way I'm currently using Vue-i18n, so I can base it on url param /lang. Examples: /en/home or /nl/home. With this, I would be able to pre-render depending on the language.
I created a prefixer function that adds to every parent route the optional param /:lang?. Here it is:
const withPrefix = (prefix: string, routes: RouteConfig[]): RouteConfig[] => routes.map((route): RouteConfig => {
// Avoiding mutations
const clonedRoute = { ...route };
// Every route except for '/'
if (clonedRoute.path !== '/') {
clonedRoute.path = prefix + clonedRoute.path;
}
return clonedRoute;
});
In Vue templates, I'm using:
<router-link :to="`/account`">
So I'm trying to manipulate the redirect to the next page according to the lang param.
First approach
The most logical one is (inside Router's beforeEach):
const { lang } = to.params;
const redirectTo = lang ? to.fullPath : `${fullToLang}${to.fullPath}`;
if (from.fullPath !== redirectTo) {
next({ path: redirectTo });
} else {
next();
}
But it enters in an endless loop because from is always the same.
Second approach
Using Router's base property.
import Vue from "vue";
import App from "./App.vue";
import VueRouter from "vue-router";
import HelloWorld from "./components/HelloWorld";
import Test from "./components/Test";
Vue.config.productionTip = false;
Vue.use(VueRouter);
const router = new VueRouter({
mode: "history",
base: "/en",
routes: [
{
path: ":lang?/",
component: HelloWorld,
beforeEnter: (to, from, next) => {
console.log(1);
next();
}
},
{
path: "/:lang?/nope",
component: Test,
beforeEnter: (to, from, next) => {
console.log(2);
next();
}
},
{
path: "/:lang?/*",
beforeEnter: (to, from, next) => {
console.log(to);
next("/nope");
}
}
]
});
new Vue({
render: h => h(App),
router
}).$mount("#app");
Or better, live:
https://codesandbox.io/embed/vue-template-0bwr9
But, I don't understand why it's redirecting to /en/nope only if the url is not found on the routes (last case). And more, would I have to create a new Router instance each time I want to change base?
Third approach
Wrapper component for router-link injecting :to based on this.$route.params.lang.
This would do it for navigation after the app is loaded but not at the first refresh/initialization.
So, how should I resolve this?
~ Solution ~
So yeah, first approach was the correct way to go but I missunderstood how Router behaves with next and redirects. The condition should be checking the to not the from.
const redirectTo = lang ? to.fullPath : `${fullToLang}${to.fullPath}`;
if (to.fullPath !== redirectTo) {
// Change language at i18n
loadLanguageAsync(toLang as Language);
next({ path: redirectTo });
return;
}
I am not entirely sure what you are asking. But I assume you want to prefix your navigations with the current language param (../en/..) if they do not already have one?
You could resolve this with a beforeEach() hook and only redirecting if there is no lang param present.
const { lang } = to.params
if(!lang) {
next({ path: redirectTo })
}
next()
If that's not what you want please clarify and I'll edit my answer
Something like this? The assumption is that the new path starts /[lang]/...
as a note - there are still errors when routing e.g. /:lang/bar -> /foo/bar
Vue.lang = 'en'
function beforeEnter(to, from, next){
if ((new RegExp(`^/${Vue.lang}$`))
.test(to.path)
||
(new RegExp(`^/${Vue.lang}/`))
.test(to.path))
{
next();
} else {
next({path: `/${Vue.lang}${to.path}`})
}
};
Vue.mixin({
beforeRouteEnter: beforeEnter
})
const Foo = { template: '<div>foo - {{$route.path}}</div>' }
const Bar = { template: '<div>bar - {{$route.path}}</div>' }
const Root = { template: '<div>Root - {{$route.path}}</div>' }
const Invalid = { template: '<div>404</div>' }
const routes = [
{ path: '/:lang/foo', component: Foo },
{ path: '/:lang/bar', component: Bar },
{ path: '/:lang/*', component: Invalid },
{ path: '/:lang', name: 'Home', component: Root },
// some weird issue that prevents beforeRouteEnter ? so redirect, but else next is needed
{ path: '/', redirect: to => `/${Vue.lang}`}
]
const router = new VueRouter({
routes
})
new Vue({
data(){
return {
pLang: Vue.lang,
}
},
computed: {
lang: {
get(){
return this.pLang
},
set(val){
Vue.lang = val
this.pLang = val
}
}
},
router,
}).$mount('#app');
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<h1>Hello App!</h1>
<p>
{{lang}}
<select v-model="lang">
<option value="en">en</option>
<option value="cn">cn</option>
</select>
<!-- use router-link component for navigation. -->
<!-- specify the link by passing the `to` prop. -->
<!-- `<router-link>` will be rendered as an `<a>` tag by default -->
<router-link to="/">Root</router-link>
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
<router-link to="/foo/bar">Go to Foo/Bar - not defined</router-link>
</p>
<!-- route outlet -->
<!-- component matched by the route will render here -->
<router-view></router-view>
</div>
Using cue-cli 3. Is it possible to do this (router.js):
axios.get( `${process.env.VUE_APP_API_DOMAIN}/wp-json/api/v1/routes`).then( r => r.data ).then(routes => {
routes.pages.forEach( (e) => {
router.addRoutes([
{
path: `/${e.slug}`,
component: e.template,
},
]);
});
});
e.template is a string 'Default' and of course VueJS says:
route config "component" for path: /privacy-policy cannot be a string id. Use an actual component instead. Tried with Vue.component(e.template) no luck.
What I want to do here is create dynamic routes based on XHR response.
Here is all router.js code:
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Default from './views/Default.vue'
import Test from './views/Test.vue'
import axios from "axios";
Vue.use(Router);
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
},
]
});
axios.get( `${process.env.VUE_APP_API_DOMAIN}/wp-json/api/v1/routes`).then( r => r.data ).then(routes => {
routes.pages.forEach( (e) => {
router.addRoutes([
{
path: `/${e.slug}`,
component: e.template,
},
]);
});
});
export default router;
Currently I ended up with this solution:
function getComponent(name) {
let component = null;
switch(name)
{
case 'Default':
component = Default;
break;
case 'Test':
component = Test;
break;
}
return component;
}
axios.get( `${process.env.VUE_APP_API_DOMAIN}/wp-json/api/v1/routes`).then( r => r.data ).then(routes => {
routes.pages.forEach( (e) => {
router.addRoutes([
{
path: `/${e.slug}`,
component: getComponent(e.template),
},
]);
});
});
Another one more cleaner solution:
const components = { Default, Test }
axios.get( `${process.env.VUE_APP_API_DOMAIN}/wp-json/api/v1/routes`).then( r => r.data ).then(routes => {
routes.pages.forEach( (e) => {
router.addRoutes([
{
path: `/${e.slug}`,
component: components[e.template],
},
]);
});
});
If e.template stores the template string,
You should wrap it as one options object like {template: e.template, props: {}, data: function () {} }, then call Vue.extend to construct the component.
or you can ignore Vue.extend because Vue will call Vue.extend to construct the component automatically.
Check the usage at Vue Guide: Vue.component
Edit as the OP states e.tempate is one component name:
if e.template is the name of component, uses Vue.component(e.template).
Vue.config.productionTip = false
const router = new VueRouter({
routes: [
]
})
Vue.component('test', {
template: '<div>I am Predefined component -> {{index}}</div>',
props: ['index']
})
let routerIndex = 1
setInterval(()=> {
let newComponent = routerIndex%2 ? {template: '<div>I am User -> {{index}}</div>', props: ['index']} : Vue.component('test')
router.addRoutes([{
path: '/Test' + routerIndex,
name: 'Test' + routerIndex,
component: newComponent,
props: { index: routerIndex }
}])
console.log('add route = ', '/Test' + routerIndex, ' by ', routerIndex%2 ? 'options object' : 'Vue.component')
routerIndex++
}, 2000)
Vue.use(VueRouter)
app = new Vue({
el: "#app",
router,
data: {
routeIndex: 0
},
watch: {
routeIndex: function (newVal) {
this.$router.push({'name': 'Test'+newVal})
}
}
})
div.as-console-wrapper {
height: 100px;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<p>Current Route Index: {{routeIndex}}</p>
Test Route: <input v-model="routeIndex" type="number">
<router-view></router-view>
</div>
I have 2 components
My first component like this :
<template>
...
<b-form-input type="text" class="rounded-0" v-model="keyword"></b-form-input>
<b-btn variant="warning" #click="search"><i class="fa fa-search text-white mr-1"></i>Search</b-btn>
...
</template>
<script>
export default {
data () {
return {
keyword: ''
}
},
methods: {
search() {
this.$root.$emit('keywordEvent', this.keyword)
location.href = '/#/products/products'
}
}
}
</script>
My second component like this :
<template>
...
</template>
<script>
export default {
data () {
return{
keyword: ''
}
},
mounted: function () {
this.$root.$on('keywordEvent', (keyword) => {
this.keyword = keyword
})
this.getItems()
},
methods: {
getItems() {
console.log(this.keyword)
....
}
}
}
</script>
I using emit to pass value between components
I want to pass value of keyword to second component
/#/products/products is second component
I try console.log(this.keyword) in the second component. But there is no result
How can I solve this problem?
Update :
I have index.js which contains vue router like this :
import Vue from 'vue'
import Router from 'vue-router'
...
const Products = () => import('#/views/products/Products')
Vue.use(Router)
export default new Router({
mode: 'hash', // https://router.vuejs.org/api/#mode
linkActiveClass: 'open active',
scrollBehavior: () => ({ y: 0 }),
routes: [
{
path: '/',
redirect: '/pages/login',
name: 'Home',
component: DefaultContainer,
children: [
{
path: 'products',
redirect: '/products/sparepart',
name: 'Products',
component: {
render (c) { return c('router-view') }
},
children : [
...
{
path: 'products',
name: 'products',
component: Products,
props:true
}
]
},
]
},
{
path: '/products/products',
name: 'ProductsProducts', // just guessing
component: {
render (c) { return c('router-view') }
},
props: (route) => ({keyword: route.query.keyword}) // set keyword query param to prop
}
]
})
From this code...
location.href = '/#/products/products'
I'm assuming /#/products/products maps to your "second" component via vue-router, I would define the keyword as a query parameter for the route. For example
{
path: 'products',
name: 'products',
component: Products,
props: (route) => ({keyword: route.query.keyword}) // set keyword query param to prop
}
Then, in your component, define keyword as a string prop (and remove it from data)
props: {
keyword: String
}
and instead of directly setting location.href, use
this.$router.push({name: 'products', query: { keyword: this.keyword }})
There are some ways to do it in Vue.
Use EventBus with $emit like you did;
event-bus.js
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
component1.vue :
import EventBus from './event-bus';
...
methods: {
my() {
this.someData++;
EventBus.$emit('invoked-event', this.someData);
}
}
component2.vue
import EventBus from './event-bus';
...
data(){return {changedValue:""}},
...
mounted(){
EventBus.$on('invoked-event', payLoad => {
this.changedValue = payload
});
}
Use Vuex store, will be accessible at any component, at any page; (my favorite way)
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = () =>
new Vuex.Store({
store: {myStore:{value:0}},
actions:{["actionName"]:function({commit}, data){commit("actionName", data);}}, // I usualy using special constant for actions/mutations names, so I can use that here in code, and also in components
mutations:{["actionName"]:function(state, data){state.myStore.value = data;}},
getters:{myStoreValue: state => !!state.myStore.value}
})
component1.vue
...
methods:{
change:function(){
this.$store.dispatch("actionName", this.someData); //nuxt syntax, for simple vue you have to import store from "./../store" also
}
}
component2.vue
...
data(){return {changedValue:""}},
...
mounted(){
this.changedValue = this.$store.getters.myStoreValue;
}
Use props like #Phil said.
I am just trying a basic functionality with router params and I am getting undefined for router.params
my template
<div id="app><template id="image-capture">
<div class="row" >
<router-link :to="{ path: 'vc/'+item.id}" class="btn btn-primary"> ACCEPT</router-link>
</div>
</template></div>
now my url looks like this http://localhost/cams-web/#/vc/3
const ic = {
template: '#image-capture' ,
}
const vc = {
template: '#video-capture' ,
mounted () {
this.init()
},
methods: {
init () {
console.log(router); //returns object
console.log(router.params); //undefined..
},
}
}
const routes = [
{ path: '/ic', component: ic},
{ path: '/vc/:id', component: vc}
]
const router = new VueRouter({
routes
})
new Vue({
router,
}).$mount('#app')
To access the router params, you need to use this.$route.params in your code. Your code should be something like following:
const vc = {
template: '#video-capture' ,
mounted () {
this.init()
},
methods: {
init () {
console.log(this.$route); //should return object
console.log(this.$route.params); //should return object
console.log(this.$route.params.id); //should return id of URL param
},
}
}
Here is working fiddle.