Vue.js – New routes are always placed on top of current route - vue.js

In my Vue.js application I use a navigation drawer on the left side of the screen for navigation purposes. I am using Vuetify and Vue Router.
Problem
After changing my routes a lot of my previously routing functionalities got messed up. Whenever a user clicks on an element to navigate to a new site the route get's attached on top of the previous one like this.
Route before a user clicks on a link:
http://localhost:8080/organization/:organization_id/planner/events
New route after a user clicks on a link:
http://localhost:8080/organization/:organization_id/planner/management/contracts
Router
routes: [
// Login
{
path: '/login',
component: Login,
},
// Organization
{
path: '/organization',
component: Organization,
},
// Organization Detail Page
{
path: '/organization/:organization_id',
component: OrganizationDetail,
children: [
// Module: Planner
{
path: 'planner',
component: Planner
},
// Planner > Events
{
path: 'planner/events',
component: Events
},
{
path: 'planner/events/new',
component: NewEvent
},
{
path: 'planner/events/details/:event_id',
component: EventDetails
},
// Planner > Calendar
{
path: 'planner/calendar',
component: Calendar
},
// Module: Management
{
path: 'management',
component: Management
},
// Management > Users
{
path: 'management/users',
component: Events
},
{
path: 'management/users/new',
component: NewUser
},
{
path: 'management/users/details/:user_id',
component: UserDetails
},
// Planner > Contracts
{
path: 'management/contracts',
component: Contracts
}
]
}
]
NavigationDrawer.vue (updated):
<template>
<div id="navigation">
<nav>
<v-navigation-drawer
v-model="drawer"
:clipped="$vuetify.breakpoint.lgAndUp"
width="400"
fixed
app
>
<v-list>
<template v-for="item in items">
<v-row
v-if="item.heading"
:key="item.heading"
align="center"
>
</v-row>
<v-list-group
v-else-if="item.children"
:key="item.text"
v-model="item.model"
append-icon="mdi-chevron-down"
class="nav"
link
router :to="item.route"
>
<v-list-item
v-for="(child, i) in item.children"
:key="i"
link
router :to="child.route"
>
<v-list-item-action v-if="child.icon">
<v-icon>{{ child.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>
{{ child.text }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-group>
</template>
</v-list>
</v-navigation-drawer>
<v-app-bar
:clipped-left="$vuetify.breakpoint.lgAndUp"
app
dark
>
<v-app-bar-nav-icon #click.stop="drawer = !drawer"></v-app-bar-nav-icon>
</v-app-bar>
</nav>
</div>
</template>
<script>
export default {
name: 'NavigationDrawer',
el: '#app',
data: () => ({
drawer: true,
menu: false,
items: [
{
text: 'Planner',
children: [
{ text: 'Events', route:'/organization/' + this.$route.params.organization_id + '/planner/events'},
{ text: 'Calendar', route:'/organization/' + this.$route.params.organization_id + '/planner/calendar'}
],
},
{
text: 'Management',
children: [
{ text: 'Users', route:'/organization/' + this.$route.params.organization_id + '/management/users'},
{ text: 'Contracts', route:'/organization/' + this.$route.params.organization_id + '/management/contracts'}
],
},
]
}),
methods: {
logout() {
// some awesome stuff
}
}
}
</script>
Other routes like (NewUser, NewEvent) are not stored in the navigation drawer and called manually by a button.
I know that this has something to do with my routes. However those aren't misconfigured because the relations between parent and children are important for displaying the right content and implementing some header informations. The navigation drawer works also the right way. I didn't change anything there.
Also if I use router.back() on NewEvent or NewUser nothing happens. :(
Help is very much appreciated, thanks!

The issue is because of
{ text: 'Events', route:'/planner/events'},
The above route redirects the website to http://localhost:8080/planner/events because of initial forward slash.
Use the $route.params to get params from URL like the following.
<div>User {{ $route.params.organization_id }}</div>
or
this.$route.params.organization_id

Related

Vue: I want to send data to another page

I have a page that lists employees.
<template>
<v-container>
<v-card
class="mb-6 pa-2 mx-auto rounded-lg"
max-width="1000"
color=""
v-for="user in users"
:key="user.id"
>
............
<v-btn
class="mb-3 mt-3"
v-on:click="sendConfirm(user.ID)"
to="/companyApplicants/menteeListPage"
color="green"
>
<v-icon>mdi-clipboard-account</v-icon>
</v-btn>
...........
</v-col>
</v-list-item>
</v-card>
</v-container>
</template>
<script>
export default {
data() {
return {
userDatas: [],
users: [
{
.....
},
],
}
},
mounted() {
this.getUserData()
},
methods: {
getUserData() {
return this.$axios.$get('/api/MyMentors').then((response) => {
this.users = response
console.log(response)
})
},
},
}
</script>
And when they press the button and go to the other page I want to send Id of the clicked mentor. I can get the Id but couldn't figure out how to send that Id to the other page
you can add the id to the route
<v-btn
class="mb-3 mt-3"
v-on:click="sendConfirm(user.ID)"
:to="`/companyApplicants/menteeListPage/${user.ID}`"
color="green"
>
<v-icon>mdi-clipboard-account</v-icon>
</v-btn>
just need to update your route to have the data available there
{
path: "/companyApplicants/menteeListPage/:userID",
name: "menteeListPage",
component: () => import(/* webpackChunkName: "views" */ "./views/MenteeListPage.vue"),
meta: {
requiresAuth: true,
title: "Menteen List Page {{userID}}",
},
},
your variable will then be accessible in the vue components through this.$route.params.userID
If you want to make it optional for the route use ? at the end :
path: "/companyApplicants/menteeListPage/:userID?",

Separate navigation drawers for certain routes in vue-router

I'm building a Vuetify app in combination with Vuex and vue-router. Some of the views uses the default navigation drawer, but others has different items in their navigation drawers. This documentation say I can pass props to view components. So I implement it like this:
routes/index.js
{
path: '/courses/:courseId/lessons/:lessonId',
name: 'Course 1',
components: {
default: () => import('#/views/ViewLesson.vue'),
sidebar: () => import('#/components/CourseNavBar/CourseNavBar.vue')
},
props: {
items: [
{ text: "Link 1", href:"/link1" },
{ text: "Link 2", href:"/link2" }
]
}
}
src/App.vue
<template>
<v-app>
<v-app-bar
app
color="primary"
dark
>
<h1>My Project</h1>
</v-app-bar>
<v-navigation-drawer><router-view :items="items" name="sidebar"/></v-navigation-drawer>
<v-content>
<router-view />
</v-content>
</v-app>
</template>
But apparently,
src/components/CourseNavBar.vue
<template>
<!-- <v-navigation-drawer :value="1"> -->
<v-list dense>
<navbar-item v-for="(item, i) in items" :key="i" :item="item" >
{{ item.text }}
</navbar-item>
</v-list>
<!-- </v-navigation-drawer> -->
</template>
<script>
import NavBarItem from './NavBarItem.vue'
export default {
props: {
items: Array
},
components: {
'navbar-item': NavBarItem
}
}
</script>
But <CourseNavBar>'s props is still undefined. What am I doing wrong here?
There are a few issues...
Replace = with :...
items: [
{ text:"Link 1", href:"/link1" },
{ text:"Link 2", href:"/link2" }
]
And the sidebar component (not router-view) should be in the slot for the navigation-drawer...
<v-navigation-drawer><sidebar :items="items"></sidebar></v-navigation-drawer>
Demo: https://codeply.com/p/oNInfpTwvK
In your
routes\index.js
you need to define the props option for each of the named views:
{
path: '/courses/:courseId/lessons/:lessonId',
name: 'Course 1',
components: {
default: () => import('#/views/ViewLesson.vue'),
sidebar: () => import('#/components/CourseNavBar/CourseNavBar.vue')
},
props: {
default: true,
sidebar: true,
items: [
{ text: "Link 1", href: "/link1" },
{ text: "Link 2", href: "/link2" }
]
}
}
And following on from what #Zim said, you've used "=" instead of ":" when assigning the href value to the props items array.
You can use <router-view name="sidebar"/> to output the named component.

Vue Router Active Link Not working on parent but works on children

My Setup
I have vue application using vue router with nested routes. I'm using vuetify as well. My issue is this....
My Issue
When I navigate on the Navbar, the active class isn't moving on the navbar. The menu item has a border-bottom on it to be able to have a "selected" feel to it.
In the code below are my routes. This pretty much speaks to the structure of my project too.
My approach is Views/Topic/ and I have a Page/Home/History/Detail
Expectation of my navbar functionality:
Approvals -- If you are on any of these these below, This will be highlighted
-> ApprovalPage -- This contains the menu and a router-view in a column
-> ApprovalHome -- This is a menu item in the approval page. It will be highlighted if it is selected
-> ApprovalHistory -- This is a menu item in the approval page. It will be highlighted if it is selected
-> ApprovalDetail -- This is a menu item in the approval page. It will be highlighted if it is selected
Example of my folder setup:
Approvals
-> ApprovalPage -- This contains the menu and a router-view in a column
-> ApprovalHome -- This will be rendered on the Approval Page
-> ApprovalHistory -- This will be rendered on the Approval Page
-> ApprovalDetail -- This will be rendered on the Approval Page
The navigation menu inside the ApprovalPage can navigate and highlight correctly. However, it's just the top nav that doesn't move.
The other thing is, everything renders correctly. It's just the active part of the top nav that doesn't work.
Here is my code
Routes
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/requests',
name: 'requests',
component: RequestsPage,
children: [
{
path: '',
name: 'RequestsHome',
component: RequestsHome,
},
{
path: 'history',
name: 'RequestHistory',
component: RequestHistory,
},
{
path: ':id',
name: 'RequestDetail',
component: RequestDetail,
},
],
},
{
path: '/approvals',
name: 'approvals',
component: ApprovalsPage,
children: [
{
path: '',
name: 'ApprovalsHome',
component: ApprovalHome,
},
{
path: 'history',
name: 'ApprovalsHistory',
component: ApprovalHistory,
},
{
path: ':id',
name: 'ApprovalsDetail',
component: ApprovalDetail,
},
],
},
{
path: '/workflows',
name: 'workflows',
component: WorkflowPage,
children: [
{
path: '',
name: 'workflowsHome',
component: WorkflowHome,
},
{
path: 'creator',
name: 'creator',
component: WorkflowCreator,
},
],
},
],
});
NavBar toolbar items
<v-toolbar-items
class="ml-5"
>
<v-btn
text
to="/"
exact
>
Home</v-btn>
<v-btn
text
to="/approvals/"
>
Approvals</v-btn>
<v-btn
text
to="/requests/"
>
Requests</v-btn>
<v-menu
tile
right
offset-y
open-on-hover
>
<template v-slot:activator="{ on }">
<v-btn
text
v-on="on"
to="/workflows/"
>
Workflows</v-btn>
</template>
<v-list>
<v-list-item to="/workflows/" exact>
<v-list-item-title>Workflows</v-list-item-title>
</v-list-item>
<v-list-item to="/workflows/creator" exact>
<v-list-item-title>Workflow Creator</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-toolbar-items>

Dynamic Buttons in Navigation Bar using Vuetify

I have a problem...
My VueJS Application using Vuetify.
I have a <v-toolbar>, and on the right, I want to place some buttons that change depending on the component shown in <router-view>, but i can't access to component properties from $route or $route for get objects and methods bind to model of my component.
I would like to know if there is any way to assign a model to from my main component.
I have tried with "named-routes" but I do not know what is the way that properties can be shared between components that are managed by an <router-view> and updated live.
In resume:
I have my application skeleton with a navigation bar, additionally in the dynamic content I have a <router-view>. Depending on the component that is displayed in <router-view>, I would like to see buttons in the navigation bar corresponding to that component, which interact and change the data or execute methods of the component.
App.vue
<template>
<v-app>
<router-view></router-view>
</v-app>
</template>
<script>
export default {
name: 'App',
data() {
return {
};
}
};
</script>
index.js (router)
import Vue from 'vue'
import Router from 'vue-router'
import AppLogin from '#/components/AppLogin'
import Skeleton from '#/components/Skeleton'
import ShoppingCart from '#/components/ShoppingCart'
import ShoppingCartButtons from '#/components/ShoppingCartButtons'
import ProductSelection from '#/components/ProductSelection'
import ProductSelectionButtons from '#/components/ProductSelectionButtons'
import ProductDetail from '#/components/ProductDetail'
Vue.use(Router)
export default new Router({
routes: [
{
path : '/login',
name : 'AppLogin',
component : AppLogin
},
{
path : '/app',
name : 'Skeleton',
component : Skeleton,
children : [{
path : 'shopping-cart',
components : {
navigation : ShoppingCart,
navButtons : ShoppingCartButtons
}
}, {
path: 'product-selection',
name : 'ProductSelection',
components : {
navigation : ProductSelection,
navButtons : ProductSelectionButtons
}
},
{
path: 'product-detail',
name : 'ProductDetail',
components : {
navigation : ProductDetail
},
props : true
}
]
}
]
})
Skeleton.vue
<template>
<v-container fluid>
<v-navigation-drawer
persistent
:mini-variant="miniVariant"
:clipped="true"
v-model="drawer"
enable-resize-watcher
fixed
app
>
<v-list>
<v-list-tile
value="true"
v-for="(item, i) in items"
:key="i"
:to="item.path">
<v-list-tile-action>
<v-icon v-html="item.icon"></v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title v-text="item.title"></v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
</v-navigation-drawer>
<v-toolbar
app
:clipped-left="clipped"
>
<v-toolbar-side-icon #click.stop="drawer = !drawer">
</v-toolbar-side-icon>
<v-toolbar-title v-text="$route.meta.title"></v-toolbar-title>
<v-spacer></v-spacer>
<router-view name="navButtons"></router-view>
</v-toolbar>
<v-content>
<router-view name="navigation"/>
</v-content>
<v-footer :fixed="true" app>
<p style="text-align : center; width: 100%">© CONASTEC 2018</p>
</v-footer>
</v-container>
</template>
<script>
export default {
data() {
return {
clipped: true,
drawer: false,
fixed: false,
items: [
{
icon: "shopping_cart",
title: "Carrito de Compras",
path : "/app/shopping-cart"
},
{
icon: "attach_money",
title: "Facturas"
},
{
icon: "account_balance_wallet",
title: "Presupuestos"
},
{
icon: "insert_chart",
title: "Informes"
},
{
icon: "local_offer",
title: "Productos"
},
{
icon: "person",
title: "Clientes"
},
{
icon: "layers",
title: "Cuenta"
},
{
icon: "comment",
title: "Comentarios"
},
{
icon: "settings",
title: "Ajustes"
}
],
buttons : [],
miniVariant: false,
right: true,
rightDrawer: false
};
},
name: "Skeleton"
};
</script>
EDITED
My solution is create a new component Toolbar and add slots for buttons to right and left.
<template>
<div>
<v-navigation-drawer persistent :mini-variant="false" :clipped="true" v-model="drawer" enable-resize-watcher fixed app>
<v-list>
<v-list-tile value="true" v-for="(item, i) in items" :key="i" :replace="true" :to="item.path">
<v-list-tile-action>
<v-icon v-html="item.icon"></v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title v-text="item.title"></v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
</v-navigation-drawer>
<v-toolbar app :clipped-left="true" color="primary" :dark="true" flat>
<v-toolbar-side-icon v-if="showDrawer" #click.stop="drawer = !drawer">
</v-toolbar-side-icon>
<v-toolbar-side-icon v-if="!!back" #click="back">
<v-icon>keyboard_backspace</v-icon>
</v-toolbar-side-icon>
<v-toolbar-title v-text="title" style="font-size: 1.4em"></v-toolbar-title>
<v-spacer></v-spacer>
<v-card-actions>
<slot name="right"></slot>
</v-card-actions>
</v-toolbar>
<v-snackbar
:timeout="5000"
:top="true"
:multi-line="true"
:vertical="true"
v-model="snackbar.show"
>
{{ snackbar.content }}
<v-btn flat color="white" #click.native="snackbar.show = false">Cerrar</v-btn>
</v-snackbar>
</div>
</template>
<script>
export default {
name: 'app-toolbar',
props: ['title','showDrawer', 'back'],
data() {
return {
drawer : false,
items: [{
icon: "shopping_cart",
title: "Carrito de Compras",
path: "/carrito-compras"
}, {
icon: "attach_money",
title: "Facturas",
path: "/documentos-tributarios"
}, {
icon: "account_balance_wallet",
title: "Presupuestos"
}, {
icon: "insert_chart",
title: "Informes"
}, {
icon: "local_offer",
title: "Productos"
}, {
icon: "person",
title: "Clientes"
}, {
icon: "layers",
title: "Cuenta"
}, {
icon: "comment",
title: "Comentarios"
}, {
icon: "settings",
title: "Ajustes"
}]
};
},
computed : {
snackbar() {
return this.$store.getters.snackbar;
}
}
}
</script>
and use is:
<app-toolbar title="Carrito de Compras" :showDrawer="true">
<template slot="right">
<v-toolbar-side-icon #click="confirm">
<v-icon>monetization_on</v-icon>
</v-toolbar-side-icon>
</template>
</app-toolbar>
I did the same thing as you in a recent project and found altering the structure was the easier way to fix issues like this.
My structure was as follows:
app.vue: Only contains <router-view> no other components
router.js: Parent route is a layout component, all sub routes which contains my toolbars and other layout components and it's own <router-view> which receives child routes
ex:
{
path: '/login',
name: 'Login',
component: load('login')
},
{
path: '/',
component: load('main-layout'),
children: [
{
path: '',
name: 'Home Page',
component: load('homePage')
},
{
path: '/settings',
name: 'Settings',
component: load('settings'),
}
]
}
Now in your main-layout:
computed: {
showHomeButton () {
if (this.$route.path === '/') {
return true
}
return false
// Repeat for other routes, etc...
},
}
If you are using Vuex, you can use vuex-router-sync, then you can
access the route from any component with this.$state.route.path.
If not, Scott's answer is probably the best way to do it.
I had the same problem, My solution was manage the left button action from de meta of vue router like this:
{
path: '/feedstocks/:categoryId/:id',
name: 'Feedstock',
component: () =>
import(
/* webpackChunkName: "client-chunk-feedstock-details" */ '#/views/FeedstockDetails.vue'
),
props: true,
meta: {
authNotRequired: true,
backRoute: 'Material'
}
}
Then I'm able to check that metadata in app bar button action:
<v-app-bar app color="primary" dark>
<v-btn text icon color="white" #click="leftButtonAction">
<v-icon>{{ leftButtonIcon }}</v-icon>
</v-btn>
<v-toolbar-title>
{{ currentAppTitle }}
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
leftButtonIcon() {
if (this.$route.meta.backRoute) {
return 'mdi-chevron-left'
}
return 'mdi-menu'
}
leftButtonAction() {
if (this.$route.meta.backRoute) {
this.$router.push({ name: this.$route.meta.backRoute })
} else {
this.toggleDrawer()
}
}
I guess it's a time for a more up-to-date answer.
For my requirement to dynamically modify the item list in the app-bar based on the selected view, the solution was to use the Named Views

vue-router, can't refresh routes with dynamic id's

I'm having trouble with my vue-router. I've copied a portion of my router for you to view and also a portion of the component in which I am using the the router-view. As you can see, I am using props as it's my understanding that props are the advanced way of passing $router.params. Also, this is a nested router-view. I can route to and from the nested routes just fine, but when I click refresh, I get these errors in the console.
"Failed to load resource: the server responded with a status of 404 (Not Found)"
"GET http://localhost:8080/distilleries/1/dist/build.js net::ERR_ABORTED"
I am wondering if anyone can help.
{
path: '/distilleries/:id',
component: Distillery,
props: true,
children: [
{
path: '',
component: DistilleryAbout,
name: 'DistilleryAbout',
props: true
},
{
path: 'spirits',
props: true,
component: DistilleryProducts,
name: 'DistilleryProducts'
},
{
path: 'awards',
props: true,
component: DistilleryAwards,
name: 'DistilleryAwards'
},
{
path: 'reviews',
props: true,
component: DistilleryReviews,
name: 'DistilleryReviews'
},
{
path: 'recipes',
props: true,
component: DistilleryRecipes,
name: 'DistilleryRecipes'
},
{
path: 'media',
props: true,
component: DistilleryMedia,
name: 'DistilleryMedia'
}
]
}
<v-flex xs12>
<v-toolbar class="lb" flat>
<v-toolbar-items style="margin: 0 auto">
<v-layout row wrap>
<v-flex xs6 md12 class="mt-3">
<router-link
tag="span"
v-for="items in menuItems"
:key="items.id"
class="itemsButton ma-3 pa-3 cb"
:to="{ name: items.link }"
>{‌{ items.title }}
</router-link>
</v-flex>
</v-layout>
</v-toolbar-items>
</v-toolbar>
<router-view :distillery="distillery"></router-view>
</v-flex>
From vue-router docs:
Since our app is a single page client side app, without a proper
server configuration, the users will get a 404 error if they access
http://oursite.com/user/id directly in their browser. Now that's ugly.
So you need to add mode: 'history' in router:
const router = new VueRouter({
mode: 'history',
routes: [...],
})
And add configuration depending on your server.