Guide to using Vue.js and Vue Router to create a sub menu for the children of the current route

I am currently working on customizing the navigation for my Vue application. My goal is to create a main menu at the top that displays all the root level routes, and a side menu that appears when navigating through child routes. Here is an example of what I'm trying to achieve:

The current design features the main navigation with routing links at the top and the router-view below it. I have successfully implemented the side menu to only display when selecting "Travellers" and it updates the components/content correctly. However, I am facing issues with routing. When clicking on a link in the sub-menu, it does not append to the current path. For example, when I click on "View" while on localhost/Traveler, the URL changes to localhost/View/ instead of localhost/Traveler/View. Additionally, the selection in the top menu gets unselected when choosing something in the child menu.

I also encounter difficulty accessing pages via paths like localhost/Traveler/View; only localhost/View seems to work.

As I started looking into the documentation on nested routes during this post, I realized that I may need to create a new router at each level, which I haven't done in my current code below.

Furthermore, I am unsure how to access the children of the current route. I attempted to display them using the following code:

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

However, I did not get any results. Should the children be passed as parameters or is there another method to access them easily?

Any guidance or assistance on this matter would be greatly appreciated.

Root

This section contains the top Menu-Nav with router links and the router-view component below it.

<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

This menu is populated with the available 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

This page represents the Traveller Page, featuring a sidebar menu and another router view for 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'

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

Vue.prototype.$http = axios

sync(store, router)

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

export {
  app,
  router,
  store
}

Please let me know if you require additional information, context, or code snippets.

Answer №1

To avoid creating new router instances, you can monitor the changes in the $route property and dynamically update the sidebar navigation menu based on those changes. By fetching the child routes from $router.options.routes, you can achieve this functionality. Below is a sample code snippet to demonstrate this approach:

const router = new VueRouter({
  routes: [{
      name: 'home',
      path: '/',
      component: {
        template: '<div>Home</div>'
      }
    },
    // More route configurations here
  ]
})

new Vue({
  el: '#app',
  router,
  data() {
    return {
      children: []
    }
  },
  watch: {
    $route: function(current) {
      const route = this.$router.options.routes.find(route => route.path === current.path)

      if (route && Array.isArray(route.children)) {
        this.children = route.children
      } else if (route) {
        this.children = []
      }
    }
  }
})
* {
  margin: 0;
  padding: 0;
}

html,
body,
#app {
  width: 100%;
  height: 100%;
}

// CSS styles for layout components here
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
  <!-- HTML markup for sidebar navigation and content display -->
</div>

Answer №2

After making some adjustments to DigitalDrifter's solution to locate the children using template syntax, I have a feeling that my modifications may not work beyond another level of children. Therefore, I still believe that DigitalDrifter has the superior answer.

I am also uncertain about how to maintain the sub-menu route (localhost/Traveller) so that it does not search the children of (locathost/Traveller/View) when clicking on something like "view." Currently, I am handling it in a somewhat makeshift manner:

v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.path||$route.path.includes(x.path))"

The complete SFC without any styling is provided below:

<template>
  <nav id="sidebar">
    <div class="sidebar-header">
      <h3>Bootstrap Sidebar</h3>
    </div>

    <ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.path||$route.path.includes(x.path))">
      <li v-for="child in route.children">
        <a class="nav-item" :key="index">
          <router-link :to="{path: route.path+'/'+child.path}" exact-active-class="active">
            <icon :icon="route.icon" class="mr-2" /><span>{{ child.path }}</span>
          </router-link>
        </a>
      </li>
    </ul>

  </nav>

</template>

<script>
  export default {
    data() {
      return {
      }
    },
    methods: {
    }
  }
</script>

Edit: I am aware that Vue contains the desired data associated with the router views, as shown in this image,

However, I am unsure how to access those properties.

Update 1/29/2019:

I have discovered that by utilizing $Route.matched[0].path, you can easily obtain the path information. More details can be found here.

As a result, I have managed to simplify the menu structure within an SFC like so:

<template>
    <nav id="sidebar">
      <div class="sidebar-header">
        <h3>Bootstrap Sidebar</h3>
      </div>

      <ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.matched[0].path)">
        <li v-for="child in route.children">
          <a class="nav-item"  :key="index">
            <router-link :to="route.path+'/'+child.path" exact-active-class="active">
              <icon :icon="route.icon" class="mr-2" /><span>{{ child.name }}</span>
            </router-link>
          </a>
        </li>
      </ul>

    </nav>
 
</template>

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

CSS styling is not included.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Check to see if it is possible to click an HTML div

Currently, I am engaged in Selenium and Robot Framework automated testing and in search of a method to automatically locate div tags that are capable of being clicked. The keyword Click Element works well when provided with a specific CSS selector, but ho ...

The PDF file appeared blank after receiving a response from the API using Node.js

When I call a REST API that returns a PDF file, the document appears blank when opened. The console indicates that the data may be corrupted. let url ="API-URL"; var options = { 'method': 'GET', 'url': url ...

The module ~/assets/images/flags/undefined.png could not be found in the directory

When I use the img tag with a dynamic address filled using require, it works well in the component. However, when I try to write a test for it, an error is thrown. console.error Error: Configuration error: Could not locate module ~/assets/ima ...

Dealing with Vue's performance problems when using input type color and v-model

I am encountering a problem with setting the v-model on an input of type color. Whenever I change the color, there is a noticeable drop in frame rate and the application's FPS spikes from 60 to 3. You can see it reflected in the Vue performance graph ...

Timeout for jQuery animations

My jQuery animation script is causing an issue where it does not finish the animation before returning to the parent function. Specifically, the JavaScript code changes a background color, calls the animate function, the animation starts but doesn't c ...

Seamless changes with graceful fades while transitioning between classes

Is it more efficient to handle this in CSS than using jQuery? I'm not entirely sure. If anyone has a solution, that would be greatly appreciated. However, I am currently facing an issue with the jQuery method I have implemented. It successfully fades ...

Transferring files to Django using AJAX

I am struggling with the process of uploading files to django using ajax. The upload is done within a modal window. The Form <div class="modal fade bs-example-modal-lg" id="fileUploadModal" role="dialog" aria-hidden="true"> <div class="modal ...

Is it possible to bypass the confirmation page when submitting Google Form data?

Is there a way to bypass the confirmation page that appears after submitting a form? What I would like is for the form to simply refresh with empty data fields and display a message saying "your data has been submitted" along with the submitted data appea ...

"Failure to manipulate the style of an HTML element using Vue's

<vs-tr :key="i" v-for="(daydatasT, i) in daydatas"> <vs-td>{{ daydatasT.Machinecd }}</vs-td> <vs-td>{{ daydatasT.Checkdt }}</vs-td> <vs-td>{{ daydatasT.CheckItemnm }}< ...

What is the process for obtaining a compilation of JavaScript files that are run when an event is triggered?

How can I generate a list of all the JavaScript files that are triggered when a button is clicked or an input field is selected in a complex system? Is it possible to achieve this with Chrome DevTools, or are there alternative solutions available? If Chro ...

The issue with the max-height transition not functioning properly arises when there are dynamic changes to the max-height

document.querySelectorAll('.sidebarCategory').forEach(el =>{ el.addEventListener('click', e =>{ let sub = el.nextElementSibling if(sub.style.maxHeight){ el.classList.remove('opened&apos ...

What is the speed of communication for Web Worker messages?

One thing I've been pondering is whether transmission to or from a web worker could potentially create a bottleneck. Should we send messages every time an event is triggered, or should we be mindful and try to minimize communication between the two? ...

Can you explain the contrast between EJS's render() function and renderFile() method?

const express = require('express'); const ejs = require('ejs'); const app = express(); app.engine('ejs', ejs.renderFile); app.get('/', (req, res) => { res.render('index.ejs', { ...

What are the steps for creating a new npm package based on an existing one?

I'm a newcomer to the node ecosystem and the npm package system. In my redux/react web app, I currently make use of the photoswipe package alongside react-photoswipe. Recently, I decided to extend the functionality of the photoswipe package by making ...

Full-screen modal fade not functioning properly

I am trying to implement multiple fullscreen modals on a single page, but I am facing an issue where they slide in and out at an angle instead of just fading in and out. I have been unable to achieve the desired effect of a simple fade transition. If you ...

What is the best way to create a floating navigation bar that appears when I tap on an icon?

As a beginner in the realm of React, I recently explored a tutorial on creating a navigation bar. Following the guidance, I successfully implemented a sidebar-style navbar that appears when the menu icon is clicked for smaller screen sizes. To hide it, I u ...

What is the best way to manipulate the canvas "camera" in HTML?

I am currently developing a 3D game that involves navigating through skyboxes. My goal is to have the camera respond to user input, specifically detecting key presses (using WASD controls) to move the camera accordingly. Do you have any suggestions on how ...

Making sure to consistently utilize the service API each time the form control is reset within Angular 4

In the component below, an external API service is called within the ngOnInit function to retrieve an array of gifs stored in this.items. The issue arises when the applyGif function is triggered by a user clicking on an image. This function resets the For ...

In my Express Javascript application, I recently encountered a challenge where I needed to export a const outside of another

I am currently working with a file named signupResponse.js const foundRegisterUser = function(err, foundUser){ if(!err){ console.log("No errors encountered") if(!foundUser){ if(req.body.password == req.body. ...

Retrieve Next Element with XPath

I recently started working with XPATH and have a few questions about its capabilities and whether it can achieve what I need. The XML Document structure I am dealing with is as follows: <root> <top id="1"> <item id="1"> < ...