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

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.

Related

Change <v-autocomplete> chip delete icon

Autocomplete.vue
<template>
<v-autocomplete
v-bind="$props"
v-on="$listeners"
>
<!-- Cycling through slots -->
<template v-for="(_, name) in $slots">
<template :slot="name">
<slot :name="name"></slot>
</template>
</template>
<!-- Cycling through scoped slots -->
<template
v-for="(_, name) in $scopedSlots"
#[name]="data"
>
<slot
:name="name"
v-bind="data"
></slot>
</template>
</v-autocomplete>
</template>
<script>
import { VAutocomplete } from 'vuetify/lib'
export default {
extends: VAutocomplete,
props: {
dense: {
default: true
},
smallChips: {
default: true
}
}
}
</script>
I want to change the trash icon.
I looked in the documentation, but I didn't find any prop able to directly change the icon. I might be wrong, and I hope so, but it could be necessary to access the item slot to change the icon.
If so, can anyone provide the necessary code to change the icon WITHOUT losing any feature/functionality?
try the clear-icon prop.
from the documentation-

How to call submit.prevent on child component

I've got a parent component with a submit button
<v-card flat>
<child-component :submit="submit()"></child-component>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn #click="submit" type="submit">Change password</v-btn>
</v-card-actions>
</v-card>
and a child component with a form that has a #submit.prevent that I need to call whenever I click the button on the parent:
<v-form #submit.prevent="submit">....</v-form>
My submit() is on the parent, it's just a simple function that submits the data to a database. It requires parameter X.
The reason I have a #submit.prevent on the child form is that I can press enter to submit the form.
How can I pass the submit() as a prop to the child? Or how can I call submit from the child component?
The child should emit an event on submit:
<v-form #submit.prevent="$emit('submit')">....</v-form>
And the parent can listen for that event to call the method:
<child-component #submit="submit"></child-component>
You can use slots to pass your submit button from parent to child form and it will work. Also you can define default template, if no slots is passed.
So you will be able to use form itself, without passed submit button (it will have it's own, default, button), or pass slot with any button you need.
Then you just listen to submit event of form (submit.prevent on form) and do whatever you want in parent component.
Vue.component('form-component', {
template: `
<form #submit.prevent="submitEvent">
<input type="text" v-model="value"/>
<slot>
<button type="submit"> Default Submit </button>
</slot>
</form>
`,
data: () => ({
value: 'Hello Vue!'
}),
methods: {
submitEvent(e) {
this.$emit('submit', e);
}
}
});
const app = new Vue({
el: '#app',
methods: {
submitForm(e) {
console.log(e.target);
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form-component #submit="submitForm">
<template v-slot>
<button type="submit"> Submit </button>
</template>
</form-component>
</div>
You can use ref
<v-card flat>
<child-component ref="form"></child-component>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn #click="submit" type="submit">Change password</v-btn>
</v-card-actions>
</v-card>
methods: {
submit() {
this.$refs.form.submit()
}
},

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;
},
},
}

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

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.