Skip to content

Instantly share code, notes, and snippets.

@renoirb
Created January 13, 2023 14:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save renoirb/3ac53c986c0284d6f0be585cc5bac3c9 to your computer and use it in GitHub Desktop.
Save renoirb/3ac53c986c0284d6f0be585cc5bac3c9 to your computer and use it in GitHub Desktop.
Date Range component logic

Component to select a date range

This was logic written in a pregnancy scheduling mini web app that I wanted to separate the date range out.

<template>
<fieldset>
<label for="min"> Begin </label>
<input
:max="distanceDates.max.value"
name="min"
type="date"
v-model="distanceDates.min.value"
/>
<span class="validity"></span>
<label for="max"> End </label>
<input
:min="distanceDates.min.value"
name="max"
type="date"
v-model="distanceDates.max.value"
/>
<span class="validity"></span>
</fieldset>
</template>
<script lang="ts">
import { useDistanceDates } from './use-distance-dates'
// reminder: this is scavenged code from a 3 y.o. Project when Vue 3 was still in beta.
// also. It's been 2 years I don't write Vue full time
// and am in parental leave, writing this on an iPhone while baby is napping!
// (Yeah. I miss programming!)
export default {
name: 'DateRange',
setup() {
const { distanceDates } = useDistanceDates()
// the onMounted and watchEffect should be initialized
return {
distanceDates,
}
},
}
</script>
<style scoped>
label {
display: flex;
align-items: center;
}
span::after {
padding-left: 5px;
}
input:invalid + span::after {
content: '✖';
}
input:valid + span::after {
content: '✓';
}
</style>
import {
reactive,
onMounted,
watchEffect,
toRefs,
ToRefs,
unref,
ref,
readonly,
} from 'vue'
import { DateTime, Interval, DurationUnit } from 'luxon'
export interface IDateRange {
max: string
min: string
}
export interface IDistanceDatesSurface {
distanceDates: ToRefs<IDateRange>
duration: Ref<number>
mutate(changeset: Partial<IDateRange>): void
}
// reminder: The original code had more than those two fields.
const distanceDatesModelFields = new Set([
'max',
'min',
])
export const isInDistanceDatesModelFields = (field: string): boolean =>
distanceDatesModelFields.has(field)
export const isDateTimeString = (input: unknown): input is DateTime => {
let outcome = false
try {
const _ = DateTime.fromISO(input as string)
outcome = true
} catch {
// Nothing to do
}
return outcome
}
export const useDistanceDates = (
): IDistanceDatesSurface => {
const duration = ref(0)
const today = DateTime.local().toISODate()
const distanceDates = reactive({
max: today,
min: today,
} as IDateRange)
// this method was doing more than now just
// changing min, max dates.
// reminder: this was edited code on an iPhone from 3 y.o. hobby project
const mutate = (changeset: Partial<IDateRange> = {}): void => {
// Here there was logic about whether min or max be as today's date
for (const [key, value] of Object.entries(changeset)) {
if (distanceDatesModelFields.has(key)) {
// distanceDates[key] = value
Reflect.set(distanceDates, key, value)
}
}
}
// should one want to use URL search ?min=2023-01-11
onMounted(() => {
// this assumes your view would ONLY support min,max URL Search once.
const { search = '' } = window.location
const parsed = String(search || '')
.replace(/^\?/, '')
.split('&')
.map((i) => i.split('='))
const changeset: Partial<IDateRange> = {}
for (const [key, value] of parsed) {
if (distanceDatesModelFields.has(key)) {
// validate "value" as yyyy-mm-dd
// yeah. It's a Date without Time.
// This is 3 years old code!
// I realize I'll have to change that
if (!isDateTimeString(value)){
// do nothing or error out! TODO
} else {
// prefer reflect for changing value
// changeset[key] = value
Reflect.set(changeset, key, value)
}
}
}
mutate(changeset)
})
watchEffect(() => {
const max = DateTime.fromISO(distanceDates.max)
const min = DateTime.fromISO(distanceDates.min)
const interval = Interval.fromDateTimes(min, max)
// should you want to use another duration than days.
// see Luxon docs!
const unit: DurationUnit = 'days'
const computedDuration = Math.ceil(interval.length(unit))
duration.value = computedDuration
})
// https://github.com/vuejs/vue-next/blob/master/packages/reactivity/__tests__/effect.spec.ts#L117
return {
mutate,
distanceDates: toRefs(distanceDates),
duration: readonly(duration),
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment