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:

https://i.sstatic.net/C0VFJ.png

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.

https://i.sstatic.net/zb0bx.png

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,

https://i.sstatic.net/kXATk.png

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

Changing array indices in JavaScript by splicing elements

I'm experiencing a curious issue that seems to involve overwriting array indices after splicing, or at least that's what I suspect. This problem arises in a small game project built using phaser 2 - essentially, it's a multiplayer jumping ga ...

Website API: decouple backend and frontend functionality through an API

I am currently working on the development of a website and an app created through Cordova. The app will essentially mirror the functionalities of the website. The website is already established and heavily relies on JavaScript, with potential considerati ...

The React useEffect hook runs whenever there is a change in the state

Within my React component, I have the following code snippet (excluding the return statement for relevance): const App = () => { let webSocket = new WebSocket(WS_URL); const [image, setImage] = useState({}); const [bannerName, setBannerName] = use ...

Turn off the ability to drag on an HTML page

Need help with creating an HTML etch-a-sketch! I have a div container with multiple div elements inside it, all set up with CSS grid display. HTML structure: <div id="canvas"></div> To populate the canvas with div elements, I'v ...

"multer's single file upload functionality is not functioning properly, but the array upload

Currently, I am facing an issue while using react js to upload a single file. Interestingly, the multer.single() method seems to fail, whereas the multer.array() method works perfectly fine. What could be causing this problem? //Node.js const upload = mult ...

Tips for troubleshooting the flowbite modal issue in vue js

Currently, I am utilizing Vue.js along with Tailwind CSS and Flowbite for component library support. However, I am encountering an issue with the modal component from Flowbite. Despite following the installation and configuration steps outlined in the docu ...

Exploring Angular 4: Embracing the Power of Observables

I am currently working on a project that involves loading and selecting clients (not users, but more like customers). However, I have encountered an issue where I am unable to subscribe to the Observables being loaded in my component. Despite trying vario ...

JEST does not include support for document.addEventListener

I have incorporated JEST into my testing process for my script. However, I have noticed that the coverage status does not include instance.init(). const instance = new RecommendCards(); document.addEventListener('DOMContentLoaded', () => ...

When an AJAX call is made during a PHP session that has timed out

I am working on an AJAX form that handles data authentication. In the event of a session timeout, I need to implement a redirect to the login page. How can I go about achieving this? Here is an excerpt from my simplified server-side code: function doExecu ...

The `background-size` property in jQuery is not functioning as expected

I am facing the following issue: When I click on a div with absolute positioning, I want to animate its position, width, and height. In addition, I want to change the background-size using jQuery. However, the problem is that all CSS properties are updat ...

Avoid displaying identical items when rendering a page from JSON data

I am using ajax and json to render a page. The structure of my json is as follows: {"status":"ok","rewards":[{"id":201,"points":500},{"id":202,"points":500}]}. I want to load the data using ajax only once if 'points' have duplicates in any of the ...

Adjusting the size of MUI StaticDatePicker

Struggling to resize the MUI staticDatePicker component. It seems the only way is to adjust the sub-components individually, but I can't locate all of them. Here's what I've managed so far: <Field as={StaticDatePicker} id='bookin ...

Adjust the map automatically as the cursor approaches the map's edge in Google Maps API V3

My latest project involved creating a selection tool using the Rectangle shape tool. With this tool, users can easily select markers by drawing a rectangle over them and releasing their mouse to erase the selection (similar to selecting items on a desktop ...

Avoid repeated appending of data in JavaScript and HTML

I am working with an HTML table and I need to ensure that the values in the second column do not repeat within the grid. Below is the JavaScript code I have written for this purpose: var $addedProductCodes = []; function getProductData(value){ $t ...

Plugin for Vegas Slideshow - Is there a way to postpone the beginning of the slideshow?

Is there a way to delay the start of a slideshow in JavaScript while keeping the initial background image visible for a set amount of time? I believe I can achieve this by using the setTimeout method, but I'm struggling to implement it correctly. Be ...

Ways to eliminate numerous if statements in JavaScript programming

Here is the code snippet I'm working with: a = [] b = [] c = [] useEffect(() => { if(isEmpty(a) && isEmpty(b) && isEmpty(c)) { data.refetch() } if(data.isFetching){ //do something } if(response.isFetching){ //do som ...

When you click, apply the hidden class to all the div elements

Is there a way to apply the .hide class to all .slide divs whenever the .option button is clicked? If so, how can I modify my JavaScript code so that all .slide divs receive the .hide class (if they don't already have it) upon clicking the .option bu ...

The Firefox form is experiencing issues when the cursor is set to 'move'

I have an HTML form with a specific code snippet: #stoppage_section .stoppage{ cursor: move; /* fallback if grab cursor is unsupported */ cursor: grab; cursor: -moz-grab; cursor: -webkit-grab; } <div id="st ...

Fade out the notification div when the user clicks anywhere outside of it

I am currently working on a website with a notification feature. When the user clicks on the notification button, a notification box will appear at the bottom of the button. I would like the behavior of this notification to be similar to Facebook's - ...

Adjust the width of every column across numerous instances of ag-grid on a single webpage

I am facing an issue with Ag-grid tables on my webpage. I have multiple instances of Ag-grid table in a single page, but when I resize the browser window, the columns do not automatically adjust to the width of the table. We are using only one Ag-grid in ...