vue js how to get notified when any property's value is being read? - vue.js

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>

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.

Keyboard movement Vuetify v-list-item

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>

Vue.js pass $store data from different modules

Hi I need to understand how to "pass" some $store values from settings module to header module, being the $store updated by settingsmodule
I have this app.vue module:
<template>
<v-app>
<router-view name="sidebar" />
<router-view name="header" :handleSettingsDrawer="handleSettingsDrawer" />
<v-main class="vue-content">
<v-fade-transition>
<router-view name="settings" />
</v-fade-transition>
</v-main>
<router-view name="footer" />
<app-settings-drawer
:handleDrawer="handleSettingsDrawer"
:drawer="settingsDrawer"
/>
</v-app>
</template>
in the <router-view name="header" /> there are a couple of v-menu to select currency and language:
<v-menu offset-y close-on-click>
<template v-slot:activator="{ on }">
<v-btn v-on="on" small fab class="mr-3">
<v-avatar size="30">
<v-img :src="currentLocaleImg"></v-img>
</v-avatar>
</v-btn>
</template>
<v-list dense class="langSel">
<v-list-item
v-for="(item, index) in langs"
:key="index"
#click="handleInternationalization(item.value,item.rtl)"
>
<v-list-item-avatar tile class="with-radius" size="25">
<v-img :src="item.img"></v-img>
</v-list-item-avatar>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
in the script section I import the availableLanguages and availableCurrencies , I set them in data() where I also reference the user:
data() {
return {
items: [
{ icon: "settings", text: "Settings",link: "/settings" },
{ icon: "account_balance_wallet", text: "Account", link:"/tos" },
{ divider: true },
{ icon: "login", text: "Log In",link:"/auth/login" },
{ icon: "desktop_access_disabled", text: "Lock Screen",link:"/auth/lockscreen" },
{ icon: "exit_to_app", text: "Logout",link:"/auth/logout" },
],
langs: availableLocale,
currs: availableCurrency,
user: this.$store.state.userdata.user,
};
},
then, in computed: I have the 2 functions that should get the current value of curr and lang:
currentLocaleImg()
{
return this.langs.find((item) => item.value === this.$store.state.userdata.user.locale).img;
},
currentCurrencyImg() {
return this.currs.find((itemc) => itemc.value === this.$store.state.userdata.user.currency).img;
}
}
BUT the value of this.$store.state.userdata.user is updated firing the loadData() function mounted in the <router-view name="settings" /> module of App.vue
mounted(){
this.loadData();
},
methods:
{
async loadData(){
let jwt=sessionStorage.getItem('jwt');
try{
const res = await this.$http.post('/ajax/read_settings.php', {"jwt":jwt});
this.$store.dispatch("setUser", res.data.user);
this.$store.dispatch("setLocale", res.data.user.locale);
this.$store.dispatch("setCurrency", res.data.user.currency);
}
the problem is that header module does not have the value of this.$store.state.userdata.user.locale
(and .currency) and I get twice the following message:
vue.runtime.esm.js:1888 TypeError: Cannot read property 'img' of undefined
I do not know how to "pass" the data from one module to the other (or perhaps because the header is rendered before the settings module) and therefore the value of the currency and of the language are not known yet
What's wrong in this procedure?
How can I fix it?
There's a race condition in which you try to use data in the template that isn't loaded yet. But first, avoid setting store data directly into component data because the component data won't be updated when the store changes. Replace this:
user: this.$store.state.userdata.user
With a computed:
computed: {
user() {
return this.$store.state.userdata.user;
}
}
You can use v-if to conditionally render only when some data is available:
<v-img :src="currentLocaleImg" v-if="user"></v-img>
The currentLocaleImg computed will not even trigger until user has some data so you will avoid the error. Do the same for currentCurrencyImg if necessary.

Vue-i18n: how to save the selected locale

There is a site on vue to which i18n library is connected.There is a button for switching languages.
<div class="locale-changer">
<v-menu v-model="changeLocaleState" offset-y>
<template v-slot:activator="{ on }">
<v-btn
color="primary"
dark
v-on="on"
/>
</template>
<v-list>
<v-list-tile
v-for="(lang, i) in langs"
:key="`lang.lacale${i}`"
:value="lang.locale"
#click="$i18n.locale = lang.locale"
>
<v-list-tile-title><img :src="lang.img">{{ lang.locale }}</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
export default {
name: 'VeLanguageSwitcher',
data () {
return { langs: [
{
name: 'en',
locale: 'en-US',
img: '/assets/img/flag/GB.png'
},
{
name: 'ru',
locale: 'ru-RU',
img: '/assets/img/flag/RU.png'
}
] }
},
computed: {
changeLocaleState: {
get () {
return this.$store.state.ui.changeLocale
},
set (val) {
this.$store.commit('ui/setChangeLocale', val)
}
}
}
}
</script>
The selected locale is not saved.Theoretically, I understand that when choosing a language you make SET in vuex, and GET displays a picture of the current locale inside the button.But I don't know how to do it

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>