DEV Community

Pantelis Theodosiou
Pantelis Theodosiou

Posted on • Edited on

Dynamic Menu In Vue

We've all created a web app using Vue and for sure these apps have a menu in it.

The main idea came to life when I was developing a side project and I had to create too many routes and child routes too. So, I decided to create this tutorial to show you how I solved my problem.


Let's get started

First thing first, create a project with Vue CLI

vue create dynamic-menu-vue
Enter fullscreen mode Exit fullscreen mode
Vue CLI v3.11.0
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, CSS Pre-processors, Linter
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Basic
? Pick additional lint features: (Press <space> to select, <a> to toggle
all, <i> to invert selection)Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json
? Save this as a preset for future projects? No
Enter fullscreen mode Exit fullscreen mode

Once your project is ready, you can serve it as normal

npm run serve -- --open
Enter fullscreen mode Exit fullscreen mode

View source code

View live demo


Let's break down the code I wrote for src/router.js

import Vue from "vue";
import Router from "vue-router";

Vue.use(Router);

export default new Router({
  mode: "history",
  base: "/dynamic-menu-vue/",
  routes: [
    {
      path: "/",
      redirect: { path: "/home" },
      meta: {
        visible: false
      }
    },
    {
      path: "/home",
      name: "home",
      component: () =>
        import(/* webpackChunkName: "home" */ "./views/Home.vue"),
      meta: {
        visible: true
      },
      children: [
        {
          path: "sub-view-1",
          name: "sub-view-1",
          component: () =>
            import(
              /* webpackChunkName: "home-sub-view-1" */ "./components/Home/SubView1.vue"
            ),
          meta: {
            visible: true
          }
        },
        {
          path: "sub-view-2",
          name: "sub-view-2",
          component: () =>
            import(
              /* webpackChunkName: "home-sub-view-2" */ "./components/Home/SubView2.vue"
            ),
          meta: {
            visible: true
          }
        }
      ]
    },
    {
      path: "/about",
      name: "about",
      component: () =>
        import(/* webpackChunkName: "about" */ "./views/About.vue"),
      meta: {
        visible: true
      }
    },
    {
      path: "*",
      name: "not-found",
      component: () =>
        import(/* webpackChunkName: "not-found" */ "./views/NotFound.vue"),
      meta: {
        visible: false  
      }
    }
  ]
});
Enter fullscreen mode Exit fullscreen mode
  • meta: This attribute is used to enhance routes. In this situation, we just use it to make routes visible on the menu,
  • lazy loading: there is no actual need to use lazy loading at this project but it's a good trick to downsize the bundle size,
  • base: I set this base URL in order to deploy this project on GitHub Pages.

OK, now that we have routes we need to create the menu component. I'm going to use bootstrap for this one. Let's add bootstrap to the project.

npm install bootstrap --save
Enter fullscreen mode Exit fullscreen mode

Then create a new file named styles.scss under the src folder and add these lines of code

@import "./assets/variables";
@import "node_modules/bootstrap/scss/bootstrap.scss";
@import "./assets/bootswatch";
Enter fullscreen mode Exit fullscreen mode

Now, add styles.scss in main.js

...
import "./styles.scss";
...
Enter fullscreen mode Exit fullscreen mode

Menu Component

Under the src/components create a new folder named Menu and create two new files in it.

  1. Navbar.vue
  2. MenuItem.vue

Let's continue with Navbar.vue

<template>
  <nav class="nav flex-column p-3">
    <menu-item v-for="(r,i) in routes" :key="i" :route="r"></menu-item>
  </nav>
</template>

<script>
export default {
  name: "navbar",
  components: {
    MenuItem: () => import(/* webpackChunkName: "menu-item" */ "./MenuItem")
  },
  computed: {
    routes() {
      return this.$router.options.routes;
    }
  }
};
</script>

<style>
</style>
Enter fullscreen mode Exit fullscreen mode

The computed property routes() returns the content of the router.js file.

Then the MenuItem.vue

<template>
  <div>
    <li v-if="isVisible" class="nav-item rounded shadow-sm mb-2">
      <router-link
        exact-active-class="text-success"
        :to="{name: route.name}"
        class="nav-link"
      >{{name}}</router-link>
    </li>

    <div v-if="route.children && route.children.length">
      <menu-item v-for="(r,i) in route.children" :key="i" :route="r" class="ml-3"></menu-item>
    </div>
  </div>
</template>

<script>
export default {
  name: "menu-item",
  props: {
    route: {
      type: Object
    }
  },
  computed: {
    isVisible() {
      if (
        this.route.meta &&
        (this.route.meta.visible === undefined || this.route.meta.visible)
      ) {
        return true;
      }
      return false;
    },
    name() {
      return this.route.name
        .toLowerCase()
        .split("-")
        .map(s => s.charAt(0).toUpperCase() + s.slice(1))
        .join(" ");
    }
  }
};
</script>
Enter fullscreen mode Exit fullscreen mode

Properties

  • route the exact route that comes from parent element Navbar.vue

Computed properties

  • isVisible() returns true if a route should be on the menu, false otherwise
  • name() returns the route's name

Active route class

  • exact-active-class="text-success" this, adds a class to the active menu item.

As you can notice we have a recursive call to the same component if the route has child component.


That's all for now. If you have any questions let me know, I'll be glad to help.

Happy coding!


If you found this post helpful or enjoyed it, consider supporting me by buying me a coffee. Your support helps me create more valuable content. ☕ Thank you!

Top comments (6)

Collapse
 
ktiedt profile image
Karl Tiedt

Have you tried to apply this to routes that require dynamic route data? For example, a user's profile page? Or a link to a specific article ID?

Curious how that would be wired in the router-link element...

Collapse
 
ptheodosiou profile image
Pantelis Theodosiou

Sorry for late answer Karl and @ahmed7fathi
Sure you can have dynamic routes like in every VueJS project. You will need to specify the names of the routes, but you can easily have dynamic route matching.
I'll update the project if you would like to take a look.

Collapse
 
ahmed7fathi profile image
Ahmed7fathi

Have u figured out this yet ?

Collapse
 
gauravgavhane05 profile image
Gaurav Gavhane

How to show nav items based on user roles like admin guest in vue dynamically

Collapse
 
ptheodosiou profile image
Pantelis Theodosiou

My first thought is about using vue-store. State management will help you to check if you have logged in user or not. Based on that info you can dynamically display wanted nav-items

Collapse
 
gauravgavhane05 profile image
Gaurav Gavhane

Yes. I found the solution with vuex and dynamic routing. Thanks for reply.