hae
Version:
Mobile web UI based on Vux
582 lines (503 loc) • 16.7 kB
JavaScript
import Scroller from '../picker/scroller'
import { isToday, generateRange, each, trimZero, addZero, getMaxDay, parseRow, parseDate, getElement, toElement, removeElement } from './util'
import { getYears, getMonths, getDays } from './makeData'
const isBrowser = typeof window === 'object'
const MASK_TEMPLATE = '<div class="dp-mask"></div>'
const TEMPLATE = `<div class="dp-container">
<div class="dp-header">
<div class="dp-item dp-left vux-datetime-cancel" data-role="cancel">cancel</div>
<div class="dp-item vux-datetime-clear" data-role="clear"></div>
<div class="dp-item dp-right vux-datetime-confirm" data-role="confirm">done</div>
</div>
<div class="dp-content">
<div class="dp-item" data-role="year"></div>
<div class="dp-item" data-role="month"></div>
<div class="dp-item" data-role="day"></div>
<div class="dp-item" data-role="noon"></div>
<div class="dp-item" data-role="hour"></div>
<div class="dp-item" data-role="minute"></div>
</div>
</div>`
const SHOW_ANIMATION_TIME = 200
const SHOW_CONTAINER_TIME = 300
const TYPE_MAP = {
year: ['YYYY'],
month: ['MM', 'M'],
day: ['DD', 'D'],
hour: ['HH', 'H'],
minute: ['mm', 'm'],
noon: ['A']
}
let MASK = null
let CURRENT_PICKER
const NOW = new Date()
const DEFAULT_CONFIG = {
template: TEMPLATE,
trigger: null,
output: null,
currentYear: NOW.getFullYear(),
currentMonth: NOW.getMonth() + 1,
minYear: 2000,
maxYear: 2030,
minHour: 0,
maxHour: 23,
hourList: null,
minuteList: null,
startDate: null,
endDate: null,
yearRow: '{value}',
monthRow: '{value}',
dayRow: '{value}',
noonRow: '{value}',
hourRow: '{value}',
minuteRow: '{value}',
format: 'YYYY-MM-DD',
value: NOW.getFullYear() + '-' + (NOW.getMonth() + 1) + '-' + NOW.getDate(),
onSelect () {},
onConfirm () {},
onClear () {},
onShow () {},
onHide () {},
confirmText: 'ok',
clearText: '',
cancelText: 'cancel',
destroyOnHide: false,
renderInline: false,
computeHoursFunction: null,
computeDaysFunction: null,
isOneInstance: false,
orderMap: {}
}
function renderScroller (el, data, value, fn) {
data = data.map(one => {
one.value = one.value + ''
return one
})
return new Scroller(el, {
data,
defaultValue: value + '',
onSelect: fn
})
}
function showMask () {
if (!isBrowser) {
return
}
if (!MASK) {
MASK = toElement(MASK_TEMPLATE)
document.body.appendChild(MASK)
MASK.addEventListener('click', function () {
CURRENT_PICKER && CURRENT_PICKER.hide('cancel')
}, false)
MASK.addEventListener('touchmove', function (e) {
e.preventDefault()
}, false)
}
MASK.style.display = 'block'
setTimeout(function () {
MASK && (MASK.style.opacity = 0.5)
}, 0)
}
function hideMask () {
if (!MASK) {
return
}
MASK.style.opacity = 0
setTimeout(function () {
MASK && (MASK.style.display = 'none')
}, SHOW_ANIMATION_TIME)
}
function DatetimePicker (config) {
const self = this
self.config = {}
self.value = config.value || ''
each(DEFAULT_CONFIG, function (key, val) {
self.config[key] = config[key] || val
})
this.renderInline = self.config.renderInline
if (config.defaultSelectedValue && !config.value) {
self.config.value = config.defaultSelectedValue
}
if (typeof this.config.startDate === 'string') {
this.config.startDate = new Date(this.config.startDate.replace(/-/g, '/'))
}
if (typeof this.config.endDate === 'string') {
this.config.endDate = new Date(this.config.endDate.replace(/-/g, '/'))
}
if (this.config.startDate && !this.config.endDate) {
this.config.endDate = new Date('2030/12/31')
}
if (!this.config.startDate && this.config.endDate) {
this.config.startDate = new Date(`${this.config.minYear}/01/01`)
}
this.reMakeData = !!this.config.startDate && !!this.config.endDate
if (!this.renderInline) {
let trigger = self.config.trigger
this.triggerHandler = function (e) {
e.preventDefault()
self.show(self.value)
}
if (trigger && isBrowser) {
trigger = self.trigger = getElement(trigger)
this.trigger = trigger
this.trigger && this.trigger.addEventListener('click', this.triggerHandler, false)
}
}
}
DatetimePicker.prototype = {
_show (newValueMap) {
const self = this
self._setText()
self.container.style.display = 'block'
if (this.renderInline) {
self.container.classList.add('vux-datetime-view')
}
each(TYPE_MAP, function (type) {
self[type + 'Scroller'] && self[type + 'Scroller'].select(type === 'noon' ? newValueMap[type] : trimZero(newValueMap[type]), false)
})
setTimeout(function () {
self.container.style['-webkit-transform'] = 'translateY(0)'
self.container.style.transform = 'translateY(0)'
}, 0)
},
show (value) {
if (!isBrowser) {
return
}
const self = this
const config = self.config
if (config.isOneInstance) {
if (document.querySelector('#vux-datetime-instance')) {
return
}
self.willShow = true
}
CURRENT_PICKER = self
const valueMap = self.valueMap = parseDate(config.format, value || config.value)
let newValueMap = {}
each(TYPE_MAP, function (type, list) {
newValueMap[type] = list.length === 1 ? valueMap[list[0]] : (valueMap[list[0]] || valueMap[list[1]])
})
if (self.container) {
self._show(newValueMap)
} else {
let template = config.template
for (let i in config.orderMap) {
template = template.replace(`data-role="${i}"`, `data-role="${i}" style="order:${config.orderMap[i]}"`)
}
const container = self.container = toElement(template)
if (config.isOneInstance) {
container.id = 'vux-datetime-instance'
}
if (!self.renderInline) {
document.body.appendChild(container)
self.container.style.display = 'block'
} else {
document.querySelector(self.config.trigger).appendChild(container)
}
each(TYPE_MAP, function (type) {
const div = self.find('[data-role=' + type + ']')
if (newValueMap[type] === undefined) {
removeElement(div)
return
}
let data
if (type === 'day') {
data = self._makeData(type, trimZero(newValueMap.year), trimZero(newValueMap.month))
} else if (type === 'hour') {
data = self._makeData(type, trimZero(newValueMap.year), trimZero(newValueMap.month), trimZero(newValueMap.day))
} else {
data = self._makeData(type)
}
self[type + 'Scroller'] = renderScroller(div, data, trimZero(newValueMap[type]), function (currentValue) {
setTimeout(function () {
config.onSelect.call(self, type, currentValue, self.getValue())
}, 0)
if (type === 'year' || type === 'month' || type === 'day') {
self.hourScroller && self._setHourScroller(self.yearScroller.value, self.monthScroller.value, self.dayScroller.value, self.hourScroller.value)
}
let currentDay
if (type === 'year') {
const currentMonth = self.monthScroller ? self.monthScroller.value : config.currentMonth
self._setMonthScroller(currentValue, currentMonth)
if (self.dayScroller) {
currentDay = self.dayScroller.value
self._setDayScroller(currentValue, currentMonth, currentDay)
}
} else if (type === 'month') {
const currentYear = self.yearScroller ? self.yearScroller.value : config.currentYear
if (self.dayScroller) {
currentDay = self.dayScroller.value
self._setDayScroller(currentYear, currentValue, currentDay)
}
}
})
})
if (!self.renderText && !self.renderInline) {
if (self.config.confirmText) {
self.find('[data-role=confirm]').innerText = self.config.confirmText
}
if (self.config.cancelText) {
self.find('[data-role=cancel]').innerText = self.config.cancelText
}
if (self.config.clearText) {
self.find('[data-role=clear]').innerText = self.config.clearText
}
self.renderText = true
}
this._show(newValueMap)
self.find('[data-role=cancel]').addEventListener('click', function (e) {
e.preventDefault()
self.hide('cancel')
}, false)
self.find('[data-role=confirm]').addEventListener('click', function (e) {
e.preventDefault()
self.confirm()
}, false)
if (self.config.clearText) {
self.find('[data-role=clear]').addEventListener('click', function (e) {
e.preventDefault()
self.clear()
}, false)
}
}
if (!this.renderInline) {
showMask()
config.onShow.call(self)
}
},
_setText () {
if (typeof V_LOCALE !== 'undefined' && V_LOCALE === 'MULTI' && !this.config.renderInline) { // eslint-disable-line
const trigger = this.trigger
if (trigger) {
const confirmText = trigger.getAttribute('data-confirm-text')
const cancelText = trigger.getAttribute('data-cancel-text')
this.find('[data-role=confirm]').innerText = confirmText
this.find('[data-role=cancel]').innerText = cancelText
}
}
},
_makeData (type, year, month, day) {
const config = this.config
const valueMap = this.valueMap
const list = TYPE_MAP[type]
let data = []
let min
let max
if (type === 'year') {
min = config.minYear
max = config.maxYear
if (this.reMakeData) {
const { minYear, maxYear } = getYears(this.config.startDate, this.config.endDate)
min = minYear
max = maxYear
}
} else if (type === 'month') {
min = 1
max = 12
if (this.reMakeData) {
const { minMonth, maxMonth } = getMonths(this.config.startDate, this.config.endDate, this.yearScroller.value * 1)
min = Math.max(min, minMonth)
max = Math.min(max, maxMonth)
}
} else if (type === 'day') {
min = 1
max = getMaxDay(year, month)
if (this.reMakeData) {
const { minDay, maxDay } = getDays(this.config.startDate, this.config.endDate, this.yearScroller.value * 1, this.monthScroller.value * 1)
min = Math.max(min, minDay)
max = Math.min(max, maxDay)
}
} else if (type === 'hour') {
min = this.config.minHour
max = this.config.maxHour
} else if (type === 'minute') {
min = 0
max = 59
}
for (let i = min; i <= max; i++) {
let name
if (type === 'year') {
name = parseRow(config.yearRow, i)
} else {
const val = valueMap[list[0]] ? addZero(i) : i
name = parseRow(config[type + 'Row'], val)
}
data.push({
name: name,
value: i
})
}
if (type === 'noon') {
data.push({
name: '上午',
value: 'AM'
})
data.push({
name: '下午',
value: 'PM'
})
}
if (type === 'hour' && this.config.hourList) {
data = this.config.hourList.map(hour => {
return {
name: parseRow(config['hourRow'], hour),
value: Number(hour)
}
})
}
if (type === 'day' && this.config.computeDaysFunction) {
const rs = this.config.computeDaysFunction({
year,
month,
min,
max
}, generateRange)
if (rs) {
data = rs.map(day => {
return {
name: parseRow(config['dayRow'], addZero(day)),
value: Number(day)
}
})
}
}
if (type === 'hour' && this.config.computeHoursFunction) {
const isTodayVal = isToday(new Date(`${year}/${month}/${day}`), new Date())
const rs = this.config.computeHoursFunction(`${year}-${month}-${day}`, isTodayVal, generateRange)
data = rs.map(hour => {
// #2296
return {
name: parseRow(config['hourRow'], hour),
value: Number(hour)
}
})
}
if (type === 'minute' && this.config.minuteList) {
data = this.config.minuteList.map(minute => {
return {
name: parseRow(config['minuteRow'], minute),
value: Number(minute)
}
})
}
return data
},
// after year change
_setMonthScroller (currentValue, month) {
if (!this.monthScroller) {
return
}
const self = this
this.monthScroller.destroy()
const div = self.find('[data-role=month]')
self.monthScroller = renderScroller(div, self._makeData('month'), month, function (currentValue) {
self.config.onSelect.call(self, 'month', currentValue, self.getValue())
const currentYear = self.yearScroller ? self.yearScroller.value : self.config.currentYear
if (self.dayScroller) {
const currentDay = self.dayScroller.value
self._setDayScroller(currentYear, currentValue, currentDay)
}
if (self.yearScroller && self.monthScroller && self.hourScroller) {
self._setHourScroller(currentYear, currentValue, self.dayScroller.value, self.hourScroller.value)
}
})
},
_setDayScroller (year, month, day) {
if (!this.dayScroller) {
return
}
const self = this
const maxDay = getMaxDay(year, month)
if (day > maxDay) {
day = maxDay
}
self.dayScroller.destroy()
const div = self.find('[data-role=day]')
self.dayScroller = renderScroller(div, self._makeData('day', year, month), day, function (currentValue) {
self.config.onSelect.call(self, 'day', currentValue, self.getValue())
self.hourScroller && self._setHourScroller(year, month, currentValue, self.hourScroller.value)
})
},
_setHourScroller (year, month, day, hour) {
if (!this.hourScroller) {
return
}
const self = this
self.hourScroller.destroy()
const div = self.find('[data-role=hour]')
self.hourScroller = renderScroller(div, self._makeData('hour', year, month, day), hour || '', function (currentValue) {
self.config.onSelect.call(self, 'hour', currentValue, self.getValue())
})
},
find (selector) {
return this.container.querySelector(selector)
},
hide (type) {
if (!this.container) {
return
}
const self = this
self.container.style.removeProperty('transform')
self.container.style.removeProperty('-webkit-transform')
setTimeout(function () {
self.container && (self.container.style.display = 'none')
}, SHOW_CONTAINER_TIME)
hideMask()
self.config.onHide.call(self, type)
if (self.config.destroyOnHide) {
setTimeout(() => {
self.destroy()
}, 500)
}
},
select (type, value) {
this[type + 'Scroller'].select(value, false)
},
destroy () {
const self = this
this.trigger && this.trigger.removeEventListener('click', this.triggerHandler, false)
if (!self.config.isOneInstance && !self.willShow) {
removeElement(MASK)
MASK = null
}
removeElement(self.container)
self.container = null
},
getValue () {
const self = this
const config = self.config
let value = config.format
function formatValue (scroller, expr1, expr2) {
if (scroller) {
const val = scroller.value
if (expr1) {
value = value.replace(new RegExp(expr1, 'g'), addZero(val))
}
if (expr2) {
value = value.replace(new RegExp(expr2, 'g'), trimZero(val))
}
}
}
each(TYPE_MAP, function (key, list) {
formatValue(self[key + 'Scroller'], list[0], list[1])
})
return value
},
confirm () {
const value = this.getValue()
this.value = value
if (this.config.onConfirm.call(this, value) === false) {
return
}
this.hide('confirm')
},
clear () {
const value = this.getValue()
if (this.config.onClear.call(this, value) === false) {
return
}
this.hide('clear')
}
}
export default DatetimePicker