Vuetifyでのv-list-groupでのリンク設定方法
Vuetifyでサイドバーにメニューを追加したけれどもメニューに階層がある場合にうまくリンク設定、表示設定ができず悩んでいるという人を対象に階層があるメニューのリンク設定と表示設定について解説を行っています。
Vuefityでサイドメニューを表示させようとしている人はぜひ参考にしてみてください。
本文書を読み終えると下記のようなサイドメニューを実装すること可能です。
動作確認を行う環境は下記の文書で構築したものを利用します。
利用するメニューデータ
サイドにあるナビゲーションバーを構成するデータは下記となります。データプロパティnav_listsにリスト情報を設定しています。階層があるメニューと解消がないメニューの2つが混在している状態です。例えばGatting Startedは階層を持っているメニューでCustomizationは階層を持っていないメニューです。
data() {
return {
//略,
nav_lists: [
{
name: "Getting Started",
icon: "mdi-speedometer",
link: "",
lists: [
{
name: "Quick Start",
link: "/quick-start",
},
{
name: "Pre-made layouts",
link: "/pre-made-layouts",
},
],
},
{
name: "Customization",
icon: "mdi-cogs",
link: "/customization",
},
{
name: "Styles & animations",
icon: "mdi-palette",
link: "",
lists: [
{
name: "Colors",
link: "/colors",
},
{
name: "Content",
link: "/content",
},
{
name: "Display",
link: "/display",
},
],
},
{
name: "UI Components",
icon: "mdi-view-dashboard",
link: "/components",
},
{
name: "Directives",
icon: "mdi-function",
link: "/directives",
},
{
name: "Preminum themes",
icon: "mdi-vuetify",
link: "/preminum_themes",
},
],
};
},
階層があるメニューにはリンクを空設定し下の階層のリストにはリンク先を設定しています。下の階層のないメニューはそのメニュー項目にリンク先を設定しています。
v-list-groupのみを使用した場合の問題点
v-list-groupだけを利用すると下の階層がないメニュー項目のリンクをうまく設定することができません。設定がうまくいかないとはどのような状況なのかを下記で説明しています。
v-list-groupでサイドにメニューを作成するためのコードは以下の通りです。
<v-list nav dense>
<v-list-group
v-for="nav_list in nav_lists"
:key="nav_list.name"
:prepend-icon="nav_list.icon"
no-action
:append-icon="nav_list.lists ? undefined : ''"
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title>{{ nav_list.name }}</v-list-item-title>
</v-list-item-content>
</template>
<v-list-item
v-for="list in nav_list.lists"
:key="list.name"
:to="list.link"
>
<v-list-item-content>
<v-list-item-title>{{ list.name }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-group>
</v-list>
上記のコードでは下の階層のメニュー項目(サブメニュー)のみv-list-itemタブのpropsのtoを利用してリンクの設定を行なっています。そのため下の階層のメニュー項目をクリックするとURLがQuick Startのページの/quick-startに変わっていることが確認できます。
下の階層を持っていないメニューであるUI Componentsをクリックしてもブラウザに表示されているURLはquick-startのままで変わらないことが確認できます。
下の階層を持たないメニューはv-group-list内のtemplateタグにあるv-list-item-title中でメニューの表示を行っています。しかし、メニューをラップしているv-list-item-content, v-list-item-titleコンポーネントはslotだけしか設定できないためpropsのtoを利用してリンク情報を渡すことはできません。
v-list-itemコンポーネントであればpropsのtoでリンクを渡すことができるのでtemplateタグの中にv-list-itemコンポーネントを追加します。下の階層を持たないメニューだけにpropsのtoを設定すできるようにコードの更新を行います。v-ifとv-elseを利用して下の階層があるものとないものでわけています。nav_list.listsがない場合のみproprのtoを設定できるようにしています。
<v-list nav dense>
<v-list-group
v-for="nav_list in nav_lists"
:key="nav_list.name"
:prepend-icon="nav_list.icon"
no-action
:append-icon="nav_list.lists ? undefined : ''"
>
<template v-slot:activator>
<v-list-item v-if="nav_list.lists">
<v-list-item-content>
<v-list-item-title>{{ nav_list.name }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item v-else :to="nav_list.link">
<v-list-item-content>
<v-list-item-title>{{ nav_list.name }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
<v-list-item
v-for="list in nav_list.lists"
:key="list.name"
:to="list.link"
>
<v-list-item-content>
<v-list-item-title>{{ list.name }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-group>
</v-list>
v-list-itemとif文での分岐を追加することにより下の層を持たないメニューに対してリンクを設定することはできました。しかしインデントがずれ、クリックを押すと下記のように背景色が2色ずれて表示されます。動作はしていますが、見た目が非常に悪い状態です。
分岐を使ってv-group-listの外で階層がありとなしをわける
先程までの例ではv-group-listタグのv-forディレクティブでデータプロパティnav_listsの展開を行い、v-group-listタグの中で分岐を行なっていました。今回はtemplateタグを追加し、その中でv-forディレクティブを利用してデータプロパティnav_listsしています。分岐を利用することで下の階層メニューを持つものはv-group-listを使い、下の階層メニューの持たないものはv-group-listを利用しない設定としています。
<v-list nav dense>
<template v-for="nav_list in nav_lists">
<v-list-item
v-if="!nav_list.lists"
:to="nav_list.link"
:key="nav_list.name"
>
<v-list-item-icon>
<v-icon>{{ nav_list.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>
{{ nav_list.name }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-group
v-else
no-action
:prepend-icon="nav_list.icon"
:key="nav_list.name"
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title>
{{ nav_list.name }}
</v-list-item-title>
</v-list-item-content>
</template>
<v-list-item
v-for="list in nav_list.lists"
:key="list.name"
:to="list.link"
>
<v-list-item-title>
{{ list.name }}
</v-list-item-title>
</v-list-item>
</v-list-group>
</template>
</v-list>
下の階層メニュー持っていない場合はv-group-listを使わずv-list-itemの中でメニューを設定します。リンクはv-list-itemのpropsのtoを利用します。メニューのアイコンについてはv-list-item-iconコンポーネントを使います。
下の階層メニューを持っている場合はv-group-listを利用します。v-group-list内のv-list-itemのpropのtoでリンクを設定します。
動作確認を行うと下の階層メニューがないメニューをクリックすると指定したページに移動することができます。またインデント等も崩れていません。ここまでは期待通りの動作になっています。
しかし、サブメニューが開いた状態で階層メニューを持たないメニューをクリックすると先ほど開いたサブメニューは閉じることなく開いたままの状態になります。
上記の図ではStyles&animationのメニューをクリックしてサブメニューを開いた後にDirectivesをクリックすると開いたStyles&animationのサブメニューは開いたままとなります。この問題を解決するために次の処理を行います。
開いたサブメニューを閉じる方法
開いたサブメニューを閉じるための制御を行うためにnav_listsに新たにactiveプロパティを追加します。下の階層を持つメニューすべてにactiveプロパティの追加を行なってください。下記ではGetting Startedのみ表示していますがStyles & animationsでも行ってください。
nav_lists:[
{
name: 'Getting Started',
icon: 'mdi-speedometer',
active: false,
link: '',
lists:[{
name:'Quick Start',link:'/quick-start'
},
{
name:'Pre-made layouts',link:'/pre-made-layouts'
}
]
},
//省略
このactiveの値でメニューが開いているか閉じているかを管理します。そのためv-list-groupにv-modelを追加します。
<v-list-group
v-else
no-action
:prepend-icon="nav_list.icon"
:key="nav_list.name"
v-model="nav_list.active" //追加
>
下の階層を持たないメニュー項目をクリックした際に開いたサブメニューを閉じたいので下の階層を持たないメニュー項目のv-list-itemにclickイベントを追加します。メソッド名はmenu_closeとします。
<v-list nav dense>
<template v-for="nav_list in nav_lists">
<v-list-item
v-if="!nav_list.lists"
:to="nav_list.link"
:key="nav_list.name"
@click="menu_close" //追加
>
vueに最後にmenu_closeメソッドを追加します。forEachを使ってnav_listsのオブジェクトのactiveプロパティをすべてfalseに設定しています。
methods: {
menu_close() {
this.nav_lists.forEach((nav_list) => (nav_list.active = false));
},
},
これらの設定が完了すると下の階層を持たないメニュー項目をクリックすると開いたサブメニューを閉じることができます。
下記のように画面を移動することができます。
作成したコード全体は下記の通りです。
<template>
<v-app>
<v-navigation-drawer app v-model="drawer" clipped >
<v-container>
<v-list-item>
<v-list-item-content>
<v-list-item-title class="title grey--text text--darken-2">
Navigation lists
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
<v-list nav dense>
<template v-for="nav_list in nav_lists">
<v-list-item
v-if="!nav_list.lists"
:to="nav_list.link"
:key="nav_list.name"
@click="menu_close"
>
<v-list-item-icon>
<v-icon>{{ nav_list.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>
{{ nav_list.name }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-group
v-else
no-action
:prepend-icon="nav_list.icon"
:key="nav_list.name"
v-model="nav_list.active"
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title>
{{ nav_list.name }}
</v-list-item-title>
</v-list-item-content>
</template>
<v-list-item
v-for="list in nav_list.lists"
:key="list.name"
:to="list.link"
>
<v-list-item-title>
{{ list.name }}
</v-list-item-title>
</v-list-item>
</v-list-group>
</template>
</v-list>
</v-container>
</v-navigation-drawer>
<v-app-bar color="primary" dark app clipped-left>
<v-app-bar-nav-icon @click="drawer=!drawer"></v-app-bar-nav-icon>
<v-toolbar-title>Vuetify</v-toolbar-title>
</v-app-bar>
<v-content>
<router-view />
</v-content>
<v-footer color="primary" dark app>
Vuetify
</v-footer>
</v-app>
</template>
<script>
export default {
methods:{
menu_close(){
this.nav_lists.forEach( nav_list => nav_list.active = false)
}
},
data(){
return{
drawer: null,
supports:[
{
name: 'Consulting and suppourt',
icon: 'mdi-vuetify',
link:'/consulting-and-support'
},
{
name: 'Discord community',
icon: 'mdi-discord',
link:'/discord-community'},
{
name: 'Report a bug',
icon: 'mdi-bug',
link:'/report-a-bug'
},
{
name: 'Github issue board',
icon: 'mdi-github-face',
link:'/guthub-issue-board'
},
{
name: 'Stack overview',
icon: 'mdi-stack-overflow',
link:'/stack-overview'
},
],
nav_lists:[
{
name: 'Getting Started',
icon: 'mdi-speedometer',
active: false,
link: '',
lists:[{
name:'Quick Start',link:'/quick-start'
},
{
name:'Pre-made layouts',link:'/pre-made-layouts'
}
]
},
{
name: 'Customization',
icon: 'mdi-cogs',
link: '/customization'
},
{
name: 'Styles & animations',
icon: 'mdi-palette',
link: '',
active: false,
lists:[{
name :'Colors', link:'/colors'
},
{
name :'Content', link:'/content'
},
{
name :'Display', link:'/display'}
]
},
{
name: 'UI Components',
icon: 'mdi-view-dashboard',
link: '/components'
},
{
name: 'Directives',
icon: 'mdi-function',
link: '/directives'
},
{
name: 'Preminum themes',
icon: 'mdi-vuetify',
link: '/preminum_themes'
},
]
}
}
}
</script>