Problem when creating a menu by iteration - vue.js

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')
}
}
})

Related

Nuxtjs buttom DropDown

I'm building a buttom like Dropdown, this button is for print and dowloand document, so this is my code:
<template>
<v-menu
transition="slide-y-transition"
bottom
offset-y
>
<template v-slot:activator="{ on, attrs }" >
<v-btn
v-bind="attrs"
v-on="on"
>
Actions
</v-btn>
</template>
<v-list>
<v-list-item
v-for="(item, index) in actions"
:key="index"
#click="item.to"
>
<v-list-item-title class="white--text">
{{item.title}}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</template>
<script>
export default {
data()=>({
actions: [
{
title: 'Print',
to: 'print()'
},
{
title: 'Download',
to: 'download()'
}
]
}),
methods: {
print() {
//some code
},
download() {
//some code
}
}
</script>
so, when a try to click in button and select opction "Print" this error is showing me:
TypeError: handler.apply is not a function
why this error is showing me??
is possible call one function like string??
Give the method names without () then add a handler method for the click event as follows :
#click="handleMethod(item.to)"
and in script :
export default {
data()=>({
actions: [
{
title: 'Print',
to: 'print'
},
{
title: 'Download',
to: 'download'
}
]
}),
methods: {
print() {
//some code
},
download() {
//some code
},
handleMethod(method){
this[method]()
}
}
}
or just remove the () from the method names and try out #click="this[item.to]()"

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 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 trigger click event after v-for has create a new dom

I use v-for to generate task tabs.
Tasks can be created by users, and after users have created a new task I want the UI changing to the newly created tab automatically.
i.e. After a new tab has been added to the dom tree, itself click event will be triggered and the callback function activateTask will execute.
<template>
<v-container>
<v-btn #click="createNewTask"></v-btn>
<v-tabs>
<v-tab v-for="task in tasks" :key="task.uuid" #click="activateTask">
{{ task.name }}
</v-tab>
</v-tabs>
</v-container>
</template>
<script>
export default {
data: {
tasks: [],
},
method: {
createNewTask() {
this.tasks.push({
uuid: util.generateUUID(),
name: "name",
});
},
},
};
</script>
You can bind v-tabs with v-model to control active-tab.
<template>
<v-container>
<v-btn #click="createNewTask"></v-btn>
<v-tabs v-model="currentTab">
<v-tab v-for="task in tasks" :key="task.uuid" #click="activateTask">
{{ task.name }}
</v-tab>
</v-tabs>
</v-container>
</template>
<script>
export default {
data: {
tasks: [],
currentTab: null
},
method: {
createNewTask() {
this.tasks.push({
uuid: util.generateUUID(),
name: "name",
});
this.currentTab = this.tasks.length-1
},
},
};
</script>
Figure out a way that using the update hook and a variable to check whether a new tab dom is created. If true select the last child node from dom tree and dispatch click event.
So ugly 😔.
updated() {
this.$nextTick(() => {
if (!this.newTaskCreated) {
return
}
this.$el.querySelectorAll('.task-tab:last-child')[0].dispatchEvent(new Event('click'))
this.newTaskCreated = false
})
}

Vue draggable to follow v-chip-group selection

I am using Vue draggable to change the position of my v-chips. When you click on a chip, the chip shows Active and is binded to selection with its index. The problem is that when you drag a chip and change its position it does not update the selection index.
How can I make sure one follows the other?
<v-chip-group
v-model="selection"
active-class="primary--text"
column
>
<draggable v-model="items" #start="drag=true" #end="drag=false">
<v-chip v-for="(item, i) in items" :key="i"
close
draggable>
{{item.name}}
</v-chip>
</draggable>
</v-chip-group>
You can able to set the selection index using #start and #end event in draggable component
Here is the working codepen: https://codepen.io/chansv/pen/zYvOYyd?editors=1010
Find the working code here:
<div id="app">
<v-app id="inspire">
<v-card
max-width="400"
class="mx-auto"
>
<v-card-text>
<v-chip-group
v-model="selection"
column
active-class="primary--text"
>
<draggable v-model="tags" #start="dragStart" #end="dragEnd">
<v-chip v-for="(tag, i) in tags" :key="i" draggable>
{{ tag.name }}
</v-chip>
</draggable>
</v-chip-group>
</v-card-text>
</v-card>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
selection: null,
currentTag: null,
tags: [{
name: 'Shoping',
},{
name: 'Art',
}, {
name: 'Tech',
}, {
name: 'Creative Writing'
}
],
}),
methods: {
dragStart() {
if (this.tags[this.selection]) this.currentTag = this.tags[this.selection].name;
else this.currentTag = null;
},
dragEnd() {
var self = this;
if (this.currentTag) {
this.tags.forEach((x, i) => {
if (x.name === self.currentTag) self.selection = i;
});
}
}
}
})