In Vue/Vuetify, how do we hide/show dialogs from parent? I'm trying to use v-model and here is a simplified version of my setup:
Parent component (just a button that triggers the child component to show)
<template>
<div>
<v-btn class="ma-2" outlined fab color="red" small #click.stop="editItem()">
<v-icon size="16">mdi-close-circle</v-icon>
</v-btn>
<user-dialog v-model="dialog" :eitem="editedItem" class="elevation-2" />
</div>
</template>
<script>
import UserDialog from "./UserDialog.vue";
export default {
components:{
UserDialog
},
data() {
return {
counter: 0,
dialog: false,
editedItem: {},
}
},
methods: {
editItem: function() {
this.counter++;
this.editedItem = Object.assign({}, {
title: 'some title' + this.counter,
details: 'some details for this item'
});
this.dialog = true;
},
},
}
</script>
Child component (basically a dialog box)
<template>
<v-dialog v-model="value" max-width="500px">
<v-card>
<v-card-title>
<span class="headline">A Dialog</span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12>
<v-text-field v-model="eitem.title" label="Title"></v-text-field>
</v-flex>
<v-flex xs12>
<v-text-field v-model="eitem.details" label="Details"></v-text-field>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click.stop="save">Save</v-btn>
<v-btn color="blue darken-1" text #click.stop="close">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
props: {
value: Boolean,
eitem: Object,
},
data() {
return {
editedItem: this.eitem,
}
},
methods: {
save() {
//perform save
this.$emit('input', false);
},
close() {
this.$emit('input', false);
},
},
}
</script>
This setup works, but give the following warning:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
But if act upon this advice and declare a data item in the child component and set v-model of the v-dialog to this data item, the dialog stops showing up upon click.
I perhaps understand why it does that, but cannot figure out a proper way of fixing this that doesn't show warnings. Can anyone help me with this?
Since Vue throws a warning when you mutate props, you should not use v-model with props. To handle this use the following pattern:
computed: {
propModel: {
get () { return this.value },
set (value) { this.$emit('input', value) },
},
},
Define computed property with getter, that returns props.value, and setter that emits input event (that will be successfully handled in parent, since you use v-model)
Don't forget to chage your template:
<v-dialog v-model="propModel" max-width="500px">
This works for me and do not need to create a computed data.
<v-dialog
width="600px"
:value="value"
#input="$emit('input', $event)"
>
</v-dialog>
Related
It is suprisingly hard to have an v-text-area with an appended (outer or not) button (icon) that appears only when the text inside the text-area is modified and disaper only once it is clicked on.
Here is my stripped code to show relevant info:
<template>
<v-container fluid>
<v-card>
<v-row>
<v-col>
<v-card flat>
<v-col>
<v-text-field
dense
solo
name="requesterNameTextArea"
filled
hide-details
v-model="exceptionObj.requesterName"
:append-icon="showSaveIcon ? 'mdi-check' : null"
:flat="!showSaveIcon"
type="text"
#keydown="showSave"
#click:append="putException(exceptionObj), GoToException, hideSave"
#blur="hideSave"
>
</v-text-field>
</v-col>
</v-row>
</v-card>
</v-col>
</v-row>
</v-card>
</v-container>
</template>
<script>
import ...
export default {
components: {
},
data() {
return {
showSaveIcon: false,
exceptionObj: [],
};
},
methods: {
putException, //Api put command
GoToException: function (item) {
this.$router.push({
name: "UniversalException",
params: {
id: item.exceptionId,
},
});
},
showSave() {
this.showSaveIcon = true;
},
hideSave() {
this.showSaveIcon = false;
},
},
async mounted() {
this.exceptionObj = await getExceptionObj(this.ExceptionId);
},
};
</script>
I basically tried every possible combination of event prop like thoses:
#keydown="showSave"
#change="showSave"
#focus="showSave"
#blur="hideSave"
#click:append="putException(exceptionObj), GoToException, hideSave"
And having multiple "commands" on a single #click command doesn't do anything else than the api command (like if props had priority over one another). The GoToException was to try and force a reload of the page in order to lose focus. Having the icon appended on outer or normal changes the behavior but I never succeded on obtaining the desired result.
Any idea on how to go around that problem would be apreciated. Especially infuriating as so many websites implement it and I have yet to find a way to do it ;_;
As it turns out, it is simply impossible to use the
#click:append="putException(exceptionObj), hideSave"
But it is possible to use a new method like so:
SaveException() {
putException(this.exceptionObj);
this.hideSave();
},
And so, I ended up with a simple:
#click:append-outer="SaveException()"
I have a vue app where I send one array from one component to another. which works. But now I want to visually show this. What I mean is like this:
Array 1 has the field date and time inside it which is copied to the parent Array 2 I can log this but if I want to show a specific value for example date itself it does not show anything and gives the error the property can not be read. I tried looping through the parent array but it did not work causing in the can not read property error.
Could someone give me a pointer on how to fix this.
Childcomponent method and array to be copied the method fills the array here with data:
data: () => ({
selectedTime: [],
dates: [{date : new Date().toISOString().substr(0,10), time: []}],
}),
methods:{
addTimeFields(){
this.selectedTime.push({
startTime:"",
endTime: "",
})
this.dates[0].time.push(
this.selectedTime
)
},
Parent component which should get the array here I am trying to push the array from child to parent array which works (save method) but when I try to loop though it gives me an error:
<v-card-text v-for="(i,index) in finalDate">
<v-btn >
{{i.finalDate}}
</v-btn>
</v-card-text>
<v-divider class="mx-4"></v-divider>
<v-card-actions>
<v-spacer />
<v-col>
<vs-button>Generate Meeting Link</vs-button>
</v-col>
{{finalDate}}
</v-card-actions>
</v-card>
</template>
<script>
import MeetingsTableComponent from "#/components/MeetingsTableComponent";
import DatePickerComponent from "#/components/DatePickerComponent";
export default {
name: "NextMeetingCardComponent",
components: { DatePickerComponent },
data: () => ({
dialog: false,
finalDate: [],
menu: false,
modal: false,
menu2: false
}),
methods:{
save() {
this.finalDate.push(
this.$refs.datepicker.dates
)
}
Error:
[Vue warn]: Property or method "date" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <NextMeetingCardComponent>
<DashboardComponent> at src/views/DashboardComponent.vue
<VMain>
<VApp>
<App> at src/App.vue
<Root>
You have a random {{ date }} in your component:
<v-card-text v-for="(i,index) in finalDate">
<v-btn >
{{i.date}}
</v-btn>
</v-card-text>
<v-divider class="mx-4"></v-divider>
<v-card-actions>
<v-spacer />
<v-col>
<vs-button>Generate Meeting Link</vs-button>
</v-col>
{{date}} <---- HERE
</v-card-actions>
</v-card>
</template>
Could this be the issue?
I have a custom component using the v-date-picker which is used in a lot of places. I want to be able to dynamically set the "default" date picked from the parent component, while being able to modify the date from the child component.
Here's the code in the parent component:
<template>
<DatePickerMenu #selectedDate="selectedExpirationDate" :selectedDate="this.date"></DatePickerMenu>
</template>
<script>
data() {
return {
date: '2021-04-29', //used for testing, will eventually come from a certain calculation inside this parent component
}
},
methods: {
selectedExpirationDate(value) {
this.expiration_date = value;
},
},
</script>
In the child component:
<template>
<v-menu
ref="datePickerMenu"
v-model="datePickerMenu"
:close-on-content-click="false"
:return-value.sync="selectedDate"
transition="scale-transition"
offset-y
min-width="auto"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
class="form"
v-model="selectedDate"
label="Expiration date *"
hint="Minimum expiration date: one week from today"
prepend-icon="mdi-calendar"
readonly
v-bind="attrs"
v-on="on"
:rules="requiredRules"
></v-text-field>
</template>
<v-date-picker
v-model="selectedDate"
no-title
scrollable
color="primary"
>
<v-spacer></v-spacer>
<v-btn
text
color="primary"
#click="datePickerMenu = false"
>
Cancel
</v-btn>
<v-btn
text
color="primary"
#click="$refs.datePickerMenu.save(selectedDate)"
>
OK
</v-btn>
</v-date-picker>
</v-menu>
</template>
<script>
export default {
name: "DatePickerMenu",
data () {
return {
datePickerMenu: false,
//selectedDate: this.setSelectedDate, and changing the 'selectedDate' props to setSelectedDate
}
},
props: ['selectedDate'],
watch: {
selectedDate: function() {
this.$emit('selectedDate', this.selectedDate);
},
},
}
When I do this, the date-picker shows the correct date passed from the parent component, but when I change the selected date, the following message appears:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "selectedDate"
So as you can see, I tried setting a local data with the passed props //selectedDate: this.setSelectedDate, but when I do so, the default selected date works the first time, but when it's changed in the parent component, it won't update in the child's.
Hopefully my problem is clearly explained.. Any solution ideas?
Thanks in advance.
You can use the .sync modifier like so:
in your parent:
<DatePickerMenu
:selectedDate.sync="this.date"
#selectedDate="selectedExpirationDate"
/>
and in your child component, create a computed like so:
<v-date-picker
v-model="selectedDateComputed"
no-title
scrollable
color="primary"
>
computed: {
selectedDateComputed: {
get(): {
return this.selectedDate;
}
set(newDate): {
this.$emit('update:selectedDate', newDate);
}
}
}
of course you need to use this for v-text-field as well.
you can see the vue sync modifier docs for more information.
Seems to be working like a charm, big thanks!
FYI, it was the perfect approach, but there were some syntax errors:
computed: {
selectedDateComputed: {
get: function () {
return this.selectedDate;
},
set: function(newDate) {
this.$emit('update:selectedDate', newDate);
}
},
Vuetify give props activator in many components such as v-menu or v-dialog,but there are no more details about how to creat a node to work properly.
The document describe like this
Designate a custom activator when the activator slot is not used. String can be any valid querySelector and Object can be any valid Node.
But i use querySelector to pick a simple element and it didn't work,any additional property should i add?
As far as I know, there is no additional props. The activator prop accept 3 different types.
With Selector:
<v-app>
<v-btn class="my-btn">Dropdown</v-btn>
<v-menu activator=".my-btn">
<v-list>
...
</v-list>
</v-menu>
</v-app>
Example
With Component:
<v-app>
<v-btn ref="myBtn">Dropdown</v-btn>
<v-menu :activator="myBtnRef">
<v-list>
...
</v-list>
</v-menu>
</v-app>
new Vue({
data: () => ({
myBtnRef: null,
...
}),
mounted() {
this.myBtnRef = this.$refs.myBtn
}
}).$mount('#app')
Example
With HTMLElement:
<v-app>
<v-btn>Dropdown</v-btn>
<v-menu :activator="myBtn">
<v-list>
...
</v-list>
</v-menu>
</v-app>
new Vue({
data: () => ({
myBtn: null,
...
}),
mounted() {
let button = document.createElement('button')
button.textContent = 'Dropdown'
document.body.insertBefore(button, document.body.firstChild)
this.myBtn = button
}
}).$mount('#app')
Example
I made two different components.
in one v-navigation-drawer
the second has a button that changes the States of v-navigation-drawer in vuex.
When on small screens, there is a call to v-navigation-drawer, and after clicking on the gray area, the state in vuex does not change.
enter code here
drawer.vue
<template>
<v-navigation-drawer
fixed
:value="drawer"
app
:class="getSettingsTheme"
:mini-variant="mini"
>
<b>menu</b>
</v-navigation-drawer>
</template>
sitebar.vue
<template>
<v-toolbar fixed app clipped-right :class="getSettingsTheme">
<v-tooltip right>
<v-btn slot="activator" icon #click.stop="drawerMut">
<v-icon class="theme--light grey--text" color="grey lighten-1">menu</v-icon>
</v-btn>
<span>Show/Hide drawer</span>
</v-tooltip>
</v-toolbar>
</template>
vuex
export default new Vuex.Store({
state: {
drawer: true,
},
mutations: {
drawerMut(state) {
state.drawer = !state.drawer
}
},
getters: {
drawer(state) {
return state.drawer
},
}})
You can read the drawer state from the store and update to the store
in Vue we have computed setter please go through the docs.
if you use :value then on backdrop click the state/value of the drawer won't be updated, so use v-model with drawer as computed with setter and getter.
export default {
data() {
return {
mini: true,
getSettingsTheme: ''
}
}
computed: {
drawer: {
get() {
return this.$store.getters['drawer'];
},
set(val) {
this.$store.commit('drawerMut', val);
},
}
}
}
<template>
<v-navigation-drawer fixed
v-model="drawer"
app
:class="getSettingsTheme"
:mini-variant="mini">
<b>menu</b>
</v-navigation-drawer>
</template>