'Bind HTML date input to Vue model, but delay applying changes till user is done typing
I have a Vue component that allows users to edit a term record in real time. The term, loaded from an API into an underlying VueX store, is provided as a property; for each field, I use a computed property where the getter reads the value from the property and the setter fires an event. The parent component listens for the event, makes an API call, updates the store, and we get the updated object via the property.
This works fine for <input type="text"/>:
<template>
<input :id="`term-${term.id}-name`" v-model.lazy="name" type="text">
</template>
export default {
props: {
term: { type: Object, default: () => {} }
},
computed: {
name: {
get () { return this.term.name },
set (name) { this.edited({ name: name }) }
}
},
methods: {
edited (edit) {
this.$emit('edited', edit) // triggers a PATCH
}
}
}
I also have <input type="date"/> more or less working, thanks to some slightly more complicated computed properties:
<input :id="`term-${term.id}-start-date`" v-model.lazy="startDate" type="date"/>
startDate: {
get() {
// converts e.g. "2021-12-21T00:00:00.000-08:00" to "2021-12-21"
return this.dateToDateInput(term.start_date)
},
set(dateVal) {
// converts e.g. "2021-12-21" to "2021-12-21T00:00:00.000-08:00"
const date = this.dateToISO8601(dateVal)
this.$emit('edited', { start_date: date })
}
}
The drawback of this approach is that when the user is editing a date, I end up getting a model change and firing an edit event on every keystroke, which means making a whole lot of unnecessary API calls, and also sending a lot of garbage dates -- e.g., if the user tries to change the date from 07/21/2020 to 07/21/2023 by typing in the year section of the date field, I get 0002-07-21, 0020-07-21, and 0202-07-21 before I get the final value. (Note that Chrome at least will not let you select just the last digit of the year.)
So far the only workaround I've been able to come up with is adding a second "shadow" date field to record set events, and only firing the edit event (with the shadow value) on a blur or keyup.enter:
<td><input :id="`term-${term.id}-start-date`" v-model.lazy="startDate" type="date"
@keyup.enter="commitStartDate" @blur="commitStartDate"></td>
data: function () {
return ({
shadowStartDate: this.term.start_date,
})
},
computed: {
startDate: {
get () {
// note we always read from the real value
const startDate = this.term.start_date
return this.dateToDateInput(startDate)
},
set (dateVal) {
this.shadowStartDate = this.dateToISO8601(dateVal)
}
},
},
methods: {
commitStartDate () {
if (this.shadowStartDate !== this.term.start_date) {
this.edited({ start_date: this.shadowStartDate })
}
}
}
This works, but it feels very clunky, and I don't entirely trust that blur and keyup.enter are the only events I need to care about. I know that events on <input type="date"/> are tricky because reasons, but isn't there a better way to do this?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
