Vuetify timepicker return value to model - vue.js

I'm using Vuetify timepicker within my project, however after selecting a time it doesn't appear to be updating my model, how can I get it to do this?
This is my component which is using the timepicker:
time-select.vue
<template>
<v-layout row wrap>
<v-flex>
<v-menu
ref="menu"
v-model="menu2"
:close-on-content-click="false"
:nudge-right="40"
:return-value.sync="time"
lazy
transition="scale-transition"
offset-y
full-width
max-width="290px"
min-width="290px">
<v-text-field
slot="activator"
v-model="time"
:label="this.label"
:name="this.name"
prepend-icon="access_time"
readonly></v-text-field>
<v-time-picker
v-if="menu2"
v-model="time"
full-width
#click:minute="$refs.menu.save(time)"
></v-time-picker>
</v-menu>
</v-flex>
</v-layout>
</template>
<script>
export default {
props: [ 'label', 'name', 'value' ],
data () {
return {
time: this.value,
menu2: false,
modal2: false
}
}
}
</script>
And this is how I'm adding the timepicker to my page, as you can see I'm using v-model so the value is updated.
<time-select v-model="hours.open_time"
name="businessHoursTimeFrom[]"
label="Open Time"></time-select>

Don't forget that v-model="someVar" used with custom components is just a shortcut for:
:value="someVar" #input="someVar = $event"
Thus, this:
<time-select v-model="hours.open_time" />
Under the hood is:
<time-select :value="hours.open_time" #input="hours.open_time = $event" />
That means that your custom component should trigger input event itself.
Like this:
<v-time-picker
v-if="menu2"
:value="time"
#input="$emit('input', $event)"
full-width
#click:minute="$refs.menu.save(time)" />
Useful link: Vue.js → Using v-model on components

Related

Vuetify Brithday Picker not working. Error: Property or method "on" is not defined on the instance but referenced during render

I use birthday picker on my Vuetify form. All other picker are working fine. But when Im adding Birthday Picker, I got this error.
Property or method "on" 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.
Cant understand where I made the mistake. I used exactly the same code that provided by Vuefity.
Here is my code Details
<template>
<v-menu
ref="menu"
v-model="menu"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="290px"
>
<template v-slot:activator="{ on }">
<v-text-field
v-model="date"
label="Birthday date"
prepend-icon="event"
readonly
v-on="on"
></v-text-field>
</template>
<v-date-picker
ref="picker"
v-model="date"
:max="new Date().toISOString().substr(0, 10)"
min="1950-01-01"
#change="save"
></v-date-picker>
</v-menu>
</template>
<script>
export default {
data: () => ({
date: null,
menu: false,
}),
watch: {
menu (val) {
val && setTimeout(() => (this.$refs.picker.activePicker = 'YEAR'))
},
},
methods: {
save (date) {
this.$refs.menu.save(date)
},
},
}
</script>
Taken and modified from vuetify example:
<v-menu
ref="menu"
v-model="menu"
:close-on-content-click="false"
:return-value.sync="date"
transition="scale-transition"
offset-y
min-width="290px"
<template v-slot:activator="{ on }">
<v-text-field
v-model="date"
label="Birthday Picker"
prepend-icon="event"
readonly
v-on="on"
></v-text-field>
</template>
<v-date-picker v-model="date" #change="$refs.menu.save(date)" no-title scrollable>
</v-date-picker>
</v-menu>
Shouldn't have removed the :return-value.sync="date" or this.$refs.menu.save() doesn't do anything. I'm not sure how this would trigger 'on is not defined on this instance' error, trying to replicate.
I have encountered the same error with v-menu in Vuetify.
What I did is just add on in data as following:
...
<script>
export default {
data: () => ({
on: undefined
}),
...
}
</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;
},
},
}

v-slot:badge and v-if does not work with a computed property

I'm working on a CMS project and I have an issue I can't figure out.
I Have a component where I'm showing IP's. On change I want a badge to appear, so the user knows "this field is changed".
But the thing is the badge won't show if I'm using "v-slot:badge".
In the v-if is a computed property, If I inspect the page with vue devtools ‘isStartIpValueChanged’ will be true on a change. So, it should work right?
Template
<v-list-item-content>
<v-form ref="form" v-model="valid">
<v-hover v-slot:default="{ hover }">
<v-row align-content="center" no-gutters>
<v-col>
<v-badge overlap color="red" right>
<template v-slot:badge v-if="isStartIpValueChanged">
<v-avatar color="red" size="6"></v-avatar>
</template>
<v-text-field
dense
:rules="apiIpRules"
v-model="apiIp.startIp"
#input="valueChanged()"
ref="startIp"
:class="hover ? 'hover-text-color' : ''"
placeholder="###.###.###.###">
</v-text-field>
</v-badge>
</v-col>
<v-col cols="1" class="text-center" align-self="center">
<p>-</p>
</v-col>
<v-col cols="1" class="text-center" align-self="center">
<v-btn v-show="hover" #click="deleteIp()" icon small color="red"><v-icon>mdi-minus-circle</v-icon></v-btn>
</v-col>
</v-row>
</v-hover>
</v-form>
Created and Computed (apiIp is a prop I get from the parent component)
created () {
this.apiIpsOriginalValueStartIp = this.apiIp.startIp
this.apiIpsOriginalValueEndIp = this.apiIp.endIp
this.apiIp.uuid = this.GenerateUUID()
},
computed: {
isStartIpValueChanged () {
return this.apiIp &&
(this.apiIp.startIp !== this.apiIpsOriginalValueStartIp ||
this.apiIp.uuid === null)
},
isEndIpValueChanged () {
return this.apiIp &&
(this.apiIp.endIp !== this.apiIpsOriginalValueEndIp ||
this.apiIp.uuid === null)
}
},
Anyone know what is going wrong here?
As according to Vuetify's own documentation, you should be using the v-model, directly on the v-badge, to show it only when you want it to.
<v-badge overlap color="red" right v-model="isStartIpValueChanged">
<template v-slot:badge>
<v-avatar color="red" size="6"></v-avatar>
</template>
<v-text-field
dense
:rules="apiIpRules"
v-model="apiIp.startIp"
#input="valueChanged()"
ref="startIp"
:class="hover ? 'hover-text-color' : ''"
placeholder="###.###.###.###">
</v-text-field>
</v-badge>
Doc: https://vuetifyjs.com/en/components/badges#show-on-hover

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.

How to trigger a method at component opening in vuejs?

I have a main component that is used to display items using a loop:
<v-list-tile v-for="(item, index) in items" :key="item.title">
...
<report type="item.type"> </report>
</v-list>
The report component is used to report abuse on the system, and report type may vary depending on the item from the parent loop.
As users are very unlikely to use report on a regular basis I would like to only load v-select elements when the Dialog (modal) is opened.
Using created or mounted triggers the loading method everytime the report component is generated and not when the report component is opened.
Is there a smart way to prevent this and only have the loading method within report being triggered only when the component is opened.
=== Report.vue file ===
=== This file is loaded in the parent component
<template lang="html">
<v-dialog v-model="dialog" persistent max-width="800px" lazy>
<v-btn icon slot="activator">
<v-icon>error_outline</v-icon>
</v-btn>
<v-card>
<v-card-title>
<div class="headline"><v-icon large>error_outline</v-icon> Reporting</div>
</v-card-title>
<v-card-text>You are about to report the following {{ reportType }}: "<i>{{ reportContent.title }}</i>"
<v-container v-if="this.$store.getters['report/getLoadedState']" grid-list-md >
<v-layout wrap>
<v-flex xs12 sm12>
<v-select
label="Select a reason"
required
cache-items
:items="items"
item-text="title"
item-value="id"
></v-select>
</v-flex>
<v-flex xs12 sm12>
<v-text-field
label="Please provide additional information here"
multi-line></v-text-field>
</v-flex>
</v-layout>
</v-container>
<v-container v-else grid-list-md>
<v-layout>
<v-flex xs12 sm12>
Loading
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="green darken-1" flat="flat" #click.native="cancel">Cancel</v-btn>
<v-btn color="green darken-1" flat="flat" #click.native="report">Report</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'report',
data () {
return {
dialog: false,
items: this.$store.getters['report/getItems']
}
},
props: ['reportType', 'reportContent'],
methods: {
cancel () {
this.dialog = false
},
report () {
this.dialog = false
},
loadReasons (type) {
if (!this.$store.getters['report/getLoadedState']) {
this.$store.dispatch('report/setItems', type)
}
}
}
}
</script>
<style lang="css" scoped>
</style>
PS 1: I'm not using JQuery and do not intend to use it
PS 2: Calling the method outside of the report component is not an option as I want to maximize reusability of this compenent and only pass arguments to it using props
How I have done this in the past is to use a "dynamic component." Vue uses a special syntax for dynamic components <component v-bind:is="currentView"></component> see: Vue dynamic components
You can set the "currentView" property to null and then on a button click another event set the currentView to report. Doing it this way will allow you to use the mounted method as the component is not rendered or created until it is dynamically called.
<component :is="myComponent"></component>
<v-btn #click="loadComponent">Show Report</v-btn>
then...
data:{
myComponent: null;
},
methods: {
loadComponent: function(){
this.myComponent = 'report';
}
}
P.S. You can of course use this method to render any other components in the same place. i.e. you can set the 'currentView' to the name of any available component and it will be instantly rendered in its place.
I ended up using a watch on the boolean managing the dialog...
Make a function which loads the v-select. Don't initialize it just create it. Now whenever user clicks the report model you can initialize multiple functions using v-on:click or #click.
For example :
<div v-on:click="return function() { fn1('foo');fn2('bar'); }()"> </div>
This solution can also be found on :
Stackoverflow Link