UNPKG

hae

Version:

Mobile web UI based on Vux

582 lines (503 loc) 16.7 kB
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