How to make navigation drawer shrink (mini version) once hamburger is clicked? - vue.js

I create Vue2 app using Vuex. I added navbar and navigation drawer. There is a hamburger on the left side of the navbar which toggles navigation drawer. I can click on drawer's element and expand the drawer. The issue is that when I toggle a drawer then it disappears completely, the it appears again when hamburger is clicked again. What I want to achieve is to toggle a drawer (when expanded) in a way it's shrinked to minified version.I'll show what I got now:
initial view:
I click on drawer's element (e.g. plane) to expand it:
I click the hamburger to shrink the drawer (to mini version with icons only):
And as you can see drawer is not visible, it disappears. And here is the example app where you see what I bear in mind. Look how navigation drawer and hamburger works: https://demos.creative-tim.com/vue-paper-dashboard-pro/?_ga=2.12802551.151259280.1666643495-739984285.1666643495#/admin/overview
Here is my code:
Navbar.vue:
<template>
<v-app-bar app>
<v-app-bar-nav-icon #click="setDrawer"></v-app-bar-nav-icon>
<v-toolbar-title>Application</v-toolbar-title>
</v-app-bar>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
methods: {
...mapMutations('custom', ['setDrawer'])
}
}
</script>
NavDrawer.vue
<template>
<v-navigation-drawer
app
:mini-variant.sync="mini"
v-model="toggleDrawer"
>
<v-list>
<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-list-item>
</v-list>
<v-divider></v-divider>
<v-list
nav>
<v-list-item-group
v-model="selectedItem"
color="primary"
>
<v-list-item
v-for="item in items"
:key="item.title"
>
<v-list-item-icon>
<v-icon> {{ item.icon }} </v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title> {{ item.text }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-navigation-drawer>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
selectedItem: 0,
mini: true,
items: [
{ text: "1", icon: "mdi-airplane" },
{ text: "2", icon: "mdi-ferry" },
{ text: "3", icon: "mdi-truck" }
]
}
},
computed: {
...mapState('custom', ['toggleDrawer']),
}
}
</script>
Vuex - custom.js:
const state = {
toggleDrawer: true,
};
const mutations = {
setDrawer: (state) => state.toggleDrawer = !state.toggleDrawer
};
export default {
namespaced: true,
state,
mutations
};

Replace :mini-variant.sync="mini" with :mini-variant.sync="toggleDrawer" and remove the v-model. This binds your Vuex store's variable with Vuetify's default expand/collapse behaviour. Moreover, you can remove "mini" from your data attributes.

Related

Vuetify How to update component data when list item group select changed

I'm using v-list-item-group and I want to show data in another component when the list item is selected. clear data when I unSelect item, and change data when I click on another list item
how can I possibly do it in vue?
the list item which I select:
Here I want to clear curr step data if the list.eid changed or when index changed
<v-list-item-group v-model="wfs">
<v-list-item v-for="(list,index) in workflowStepsList" :key="index"
#click="getWorkflowStep(list.eid)">
<v-list-item-action-text class="pe-4"> {{ index+1 }}</v-list-item-action-text>
<v-list-item-content v-if="!list.title">
{{ list.stepTitle }}
</v-list-item-content>
<v-list-item-content v-if="!list.stepTitle">
{{ list.title }}
</v-list-item-content>
<v-list-item-icon>
<v-icon small color="red">mdi-delete</v-icon>
</v-list-item-icon>
</v-list-item>
<v-list-item v-if="!workflowStepsList.length">
مرحله ای وجود ندارد
</v-list-item>
</v-list-item-group>
and the list I render data based on what I selected:
<v-card>
<v-card-title class="bg-success text-white d-flex justify-space-between">
مرحله فعلی
<add-curr :getSteps="getSteps"/>
</v-card-title>
<v-card-text>
<v-list>
<v-list-item-group class="v-list-item-group" v-model="stepId">
<v-list-item
v-if="!currStep.length"
class="text-muted"
>
یک مرحله انتخاب کنید
</v-list-item>
<v-list-item
v-for="(element) in currStep"
:key="element.eid"
v-show="element.eid !== null"
>
{{ element.title }}
</v-list-item>
</v-list-item-group>
</v-list>
</v-card-text>
</v-card>
the function:
async getWorkflowStep(weid) {
await this.$store.dispatch("axiosGet",
{url: `folder/api/workflow-steps/${weid}`, params: {workflowId: this.id}}).then(response => {
if (response.status === 'error') return
this.workflowStepsObj = response.data.data
const x = response.data.data
const curr = {
title: x.stepTitle,
eid: x.stepEid
}
this.currStep.push(curr)
})
},
I just added an another component and passing the model value as a prop in that component.
Live Demo :
Vue.component('another-component', {
props: ['val'],
template: '<h3>{{ val }}</h3>'
});
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
items: [
{ text: 'Inbox' },
{ text: 'Star' },
{ text: 'Send' },
{ text: 'Drafts' }
],
model: 1,
}),
});
<script src="https://unpkg.com/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.6.12/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vuetify#2.6.12/dist/vuetify.min.css"/>
<div id="app">
<v-list>
<v-list-item-group v-model="model">
<v-list-item v-for="(item, i) in items" :key="i">
{{ item.text }}
</v-list-item>
</v-list-item-group>
</v-list>
<another-component :val="items[model].text"></another-component>
</div>
You can either use store value and watch it from the component you want to track the change (it is possible to watch store properties if they change). If using vuex: https://vuex.vuejs.org/api/#watch
Or you can use emits and listen to the change on desired component, if there's depth issue for the emit you could pass emits on top level using (v-on="$listeners"): https://v2.vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components
Since there's no statement about Vue or Vuetify version, inferring it is the 2nd version.
Sometimes when using Vuetify or UI libraries we may forget the features that Vue provides. Despite Vuetify having built-in features most probably they can be overridden by implementing yours on top of them.

How can I solve the issue with redirection?

I am setting up a new project on nuxt and I've made one new layout for a login page, and created a page login.
In my default layout I am setting middleware: 'auth' and in my middleware I am checking for a token and if not authenticated I am redirecting the user to the login page.
The funny thing is that when I've just set it up it worked fine but after some time (I tried to go back with my code to find the issue) I started to receive an Error Redirected when going from "/" to "/login" via a navigation guard.
I don't have any redirects but the one in the auth middleware.
What can be a problem here that I cannot see?
// middleware/auth.js
export default ({ app, error, redirect }) => {
const hasToken = !!app.$apolloHelpers.getToken()
if (!hasToken) {
error({
errorCode: 503,
message: 'You are not allowed to see this'
})
return redirect('/login')
}
}
// layouts/default.vue
<template>
<v-app dark>
<v-navigation-drawer
v-model="drawer"
:mini-variant="miniVariant"
:clipped="clipped"
fixed
app
>
<v-list>
<v-list-item
v-for="(item, i) in items"
:key="i"
:to="item.to"
router
exact
>
<v-list-item-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title v-text="item.title" />
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
<v-app-bar :clipped-left="clipped" fixed app>
<v-app-bar-nav-icon #click.stop="drawer = !drawer" />
<v-btn icon #click.stop="clipped = !clipped">
<v-icon>mdi-application</v-icon>
</v-btn>
<v-toolbar-title v-text="title" />
<v-spacer />
</v-app-bar>
<v-main>
<v-container>
<nuxt />
</v-container>
</v-main>
<v-footer :absolute="!fixed" app>
<span>© {{ new Date().getFullYear() }}</span>
</v-footer>
</v-app>
</template>
<script>
export default {
middleware: ['auth'],
data() {
return {
clipped: false,
drawer: true,
fixed: true,
items: [
{
icon: 'mdi-apps',
title: 'Welcome',
to: '/',
},
{
icon: 'mdi-account-group-outline',
title: 'Clients',
to: '/clients',
},
{
icon: 'mdi-briefcase-check-outline',
title: 'Orders',
to: '/orders',
},
{
icon: 'mdi-briefcase-clock-outline',
title: 'Pending Orders',
to: '/pending-orders',
},
],
miniVariant: false,
right: true,
rightDrawer: false,
title: 'Title',
}
},
}
</script>
// layouts/login.vue
<template>
<v-app dark>
<v-main>
<v-container>
<nuxt />
</v-container>
</v-main>
</v-app>
</template>
<script>
export default { }
</script>
// pages/login.vue
<template>
<div>test login</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
Sorry) apparently I removed layout property from the login page component

How can I make Vuetify mobile responsive navigation bar and linked drawer have NESTED menus?

I am making a navbar component through the Vue framework using Vuetify. I would like to make the products item have a drop down into two links.
This is the template html and script code (I have some additional custom CSS for color and such that I am not adding here):
<template>
<div>
<v-toolbar id="navbar" dense elevation=1 dark >
<v-app-bar-nav-icon class="hidden-md-and-up" #click="sidebar = !sidebar"></v-app-bar-nav-icon>
<v-navigation-drawer v-model="sidebar" app hide-overlay temporary>
<v-list>
<v-list-item v-for="(item, i) in menuItems" exact :key="i" :to="item.path">{{item.title}}</v-list-item>
</v-list>
</v-navigation-drawer>
<v-toolbar-items d-flex>
<v-btn href="#" id="logo" flat depressed text>Company Name</v-btn>
</v-toolbar-items>
<v-spacer></v-spacer>
<v-toolbar-items class="hidden-sm-and-down">
<v-btn text v-for="item in menuItems" :key="item.title">
<router-link :to="item.path">{{item.title}}</router-link>
</v-btn>
</v-toolbar-items>
</v-toolbar>
</div>
</template>
<script>
export default {
data: function() {
return {
sidebar: false,
menuItems: [
{ path: "/product", name: "product", title: "Product" },
{ path: "/us", name: "us", title: "Us" },
{ path: "resources", name: "resources", title: "Resources" },
{ path: "/portal", name: "login", title: "Login" }
]
};
}
};
</script>
How about app-bar?
v-menu tag support drop down
https://vuetifyjs.com/en/components/app-bars/#dense

How do I overcome error 'Avoid using JavaScript keyword as "v-on" value'

How do I get rid of this error in my App.vue component?
Failed to compile.
./src/App.vue
Module Error (from ./node_modules/eslint-loader/index.js):
C:\xampp2\htdocs\exchproto\src\App.vue
15:69 error Avoid using JavaScript keyword as "v-on" value: "" vue/valid-v-on
✖ 1 problem (1 error, 0 warnings)
I tested the whole code (including sub-components) with CDN pull of vuejs/vuetify css/js etc and all worked well. This error only comes up as I am breaking down the code into components and doing everything by "yarn install". Been poking at it for some time with no luck.
<template>
<v-app>
<v-app-bar app>
<v-app-bar-nav-icon #click="drawer = !drawer"></v-app-bar-nav-icon>
<v-spacer></v-spacer>
<v-menu :offset-y="true">
<template v-slot:activator="{ on }">
<v-btn icon v-on="on">
<v-icon>mdi-dots-horizontal</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item v-for="(item, i) in rightMenuitems" :key="i" #click="">
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-app-bar>
<!--router-view></!--router-view-->
</v-app>
</template>
<script>
export default {
name: 'App',
components: {},
data() {
return {
drawer: false,
rightMenuitems: [
{ title: 'Choice 1' },
{ title: 'Choice 2' },
{ title: 'Choice 3' },
{ title: 'Choice 4' }
],
}
}
}
</script>
You can't have an empty #click="" You must have the click point to a function:
<v-list-item
v-for="(item, i) in rightMenuitems"
:key="i"
#click="doSomething($event, i)"
>
<v-list-item-title>
{{ item.title }}
</v-list-item-title>
</v-list-item>
// ...
methods: {
someFunction(event, i) {
console.log("clicked item " + i);
}
}
// ...
Remove 'click event' and add to property in v-list-item where v-for is being used.
Note:
You can bind links with the to props, and also links in every objects of rightMenuitems.
Here is an example:

Vue components data and methods disappear on one item when rendered with v-for as Vuetify's cards

I have Vue component that renders a list of Vuetify cards:
<restaurant-item
v-for="card in userRestaurantCards"
:key="card['.key']"
:card="card"
>
</restaurant-item>
The card displays info obtained from props, Vuex, as well as info defined in the restaurant-item card itself:
<v-card>
<v-img
class="white--text"
height="200px"
:src="photo"
>
<v-container fill-height fluid class="card-edit">
<v-layout fill-height>
<v-flex xs12 align-end flexbox>
<v-menu bottom right>
<v-btn slot="activator" dark icon>
<v-icon>more_vert</v-icon>
</v-btn>
<v-list>
<edit-restaurant-dialog :card="card" :previousComment="comment"></edit-restaurant-dialog>
<v-list-tile >
<v-list-tile-title>Delete</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</v-flex>
</v-layout>
</v-container>
</v-img>
<v-card-title>
<div>
<span class="grey--text">Friends rating: {{ card.rating }}</span><br>
<h3>{{ card.name }}</h3><br>
<span>{{ card.location }}</span>
</div>
</v-card-title>
<v-card-actions>
<v-btn flat color="purple">Comments</v-btn>
<v-spacer></v-spacer>
<v-btn icon #click="show = !show">
<v-icon>{{ show ? 'keyboard_arrow_down' : 'keyboard_arrow_up' }}</v-icon>
</v-btn>
</v-card-actions>
<v-slide-y-transition>
<v-card-text v-show="show">
<div> {{ comment.content }} </div>
</v-card-text>
</v-slide-y-transition>
</v-card>
The script is:
import { find, isEmpty } from 'lodash-es'
import { mapGetters } from 'vuex'
import EditRestaurantDialog from '#/components/dashboard/EditRestaurantDialog'
export default {
name: 'restaurant-item',
components: {
EditRestaurantDialog
},
props: {
card: Object
},
data() {
return {
show: false,
name: this.card.name,
location: this.card.location,
rating: this.card.rating,
link: this.card.link,
photo: this.getPhotoUrl()
}
},
computed: {
comment() {
// Grab the content of the comment that the current user wrote for the current restaurant
if (isEmpty(this.card.comments)) {
return { content: 'You have no opinions of this place so far' }
} else {
const userComment = find(this.card.comments, o => o.uid === this.currentUser)
return userComment
}
},
...mapGetters(['currentUser'])
},
methods: {
getPhotoUrl() {
const cardsDefault = find(this.card.photos, o => o.default).url
if (isEmpty(cardsDefault)) {
return 'https://via.placeholder.com/500x200.png?text=No+pics+here+...yet!'
} else {
return cardsDefault
}
}
}
}
Here is the kicker: when I have 2 objects in the data, the first card component renders correctly... while the second doesn't have any of the methods or data defined right there in the script.
Here's a link to a screenshot of the Vue Devtools inspecting the first card:
https://drive.google.com/file/d/1LL4GQEj0S_CJv55KRgJPHsCmvh8X3UWP/view?usp=sharing
Here's a link of the second card:
https://drive.google.com/open?id=13MdfmUIMHCB_xy3syeKz6-Bt9R2Yy4Xe
Notice how the second one has no Data except for the route?
Also, note that both components loaded props, vuex bindings and computed properties just as expected. Only the Data is empty on the second one...
I've been scratching my head for a while over this. Any ideas would be more than welcome.
I got it to work after I moved the method getPhotoUrl method to a computed property:
computed: {
comment() {
// Grab the content of the comment that the current user wrote for the current restaurant
if (isEmpty(this.card.comments)) {
return { content: 'You have no opinions of this place so far' }
} else {
const userComment = find(this.card.comments, o => o.uid === this.currentUser)
return userComment
}
},
photoUrl() {
const cardsDefault = find(this.card.photos, o => o.default)
if (isEmpty(cardsDefault)) {
return 'https://via.placeholder.com/500x200.png?text=No+pics+here+...yet!'
} else {
return cardsDefault.url
}
},
...mapGetters(['currentUser'])
}