Reputation: 123
I have a navigation sidebar with nested v-list-groups. According to the documentation the "group" prop of v-list-group will expand the group based on the route namespace.
See: https://vuetifyjs.com/en/components/lists/
This works well with the top level v-list-group.
It opens up if I reload the page and the route matches the "group" prop.
It does not work for the sub-group v-list-groups. When I reload the page I can see the subgroup closing, so I think the group prop is working. But something is making it close immediately after.
My code is a little complex, and I have to excuse my variable naming. I am rendering a list of navigation items, if they don't have a subgroup defined it renders as a regular item. If they have a subgroup it renders a subgroup. This goes two levels down.
This way I can define the whole navigation bar in a single json file and import it.
<template>
<v-list dense nav>
<template v-for="(sidebarItem, sidebarIndex) in sidebarItems">
<v-list-item
v-if="arrayIsEmptyOrUndefined(sidebarItem.subGroup)"
:key="sidebarIndex"
:to="sidebarItem.linkTo"
nuxt
>
<v-list-item-action>
<v-icon>{{ sidebarItem.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title v-text="sidebarItem.title" />
</v-list-item-content>
</v-list-item>
<v-list-group
v-if="
(!arrayIsEmptyOrUndefined(sidebarItem.subGroup) &&
typeof sidebarItem.subGroup !== 'undefined')
"
:key="sidebarIndex"
:prepend-icon="sidebarItem.icon"
:group="sidebarItem.linkTo"
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title>{{ sidebarItem.title }}</v-list-item-title>
</v-list-item-content>
</template>
<v-list-item
v-if="arrayIsEmptyOrUndefined(sidebarItem.subGroup)"
:key="subGroupIndex"
:to="sidebarItem.linkTo"
nuxt
>
<v-list-item-action>
<v-icon>{{ sidebarItem.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title v-text="sidebarItem.title" />
</v-list-item-content>
</v-list-item>
<template v-for="(subGroupItem, subGroupIndex) in sidebarItem.subGroup">
<v-list-item
v-if="arrayIsEmptyOrUndefined(subGroupItem.subGroup)"
:key="subGroupIndex"
:to="subGroupItem.linkTo"
nuxt
>
<v-list-item-action>
<v-icon>{{ subGroupItem.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title v-text="subGroupItem.title" />
</v-list-item-content>
</v-list-item>
<v-list-group
v-if="
(!arrayIsEmptyOrUndefined(subGroupItem.subGroup) &&
typeof subGroupItem.subGroup !== 'undefined')
"
:key="subGroupIndex"
sub-group
:group="subGroupItem.linkTo"
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title>{{ subGroupItem.title }}</v-list-item-title>
</v-list-item-content>
</template>
<v-list-item
v-for="(subGroupSubGroupItem, subGroupSubGroupIndex) in subGroupItem.subGroup"
:key="subGroupSubGroupIndex"
:to="subGroupSubGroupItem.linkTo"
nuxt
exact
>
<v-list-item-content>
<v-list-item-title v-text="subGroupSubGroupItem.title" />
</v-list-item-content>
<v-list-item-action>
<v-icon>{{ subGroupSubGroupItem.icon }}</v-icon>
</v-list-item-action>
</v-list-item>
</v-list-group>
</template>
</v-list-group>
</template>
</v-list>
</template>
If the route namespace matches both the group and the subgroup only the group opens up. The vuetify documentation site has a working implementation of this. If you go there and navigate down to a sub group and refresh the page. The subgroup will be open.
Upvotes: 2
Views: 7605
Reputation: 71
Another way to do this is by using 'to' as a routing, This key will automatically detect the current route.
<v-list v-model:opened="open">
<template v-for="(menu, j) in mainMenu">
<v-list-group :value="menu.title" >
<template v-slot:activator="{ props }">
<v-list-item
v-bind="props"
:key="menu.title + j"
:value="menu.title + j"
>
<v-icon>{{ menu.icon }}</v-icon>
{{ $t(menu.title) }}
</v-list-item>
</template>
<v-list-item
dense
v-for="(item, i) in menu.subMenu"
:key="item.title + j + i"
:value="item.title + j + i"
:title="item.title"
:to="item.path"
>
</v-list-item>
</v-list-group>
</template>
</v-list>
Upvotes: 0
Reputation: 7975
Wow! This ended up being more difficult than I originally expected. I solved this by looking directly at the source code for the Vuetify docs website, specifically the main nav drawer component, the base-group
component, the base-subgroup
component, and the base-item
component. These components were separated out into separate files for ease of composition and maintainability, but I've recombined them in the template below:
<template>
<v-navigation-drawer permanent>
<v-toolbar color="indigo" dark>
<v-app-bar-nav-icon></v-app-bar-nav-icon>
<v-toolbar-title>Main Menu</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon>
<v-icon>mdi-dots-vertical</v-icon>
</v-btn>
</v-toolbar>
<v-list
dense
expand
nav
>
<!-- ENTIRE list is wrapped in a template -->
<template v-for="item in $router.options.routes">
<!-- use v-if to determine if 2nd level nesting is needed -->
<!-- if it's a menu item with no children -->
<v-list-item
v-if="!item.children"
color="indigo"
:key="item.name`"
:to="item.path"
>
<v-list-item-icon>
<v-icon>{{ `mdi-${item.meta.icon}` }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.name }}</v-list-item-title>
</v-list-item>
<!-- else if it has children -->
<v-list-group
v-else
:group="item.path"
color="indigo"
>
<!-- this template is for the title of top-level items with children -->
<template #activator>
<v-list-item-content>
<v-list-item-title>
<v-icon>{{ `mdi-${item.meta.icon}` }}</v-icon>
{{ item.name }}
</v-list-item-title>
</v-list-item-content>
</template>
<!-- this template is for the children/sub-items (2nd level) -->
<template v-for="subItem in item.children">
<!-- another v-if to determine if there's a 3rd level -->
<!-- if there is NOT a 3rd level -->
<v-list-item
v-if="!subItem.children"
class="ml-5"
:key="subItem.name"
:to="item.path + subItem.path"
>
<v-list-item-icon class="mr-4">
<v-icon v-text="`mdi-${subItem.meta.icon}`" />
</v-list-item-icon>
<v-list-item-title class="ml-0">
{{ subItem.name }}
</v-list-item-title>
</v-list-item>
<!-- if there is a 3rd level -->
<v-list-group
v-else
color="indigo"
:group="subItem.path"
sub-group
>
<template #activator>
<v-list-item-content>
<v-list-item-title>
<v-icon>{{ `mdi-${subItem.meta.icon}` }}</v-icon>
{{ subItem.name }}
</v-list-item-title>
</v-list-item-content>
</template>
<template v-for="(subSubItem, k) in subItem.children">
<v-list-item
:key="`subheader-${k}`"
color="indigo"
:value="true"
:to="item.path + subItem.path + subSubItem.path"
>
<v-list-item-title>{{ subSubItem.name }}</v-list-item-title>
<v-list-item-icon>
<v-icon>{{ `mdi-${subSubItem.meta.icon}` }}</v-icon>
</v-list-item-icon>
</v-list-item>
</template>
</v-list-group>
</template>
</v-list-group>
</template>
</v-list>
</v-navigation-drawer>
</template>
As you may have noticed, the menu is generated from the list of routes in Vue Router. The router definition looks like this (NOTE: since this just an example, I did NOT associate a component with each route, but in practice, that would be necessary):
const router = new VueRouter({
routes: [
{
name: 'No Children (1 level)',
path: '/no-children',
meta: {
icon: 'baby-carriage-off',
},
},
{
name: 'Attractions (2 levels)',
path: '/attractions',
meta: {
icon: 'airballoon',
},
children: [
{
name: 'Carnivals',
path: '/carnivals',
meta: {
icon: 'drama-masks',
},
},
{
name: 'Museums',
path: '/museums',
meta: {
icon: 'bank',
},
},
]
},
{
name: 'Restaurants (3 levels)',
path: '/restaurants',
meta: {
icon: 'silverware-fork-knife',
},
children: [
{
name: 'Japanese',
path: '/japanese',
meta: {
icon: 'map-marker-radius-outline',
},
children: [
{
name: 'Hikari Sushi',
path: '/hikari-sushi',
meta: {
icon: 'food-croissant',
},
},
{
name: 'Late Night Ramen',
path: '/late-night-ramen',
meta: {
icon: 'noodles',
},
},
]
},
{
name: 'Italian',
path: '/italian',
meta: {
icon: 'map',
},
children: [
{
name: 'Jersey Pizza',
path: '/jersey-pizza',
meta: {
icon: 'pizza',
},
},
{
name: 'Im-pasta-ble',
path: '/im-pasta-ble',
meta: {
icon: 'pasta',
},
},
]
},
{
name: 'Mexican',
path: '/mexican',
meta: {
icon: 'map-marker',
},
children: [
{
name: 'Taco Gato',
path: '/taco-gato',
meta: {
icon: 'taco',
},
},
{
name: 'A-maize-ing',
path: '/a-maize-ing',
meta: {
icon: 'corn',
},
},
]
},
]
},
]
})
Here's a full, working example on codepen. I configured it to have a starting route of /restaurants/japanese/late-night-ramen
and when you click on any of the endpoints, the path to which the application will be routed is logged in the console. Hope this helps!
Upvotes: 10