Skip to content
Fix Code Error

How to implement sub menu of the current route’s children with vue.js and vue router

March 23, 2021 by Code Error
Posted By: Anonymous

I’m trying to have the navigation on my Vue app to have a main menu up top populated by all the root level routes, and a side menu if the route has any children. Like so:

Goal

The current Design I have has the main navigation with routing links on the top with the router-view below. I have been able to get the side menu working so it it only shows up when I choose Travellers and updates the components/content correctly. However I’m having trouble routing things correctly, when I click on one of the links in the sub menu it does not append to the current path. So when I click on View when I’m in localhost/Traveler and click View the url changes to localhost/View/ instead of localhost/Traveler/View. Also the selection on the top menu gets unselected when I choose something in the child menu.

Unselecting

And I cannot get to the pages via something like localhost/Traveler/View only localhost/View

I started rereading the documentation on nested routes as I began making this post and I think I realized that I should be creating an new router at each level which is not something I have done in my code below.

I’m also not sure how to access the children of the current route. I’ve tried to display them like so:

  <h2>Route: {{ $route.name }}</h2>
  <ul id="example-1">
    <li v-for="child in $route.children">
      {{ child.name }}
    </li>
  </ul>

But I get nothing. Should they be passed as Params or something? Or are the not that easily accessible?

Any advice or help will be greatly appreciated.

Root

Contains top Menu-Nav with router links and router-view below.

<template>
  <div id="app" class="container-fluid">
    <div class="row">
      <div style="width:100%">
        <nav-menu params="route: route"></nav-menu>
      </div>

    </div>

    <div class="row">
      <div>
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>
<script>
  import NavMenu from './nav-menu'

  export default {
    components: {
      'nav-menu': NavMenu
    },

    data() {
      return {}
    }
  }
</script>

Top Nav-Menu

Gets populated with the routes

<template>
  <nav class="site-header sticky-top py-1">
    <div class="container d-flex flex-column flex-md-row justify-content-between">
      <a class="nav-item" v-for="(route, index) in routes" :key="index">
        <router-link :to="{path: route.path, params: { idk: 1 }}" exact-active-class="active">
          <icon :icon="route.icon" class="mr-2" /><span>{{ route.display }}</span>
        </router-link>
      </a>
    </div>
  </nav>
</template>
<script>
  import { routes } from '../router/routes'

  export default {
    data() {
      return {
        routes,
        collapsed: true
      }
    },
    methods: {
      toggleCollapsed: function (event) {
        this.collapsed = !this.collapsed
      }
    }
  }
</script>

Traveler Page/View

Currently the Traveller Page which has a side bar menu and another router view for the content:

<template>
  <div id="app" class="container-fluid">
    <div class="wrapper">
      <traveler-menu params="route: route"></traveler-menu>

      <div id="content">
        <router-view name="travlerview"></router-view>
        </div>
      </div>
    </div>
</template>
<script>
  import TravelerMenu from './traveler-menu'
  export default {
    components: {
      'traveler-menu': TravelerMenu
    },
    data() {
      return {}
    }
  }
</script>

Side Bar/ Traveler Menu

<template>
    <nav id="sidebar">
      <div class="sidebar-header">
        <h3>Route's Children:</h3>
      </div>

      <ul class="list-unstyled components">
        <li>
          <a class="nav-item" v-for="(route, index) in travelerroutes" :key="index">
            <router-link :to="{path: route.path, params: { idk: 1 }}" exact-active-class="active">
              <icon :icon="route.icon" class="mr-2" /><span>{{ route.display }}</span>
            </router-link>
          </a>
        </li>
      </ul>

    </nav>

</template>

<script>
    import { travelerroutes } from '../../router/travelerroutes'
    export default {
    data() {
      console.log(travelerroutes);
        return {
          travelerroutes,
          collapsed: true
        }
      },
      methods: {
        toggleCollapsed: function (event) {
          this.collapsed = !this.collapsed
        }
      }
    }
</script>

Routes

import CounterExample from 'components/counter-example'
import FetchData from 'components/fetch-data'
import HomePage from 'components/home-page'
import TestPage from 'components/test-page'
import Travelers from 'components/Traveler/traveler-root'
import { travelerroutes } from './travelerroutes'

export const routes = [
  { name: 'home', path: '/', component: HomePage, display: 'Home', icon: 'home' },
  { name: 'counter', path: '/counter', component: CounterExample, display: 'Counter', icon: 'graduation-cap' },
  { name: 'fetch-data', path: '/fetch-data', component: FetchData, display: 'Fetch data', icon: 'list' },
  { name: 'test-page', path: '/test-page', component: TestPage, display: 'Test Page', icon: 'list' },
  {
    name: 'traveler-root', path: '/traveler', component: Travelers, display: 'Travelers', icon: 'list', children: travelerroutes
  }
]

Traveler Routes (travelerroutes.js)

import TestPage from 'components/test-page'
import ViewTravelers from 'components/Traveler/TravelerPages/view-travelers'

export const travelerroutes = [{
  name: 'View',
  path: '/View',
  display: 'View', icon: 'list',
  components: {
    travlerview: TestPage
  }
},
  {
    name: 'Create',
    path: '/Create',
    display: 'Create', icon: 'list',
    components: {
      travlerview: ViewTravelers
    }
  },
  {
    name: 'Edit',
    path: '/Edit',
    display: 'Edit', icon: 'list',
    components: {
      travlerview: ViewTravelers
    }
  }];

router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import { routes } from './routes'

Vue.use(VueRouter)

let router = new VueRouter({
  mode: 'history',
  routes
})

export default router

app.js

import Vue from 'vue'
import axios from 'axios'
import router from './router/index'
import store from './store'
import { sync } from 'vuex-router-sync'
import App from 'components/app-root'
import { FontAwesomeIcon } from './icons'

// Registration of global components
Vue.component('icon', FontAwesomeIcon)

Vue.prototype.$http = axios

sync(store, router)

const app = new Vue({
  store,
  router,
  ...App
})

export {
  app,
  router,
  store
}

Let me know if you you need anymore details, context or code.

Solution

You don’t need to create new router instances, instead watch the $route property and create the sidebar nav menu as it changes. You’ll need to pull the child routes from the $router.options.routes. Here’s an example:

_x000D_

_x000D_

const router = new VueRouter({_x000D_
  routes: [{_x000D_
      name: 'home',_x000D_
      path: '/',_x000D_
      component: {_x000D_
        template: '<div>Home</div>'_x000D_
      }_x000D_
    },_x000D_
    {_x000D_
      name: 'foo',_x000D_
      path: '/foo',_x000D_
      component: {_x000D_
        template: '<div>Foo<router-view></router-view></div>'_x000D_
      },_x000D_
      children: [{_x000D_
        name: 'foo.baz',_x000D_
        path: '/baz',_x000D_
        component: {_x000D_
          template: '<div>Baz</div>'_x000D_
        }_x000D_
      }, {_x000D_
        name: 'foo.tar',_x000D_
        path: '/tar',_x000D_
        component: {_x000D_
          template: '<div>Tar</div>'_x000D_
        }_x000D_
      }]_x000D_
    },_x000D_
    {_x000D_
      name: 'bar',_x000D_
      path: '/bar',_x000D_
      component: {_x000D_
        template: '<div>Bar<router-view></router-view></div>'_x000D_
      },_x000D_
      children: [{_x000D_
        name: 'bar.zim',_x000D_
        path: '/zim',_x000D_
        component: {_x000D_
          template: '<div>Zim</div>'_x000D_
        }_x000D_
      }, {_x000D_
        name: 'bar.zug',_x000D_
        path: '/zug',_x000D_
        component: {_x000D_
          template: '<div>Zug</div>'_x000D_
        }_x000D_
      }]_x000D_
    }_x000D_
  ]_x000D_
})_x000D_
_x000D_
new Vue({_x000D_
  el: '#app',_x000D_
  router,_x000D_
  data() {_x000D_
    return {_x000D_
      children: []_x000D_
    }_x000D_
  },_x000D_
  watch: {_x000D_
    $route: function(current) {_x000D_
      const route = this.$router.options.routes.find(route => route.path === current.path)_x000D_
_x000D_
      if (route && Array.isArray(route.children)) {_x000D_
        this.children = route.children_x000D_
      } else if (route) {_x000D_
        this.children = []_x000D_
      }_x000D_
    }_x000D_
  }_x000D_
})

_x000D_

* {_x000D_
  margin: 0;_x000D_
  padding: 0;_x000D_
}_x000D_
_x000D_
html,_x000D_
body,_x000D_
#app {_x000D_
  width: 100%;_x000D_
  height: 100%;_x000D_
}_x000D_
_x000D_
#top {_x000D_
  border: 1px solid black;_x000D_
}_x000D_
_x000D_
#top ul {_x000D_
  display: flex;_x000D_
  flex-direction: row;_x000D_
  justify-content: flex-start;_x000D_
  list-style-type: none;_x000D_
}_x000D_
_x000D_
li {_x000D_
  padding: 1rem;_x000D_
  text-align: center;_x000D_
  text-transform: uppercase;_x000D_
}_x000D_
_x000D_
li a {_x000D_
  text-decoration: none;_x000D_
  font-weight: bold;_x000D_
  color: black;_x000D_
}_x000D_
_x000D_
#sidebar {_x000D_
  height: 50%;_x000D_
  width: 100px;_x000D_
  border: 1px solid black;_x000D_
}_x000D_
_x000D_
#content {_x000D_
  width: 50%;_x000D_
}_x000D_
_x000D_
#content,_x000D_
#content>div {_x000D_
  display: flex;_x000D_
  flex-direction: column;_x000D_
  justify-content: space-around;_x000D_
  align-items: center;_x000D_
}

_x000D_

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>_x000D_
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>_x000D_
<div id="app">_x000D_
  <div id="top">_x000D_
    <ul>_x000D_
      <li v-for="item in $router.options.routes" :key="item.path" :style="{ 'background-color': $route.name.includes(item.name) ? 'rgba(197,225,165,1)' : 'white' }">_x000D_
        <router-link :to="item.path">{{ item.name }}</router-link>_x000D_
      </li>_x000D_
    </ul>_x000D_
  </div>_x000D_
  <div style="display: flex; flex-direction: row; height: 100%; width: 100%;">_x000D_
    <div id="sidebar">_x000D_
      <ul>_x000D_
        <li v-for="item in children" :key="item.path" :style="{ 'background-color': $route.name === item.name ? 'rgba(197,225,165,1)' : 'white' }">_x000D_
          <router-link :to="item.path">{{ item.name.split('.').pop() }}</router-link>_x000D_
        </li>_x000D_
      </ul>_x000D_
    </div>_x000D_
    <div id="content">_x000D_
      <router-view></router-view>_x000D_
    </div>_x000D_
  </div>_x000D_
</div>

_x000D_

_x000D_

_x000D_

Answered By: Anonymous

Related Articles

  • Convert array to nested JSON object - Angular Material tree
  • Design DFA accepting binary strings divisible by a number…
  • Having trouble with my nav bar/header, It used to work but…
  • How to separate opening and closing tag by conditional…
  • How do I keep only the first map and when the game is…
  • Polymer routing: inner routes not reflecting
  • Easy interview question got harder: given numbers 1..100,…
  • Ubuntu apt-get unable to fetch packages
  • Emberjs 1.0-pre router can't find state for path and says…
  • After a little scroll, the sticky navbar just is not fixed…

Disclaimer: This content is shared under creative common license cc-by-sa 3.0. It is generated from StackExchange Website Network.

Post navigation

Previous Post:

Vue.js aplying phone number format

Next Post:

Vuejs – How to get all unique values in a array (remove duplicates) using v-for

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • Get code errors & solutions at akashmittal.com
© 2022 Fix Code Error