Vuetify <v-list-item-group> v-model mutliple selecting doesnt work - vue.js

<v-list shaped>
<v-navigation-drawer>
<v-list shaped>
<v-list-item-group
v-model="selectedItem"
multiple
>
<template v-for="(item, i) in items">
<v-list-item
:value="item"
>
<v-list-item-title>{{item.text}}</v-list-item-title>
</v-list-item>
</template>
</v-list-item-group>
</v-list>
</v-navigation-drawer>
data: () => ({
selectedItem: 0,
drawer: null,
items: [
{ icon: 'fas fa-home', text: 'Dashboard', route: '/home' },
{ icon: 'fas fa-money-check-alt', text: 'Invoices', route: '/invoices' },
{ icon: 'fas fa-dolly', text: 'Partners', route: '/partners' },
{ icon: 'fas fa-exchange-alt', text: 'Items', route: '/items' },
],
}),
Very simple code in vuetify (2.6.1),multiple selecting doesnt work,
I can't preselect first item to be selected too. Its probably related to v-list-item-group component.

There are 2 mistakes in your code:
when you are using multiple prop, a selectedItem should be an array;
when you are applying :value="item", your selectedItem array should contain the whole object instead of its index.
So your code should be:
...
<v-list-item-group
v-model="selectedItem"
multiple
>
<template v-for="(item, i) in items">
<v-list-item>
<v-list-item-title>{{item.text}}</v-list-item-title>
</v-list-item>
</template>
</v-list-item-group>
...
data: () => ({
items: [
...
],
selectedItem: [0],
}),
...

Related

Get selection of vuetify list

How do I get the selected item / index of from a vuetify list?
Applying v-model to the <v-list> tag does not work somehow and I can not find any working example.
I'd like to have a list of images / file names and then display the selected image. My idea was to have an <v-img :src="imgpath"> and then imgpath beeing a reactive state that is changed via the list. Or is my idea completely wrong?
Minimum example of my try:
<template>
<v-app>
<v-main>
<v-card>
<v-list v-model='selection' :items='items'></v-list>
</v-card>
<v-card class='mt-5'>
{{ selection }}
</v-card>
</v-main>
</v-app>
</template>
<script>
export default {
data: () => ({
selection: 1,
items: [
{ title: 'Item 0', value: 0 },
{ title: 'Item 1', value: 1 },
{ title: 'Item 2', value: 2 },
],
}),
}
</script>
Expected behaviour:
The selection state changes according to the selected item in the list.
Observed behaviour:
Visually the selection changes (a different item is marked with a gray background), but the selection does not change.
You can add v-list-item in a loop. On <v-list-item> add #click handler to update selection data. Try this:
<template>
<v-app>
<v-main>
<v-card>
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
#click="selection = index"
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-card>
<v-card class="mt-5">
{{ selection }}
</v-card>
</v-main>
</v-app>
</template>
<script>
export default {
data: () => ({
selection: 0,
items: [
{ title: "Item 0", value: 0 },
{ title: "Item 1", value: 1 },
{ title: "Item 2", value: 2 },
],
}),
};
</script>

Vue / Vuetify 3 level nested lists

I'm new to Vuetify and I am trying to create a mobile navigation using nested lists. I am having an issue with the dropdown for the grandchildren data which is at the 3rd level of this nested list. With the code below the dropdown works for the children data but no dropdown appears for the grandchildren. I believe I am not nesting properly or my conditional isn't right. I reviewed Vuetify documentation and just couldn't figure this out.
<v-app>
<v-app-bar-nav-icon #click.stop="drawer = !drawer"></v-app-bar-nav-icon>
<v-navigation-drawer
v-model="drawer"
app
width="320px"
style="margin-top: 70px; background-color: #255e35"
class="text--white"
>
<v-list style="transform: translateX(0px)">
<div id="app">
<v-app>
<v-app-bar-nav-icon
#click.stop="drawer = !drawer"
></v-app-bar-nav-icon>
<v-navigation-drawer
v-model="drawer"
app
width="320px"
style="margin-top: 70px; background-color: #255e35"
class="text--white"
>
<v-list style="transform: translateX(0px)" class="top-level-list">
<template v-for="(link, i) in links">
<v-list-item v-if="!link.children" :key="i">
<v-list-item-title>{{ link.text }}</v-list-item-title>
</v-list-item>
<v-list-group v-else-if="link.children" :key="i"
><!--FIRST DROPDOWN-->
<template v-slot:activator>
<v-list-item-title>{{ link.text }}</v-list-item-title>
</template>
<template v-for="(child, j) in link.children">
<v-list-item v-if="!child.children" :key="j">
<v-list-item-title>{{ child.text }}</v-list-item-title>
</v-list-item>
<!--END OF FIRST SUBMENU-->
<v-list-group sub-group v-else :key="j">
<template v-slot:activator>
<v-list-item-title>{{ child.text }}</v-list-item-title>
</template>
<template v-for="(grandchild, k) in child.grandchildren">
<v-list-item v-if="!grandchild.grandchildren" :key="k">
<v-list-item-title>{{
grandchild.text
}}</v-list-item-title>
</v-list-item>
</template>
</v-list-group>
</template>
</v-list-group>
</template>
</v-list>
</v-navigation-drawer>
</v-app>
</div>
</v-list>
</v-navigation-drawer>
</v-app>
</template>
Below is how the data is structured
<script>
export default {
name: "App",
data() {
return {
drawer: false,
links: [
{
to: "/",
text: "Home",
active: false,
children: [
{
text: "Swag",
to: "/swag",
target: "_blank",
active: false,
},
{
text: "About Us",
to: "/about",
active: false,
},
{
text: "Contact - General Inquiries",
to: "/contact-general",
active: false,
},
{
text: "Contact - Advertising Inquiries",
to: "/contact-ad",
active: false,
},
{
text: "Submit An Article",
to: "/submit-article",
active: false,
},
],
},
{
to: "/events",
text: "Events",
active: false,
children: [
{
text: "Hunters Event",
to: "",
grandchildren: [ <----------GRANDCHILDREN------>
{
text: "Hunters",
to: "/events/view/hunters",
active: false,
},
{
text: "Exhibitor Information",
to: "/events/view/exhibitor",
active: false,
},
{
text: "3D Archery Tournament",
to: "/events/view/3d-archery",
active: false,
},
],
},
],
},
{
to: "/marketplace",
text: "Marketplace",
active: false,
children: [
{
text: "Land And Lease",
to: "/marketplace/category/land-and-lease",
active: false,
},
{
text: "Outdoor Gear",
to: "/marketplace/category/outdoor-gear",
active: false,
},
{
text: "Employment",
to: "/marketplace/category/employment",
active: false,
},
],
},
],
};
},
};
</script>

Vuejs sidebar menu with subitems

I am trying to create a sidebar menu dinamically with vuejs and vuenotify.
I have seen a sample on vuetify site using something as bellow.
The problem with this code, is that always shows the append-icon ">" on end of item title .
I´d like to show the append-icon ">", only if I have subittems.
I have created an item called "Manuutenção" with subtitems. No other has subitems.
Then, i´d like only "Manutençao" item or other(if I create with subitems") showing the ">".
Is possible fix it?
<v-list>
<v-list-group
v-for="item in items"
:key="item.title"
v-model="item.active"
:prepend-icon="item.icon"
no-action
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title v-text="item.title"></v-list-item-title>
</v-list-item-content>
</template>
<v-list-item
v-for="subItem in item.items"
:key="subItem.title"
link
router
:to="subItem.to"
>
<v-list-item-content>
<v-list-item-title v-text="subItem.title"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-group>
</v-list>
export default {
name: "DashboardCoreDrawer",
data: () => ({
items: [
{
icon: "mdi-view-dashboard",
title: "Dashboard",
to: "/",
},
{
icon: "mdi-account",
title: "Usuários",
to: "/usuarios",
},
{
title: "Clientes",
icon: "mdi-map-marker",
to: "/clientes",
},
{
title: "Manutenção",
icon: "mdi-clipboard-outline",
to: "",
items: [
{
title: "Convênios",
icon: "mdi-clipboard-outline",
to: "/convenios",
},
{
title: "Planos",
icon: "mdi-format-font",
to: "/planos",
},
],
},
{
title: "Convênios",
icon: "mdi-clipboard-outline",
to: "/convenios",
},
{
title: "Planos",
icon: "mdi-format-font",
to: "/planos",
},
{
title: "Tabelas",
icon: "mdi-chart-bubble",
to: "/tabelas",
},
{
title: "Atendimento",
icon: "mdi-bell",
to: "atendimentos",
},
],
}),
}
Additional information:
Is possible make adaptations to two or three submenu items. For sample:
items:[
{
title: "Manutenção",
icon: "mdi-clipboard-outline",
to: "",
items: [
{
title: "Convênios",
icon: "mdi-clipboard-outline",
to: "/convenios",
},
{
title: "Planos",
icon: "mdi-format-font",
to: "",
items: [
{
title: "Test1,
icon: "mdi-chart-bubble",
to: "/test1",
},
{
title: "Test2",
icon: "mdi-chart-bubble",
to: "/test2",
},
],
},
]
Icon at "Planos"
The main trick is to render v-list-group when item has subitems and v-list-item otherwise. See an example below:
<template>
<v-list>
<template v-for="item in items">
<v-list-group
:key="item.title"
v-if="item.items !== undefined"
v-model="item.active"
no-action
>
<template v-slot:activator>
<v-list-item-avatar left>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.title"></v-list-item-title>
</v-list-item-content>
</template>
<v-list-item
v-for="subItem in item.items"
:key="subItem.title"
link
router
:to="subItem.to"
>
<v-list-item-avatar left>
<v-icon>{{ subItem.icon }}</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="subItem.title"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-group>
<v-list-item v-else :key="item.title" link router :to="item.to">
<v-list-item-avatar left>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.title"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
</v-list>
</template>
Edited:
For a multilevel solution, basically, you have to create a component for list item and import it recursively into itself. Something like that:
mainlist.vue
<template>
<div>
<v-list>
<list-item v-for="item in items" :item="item" :key="item.title">
</list-item>
</v-list>
</div>
</template>
<script>
export default {
name: "DashboardCoreDrawer",
components: {
ListItem: () => import("./listitem.vue"),
},
data: () => ({
items: [
{
title: "Convênios",
icon: "mdi-clipboard-outline",
to: "/convenios",
},
{
title: "Planos",
icon: "mdi-format-font",
items: [
{
title: "Test1",
icon: "mdi-chart-bubble",
items: [
{
title: "Test4",
icon: "mdi-chart-bubble",
to: "/test1",
},
{
title: "Test5",
icon: "mdi-chart-bubble",
to: "/test2",
},
],
},
{
title: "Test2",
icon: "mdi-chart-bubble",
to: "/test2",
},
],
},
],
}),
};
</script>
listitem.vue
<template>
<div>
<v-list-group
v-if="item.items !== undefined"
v-model="item.active"
no-action
:sub-group="isSubGroup"
:class="isSubGroup ? 'right-icon' : ''"
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title>
<v-icon>{{ item.icon }}</v-icon>{{ item.title }}
</v-list-item-title>
</v-list-item-content>
</template>
<template v-for="subItem in item.items">
<list-item :item="subItem" :is-sub-group="true" :key="subItem.title">
</list-item>
</template>
</v-list-group>
<v-list-item v-else link router :to="item.to">
<v-list-item-avatar left>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.title"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</div>
</template>
<script>
export default {
name: "listitem",
props: ["item", "isSubGroup"],
components: {
ListItem: () => import("./listitem.vue"),
},
};
</script>
<style>
.right-icon .v-list-group__header {
display: flex !important;
flex-direction: row-reverse !important;
}
</style>

Vue-router: How can I use named routes within a data object?

I am using vue-router (and vuetify) to build my website. I have everything working great, but I would like to understand how to use the named routes instead of a path.
Here is a partial of my template as follows:
<v-list-item v-else :key="item.text" :to="item.to" link>
<v-list-item-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>
{{ item.text }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
...
Here is my data object:
data: () =>({
items: [
{icon: 'mdi-home', text: 'Home', to: '/'},
{icon: 'mdi-view-dashboard', text: 'Dashboard', to: '/dashboard'},
{
icon: 'mdi-chevron-up', 'icon-alt': 'mdi-chevron-down',
text: 'Locations',
model: false,
children: [
{
icon: 'mdi-map-marker',
text: 'Location One',
to: {name: 'location.show', params: {id: 1}} . // works perfectly
},
{
icon: 'mdi-map-marker',
text: 'Location Two',
to: {name: 'location.show', params: {id: 2}} . // works perfectly
},
],
},
],
}),
This is what I am trying to do:
{icon: 'mdi-view-dashboard', text: 'Dashboard', to: {name: 'dashboard'},
Instead of:
{icon: 'mdi-view-dashboard', text: 'Dashboard', to: '/dashboard'},
However, when I use the named route, Vuetify is applying the active class to the element. So, now all of my nav items are active.
How can I use the named route instead of a path? Thank you for any suggestions!
EDIT
Here is what my vue-router looks like:
{
path: "/dashboard",
name: "dashboard",
component: () =>
import(/* webpackChunkName: "dashboard" */ "../views/admin/Dashboard"),
meta: {requiresAuth: true}
},
{
path: "/location/:id",
name: "location.show",
component: () =>
import(/* webpackChunkName: "location.show" */ "../views/admin/location/Show"),
meta: {requiresAuth: true},
props(route) {
const props = {...route.params};
props.id = +props.id;
return props; // returns the param as a string
},
},
First of all, make sure that your route name is existing. Like below
export default [
{
path: '/admin/dashboard',
name: 'dashboard',
component: dashboard,
meta: {
title:'Dashboard'
}
},
]
So, in your items you can already use this one.
{icon: 'mdi-home', text: 'Home', to: {name: 'dashboard'}},
You should add the exact -prop to your list item. This way it will only apply the active class to the exact link.
<v-list-item v-else :key="item.text" :to="item.to" link exact>
<v-list-item-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>
{{ item.text }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
https://router.vuejs.org/api/#exact

How to use vuetify component v-navigation-drawer, toolbar, footer separately in different files

I am trying to implement the vuetify in my project. I wanted to separate the
<v-navigation-drawer>, </v-toolbar> and <v-footer> in three different files.
Currently i am using.
Layout.vue
<template>
<v-app>
<TopNav :drawer="drawer" :clipped="clipped"></TopNav>
<SideBar/>
<v-content>
<v-container fluid>
<router-view></router-view>
</v-container>
</v-content>
<FooterArea/>
</v-app>
</template>
Script- Layout.vue
<script>
import { TopNav, FooterArea, SideBar } from "../layouts/index";
export default {
name: "Full",
components: {
TopNav,
FooterArea,
SideBar
},
data() {
return {
clipped: true,
drawer: true,
fixed: false,
inset: true,
items: [
{
icon: "bubble_chart",
title: "Inspire"
}
],
miniVariant: false,
right: true,
rightDrawer: false,
title: "Vuetify.js"
};
}
};
</script>
TopNav.vue
<template>
<v-toolbar dark color="info" app :clipped-left="clipped">
<v-toolbar-side-icon v-model="drawer" #click.stop="drawer = !drawer"></v-toolbar-side-icon>
<v-toolbar-title class="white--text">Title</v-toolbar-title>
</v-toolbar>
</template>
<script>
export default {
props: {
clipped: {
type: Boolean,
default: true
},
drawer: {
type: Boolean,
default: true
}
}
};
</script>
SideBar.vue
<template>
<v-navigation-drawer
persistent
:mini-variant="miniVariant"
:clipped="clipped"
v-model="drawer"
enable-resize-watcher
fixed
app
>
<v-list>
<v-list-tile
value="true"
v-for="(item, i) in items"
:key="i"
>
<v-list-tile-action>
<v-icon v-html="item.icon"></v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title v-text="item.title"></v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
}
</script>
However have tried by using the props and passing the values from Layout.vue to TopNav.vue, but i am getting the error as:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "drawer"
As I need to emit from the TopNav.vue to Layout.vue but i couldnot understand how it can be done nicely.
Thank you in advance for the help.
I use it like this:
Parent
<template>
<v-app id="inspire">
<TheNavDrawer :items="items" v-model="drawer" />
<v-toolbar
:clipped-left="$vuetify.breakpoint.lgAndUp"
color="orange darken-3"
dark
app
fixed
>
<v-toolbar-side-icon
#click.stop="drawer = !drawer"
class="hidden-xs-only"
/>
<v-toolbar-title>Test App</v-toolbar-title>
</v-toolbar>
</v-app>
</template>
<script>
import TheNavDrawer from "#/components/Navigation/TheNavDrawer";
export default {
data: () => ({
drawer: false,
items: [
{ icon: "contacts", text: "Contacts" },
{ icon: "history", text: "Frequently contacted" },
{ icon: "content_copy", text: "Duplicates" },
{
icon: "keyboard_arrow_up",
"icon-alt": "keyboard_arrow_down",
text: "Labels",
model: true,
children: [{ icon: "add", text: "Create label" }]
},
{
icon: "keyboard_arrow_up",
"icon-alt": "keyboard_arrow_down",
text: "More",
model: false,
children: [
{ text: "Import" },
{ text: "Export" },
{ text: "Print" },
{ text: "Undo changes" },
{ text: "Other contacts" }
]
},
{ icon: "settings", text: "Settings" },
{ icon: "chat_bubble", text: "Send feedback" },
{ icon: "help", text: "Help" },
{ icon: "phonelink", text: "App downloads" },
{ icon: "keyboard", text: "Go to the old version" }
]
}),
components: {
TheNavDrawer,
}
};
</script>
TheNavDrawer.vue
<template>
<v-navigation-drawer
v-bind:value="value" # <-- mimic v-model behaviour
v-on:input="$emit('input', $event)" <-- mimic v-model behaviour
:clipped="$vuetify.breakpoint.lgAndUp"
fixed
app
>
<v-list dense>
<template v-for="item in items">
<v-layout v-if="item.heading" :key="item.heading" row align-center>
<v-flex xs6>
<v-subheader v-if="item.heading">
{{ item.heading }}
</v-subheader>
</v-flex>
<v-flex xs6 class="text-xs-center">
EDIT
</v-flex>
</v-layout>
<v-list-group
v-else-if="item.children"
:key="item.text"
v-model="item.model"
:prepend-icon="item.model ? item.icon : item['icon-alt']"
append-icon=""
>
<v-list-tile slot="activator">
<v-list-tile-content>
<v-list-tile-title>
{{ item.text }}
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile
v-for="(child, i) in item.children"
:key="i"
#click="false"
>
<v-list-tile-action v-if="child.icon">
<v-icon>{{ child.icon }}</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
{{ child.text }}
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list-group>
<v-list-tile v-else :key="item.text" #click="false">
<v-list-tile-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
{{ item.text }}
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</template>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
props: {
items: {
type: Array,
required: true
},
value: {
type: Boolean,
default: false
}
}
};
</script>
Basically, instead of using v-model inside the NavDrawer child component I define my own v-model with v-bind:value="value" and v-on:input="$emit('input', $event)" which propagates the status from the <v-navigation-drawer> up to the parent component and makes for much cleaner code. If you want to know more about what happens behind the scenes look here: https://v2.vuejs.org/v2/guide/components-custom-events.html
This is how I use navigation drawer as Parent/Child components without using vuex state management.
Parent Compontent
<Drawer :items="navigations.user.items" :drawer="navigations.user.drawer" #drawerStatus="navigations.user.drawer = $event" :position="navigations.user.position" :avatar="navigations.user.avatar" />
<v-toolbar color="transparent" flat dark absolute>
<v-toolbar-side-icon #click.native.stop="navigations.default.drawer = !navigations.default.drawer">
</v-toolbar-side-icon>
<v-toolbar-title>Open Voucher</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon>
<v-icon #click.native.stop="navigations.user.drawer = !navigations.user.drawer">more_vert</v-icon>
</v-btn>
</v-toolbar>
</div>
</template>
<script>
import Drawer from './Drawer'
export default {
components: {
Drawer
},
data () {
return {
drawer: null,
navigations:
{
default: {
drawer: false,
position: null,
avatar: false,
items: [
{ title: 'Item title', icon: 'fas fa-home', url: '/' },
{ title: 'Item title', icon: 'fas fa-user-friends', url: '/item-url' },
{ title: 'Item title', icon: 'fas fa-atlas', url: '/item-url' },
{ title: 'Item title', icon: 'fas fa-archway', url: '/item-url' },
{ title: 'Item title', icon: 'fas fa-pencil-alt', url: '/item-url' }
]
},
user: {
drawer: false,
position: 'right',
avatar: true,
items: [
{ title: 'Item title', icon: 'dashboard', url: '/item-url' },
{ title: 'Item title', icon: 'fas fa-map-marked-alt', url: '/item-url' },
{ title: 'Item title', icon: 'fas fa-heart', url: '/item-url' },
{ title: 'Item title', icon: 'question_answer', url: '/item-url' }
]
}
}
}
},
methods: {
changeDrawerStatus(value) {
this.drawer = value;
}
}
}
</script>
Child Component
<template>
<v-navigation-drawer v-model="drawerChild" fixed temporary app :right="position">
<v-list class="pa-1" v-if="avatar">
<v-list-tile avatar>
<v-list-tile-avatar>
<img src="https://randomuser.me/api/portraits/men/85.jpg">
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>John Leider</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
<v-list class="pt-0" dense>
<v-divider></v-divider>
<v-list-tile v-for="item in itemList" :key="item.title" :to="item.url">
<v-list-tile-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>{{ item.title }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
props: [
'items',
'drawer',
'position',
'avatar'
],
data() {
return {
drawerChild: null,
itemList: []
}
},
mounted() {
this.itemList = this.items;
},
watch: {
drawer (value) {
this.drawerChild = value;
},
drawerChild (value) {
this.$emit('drawerStatus', value)
}
}
}
</script>