Untitled
unknown
javascript
3 years ago
13 kB
7
Indexable
<script>
export default {
name: 'FDatetimePicker',
inheritAttrs: false,
customOptions: {}
}
</script>
<script setup>
import { ref, watch, computed, unref } from 'vue'
import {
BFormGroup,
BInputGroup,
BFormInput,
BInputGroupAppend,
BFormInvalidFeedback
} from 'bootstrap-vue-3'
import { FSelect } from '../select'
import { getDateString } from '../../services/date.service'
import { generateId } from '../utils'
const props = defineProps({
modelValue: {
type: null
},
rules: {
type: [String],
default: null
},
mode: {
type: String,
default: 'datetime',
validator: value => ['datetime', 'date', 'time', 'month'].includes(value)
},
dateConfig: {
type: Object,
default: () => ({})
},
timeConfig: {
type: Object,
default: () => ({})
},
monthConfig: {
type: Object,
default: () => ({})
},
label: {
type: String,
default: ''
},
editable: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue'])
const { date, isValidDate, onClickDateInputClearIcon } = useDate()
const { time, isValidTime, timeSelectList, onClickTimeInputClearIcon } =
useTime()
const { month, minMonth, maxMonth, getMonthForInputTag } = useMonth()
const { isVisibleDateInput, isVisibleTimeInput, isVisibleClearIcon } =
useTemplateConditions()
const validationRules = computed(() => {
const rules = []
if (props.rules) {
rules.push(unref(props.rules))
}
if (props.dateConfig.min || props.dateConfig.max) {
const min =
`${props.dateConfig.min} 00:00:00:000` || '1900-01-01 00:00:00:000'
const max =
`${props.dateConfig.max} 23:59:59:999` || '2100-12-31 23:59:59:999'
rules.push(`date_between:${min},${max}`)
}
if (props.dateConfig.weekdaysOnly) {
rules.push('date_weekdays')
}
return rules.join('|')
})
useModelValueWatchers()
function useDate() {
const date = ref(null)
const isValidDate = date => {
const dateValue = new Date(date)
return dateValue !== 'Invalid Date' && !isNaN(dateValue)
}
const onClickDateInputClearIcon = () => {
date.value = null
}
return { date, isValidDate, onClickDateInputClearIcon }
}
function useTime() {
const time = ref(null)
const timeSelectList = computed(() => {
if (props.timeConfig.intervals) {
return getTimeSelectListWithInterval()
} else {
return getTimeSelectList()
}
})
const isValidTime = time => {
return /^([0-1]?[0-9]|2[0-4]):([0-5][0-9])(:[0-5][0-9])?$/.test(time)
}
const onClickTimeInputClearIcon = () => {
time.value = null
}
const getTimeList = ({
minHour,
maxHour,
minMinute,
maxMinute,
stepHours,
stepMinutes,
onlyStepUpHours
}) => {
let date = new Date()
let maxDate = new Date()
date.setHours(minHour)
date.setMinutes(minMinute)
maxDate.setHours(maxHour)
maxDate.setMinutes(maxMinute)
const timeList = []
while (date <= maxDate) {
const hour = date.getHours()
const minute = date.getMinutes()
timeList.push(
`${hour.toString().padStart(2, '0')}:${minute
.toString()
.padStart(2, '0')}`
)
if (onlyStepUpHours) {
date.setHours(date.getHours() + stepHours)
} else {
date.setMinutes(date.getMinutes() + stepMinutes)
}
}
return timeList
}
const getTimeSelectList = () => {
const stepHours = props.timeConfig.stepHours || 1
const stepMinutes = props.timeConfig.stepMinutes || 15
let minHour = 0
let maxHour = 23
let minMinute = 0
let maxMinute = 59
if (props.timeConfig.min) {
const timeParts = props.timeConfig.min.split(':')
minHour = parseInt(timeParts[0])
minMinute = parseInt(timeParts[1])
}
if (props.timeConfig.max) {
const timeParts = props.timeConfig.max.split(':')
maxHour = parseInt(timeParts[0])
maxMinute = parseInt(timeParts[1])
}
const onlyStepUpHours = props.timeConfig.stepHours ? true : false
const timeList = getTimeList({
minHour,
minMinute,
maxHour,
maxMinute,
stepHours,
stepMinutes,
onlyStepUpHours
})
return timeList
}
const getTimeSelectListWithInterval = () => {
let parentTimeList = []
for (const interval of props.timeConfig.intervals) {
let minHour, minMinute, maxHour, maxMinute
minHour = parseInt(interval.min.split(':')[0])
minMinute = parseInt(interval.min.split(':')[1])
maxHour = parseInt(interval.max.split(':')[0])
maxMinute = parseInt(interval.max.split(':')[1])
const stepHours = interval.stepHours || 1
const stepMinutes = interval.stepMinutes || 15
const onlyStepUpHours = interval.stepHours ? true : false
const timeList = getTimeList({
minHour,
minMinute,
maxHour,
maxMinute,
stepHours,
stepMinutes,
onlyStepUpHours
})
parentTimeList = [...parentTimeList, ...timeList]
}
return parentTimeList
}
return {
time,
isValidTime,
timeSelectList,
onClickTimeInputClearIcon
}
}
function useMonth() {
const month = ref(null)
const minMonth = computed(
() => getMonthForInputTag(props.monthConfig.min) || '1900-01'
)
const maxMonth = computed(
() => getMonthForInputTag(props.monthConfig.max) || '2100-12'
)
const isValidMonth = month => {
return (
/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.test(month) ||
/^([0-9]{4})-([0-9]{2})$/.test(month)
)
}
const getMonthForInputTag = month => {
if (isValidMonth(month) && month.length >= 7) {
return month.slice(0, 7)
}
return month
}
return { month, isValidMonth, minMonth, maxMonth, getMonthForInputTag }
}
function useModelValueWatchers() {
if (props.mode === 'datetime') {
watch(
() => props.modelValue,
newValue => {
if (!newValue) return
const datetimeString = getDateString(new Date(newValue), {
time: true,
timezone: true
})
const datetimeParts = datetimeString.split('T')
const timeParts = datetimeParts[1].split(':')
date.value = datetimeParts[0]
time.value = `${timeParts[0]}:${timeParts[1]}`
},
{ immediate: true }
)
watch(
() => date.value,
newValue => {
let value = null
if (!time.value) {
const { defaultValue } = props.timeConfig
time.value = defaultValue
}
if (
newValue &&
time.value &&
isValidDate(newValue) &&
isValidTime(time.value)
) {
value = getDateString(new Date(`${newValue} ${time.value}`), {
time: true,
timezone: true
})
if (!isValidDate(value)) value = null
}
emit('update:modelValue', value)
}
)
watch(
() => time.value,
newValue => {
let value = null
if (
newValue &&
date.value &&
isValidTime(newValue) &&
isValidDate(date.value)
) {
value = getDateString(new Date(`${date.value} ${newValue}`), {
time: true,
timezone: true
})
if (!isValidDate(value)) value = null
}
emit('update:modelValue', value)
}
)
} else if (props.mode === 'date') {
watch(
() => props.modelValue,
newValue => {
date.value = newValue
},
{ immediate: true }
)
watch(
() => date.value,
newValue => {
const { startOfDay, endOfDay } = props.dateConfig
let value = null
if (newValue && isValidDate(newValue)) {
if (startOfDay) {
value = getDateString(new Date(`${newValue} 00:00:00:000`), {
time: true,
timezone: true
})
} else if (endOfDay) {
value = getDateString(new Date(`${newValue} 23:59:59:999`), {
time: true,
timezone: true
})
} else {
value = newValue
}
}
emit('update:modelValue', value)
}
)
} else if (props.mode === 'time') {
watch(
() => props.modelValue,
newValue => {
time.value = newValue
},
{ immediate: true }
)
watch(
() => time.value,
newValue => {
let value = null
if (newValue && isValidTime(newValue)) {
value = newValue
}
emit('update:modelValue', value)
}
)
} else if (props.mode === 'month') {
watch(
() => props.modelValue,
newValue => {
month.value = getMonthForInputTag(newValue)
},
{ immediate: true }
)
watch(
() => month.value,
newValue => {
let value = null
value = getMonthForInputTag(newValue)
emit('update:modelValue', value)
}
)
}
}
function useTemplateConditions() {
const isVisibleDateInput = computed(
() => props.mode === 'datetime' || props.mode === 'date'
)
const isVisibleTimeInput = computed(
() =>
(props.mode === 'datetime' || props.mode === 'time') &&
!props.timeConfig.select
)
const isVisibleClearIcon = computed(() => {
return !props.disabled && !props.editable
})
return {
isVisibleDateInput,
isVisibleTimeInput,
isVisibleClearIcon
}
}
</script>
<template>
<ValidationField
v-slot="{ errorMessage }"
as="div"
class="f-datetime-picker"
:model-value="props.modelValue"
:name="generateId()"
:rules="validationRules"
:validate-on-blur="false"
:validate-on-change="false"
:validate-on-input="false"
:validate-on-model-update="true"
>
<b-form-group :label="label">
<b-input-group>
<div
v-if="isVisibleDateInput"
class="input-area"
:class="{
'date-input': props.mode === 'datetime',
fixed: props.dateConfig.fixed
}"
>
<b-form-input
v-bind="dateConfig"
v-model="date"
ref="dateInputRef"
:class="{
'editable-disabled': !props.editable,
'is-invalid': errorMessage
}"
:disabled="props.disabled"
:min="props.dateConfig.min || '1900-01-01'"
:max="props.dateConfig.max || '2100-12-31'"
:name="generateId()"
type="date"
/>
<f-icon
v-if="isVisibleClearIcon && date"
bi
class="clear-input"
icon="x"
@click="onClickDateInputClearIcon"
/>
</div>
<div
v-if="isVisibleTimeInput"
class="input-area time-input"
:class="{
fixed: props.mode === 'datetime' || props.timeConfig.fixed
}"
>
<b-form-input
v-bind="timeConfig"
v-model="time"
ref="timeInputRef"
:class="{
'editable-disabled': !props.editable
}"
:disabled="props.disabled"
:name="generateId()"
:placeholder="props.timeConfig.placeholder || 'HH:mm'"
type="time"
/>
<f-icon
v-if="isVisibleClearIcon && time"
bi
class="clear-input"
icon="x"
@click="onClickTimeInputClearIcon"
/>
</div>
<input
v-if="props.mode === 'month'"
v-model="month"
class="form-control"
:class="{ 'is-invalid': errorMessage }"
:disabled="props.disabled"
:name="generateId()"
type="month"
:min="minMonth"
:max="maxMonth"
/>
<f-select
v-if="props.mode === 'time' && props.timeConfig.select"
v-model="time"
class="time-select"
:class="{
fixed: props.timeConfig.fixed
}"
:disabled="props.disabled"
:name="generateId()"
:options="timeSelectList"
:placeholder="props.timeConfig.placeholder || 'HH:mm'"
value-type="string"
:rules="rules"
:is-error-message-visible="false"
/>
<b-input-group-append
v-if="props.mode === 'datetime' && props.timeConfig.select"
class="input-append"
>
<f-select
v-model="time"
:rules="rules"
class="time-select fixed"
:disabled="props.disabled"
:name="generateId()"
:options="timeSelectList"
:placeholder="props.timeConfig.placeholder || 'HH:mm'"
value-type="string"
:is-error-message-visible="false"
/>
</b-input-group-append>
<b-form-invalid-feedback
id="inputLiveFeedback"
:force-show="!!errorMessage"
>
{{ errorMessage }}
</b-form-invalid-feedback>
</b-input-group>
</b-form-group>
</ValidationField>
</template>
Editor is loading...