Vue.js pass $store data from different modules - vue.js

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.

Related

Vue: I want to send data to another page

I have a page that lists employees.
<template>
<v-container>
<v-card
class="mb-6 pa-2 mx-auto rounded-lg"
max-width="1000"
color=""
v-for="user in users"
:key="user.id"
>
............
<v-btn
class="mb-3 mt-3"
v-on:click="sendConfirm(user.ID)"
to="/companyApplicants/menteeListPage"
color="green"
>
<v-icon>mdi-clipboard-account</v-icon>
</v-btn>
...........
</v-col>
</v-list-item>
</v-card>
</v-container>
</template>
<script>
export default {
data() {
return {
userDatas: [],
users: [
{
.....
},
],
}
},
mounted() {
this.getUserData()
},
methods: {
getUserData() {
return this.$axios.$get('/api/MyMentors').then((response) => {
this.users = response
console.log(response)
})
},
},
}
</script>
And when they press the button and go to the other page I want to send Id of the clicked mentor. I can get the Id but couldn't figure out how to send that Id to the other page
you can add the id to the route
<v-btn
class="mb-3 mt-3"
v-on:click="sendConfirm(user.ID)"
:to="`/companyApplicants/menteeListPage/${user.ID}`"
color="green"
>
<v-icon>mdi-clipboard-account</v-icon>
</v-btn>
just need to update your route to have the data available there
{
path: "/companyApplicants/menteeListPage/:userID",
name: "menteeListPage",
component: () => import(/* webpackChunkName: "views" */ "./views/MenteeListPage.vue"),
meta: {
requiresAuth: true,
title: "Menteen List Page {{userID}}",
},
},
your variable will then be accessible in the vue components through this.$route.params.userID
If you want to make it optional for the route use ? at the end :
path: "/companyApplicants/menteeListPage/:userID?",

Pass component as prop in Vue JS

Intro: I am exploring Vue Js and got stuck while trying to make a dynamic data table component the problem I am facing is that I cannot pass a component via props and render it inside a table.
Problem: So basically what I am trying to do is to pass some custom component from headers prop in v-data-table such as:
headers = [
{ text: 'Name', value: 'name' },
{
text: 'Phone Number',
value: 'phone_number',
render: () => (
<div>
<p>Custom Render</p>
</div>
)
},
{ text: 'Actions', value: 'actions' }
]
So from the code above we can see that I want to render that paragraph from the render function inside Phone Number header, I did this thing in React Js before, but I cannot find a way to do it in Vue Js if someone can point me in the right direction would be fantastic. Thank you in advance.
You have 2 options - slots and dynamic components.
Let's first explore slots:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="{item}">
<v-chip>{{ item.phone_number }}</v-chip>
</template>
<template slot="item.company_name" slot-scope="{item}">
<v-chip color="pink darken-4" text-color="white">{{ item.company_name }}</v-chip>
</template>
</v-data-table>
</template>
The data table provides you slots where you can customize the content. If you want to make your component more reusable and want to populate these slots from your parent component - then you need to re-expose these slots to the parent component:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="props">
<slot name="phone" :props="props" />
</template>
<template slot="item.company_name" slot-scope="props">
<slot name="company" :props="props" />
</template>
</v-data-table>
</template>
If you don't know which slots will be customized - you can re-expose all of the data-table slots:
<template>
<v-data-table
:headers="headers"
:items="items"
:search="search"
hide-default-footer
:options.sync="pagination"
:expanded="expanded"
class="tbl_manage_students"
height="100%"
fixed-header
v-bind="$attrs"
#update:expanded="$emit('update:expanded', $event)"
>
<!-- https://devinduct.com/blogpost/59/vue-tricks-passing-slots-to-child-components -->
<template v-for="(index, name) in $slots" v-slot:[name]>
<slot :name="name" />
</template>
<template v-for="(index, name) in $scopedSlots" v-slot:[name]="data">
<slot :name="name" v-bind="data" />
</template>
<v-alert slot="no-results" color="error" icon="warning">
{{ $t("no_results", {term: search}) }}"
</v-alert>
<template #footer="data">
<!-- you can safely skip the "footer" slot override here - so it will be passed through to the parent component -->
<table-footer :info="data" #size="pagination.itemsPerPage = $event" #page="pagination.page = $event" />
</template>
</v-data-table>
</template>
<script>
import tableFooter from '#/components/ui/TableFooter'; // you can safely ignore this component in your own implementation
export default
{
name: 'TeacherTable',
components:
{
tableFooter,
},
props:
{
search:
{
type: String,
default: ''
},
items:
{
type: Array,
default: () => []
},
sort:
{
type: String,
default: ''
},
headers:
{
type: Array,
required: true
},
expanded:
{
type: Array,
default: () => []
}
},
data()
{
return {
pagination:
{
sortDesc: [false],
sortBy: [this.sort],
itemsPerPageOptions: [25, 50, 100],
itemsPerPage: 25,
page: 1,
},
};
},
watch:
{
items()
{
this.pagination.page = 1;
},
sort()
{
this.pagination.sortBy = [this.sort];
this.pagination.sortDesc = [false];
},
}
};
</script>
Dynamic components can be provided by props:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="{item}">
<component :is="compPhone" :phone="item.phone_number" />
</template>
<template slot="item.company_name" slot-scope="{item}">
<component :is="compCompany" :company="item.company_name" />
</template>
</v-data-table>
</template>
<script>
export default
{
name: 'MyTable',
props:
{
compPhone:
{
type: [Object, String], // keep in mind that String type allows you to specify only the HTML tag - but not its contents
default: 'span'
},
compCompany:
{
type: [Object, String],
default: 'span'
},
}
}
</script>
Slots are more powerful than dynamic components as they (slots) use the Dependency Inversion principle. You can read more in the Markus Oberlehner's blog
Okay, I don't believe this is the best way possible but it works for me and maybe it will work for someone else.
What I did was I modified the headers array like this:
headers = [
{ text: 'Name', align: 'start', sortable: false, value: 'name' },
{
text: 'Phone Number',
key: 'phone_number',
value: 'custom_render',
render: Vue.component('phone_number', {
props: ['item'],
template: '<v-chip>{{item}}</v-chip>'
})
},
{ text: 'Bookings', value: 'bookings_count' },
{
text: 'Company',
key: 'company.name',
value: 'custom_render',
render: Vue.component('company_name', {
props: ['item'],
template:
'<v-chip color="pink darken-4" text-color="white">{{item}}</v-chip>'
})
},
{ text: 'Actions', value: 'actions', sortable: false }
]
And inside v-data-table I reference the slot of custom_render and render that component there like this:
<template v-slot:[`item.custom_render`]="{ item, header }">
<component
:is="header.render"
:item="getValue(item, header.key)"
></component>
</template>
To go inside the nested object like company.name I made a function which I called getValue that accepts 2 parametes, the object and the path to that value we need which is stored in headers array as key (ex. company.name) and used loadash to return the value.
getValue function:
getValue (item: any, path: string): any {
return loadash.get(item, path)
}
Note: This is just the initial idea, which worked for me. If someone has better ideas please engage with this post. Take a look at the props that I am passing to those dynamic components, note that you can pass more variables in that way.

Nuxt js id through url

I want to get ID user, so I have a button DropDown=
<v-menu
transition="slide-y-transition"
bottom
offset-y
>
<template v-slot:activator="{ on, attrs }" >
<v-btn
color="primary"
dark
v-bind="attrs"
v-on="on"
>
List
</v-btn>
</template>
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
:to="item.url"
>
<v-list-item-title>{{item.title}}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
and this data:
<script>
export default {
data: () => ({
items: [
{
title: 'List User',
url: '`/user/function/listUser/${route.params.id}`'
},
{
title: 'structure User',
url: '`/user/function/structUser/${route.params.id}`'
}
]
})
}
</script>
My intention is to send user ID. This way, I can get with route.params.id actually
url: '`/user/function/structUser/${route.params.id}`'
is not working, what I'm doing wrong?
This this one
`/user/function/structUser/${this.$route.params.id}`
Also, maybe try this one in a computed because it may not be computed.
Template strings use backticks only, but yours are using both single quotes and backticks.
The substitution value (route.params.id) refers to a route variable that appears to be undefined in your example. I believe you're trying to access this.$route, so the actual substitution should be this.$route.params.id
The items array should look like this:
export default {
data() {
return {
items: [
{
title: 'List User',
url: `/user/function/listUser/${this.$route.params.id}`
},
{
title: 'structure User',
url: `/user/function/structUser/${this.$route.params.id}`
}
]
}
}
}
demo

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>

Problem when creating a menu by iteration

I'm new to vue and vuetify. I need to create a submenu and for that I am using v-menu. Its construction is by iteration, where I need each sub menu to assign it a method. But it turns out that the way I'm doing it generates an error
[Vue warn]: Error in v-on handler: 'TypeError: handler.apply is not a function'
. What am I doing wrong?
https://codepen.io/maschfederico/pen/vMWBPV?editors=1011
<div id="app">
<v-app id="inspire">
<div class="text-xs-center">
<v-menu>
<template #activator="{ on: menu }">
<v-tooltip bottom>
<template #activator="{ on: tooltip }">
<v-btn
color="primary"
dark
v-on="{ ...tooltip, ...menu }"
>Dropdown w/ Tooltip</v-btn>
</template>
<span>Im A ToolTip</span>
</v-tooltip>
</template>
<v-list>
<v-list-tile
v-for="(item, index) in items"
:key="index"
#click="item.f"
>
<v-list-tile-title>{{ item.title }}</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</div>
</v-app>
</div>
new Vue({
el: '#app',
data: () => ({
items: [
{ title: 'Click Me1',f:'login'},
{ title: 'Click Me2',f:'login' },
{ title: 'Click Me3',f:'login' },
{ title: 'Click Me4' ,f:'login' }
]
}),
methods: {
login(){console.log('login')}
}
})
Try to pass the method name to another one and handle it inside the last one like :
<v-list-tile
v-for="(item, index) in items"
:key="index"
#click="handle(item.f)"
>
inside the methods :
methods: {
handle(f){
this[f]();
},
login(){console.log('login')}
}
check this codepen
You are passing the method's name - a string - instead of a function. The click event listener generated by vue is trying to call a function using apply, this is why you are getting that error.
One solution would be to pass directly the function when the Vue instance is created (before that, the method might not be available, so passing it directly to the data { title: 'Click Me1', f: this.login } would not work).
For example, you could keep having method names in the data, and replace them with the actual methods at create:
new Vue({
el: '#app',
data: () => ({
items: [
{ title: 'Click Me1', f: 'login' }
]
}),
created () {
this.items.forEach(item => {
item.f = this[item.f]
})
},
methods: {
login (){
console.log('login')
}
}
})