Keyboard movement Vuetify v-list-item - vuejs2

I have a Vuetify v-list-item that is iterated over to create me a list, I want to be able to use the up and down arrows to traverse it. How can I do this? It doesn't seem to be default behavior.

The default focusing between elements behavior works through tab. You can try something like this in order to use arrows keys:
<template>
<v-card
class="mx-auto"
max-width="300"
tile
>
<v-list dense>
<v-subheader>REPORTS</v-subheader>
<v-list-item-group
v-model="selectedItem"
color="primary"
>
<v-list-item
v-for="(item, i) in items"
:key="i"
>
<v-list-item-icon>
<v-icon v-text="item.icon"></v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</template>
<script>
export default {
data: () => ({
selectedItem: 1,
items: [
{ text: 'Real-Time', icon: 'mdi-clock' },
{ text: 'Audience', icon: 'mdi-account' },
{ text: 'Conversions', icon: 'mdi-flag' },
],
}),
methods: {
nextItem () {
if (event.keyCode == 38 && this.selectedItem > 0) {
this.selectedItem--
} else if (event.keyCode== 40 && this.selectedItem < 3) {
this.selectedItem++
}
}
},
mounted () {
document.addEventListener("keyup", this.nextItem);
},
}
</script>

Related

How to modify Vuetify's auto-generated CSS

What I'm Using
Vuetify 2.5.6
The Problem
I'm trying to disable an odious scroll bar
But can't quite figure out how to destroy it (and its ilk). Every suggestion I've tried still yields a Vuetify auto-generated class "v-navigation-drawer__content" that has overflow-y: auto;.
I'd like to learn how to modify the default behavior of these Vuetify-generated CSS files (for this issue and for future ones).
What I've Tried
I've tried:
adding style="overflow: hidden;" to the v-navigation-bar tag.
modifying adding .v-navigation-drawer__content { overflow: hidden !important } to the style section in the view component.
adding the following CSS and also adding mounted() and destroyed() hooks from this answer
Minimal Reproducible Example
<template>
<v-navigation-drawer
app
clipped
class="side-nav-bar"
permanent>
<v-menu
bottom
offset-y>
<template v-slot:activator="{ on, attrs }">
<v-list-item
two-line
v-bind="attrs"
v-on="on">
<v-list-item-avatar>
<v-img src="https://randomuser.me/api/portraits/women/85.jpg"></v-img>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class="text-h6">Sandra Adams</v-list-item-title>
<v-list-item-subtitle>sandra_a88#gmail.com</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</template>
<v-list>
<v-list-item
v-for="(workspace, i) in workspaces"
:key="i"
#click="changeWorkspaces(workspace)">
<v-list-item-title>{{ workspace.title }}</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item
#click="createNewWorkspace">
<v-list-item-title>Create Workspace</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-divider />
<v-list
nav
dense
v-for="(item, i) in sideNavOptions"
:key="i">
<v-list-item
v-if="!item.subList"
:key="item.title"
:to="item.link">
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title class="title-bold">{{ item.title }}</v-list-item-title>
</v-list-item>
<v-list-group
v-else
:key="item.title"
:prepend-icon="item.icon"
no-action>
<template v-slot:activator>
<v-list-item>
<v-list-item-content>
<v-list-item-title class="title-bold">{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
<v-list-item
v-for="sublist in item.subList"
:to="sublist.link"
:key="sublist.title">
<v-list-item-title>{{ sublist.title }}</v-list-item-title>
</v-list-item>
</v-list-group>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
name: "SideNavBar",
data() {
return {
workspaces: [],
sideNavOptions:[
{ title: "Dashboard", icon: "mdi-monitor-dashboard", link: "/dashboard" },
{
title: "Workflow",
icon: "mdi-cog",
subList: [
{ title: "Inbox", link: "/workflows/inbox" },
{ title: "Action Required", link: "/workflows/action_required" },
{ title: "Waiting for Others", link: "/workflows/waiting_for_others" },
{ title: "Approved", link: "/workflows/approved" },
{ title: "Sent", link: "/workflows/sent" },
{ title: "Completed", link: "/workflows/completed" },
]
},
{
title: "Templates",
icon: "mdi-cog",
subList: [
{ title: "Placeholder", link: "/templates/placeholder" }
]
},
{
title: "Contacts",
icon: "mdi-cog",
subList: [
{ title: "Placeholder", link: "/contacts/placeholder" }
]
},
{
title: "Settings",
icon: "mdi-cog",
subList: [
{ title: "Workspace Settings", link: "/settings/workspace" },
{ title: "Company Settings", link: "/settings/company" },
{ title: "Department Settings", link: "/settings/department" }
]
},
{ title: "Reminders", icon: "mdi-cog", link: "/reminders" }
]
}
},
async beforeMount() {
await this.getUserWorkspaces()
},
methods: {
changeWorkspaces(workspace) {
console.log(workspace)
},
createNewWorkspace() {
console.log("Creating new workspace")
},
async getUserWorkspaces() {
console.log("Getting user workspaces")
this.workspaces = [ { title: "Placeholder_1" }, { title: "Placeholder_2" } ]
}
}
}
</script>
<style lang="sass" scoped>
.side-nav-bar {
overflow: hidden !important;
color: $white !important;
background: $light_gray !important;
}
.v-navigation-drawer.v-navigation-drawer__content {
overflow: hidden !important;
}
</style>
Thanks in advance for any help!
Adding .v-navigation-drawer__content { overflow: hidden !important } should work but it's not working, because you are trying to change the style of a component which not a part of your current component using scoped css.
Try to remove scoped from your <style> and it will work. I usually create a global stylesheet and add it in App.vue file and make changes in that file.
Read more here about the scoped feature.

Add/Edit list item at parent from child component form

I am developing a recipe app. At my CreateRecipe component, I have child component to add ingredients to the recipe or edit existing ingredients. Ill start by showing the code and what i want to achieve and then the problem
Parent component:
<template>
...
<v-dialog v-model="AddIgredientsDialog" max-width="800px">
<template v-slot:activator="{ on, attrs }">
<v-btn color="secondary" dark v-bind="attrs" v-on="on">
Add addIngredients
</v-btn>
</template>
<AddItemsForm
#addIngredient="SaveNewIgredient"
:newIngredientsItem="editedIgredient"
/>
</v-dialog>
</template>
<script>
import AddItemsForm from "./AddItemsForm"; //Child Component
data: () => ({
AddIgredientsDialog:false,
article: {
headline: "",
img: "",
content: "",
subHeader: "",
description: "",
igredients: [], //List to add/edit item at AddItemsForm child component
preperation: []
},
editedIgredient: { //Item to use for editing or adding new item to article.igredients
title: "",
subIgredients: []
},
defaultItem: { //Item used for resetting editedIgredient item
title: "",
subIgredients: []
},
editedIndex: -1, helper variable for knowing whether i need to add new item or edit exiting item
}),
methods:{
editIngredients(item) {
this.editedIndex = this.article.igredients.indexOf(item);
this.editedIgredient = Object.assign({}, item);
this.AddIgredientsDialog = true;
},
SaveNewIgredient(newItem) { //Triggered on #click of save button at child component New item is the
//item passed from children
if (this.editedIndex > -1) {
this.editedIgredient = Object.assign({}, newItem);
Object.assign(
this.article.igredients[this.editedIndex],
this.editedIgredient
);
} else {
this.article.igredients.push(this.editedIgredient);
}
this.AddIgredientsDialog = false;
this.$nextTick(() => {
this.editedIgredient = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
}
</script>
Child Component:
<template>
<v-card>
<v-card-title>
<span class="headline">Add Ingredients</span>
</v-card-title>
<v-card-text>
<v-text-field v-model="newIngredientsItem.title" placeholder="כותרת">
</v-text-field>
<v-row align="center">
<v-col sm="11">
<v-text-field
v-model="newIgredient"
placeholder="New Igredient"
#keyup.enter="addNewIgredient"
>
</v-text-field>
</v-col>
<v-col sm="1">
<v-btn icon #click="addNewIgredient">
<v-icon>
mdi-plus
</v-icon>
</v-btn>
</v-col>
<v-col class="mt-0 pt-0" cols="12">
<v-row no-gutters>
<v-col cols="12">
<v-card flat tile>
<template
v-for="(item, index) in newIngredientsItem.subIgredients"
>
<v-list-item :key="index" class="mr-0 pr-0">
<v-list-item-content>
<v-list-item-title>
<v-edit-dialog #click.native.stop>
{{ item }}
<v-text-field
slot="input"
v-model="newIngredientsItem.subIgredients[index]"
></v-text-field>
</v-edit-dialog>
</v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-btn icon #click="removeIgredient(index)">
<v-icon small>
mdi-delete
</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
<v-divider
v-if="index + 1 < newIngredientsItem.subIgredients.length"
:key="item + index"
></v-divider>
</template>
</v-card>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-btn color="primary" #click="AddIngredients">
Save
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
props: {
newIngredientsItem: {
type: Object,
default() {
return {
title: "",
subIgredients: [ ]
};
}
}
},
data: () => ({
newIgredient: ""
}),
methods: {
addNewIgredient() {
this.newIngredientsItem.subIgredients.push(this.newIgredient);
this.newIgredient = "";
},
AddIngredients() {
this.$emit("addIngredient", this.newIngredientsItem);
},
removeIgredient(index) {
this.newIngredientsItem.subIgredients.splice(index, 1);
}
}
};
</script>
My Problem:
At the moment im only trying to use the SaveNewIgredient() method.
After 1st time of adding item the item is added as it should and the parent defaultItem state remain as is which is good:
defaultItem: {
title: "",
subIgredients: []
},
After adding a second item the defaultItem changes and takes the editedItem properties.
For example if i add at the second time
{
title:'Test 1',
subIgredients: [
'Test 1 - 1',
'Test 1 - 2',
'Test 1 - 3',
]
}
That is what the defaultItem will be and then this assignment causes a bug
this.editedIgredient = Object.assign({}, this.defaultItem);
because editedItem should be:
{
title: "",
subIgredients: []
}
I tried to solve your problem. To do this I modified and in some places simplified your code to keep only what was close to the SaveNewIgredient() function. So here is my code.
Parent Component (for me App.vue):
<template>
<AddItemsForm #addIngredient="SaveNewIgredient" />
</template>
<script>
import AddItemsForm from "./AddItemsForm"; //Child Component
export default {
name: "App",
components: { AddItemsForm },
data() {
return {
article: {
igredients: [], //List to add/edit item at AddItemsForm child component
},
editedIgredient: {
//Item to use for editing or adding new item to article.igredients
title: "",
subIgredients: [],
},
defaultItem: {
//Item used for resetting editedIgredient item
title: "",
subIgredients: [],
},
};
},
methods: {
SaveNewIgredient(newItem) {
console.log("Received: ", newItem);
this.editedIgredient = newItem;
this.article.igredients.push({ ...this.editedIgredient });
console.log("defaultClear: ", this.defaultItem);
console.log("infoItem: ", this.editedIgredient);
this.editedIgredient = this.defaultItem;
console.log("defaultClear: ", this.defaultItem);
console.log("editedWillClear: ", this.editedIgredient);
console.log("listFinal: ", this.article.igredients);
},
},
};
</script>
Child Component (for me AddItemsForm.vue):
<template>
<div>
<input v-model="newIgredient" placeholder="New Igredient" />
<button #click="addNewIgredient">ADD</button>
<div>
<button color="primary" #click="AddIngredients">Save</button>
</div>
</div>
</template>
<script>
export default {
props: {
IngredientsItem: {
type: Object,
default() {
return {
title: "",
subIgredients: [],
};
},
},
},
data() {
return {
newIgredient: "",
title: "TEST",
titleNbr: 0,
resetIgredient: false,
};
},
computed: {
newIngredientsItem() {
return this.IngredientsItem;
},
},
methods: {
addNewIgredient() {
if (this.resetIgredient === true) {
this.newIngredientsItem.subIgredients = [];
}
this.newIngredientsItem.subIgredients.push(this.newIgredient);
this.newIgredient = "";
this.resetIgredient = false;
console.log("ADD: ", this.newIngredientsItem.subIgredients);
},
AddIngredients() {
this.newIngredientsItem.title = this.title + this.titleNbr;
this.titleNbr++;
console.log("EMIT: ", this.newIngredientsItem);
this.$emit("addIngredient", this.newIngredientsItem);
this.resetIgredient = true;
},
},
};
</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 js how to get notified when any property's value is being read?

i'm working in vue js and i'm trying to achieve something which has dependency. Actually inside data i have a property of boolean, what i want is that whenever this property's value is being used or this property is accessed i'm get notified so that i'm able to change other properties before this property's value getting used.
<template>
<!-- <v-card> -->
<v-navigation-drawer
v-model="drawer"
:mini-variant.sync="mini"
permanent
height="100%"
style="border:1px solid black;"
>
<v-list-item class="px-2">
<v-list-item-avatar>
<v-img src="https://randomuser.me/api/portraits/men/85.jpg"></v-img>
</v-list-item-avatar>
<v-list-item-title>John Leider</v-list-item-title>
<v-btn
icon
#click.stop="changeMiniValue()"
>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
</v-list-item>
<v-divider></v-divider>
<v-list dense>
<v-list-item
v-for="item in items"
:key="item.title"
link
>
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
<!-- </v-card> -->
</template>
<script>
export default {
data () {
return {
drawer: true,
items: [
{ title: 'Home', icon: 'mdi-home-city' },
{ title: 'My Account', icon: 'mdi-account' },
{ title: 'Users', icon: 'mdi-account-group-outline' },
],
mini:this.getMini(),
}
},
methods:{
changeMiniValue(){
this.mini=!this.mini;
this.$store.dispatch('changeMini',!this.$store.state.mini);
},
getMini(){
this.$store.dispatch('changeColsToMin','9');
console.log('method executed');
return this.$store.state.mini;
}
},
created(){
this.$store.dispatch('changeColsToMin','11');
this.mini=this.$store.state.mini;
},
// computed:{
// getMiniValueCompute(){
// this.$store.dispatch('changeColsToMin','9');
// return this.$store.state.mini;
// }
// }
}
</script>
<style scoped>
</style>
This could be a possibile solution: create an "hidden" field and expose it through computed properties, with your custom logic.
<script>
export default {
data () {
return {
_mini: false
}
},
methods: {
// Your methods here...
},
computed: {
mini {
get: function () {
// TODO: notify your listeners, functions, etc.
return this._mini;
},
set: function (value) {
this._mini = mini;
}
}
}
}
</script>

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>