VueJS dynamic routes and components - vue.js

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>

Related

QUASAR: Redirection using "push" does not work

Good day. I have the following file "pages / Login.vue"
Here the form:
Simple form
<template>
<div>
<q-form #submit="btnlogin">
<q-input v-model="user" type="text" label="Usuario" />
<q-input v-model="pass" type="password" label="ContraseƱa" />
<q-btn color="primary" label="Ingresar" type="submit"/>
</div>
</template>
<script>
import axios from 'axios'
import { ref } from 'vue'
import { useQuasar } from 'quasar'
import router from 'src/router/index'
export default {
setup () {
const $q = useQuasar()
const user = ref(null)
const pass = ref(null)
const btnlogin = async () => {
axios.post("http://localhost:3050/loginQuest",{
uss : user.value,
pww : pass.value
})
.then(resp=>{
if(resp.data=="ERROR"){
$q.notify({
type:'negative',
message:'Datos incorrectos!'
})
}
else{
router().push({ path: '/' })
}
})
}
return {
user, pass, btnlogin
}
}
}
</script>
When the verification is successful, the address bar changes to http://localhost: 8080 but the content does not change and the Login.vue form continues to be displayed on the screen.
it sends me to the path but does not change the content, but if I refresh the page it shows the correct content
This is my router/routes.js:
const routes = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('pages/Index.vue') },
{ path: '/form', component: () => import('pages/Form.vue') },
{ path: '/user', component: () => import('pages/Usuarios.vue') },
{ path: '/prueba', component: () => import('pages/Prueba.vue') },
{ path: '/facturas', component: () => import('pages/Facturas.vue') },
],
},
{ path: '/login', component:()=> import('pages/Login.vue')},
{ path: '/:catchAll(.*)*', component: () => import('pages/Error404.vue')}
]
export default routes
I changed the quasar.conf.js setting in the "vueRouterMode" section from hash to history. I hope you can help me, I'm stuck in it. Thank you!
Your script should look something like this:
<script>
import axios from 'axios'
import { ref } from 'vue'
import { useQuasar } from 'quasar'
import { useRoute } from 'vue-router' // <- import useRoute here
export default {
setup () {
const $q = useQuasar()
const router = useRouter()
const user = ref(null)
const pass = ref(null)
const btnlogin = async () => {
axios.post("http://localhost:3050/loginQuest",{
uss : user.value,
pww : pass.value
})
.then(resp=>{
if(resp.data=="ERROR"){
$q.notify({
type:'negative',
message:'Datos incorrectos!'
})
}
else{
router.push({ path: '/' }) // << router is an object, not a function
}
})
}
return {
user, pass, btnlogin
}
}
}
</script>
Is Posible that on you are created route on yout router.js or router/index.js a method for wraper router (if you show you router definition help to know it).
but I was a similar problem, I resolve it with:
let router: Router;
...
export default route(function ( ... ) {
if (router) {
return router;
}
....
router = createRouter({
... // your code
});
return router;
});
it require unique intanciated object router. It solve for me.

Load routes from api in vue

I'm trying to load routes in a Vue application from my API. I tried pushing data to routes variable and using addRoutes method. But no luck. I think there could be an issue with async. But why the addRoutes() not working?
Here's my code:
import Vue from 'vue';
import VueRouter from 'vue-router';
import axios from 'axios';
/**
* Routes
*/
import item_index from '../../app/Tenant/Item/Views/Index.vue';
import contact_index from '../../app/Tenant/Contact/Views/Index.vue';
import eav_index from '../../app/Tenant/Eav/Views/create.vue';
import eav_create from '../../app/Tenant/Eav/Views/create.vue';
var routes = [
{ path: '/items', component: item_index, name: 'item_index' },
{ path: '/contact', component: eav_index , name: 'contact_index' , props: { entity_type_id: 1 }},
];
Vue.use(VueRouter);
const router = new VueRouter({
mode: 'history',
linkActiveClass: 'active',
routes
});
axios
.get('http://c1.fmt.dev/api/eav/entity_types')
.then(response => {
for (var i = 0; i < response.data.length; i++) {
var type = response.data[i];
var route = {
path: '/' + type.name,
component: eav_index,
name: type.name + '_index',
props: {
entity_type_id: type.id
},
};
router.addRoutes([route]);
alert(router.options.routes);
// alert(JSON.stringify(routes));
}
})
.catch(error => {
console.log(error)
});
new Vue({
el: '.v-app',
data(){
return {
page_header: '',
page_header_small: '',
}
},
router, axios
});
Try this improved code. Without postponing Vue instance creation, so without unnecessary delaying page interactivity:
import Vue from 'vue'
import VueRouter from 'vue-router'
import axios from 'axios'
import item_index from '../../app/Tenant/Item/Views/Index.vue'
import contact_index from '../../app/Tenant/Contact/Views/Index.vue'
import eav_index from '../../app/Tenant/Eav/Views/create.vue'
import eav_create from '../../app/Tenant/Eav/Views/create.vue'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
linkActiveClass: 'active',
routes: [{
path: '/items',
component: item_index,
name: 'item_index'
}, {
path: '/contact',
component: eav_index ,
name: 'contact_index' ,
props: {entity_type_id: 1}
}]
})
new Vue({
el: '.v-app',
router,
data () {
return {
page_header: '',
page_header_small: '',
}
},
methods: {
getDynamicRoutes (url) {
axios
.get(url)
.then(this.processData)
.catch(err => console.log(err))
},
processData: ({data}) => {
data.forEach(this.createAndAppendRoute)
},
createAndAppendRoute: route => {
let newRoute = {
path: `/${route.name}`,
component: eav_index,
name: `${route.name}_index`,
props: {entity_type_id: route.id}
}
this.$router.addRoutes([newRoute])
}
},
created () {
this.getDynamicRoutes('http://c1.fmt.dev/api/eav/entity_types')
}
})
For better code structure and readability, move router definition to separate file:
In your main file, leave just this code:
// main.js
import Vue from 'vue'
import router from '#/router'
import axios from 'axios'
new Vue({
el: '.v-app',
router,
data () {
return {
page_header: '',
page_header_small: '',
}
},
methods: {
getDynamicRoutes (url) {
axios
.get(url)
.then(this.processData)
.catch(err => console.log(err))
},
processData: ({data}) => {
data.forEach(this.createAndAppendRoute)
},
createAndAppendRoute: route => {
let newRoute = {
path: `/${route.name}`,
component: eav_index,
name: `${route.name}_index`,
props: {entity_type_id: route.id}
}
this.$router.addRoutes([newRoute])
}
},
created () {
this.getDynamicRoutes('http://c1.fmt.dev/api/eav/entity_types')
}
})
And in same folder as main file lies, create subfolder 'router' with 'index.js' within:
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import item_index from '../../../app/Tenant/Item/Views/Index.vue'
import contact_index from '../../../app/Tenant/Contact/Views/Index.vue'
import eav_index from '../../../app/Tenant/Eav/Views/create.vue'
import eav_create from '../../../app/Tenant/Eav/Views/create.vue'
Vue.use(VueRouter)
export default new VueRouter({
mode: 'history',
linkActiveClass: 'active',
routes: [{
path: '/items',
component: item_index,
name: 'item_index'
}, {
path: '/contact',
component: eav_index ,
name: 'contact_index' ,
props: {entity_type_id: 1}
}]
})
The vue instance is already initiated when you try to add the routes (same problem as in this question: How to use addroutes method in Vue-router? ). You could postpone the vue initalization after the routes are loaded:
//imports and stuff...
axios
.get('http://c1.fmt.dev/api/eav/entity_types')
.then(response => {
for (var i = 0; i < response.data.length; i++) {
var type = response.data[i];
var route = {
path: '/' + type.name,
component: eav_index,
name: type.name + '_index',
props: {
entity_type_id: type.id
},
};
// extend routes array prior to router init
routes.push(route);
}
// init router when all routes are known
const router = new VueRouter({
mode: 'history',
linkActiveClass: 'active',
routes
});
// init vuw instance when router is ready
new Vue({
el: '.v-app',
data(){
return {
page_header: '',
page_header_small: '',
}
},
router, axios
});
})
.catch(error => {
console.log(error)
});

How can I pass data from a component to another component on vue?

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.

Vue lifecycle events triggers on every route

According to the documentation https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram all events from beforeCreate till mounted should be called once. But they are being triggered on every vue-router path navigated
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App },
created: function () {
this.$http.get('/user/get').then(response => {
if (response.data.error) {
console.log(response.data.error)
} else {
User.set(response.data.user)
router.go('/dashboard') // this does force looping
}
}, response => {
router.go('/')
})
}
})
This is App.vue
<template>
<div id="app">
<topmenu></topmenu>
<router-view></router-view>
</div>
</template>
<script>
import Topmenu from '#/components/topmenu'
export default {
name: 'app',
components: {
topmenu: Topmenu
}
}
</script>
The Router.vue
Vue.use(Router)
let route = new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/user/signup',
name: 'Signup',
component: Signup
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard
}
]
})
route.beforeEach((to, from, next) => {
if (to.path.match(/^(\/|\/signup)$/)) {
return next()
}
if (User.valid()) {
return next()
}
route.push('/')
})
export default route
How to avoid this events from being triggered on every route switched?

Vue-router dynamic load menu tree

I'm trying to create a menu tree with vue-router by ajax request,but the $mount function was called before the ajax request responsed, so the router in the Vue instance always null.
Is there any good solution here?
Here is my code (index.js):
import Vue from 'vue';
import Element from 'element-ui';
import entry from './App.vue';
import VueRouter from 'vue-router';
import VueResource from 'vue-resource';
import Vuex from 'vuex'
import configRouter from './route.config';
import SideNav from './components/side-nav';
import Css from './assets/styles/common.css';
import bus from './event-bus';
import dynamicRouterConfig from './dynamic.router';
Vue.use(VueRouter);
Vue.use(Element);
Vue.use(VueResource);
Vue.use(Vuex);
Vue.http.interceptors.push((request, next) => {
bus.$emit('toggleLoading');
next(() => {
bus.$emit('toggleLoading');
})
})
Vue.component('side-nav', SideNav);
app = new Vue({
afterMounted(){
console.info(123);
},
render: h => h(entry),
router: configRouter
});
app.$mount('#app');
route.config.js:
import navConfig from './nav.config';
import dynamicRouterConfig from './dynamic.router';
let route = [{
path: '/',
redirect: '/quickstart',
component: require('./pages/component.vue'),
children: []
}];
const registerRoute = (config) => {
//require(`./docs/zh-cn${page.path}.md`)
//require(`./docs/home.md`)
function addRoute(page) {
if (page.show == false) {
return false;
}
let component = page.path === '/changelog' ? require('./pages/changelog.vue') : require(`./views/alert.vue`);
if (page.path === '/edit') {
component = require('./views/edit.vue');
}
let com = component.default || component;
let child = {
path: page.path.slice(1),
meta: {
title: page.title || page.name,
description: page.description
},
component: com
};
route[0].children.push(child);
}
//if (config && config.length>0) {
config.map(nav => {
if (nav.groups) {
nav.groups.map(group => {
group.list.map(page => {
addRoute(page);
});
});
} else if (nav.children) {
nav.children.map(page => {
addRoute(page);
});
} else {
addRoute(nav);
}
});
//}
return { route, navs: config };
};
const myroute = registerRoute(navConfig);
let guideRoute = {
path: '/guide',
name: 'Guide',
redirect: '/guide/design',
component: require('./pages/guide.vue'),
children: [{
path: 'design',
name: 'Design',
component: require('./pages/design.vue')
}, {
path: 'nav',
name: 'Navigation',
component: require('./pages/nav.vue')
}]
};
let resourceRoute = {
path: '/resource',
name: 'Resource',
component: require('./pages/resource.vue')
};
let indexRoute = {
path: '/',
name: 'Home',
component: require('./pages/index.vue')
};
let dynaRoute = registerRoute(dynamicRouterConfig).route;
myroute.route = myroute.route.concat([indexRoute, guideRoute, resourceRoute]);
myroute.route.push({
path: '*',
component: require('./docs/home.md')
});
export const navs = myroute.navs;
export default myroute.route;
And dynamic.router.js:
module.exports = [
{
"name": "Edit",
"path": "/edit"
}
]
Now the static route config is woking fine ,but how can I load data from server side by ajax request in the route.config.js instead of static data.
Waiting for some async request at page render is fine, just set empty initial values in the data section of component like:
data() {
someStr: '',
someList: []
}
and make sure you handle the empty values well without undefined errors trying to read things like someList[0].foo.
Then when the request comes back, set the initially empty values to those real data you get from the request.
Giving the user some visual indicate that you're fetching the data would be a good practice. I've found v-loading in element-ui useful for that.