how to make #click in Vuetify dynamic based on JSON objects - javascript

I have a component which shows a list. This list is dynamically populated by pages using this component.
<template>
<v-list
subheader
>
<v-subheader class="font-weight-black">Choose action</v-subheader>
<v-divider/>
<v-list-item
v-for="(item, i) in model.menu"
:key="i"
#click="item.action"
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</template>
And this is the TypeScript class.
<script lang="ts">
import { Component, Vue, Watch, Prop } from 'vue-property-decorator'
import { app } from 'electron'
#Component
export default class ListItem extends Vue {
#Prop() appStatus!: string
#Prop() appId!: string
model: any = {
menu: [
{ title: 'Title One', action: 'someModalAction(appId)' },
{ title: 'Title Two', action: '' },
{ title: 'Title Three', action: '' }
]
}
someModalAction( appId: string ) {
// show some modal here
}
</script>
Here model object would be dynamic and other pages would pass this object as Prop() (This is just an example here).
When I click on Title One, nothing happens. However, when I change the object to
{title: 'Title One', action: this.someModalAction(this.appId)}
Then I can see the modal when the page is loaded. When I close this modal, the list item cannot be clicked then.
So, how can I pass actions to #click dynamically?

convert data with getter:
import { Component, Vue, Watch, Prop } from 'vue-property-decorator'
import { app } from 'electron'
#Component
export default class ListItem extends Vue {
#Prop() appStatus!: string
#Prop() appId!: string
model: any = {
menu: [
{ title: 'Title One', action: 'someModalAction' },
{ title: 'Title Two', action: '' },
{ title: 'Title Three', action: '' }
]
}
get items(){
return this.model.menu
.map(item => ({...item, action: item.action && () => this[item.action](this.appId)})
}
someModalAction( appId: string ) {
// show some modal here
}
<template>
<v-list
subheader
>
<v-subheader class="font-weight-black">Choose action</v-subheader>
<v-divider/>
<v-list-item
v-for="(item, i) in items"
:key="i"
#click="item.action"
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</template>

Just make one function for calling you function on click.
To use dynamic function call it is suggested to have a helper function
that receives the function name and call the corresponding function.
data () {
return {
test: '',
submitting: false,
list: [{
title: "One",
action: "itemClicked"
},
{
title: "Two",
action: "itemClicked2"
},
{
title: "Three",
action: "itemClicked3"
}
]
}
},
methods: {
itemClicked() {
alert('item clicked')
},
itemClicked2() {
alert('item clicked 2')
},
itemClicked3() {
alert('item clicked 3')
},
funcioncall(name){
this[name]();
}
}
codepen - https://codepen.io/Pratik__007/pen/JjoVxbj?editors=1010

v-list-item v-for="(item,index) in profileItems" #click="item.action" ripple="ripple"
:key="index">
{
icon: 'mdi-exit-to-app',
href: '/',
title: 'Logout',
action: () => {
this.onUserLogout()
}
},
]

Related

Storefront UI - SfComponentSelect don't work properly

In my own component, i'm using SfComponentSelect (here in official docs)
When I click on option of select, selected option not show above the label "MySelect", this happen otherwise on sample inside the official docs.
This is my CustomComponent.vue
<template>
<SfComponentSelect label="MySelect">
<SfComponentSelectOption
v-for="option in optionsList"
:key="option.value"
:value="option.value"
class="sort-by__option"
>{{ option.label }}</SfComponentSelectOption>
</SfComponentSelect>
</template>
<script>
import { SfComponentSelect } from '#storefront-ui/vue';
export default {
name: "CustomComponent",
components: {
SfComponentSelect
},
data(){
return {
optionsList: [
{
value: "opt-1",
label: "Option 1",
},
{
value: "opt-2",
label: "Option 2",
}
]
}
}
};
</script>
After struggling on problem, i've found a solution for this problem.
Create new component (called: MyNewComponent) defining SfComponentSelect as mixins and copying from template and scss from SfComponentSelect.
Defining for MyNewComponent a new props and html for this
Using MyNewComponent as below
<SelectStore :label="name"
:size="pdvLists.length"
#change="changeLabel"
:my-new-label="myNewLabel"
persistent>
<SfComponentSelectOption
v-for="option in options"
:key="option.value"
:value="option.value"
class="sort-by__option"
>{{ option.name }}</SfComponentSelectOption>
</SelectStore>
<script>
import MyNewComponent from './MyNewComponent';
export default {
name: "ComponentBlaBla",
components: {
MyNewComponent
},
data() {
return {
options: [
{
value: "opt1",
name: "Opt1"
},
{
value: "opt2",
name: "Opt2"
}
],
myNewLabel: ''
}
},
methods: {
changeLabel(value){
// change something
let selectedOption = this.options.filter((item) => value === item.value);
this.myNewLabel = selectedOption[0].name;
}
}
};
</script>

Binding different event listeners in v-for

What I want
I have a Vue component. Inside the component I'm rendering list of actions using v-for. Each action should have a #click event handler that executes it's corresponding action (method of the component)
I do not know how to declare the actions in my data() and how to bind the actions to a #click
Code
template
<v-menu
v-for="item in items"
v-else
:key="item"
top
rounded
>
<template #activator="{ on, attrs }">
<v-btn
v-bind="attrs"
icon
class="mt-3"
v-on="on"
>
<v-icon>
mdi-dots-vertical
</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item
link
>
<v-list-item-title #click="???(todo)"> // I wont item.action...
<v-icon>{{ item.icon }}</v-icon>
{{ item.title }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
script
data () {
return {
items: [
{ title: 'edit', icon: 'mdi-pencil', action: ??? }, // => action: toEdit(todo)
{ title: 'delete', icon: 'mdi-delete', action: ??? } // => action: remove(todo)
]
},
methods: {
toEdit (todo) {
this.selectedTodo = todo
this.itemText = todo.text
this.$store.commit('todos/toggle', todo)
},
removeTodo (todo) {
this.$store.commit('todos/remove', todo)
}
}
}
What I tried myself
Changed item.action
script
data () {
return {
items: [
{ title: 'edit', icon: 'mdi-pencil', action: `${this.toEdit}` },
{ title: 'delete', icon: 'mdi-delete', action: `${this.removeTodo}` }
]
}
// handler.apply is not a function
Changed items.action to add triggerClick methods
template
...
<v-list-item-title #click="triggerClick(item.action, todo)"> // #click="item.action" => #click="triggerClick(item.action, todo)"
...
script
data () {
return {
itemText: '',
selectedTodo: [],
items: [
{ title: 'edit', icon: 'mdi-pencil', action: 'edit' }, //`${this.toEdit}` => edit
{ title: 'delete', icon: 'mdi-delete', action: 'remove' } // `${this.removeTodo}` => 'remove'
]
}
},
methods: {
...
//add
triggerClick (action, todo) {
if (action === 'edit') {
this.toEdit(todo)
} else if (action === 'remove') {
this.removeTodo(todo)
}
}
// Cannot read property 'text' of undefined
Define your actions as strings - each string should correspond to a method name you want execute on click
{ text: 'edit text', icon: 'mdi-pencil', action: 'toEdit' },
{ text: 'delete text', icon: 'mdi-delete', action: 'removeTodo' }
Create a method that receives 2 parameters - one is action (method name) and other is the todo item
methods: {
callTodoAction(action, todo) {
this[action](todo);
}
}
Use it in the template
<v-list-item-title #click="callTodoAction(item.action, todo)">
Learn how and why the callTodoAction above works: bracket notation
<v-list-item-title #click="triggerClick(item)">
<v-icon>{{ item.icon }}</v-icon>
{{ item.title }}
</v-list-item-title>
// need text properties
// used this.itemText = todo.text
// not title
items:[
{ text: 'edit text', icon: 'mdi-pencil', action: 'edit' },
{ text: 'delete text', icon: 'mdi-delete', action: 'remove' }
]
triggerClick(todo){
const { action } = tode
action === 'edit' && this.toEdit(todo)
action === 'remove' && this.removeTodo(todo)
}

how to alter a single row in v-data-table (not styling, the content)

I wasn't able to find an answer for this use case of v-data-table.I know that you can use template and slots to modify a certain column but what if i want my value to be reflected only in one row? So in my code, everytime a user right clicks on the name column it adds a logo to show the value is copied and then after 3 seconds it removes it from the name -kind of like a toggle effect.
It works well whenever i click on a name on a certain row, and it copies the link value for that specific link by using vue-clipboard's library. However, it also does the same thing for all the other columns that have link. I would like to do it for only one. I couldn't make the vue-clipboard library run in sandbox so i'm sharing my code snippets.
In order to better show the current behavior, this is a screenshot from the v-data-table. (as you can see, it shows the check icon in both rows even though i only click on the first one. The expected behavior would only show the check icon the cell that has been clicked on .
template;
<template>
<v-data-table
:headers="headers"
:items="tableData"
class="display-stats"
:items-per-page="5"
:footer-props="{
'items-per-page-options': rowsPerPageItems,
}"
>
<template v-slot:[`item.name`]="{ item }">
<span v-if="item.link" class="link-span" #contextmenu="copyLink(item.link)">
<a class="preview-link" :href="item.preview" target="_blank">{{ item.name }}</a>
<p v-show="copied">
<v-icon small :color="green">fas fa-check</v-icon>
</p>
</span>
<span v-else>
{{ item.name }}
</span>
</template>
</v-data-table>
</template>
script;
<script lang="ts">
import Vue from 'vue'
import VueClipboard from 'vue-clipboard2'
VueClipboard.config.autoSetContainer = true // add this line
Vue.use(VueClipboard)
interface PriceStats {
rowsPerPageItems: Number[]
copied: boolean
}
export default Vue.extend({
name: 'Component',
props: {
priceData: {
type: Array as () => Array<PriceStats>,
default: () => {},
},
loading: {
type: Boolean,
default: false,
},
},
data(): PriceData {
return {
rowsPerPageItems: [10, 20, 30],
copied: false,
}
},
computed: {
tableData:{
get():PriceStats[]{
if (this.priceData) {
return this.priceData
} else {
return []
}
},
set(newVal:PriceStats){
this.tableData=newVal
}
},
headers(): DataTableHeader[] {
return [
{
text: 'Name',
value: 'name',
},
{
text: 'Age',
value: 'age',
align: 'center',
},
{
text: 'Salary',
value: 'salary',
},
{
text: 'Position',
value: 'format',
},
{
text: 'Date',
value: 'date',
},
{
text: 'Premium',
value: 'premium',
align: 'right',
},
]
},
},
methods: {
copyLink(previewLink: string) {
this.$copyText(previewLink).then(
(e) => {
this.copied = true
setTimeout(()=> {
this.copied = false
},3000)
},
(e) => {
need an error logic here
this.copied = false
}
)
},
},
})
</script>
Let's assume that users cannot have the same name, you can check if the name is equal to the one on the row copied then display the icon there.
like this:
<v-data-table ...>
<span v-if="item.link" class="link-span" #contextmenu="copyLink(item.link,item.name)">
<a class="preview-link" :href="item.preview" target="_blank">{{ item.name }}</a>
<p v-show="item.name == copiedName">
<v-icon small :color="green">fas fa-check</v-icon>
</p>
</span>
</v-data-table>
copiedName can be an external variable that you assign the name of the user using the function copyLink
...
copyLink(previewLink: string,name) {
this.$copyText(previewLink).then(
(e) => {
this.copied = true
this.copiedName = name
setTimeout(()=> {
this.copied = false
},3000)
},
(e) => {
need an error logic here
this.copied = false
}
)
},

Using VMenu from vuetify with render function (scoped slot)

I'm trying to use Vuetify's VMenu component and I would like that when a user clicks the button the VMenu shows up. As far as the docs goes it says we should add a scoped slot. Doing with a normal template it works but when I switch to a render function approach it never renders the button.
I have been following the Vue's docs and ended up with:
h(VMenu, { props: { value: isMenuOpen.value } }, [
h(
"template",
{
scopedSlots: {
activator: ({ on, attrs }) => {
debugger; // it never reaches this debugger
return h(VButton, { on, attrs }, 'click me');
}
},
},
[]
),
h(VList, [h(VListItem, [h(VListItemTitle, ["Logout"])])]),
]),
I have tried using a non-arrow function as well:
scopedSlots: { activator: function({ on, attrs }) { return h('div', 'click me'); } }
and return a simple h('div', 'click me') in both non-arrow function and arrow function and nothing seems to work.
How can I pass the scoped slot activator to VMenu component?
Scoped slots are passed through the scopedSlots property of createElement's 2nd argument in the form of { name: props => VNode | Array<VNode> }. In your case, scopedSlots should have two entries: one for activator, and another for default:
import { VMenu, VList, VListItem, VBtn } from 'vuetify/lib'
export default {
render(h) {
return h(VMenu, {
scopedSlots: {
activator: props => h(VBtn, props, 'Open'),
default: () => h(VList, [
h(VListItem, 'item 1'),
h(VListItem, 'item 2'),
h(VListItem, 'item 3'),
]),
},
})
}
}
which is equivalent to this template:
<template>
<v-menu>
<template v-slot:activator="{ on, attrs }">
<v-btn v-bind="attrs" v-on="on">Open</v-btn>
</template>
<v-list>
<v-list-item>item 1</v-list-item>
<v-list-item>item 2</v-list-item>
<v-list-item>item 3</v-list-item>
</v-list>
</v-menu>
</template>
demo
I wasn't able to fully understand the problem described in my question. This is an answer not to answer the fully original question but to guide future users that may come to this question.
Instead of using a scoped slot I have used the value prop in combination with attach prop. This solution in the end ended up working without no problem.
button(
{
attrs: { "data-account-setting": true },
props: { plain: true, rounded: true, icon: true },
on: { click: onOpenMenuClick },
},
[h(VIcon, ["mdi-account-outline"])]
),
h(
VMenu,
{
props: {
value: isMenuOpen.value,
// waiting on answer on SO
// #see https://stackoverflow.com/questions/67405594/using-vmenu-from-vuetify-with-render-function-scoped-slot
attach: "[data-account-setting]",
minWidth: "300px",
left: true,
offsetY: true,
closeOnContentClick: false,
rounded: true,
},
on: {
input: (value: boolean) => {
isMenuOpen.value = value;
},
},
},
[
h(VList, { props: { dense: true } }, [
h(VListItem, { props: { to: { name: "logout" } } }, [
h(VListItemTitle, { attrs: { 'data-cy-logout': true } }, ["Logout"]),
]),
]),
]
),

how to pass default value in multiselect component to another vue-multiselect

I want to pass the default value of vue multiselect component use props, but I can't do this.
I use two selectors. When one option in select-1 selects I want the default value in select-2 is option select
No error just doesn't work properly. The value selected from the first selection does not fall into the default value of the second selection
multiselect component
<template>
<div>
<multiselect v-model="internalValue" id="currency_id" #input="onchange" placeholder="Select Your Currency" label="title" track-by="title" :options="options" :option-height="10" :show-labels="false">
<template slot="singleLabel" slot-scope="props"><img class="option__image" :src="props.option.img"><span class="option__desc"><span class="option__title">{{ props.option.title }}</span></span>
</template>
<template slot="option" slot-scope="props"><img class="option__image" :src="props.option.img">
<div class="option__desc"><span class="option__title" :id="props.option.id">{{
props.option.title }}</span><span class="option__small">{{ props.option.desc }}</span></div>
</template>
</multiselect>
</div>
</template>
import Vue from 'vue';
import Multiselect from 'vue-multiselect'
Vue.component('multiselect', Multiselect);
export default {
props: ['options', 'value'],
components: {
Multiselect
},
data() {
return {
internalValue: this.value,
}
},
methods: {
onchange(options) {
this.$emit('selectvalue', options.id);
}
},
watch: {
internalValue(v) {
this.$emit('input', v);
}
}
}
HTML
**select 1**
<multiselect #selectvalue="apiCalc":options="[
{
id: '1', title: 'Tether', img: 'https://coinnik.com/uploads/crypto-logos/006fe133d48ea7cd45cf8ccb8cb7ec42.png'
}
,
{
id: '2', title: 'ether', img: 'https://coinnik.com/uploads/crypto-logos/006fe133d48ea7cd45cf8ccb8cb7ec42.png'
}
,
{
id: '3', title: 'bitcoin', img: 'https://coinnik.com/uploads/crypto-logos/006fe133d48ea7cd45cf8ccb8cb7ec42.png'
}
]"
> </multiselect>
select2
<multiselect id="receive-currency" :options="receive_currency" v- model="selectedValue"></multiselect>
app.js
new Vue({
el: "#calculate",
data: {
receive_currency: [],
selectedValue: null,
},
methods: {
apiCalc(options) {
let self = this;
this.sendCurrencyId = options;
var receiveCurrency = [];
for (let item in responseData.data.direction.data) {
receiveCurrency.push({
title: responseData.data.direction.data[item].receiveCurrency.data.title,
img: '',
});
}
self.receive_currency = receiveCurrency;
self.selectedValue = receiveCurrency[0]
})
}
}
},
components: {
'multiselect': Multiselect
},
created() {
this.apiCalc();
},
});
In template:
<multiselect v-model="multiSelect1" :options="options" #input="onChange"></multiselect>
<multiselect v-model="multiSelect2" :options="options" :placeholder="placeholder"></multiselect>
In script:
data: () => ({
multiSelect1: "",
multiSelect2: "",
options: ["list", "of", "options"],
placeholder: "Select option"
}),
methods: {
onChange() {
this.multiSelect2 = this.multiSelect1
}
}
Please check this codesandbox: https://codesandbox.io/s/vue-template-t226h

Categories

Resources