how can i call modal component inside a v-for loop? - vue.js

I use vuetify https://vuetifyjs.com/en/
My parent component like this :
<template>
<v-container>
...
<v-col
v-for="(item, i) in items"
:key="i"
>
<v-card
>
<v-app-bar dark color="grey">
<v-toolbar-title>Weekly Schedule : {{item.name}}</v-toolbar-title>
<div class="flex-grow-1"></div>
<modal-datepicker :schedule="item" />
</v-app-bar>
</v-col>
...
</v-container>
</template>
<script>
import { mapState } from "vuex";
import modalDatepicker from "../views/modalDatepicker";
export default {
components: {modalDatepicker},
};
</script>
My child component like this :
<template>
<v-dialog
:ref="dialog"
v-model="modal"
:return-value.sync="date"
>
<template v-slot:activator="{ on }">
<v-btn color="success" dark v-on="on">Show datepicker</v-btn>
</template>
<v-row justify="center">
<v-date-picker v-model="date" scrollable>
<div class="flex-grow-1"></div>
<v-btn text color="primary" #click="modal = false">Cancel</v-btn>
<v-btn text color="primary">OK</v-btn>
</v-date-picker>
</v-row>
</v-dialog>
</template>
<script>
import { mapState } from "vuex";
import modalDatepicker from "../views/modalDatepicker";
export default {
components: {modalDatepicker},
props: ['schedule'],
data: () => ({
date: new Date().toISOString().substr(0, 10),
modal: false,
}),
mounted() {
console.log(this.schedule)
},
};
</script>
The code above works, but seems inefficient. because every time the loop, it call modal dialog. I want modal to only be called when user click show datepicker button
how do i do that?

Apologies as I am on my mobile. Have you considered having the modal in your parent component outside of the loop?
You could have a data variable to handle your modal e.g
modal: {
open: false,
schedule: null }
Essentially then when your button is clicked you could add a v-click with a function that controls this data which in turns handles the content in the modal.
Then on your date picker or modal have a function to handle the update/close to clear this data and handle whatever you need too outside.
Then you could combine this into one component.
I see you have vuex you could also use vuex to control your modal and it’s contents.

Related

<v-dialog> value prop doesn't work when extending component

I created a FormDialog component which extends v-dialog
FormDialog
<template>
<div>
<v-dialog
v-bind="$props"
:value="dialog"
:transition="false"
scrollable
persistent
no-click-animation
#keydown.ctrl.enter="handleSave"
#keydown.esc="handleCancel"
#click:outside="handleCancel"
>
<v-card>
<v-card-title>
<!-- title -->
</v-card-title>
<v-card-text>
<v-container>
<slot name="form">
missing form slot
</slot>
</v-container>
</v-card-text>
<v-card-actions>
<!-- actions -->
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { VDialog } from 'vuetify/lib'
export default {
extends: VDialog,
props: {
/* Custom props */
/* Direct binding to value prop doesn't work */
dialog: undefined
},
methods: {
handleSave () {
this.$emit('save')
},
handleCancel () {
this.$emit('cancel)
}
}
}
</script>
As you can see I'm using a prop named dialog binded to v-dialog value prop, because direct value usage doesn't work.
ChildComponent (using value, doesn't work)
<FormDialog
:value="showDialog"
#save="handleSave"
#cancel="handleCancel"
>
<!-- form -->
</FormDialog>
Obviously I removed dialog prop references.
ChildComponent (using dialog, works)
<FormDialog
:dialog="showDialog"
#save="handleSave"
#cancel="handleCancel"
>
<!-- form -->
</FormDialog>
Do anyone knows the answer? I'd like to use value prop directly.

Close v-edit-dialog from method within script tag

I have v-edit-dialog with custom slot. I want the v-edit-dialog to close from a method which is triggered by a button inside the input slot of the dialog.
At the moment I have to press outside the dialog for closing it
I searched all over, Did not found anything regarding that specific problem
<v-edit-dialog
#click.native.stop
>
{{ channel.Options}}
<template v-slot:input>
<v-select
ref="option-select"
:data-key="index"
:items="index == 0 ? options: [...options, 'Disable Channel']"
>
</v-select>
<v-col class="text-right pa-0 ma-0 mb-2">
<v-btn color="primary" outlined #click="handleOptionChange(index)"
>Apply</v-btn
>
</v-col>
</template>
</v-edit-dialog>
<script>
export default {
...
methods: {
handleOptionChange(index){
...
//Close v-edit-dialog
}
}
}
</script>
I don't see an explicit API to close the dialog.
A workaround is to programmatically click the table to simulate an outside-click, which would automatically close the dialog:
<template>
<v-data-table ref="myTable">
<template v-slot:input>
<v-btn #click="closeDialog">Close</v-btn>
</template>
</v-data-table>
</template>
<script>
export default {
methods: {
closeDialog() {
// simulate outside-click to close edit-dialog
this.$refs.myTable.$el.click()
},
}
}
</script>
demo
I tried the click outside method and it doesn't work 100% of the time.
I found a better way to do it by referencing the edit dialog and then changing its internal property isActive to false:
<v-edit-dialog ref="editDialog">
<template v-slot:input>
<v-btn #click="closeDialog">Close</v-btn>
</template>
</v-edit-dialog>
<script>
export default {
methods: {
closeDialog() {
this.$refs.editDialog.isActive = false;
}
}
}
</script>

How to avoid mutating a prop directly when all you have is a click?

How can I mutate a prop the correct way, so that I don't get the [Vue warn]: Avoid mutating a prop directly message?
I already got the v-model to work on this v-dialog. However, I also want to provide a close button in the dialog itself, which causes this mutation warning, as it's the dialog itself that's mutating the variable. How best to approach this case and solve it?
Dialog.vue:
<template>
<v-dialog
:value="value" #input="$emit('input', $event)"
scrollable
width="80vw"
:transition="false"
>
<template v-slot:activator="{ on }">
<div v-on="on" #click="$emit('open')">
<slot name="button">
<v-btn color="primary">{{ buttonText == null ? title : buttonText }}</v-btn>
</slot>
</div>
</template>
<v-card
elevation="10"
height="80vh"
>
<v-system-bar
color="light-blue darken-3"
window
>
<span>{{title}}</span>
<v-spacer></v-spacer>
<v-icon #click="value=false">mdi-close</v-icon>
</v-system-bar>
<v-card-text> <!-- required here to make the scrollable v-dialog work -->
<slot></slot>
</v-card-text>
<slot name="actions"></slot>
</v-card>
</v-dialog>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true,
},
buttonText: String,
value: Boolean,
},
}
</script>
I can use it quite nicely like:
<mydialog title="Select something" button-text="A button!" v-model="dialog" #open="loadData()">
Content goes here...
</mydialog>
The <v-icon #click="value=false">mdi-close</v-icon> is what's incorrectly mutating the "value"-variable.
Sidenote #1: The open event is there so I can populate data (loadData) from a database when the dialog is opened (vs. when its created on the DOM).
UPDATE; I can get it to work by doing:
<v-icon #click="$emit('close', $event)">mdi-close</v-icon>
and
<mydialog title="Select something" button-text="A button!" v-model="dialog" #open="loadData()" #close="dialog=false">
However, I feel this is far from being elegant. Aren't there any solutions in where I don't need to add on-Handlers to close this dialog? I almost feel that this is worse than living with the warning.. :|
Use a computed property with get and set. The dialog computed will behave exactly as a normal variable and it will eliminate the warning. Now you can use it to get the value and also to set the value.
Try this:
<template>
<v-dialog
:value="dialog"
#input="$emit('input', $event)"
scrollable
width="80vw"
:transition="false"
>
<template v-slot:activator="{ on }">
<div v-on="on" #click="dialogOpened()">
<slot name="button">
<v-btn color="primary">{{ buttonText == null ? title : buttonText }}</v-btn>
</slot>
</div>
</template>
<v-card elevation="10" height="80vh">
<v-system-bar color="light-blue darken-3" window>
<span>{{title}}</span>
<v-spacer></v-spacer>
<v-icon #click="dialog=false">mdi-close</v-icon>
</v-system-bar>
<v-card-text>
<!-- required here to make the scrollable v-dialog work -->
<slot></slot>
</v-card-text>
<slot name="actions"></slot>
</v-card>
</v-dialog>
</template>
export default {
props: ['title', 'buttonText', 'value'],
data: () => ({
dlg_close: false,
}),
computed: {
dialog: {
get() {
return this.value;
},
set(selection) {
this.$emit("input", selection);
}
}
},
methods: {
dialogOpened(newVal) {
this.dialog = newVal;
},
},
}

Vue - closing dialog from child component

I'm using Vue and Vuetify and I'm having trouble closing a dialog from within a child component using $emit. In the main component I'm using v:on:close-dialog="closeDialog" and setting this.dialog = false. I'm trying to call that function from within the child. Trying three different ways:
On the <v-icon>close</v-icon> in the child component, I'm calling a closeDialog method that calls this.$emit('close-dialog').
On the <v-btn>Cancel</v-btn>, I have v-on:click="$emit('close-dialog')".
On the <v-btn>Cancel 2</v-btn>, I have v-on:click="$emit('dialog',false)".
None of those close the dialog or fire off the closeDialog method in the main component. Code is below.
mainComponent:
<template>
<v-flex>
<v-flex xs12 class="text-xs-right">
<v-dialog v-model="dialog" fullscreen hide-overlay
transition="dialog-bottom-transition">
<v-btn fab slot="activator" small color="red" dark>
<v-icon dark >add</v-icon>
</v-btn>
<childComponent v:on:close-dialog="closeDialog" />
</v-dialog>
</v-flex>
</v-flex>
</template>
<script>
import childComponent from './childComponent'
export default {
data(){
return{
dialog: false
}
},
name: 'Test',
components: {
childComponent
},
methods:{
closeDialog: function(){
console.log('close dialog 2');
this.dialog = false;
}
}
}
</script>
childComponent:
<template>
<v-flex xs12>
<v-card>
<v-toolbar dark color="primary">
<v-btn icon dark v-on:click="closeDialog">
<v-icon>close</v-icon>
</v-btn>
<v-toolbar-title>Dialog Test</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items>
<v-btn dark flat v-on:click="$emit('close-dialog')">Cancel</v-btn>
</v-toolbar-items>
<v-spacer></v-spacer>
<v-toolbar-items>
<v-btn dark flat v-on:click="$emit('dialog',false)">Cancel 2</v-btn>
</v-toolbar-items>
</v-toolbar>
<v-flex xs12 class="px-10">
<v-form ref="form">
<v-text-field
v-model="testField"
:counter="150"
label="Test field"
required
></v-text-field>
</v-form>
</v-flex>
</v-card>
</v-flex>
</template>
<script>
export default {
data: () => ({
testField: ''
}),
methods: {
closeDialog: function(){
console.log('close dialog 1');
this.$emit('close-dialog');
}
}
}
</script>
As you might have guessed, I'm new to Vue and still fumbling my way through it. Any help would be much appreciated.
In your parent you have:
<childComponent v:on:close-dialog="closeDialog" />
it should be (hyphen replaces colon in v-on):
<childComponent v-on:close-dialog="closeDialog" />
or #close-dialog altenatively.
This method, combined with this.$emit('close-dialog'); in your child should work.

Extracting the information in a prop in a Vue child component

I'm passing a object as a prop to a child component but I can't reference one of its elements (user_id) to use in a method in that child component.
My code (slightly abbreviated) is:
<template>
<div class="back">
<v-app id="inspire">
<v-content>
<v-container fluid>
<v-card flat>
<v-card-title>
<div>
<div class="headline">
{{data.title}}
</div>
<span class="grey--text">{{data.user}} said {{data.created_at}}</span>
</div>
<v-spacer></v-spacer>
<v-badge color="deep-orange accent-3" left overlap>
<span slot="badge">7</span>
<v-icon color="grey lighten-1" large>
insert_comment
</v-icon>
</v-badge>
<!--<v-btn color="deep-orange accent-3">5 replies</v-btn>-->
</v-card-title>
<v-card-text v-html="data.body"></v-card-text>
<v-card-actions v-if="own">
<v-btn icon small>
<v-icon color="deep-orange accent-3">edit</v-icon>
</v-btn>
<v-btn icon small>
<v-icon color="red">delete</v-icon>
</v-btn>
</v-card-actions>
</v-card>
<return-button></return-button>
</v-container>
</v-content>
</v-app>
</div>
</template>
<script>
export default {
name: "ShowQuestion",
props: ['data'],
data() {
return {
own: this.Own(),
}
},
methods: {
Own: function () {
return this.UserID() == this.user_id <---HERE BE DRAGONS! (reported as 'undefined')
},
UserID: function () {
... returns the 'sub' from the JWT token
return sub;
}
},
}
</script>
While the correct information is being displayed in the template, I also need to be able to compare the user's ID from the token with that contained in the prop (i.e. data.user_id). My reading suggests the solution will involve converting the object to an array, but that's beyond my current skill level too. I'd appreciate some pointers to a solution.
Thanks,
Tom
If you can render data.user_id in your template you can use it anywhere, but I'd probably do something like this to solve your problem:
props: ['data']
data() {
return {
}
},
computed: {
UserId() {
return //however you get your id
}
},
Then your v-if could just be this:
<v-card-actions v-if="UserId === data.user_id">