UNPKG

@busy-web/ember-date-time

Version:

Date time & and range picker for ember addon

467 lines (405 loc) 12.1 kB
/** * @module Components * */ import $ from 'jquery'; import Component from '@ember/component'; import { observer, get, getWithDefault, set } from '@ember/object'; import { isNone, isEmpty } from '@ember/utils'; import keyEvents from '@busy-web/ember-date-time/mixins/key-events'; import _state from '@busy-web/ember-date-time/utils/state'; import _time from '@busy-web/ember-date-time/utils/time'; import { longFormatDate } from '@busy-web/ember-date-time/utils/format'; import layout from '../templates/components/ember-date-time-picker'; import { MONTH_FLAG, DAY_FLAG, HOUR_FLAG } from '@busy-web/ember-date-time/utils/constant'; //import stateManager from '../--private/state'; /** * `Component/Busyweb/EmberDateTimePicker` * * @class DateTimePicker * @namespace Components * @extends Component */ export default Component.extend(keyEvents, { /** * @private * @property classNames * @type String * @default busy-web-date-time-picker */ classNames: ['busyweb', 'emberdatetime', 'c-date-time-picker'], layout, /** * timestamp that is passed in as a milliseconds timestamp * * @private * @property timestamp * @type Number */ timestamp: null, /** * timestamp that is passed in as a seconds timestamp * * @public * @property unix * @type number */ unix: null, /** * can be passed in so a date after the maxDate cannot be selected * * @private * @property maxDate * @type Number * @optional */ maxDate: null, /** * can be passed in so a date before the minDate cannot be selected * * @private * @property minDate * @type Number * @optional */ minDate: null, /** * set to true if the values passed in should not be converted to local time * * @public * @property utc * @type boolean */ utc: false, format: 'MM/DD/YYYY hh:mm A', //'L LT', /** * value thats used to only allow one action to be sent each keyup/heydown for calendar * * @private * @property keyHasGoneUp * @type Boolean */ keyHasGoneUp: true, /** * The stateManager object used by busy-web-date-time-picker * * @private * @property stateManager * @type object */ stateManager: null, /** * Round the minutes by this amount of minutes. * Must be one of the following 1, 5, 10, 15, 30 * * @property round * @type {number} */ round: 1, allowTab: true, hideTime: false, hideDate: false, lockOpen: false, __timestamp: null, __calendar: null, __min: null, __max: null, __defaultFocus: 0, /** * checks if timestamp is valid calls updateInputValues * * @private * @method initialize * @constructor */ init(...args) { this._super(...args); this.initialize(...args); }, initialize() { /** * TODO: * replace stateManager with more advanced state manager * capable of passing events around to all listeners * * const __state = stateManager(); __state.setup({ timestamp: get(this, 'timestamp'), unix: get(this, 'unix'), minDate: get(this, 'minDate'), maxDate: get(this, 'maxDate'), utc: get(this, 'utc'), format: get(this, 'format'), round: get(this, 'round'), roundSelect: get(this, 'roundSelect') }); set(this, '__state', __state); __state.update({ active: HOUR_FLAG }); */ // get locale converted format str let format = get(this, 'format'); format = longFormatDate(format); setPrivate(this, 'format', format); // set timestamp and min max dates this.setupTime(); // set initial focus state let index = findSectionIndex(this, HOUR_FLAG); set(this, '__defaultFocus', index); this.setActiveState(); this.setupPicker(); this.setState(); }, setupTime() { let timestamp = get(this, 'timestamp'); let unix = get(this, 'unix'); let minDate = get(this, 'minDate'); let maxDate = get(this, 'maxDate'); if (timestamp !== get(this, '__lastTimestamp') || unix !== get(this, '__lastUnix') || minDate !== get(this, '__lastMinDate') || maxDate !== get(this, '__lastMaxDate')) { set(this, '__lastTimestamp', timestamp); set(this, '__lastUnix', unix); set(this, '__lastMinDate', minDate); set(this, '__lastMaxDate', maxDate); let time, min, max if (!isNone(unix)) { // assume all dates are unix and convert them to milliseconds time = _time.unix(unix).timestamp() if (!isNone(minDate)) { min = _time.unix(minDate).timestamp(); } if (!isNone(maxDate)) { max = _time.unix(maxDate).timestamp(); } if (get(this, 'utc')) { time = _time.utcToLocal(time).timestamp(); if (!isNone(minDate)) { min = _time.utcToLocal(min).timestamp(); } if (!isNone(maxDate)) { max = _time.utcToLocal(max).timestamp(); } } } else if (!isNone(timestamp)) { if (get(this, 'utc')) { time = _time.utcToLocal(timestamp).timestamp(); if (!isNone(minDate)) { min = _time.utcToLocal(minDate).timestamp(); } if (!isNone(maxDate)) { max = _time.utcToLocal(maxDate).timestamp(); } } } setPrivate(this, 'timestamp', time); setPrivate(this, 'calendar', time); setPrivate(this, 'min', min); setPrivate(this, 'max', max); } }, changeAttrs: observer('timestamp', 'unix', 'minDate', 'maxDate', function() { this.setupTime(); this.setState(); }), setState() { let timestamp = getPrivate(this, 'timestamp'); let calendarDate = getPrivate(this, 'calendar'); let minDate = getPrivate(this, 'min'); let maxDate = getPrivate(this, 'max'); let format = getPrivate(this, 'format'); let selectRound = parseInt(getWithDefault(this, 'round', 1), 10); timestamp = _time.round(timestamp, selectRound); if (isNone(get(this, 'stateManager'))) { set(this, 'stateManager', _state({ timestamp, calendarDate, minDate, maxDate, format, selectRound })); } else { set(this, 'stateManager.timestamp', timestamp); set(this, 'stateManager.calendarDate', calendarDate); set(this, 'stateManager.minDate', minDate); set(this, 'stateManager.maxDate', maxDate); set(this, 'stateManager.format', format); set(this, 'stateManager.selectRound', selectRound); } }, setupPicker: observer('hideTime', 'hideDate', function() { const showDate = (this.get('hideTime') || !this.get('hideDate')); const showTime = (this.get('hideDate') || !this.get('hideTime')); let section = DAY_FLAG; if (!showDate) { section = HOUR_FLAG; } this.setActiveState({ section, showDate, showTime }); }), setActiveState(options={}) { if (isNone(get(this, 'stateManager'))) { this.setState(); } if (!isEmpty(options.section)) { if (get(this, 'stateManager.section') !== options.section) { this.set('stateManager.section', options.section); } this.focusState(options.section); } else { if (this.get('hideTime') && !this.get('hideDate') && this.get('lockOpen')) { this.set('stateManager.section', DAY_FLAG); this.focusState(DAY_FLAG); } else if (!this.get('hideTime') && this.get('lockOpen')) { this.set('stateManager.section', HOUR_FLAG); this.focusState(HOUR_FLAG); } } if (!isNone(options.isOpen)) { if (!this.get('lockOpen')) { this.set('stateManager.isOpen', options.isOpen); } } if (!isNone(options.isTop)) { this.set('stateManager.isTop', options.isTop); } if (!isNone(options.showDate)) { this.set('stateManager.showDate', options.showDate); } else if (this.get('hideTime') && !this.get('hideDate') && this.get('lockOpen')) { this.set('stateManager.showDate', true); } if (!isNone(options.showTime)) { this.set('stateManager.showTime', options.showTime); } else if (!this.get('hideTime') && this.get('lockOpen')) { this.set('stateManager.showTime', true); } this.set('stateChangeInProgress', false); }, focusState(section) { let el = this.$(`input`); if (el && el.length) { let index; if (!isEmpty(section)) { index = findSectionIndex(this, section); } else { index = findSectionIndex(this, HOUR_FLAG); } if (el.data('selection') !== index) { el.data('selection', index); el.data('forceSelection', true); } el.focus(); } }, shouldPickerOpenTop() { const documentHeight = $(document).height(); const dialogHeight = this.$().find('.dialog-container').height() + 50; const elementHeight = this.$().height(); const distanceTop = this.$().offset().top; const distanceBottom = documentHeight - (distanceTop + elementHeight); return (distanceTop > distanceBottom && distanceBottom < dialogHeight); }, /** * triggeres a date change event to send off * to listeners of `onChange` * * @public * @method triggerDateChange */ triggerDateChange() { let time = getPrivate(this, 'timestamp'); if (get(this, 'utc')) { time = _time.utcFromLocal(time).timestamp(); } let timestamp; if (!isNone(get(this, 'timestamp'))) { timestamp = time; set(this, '__lastTimestamp', timestamp); set(this, 'timestamp', timestamp); } let unix; if (!isNone(get(this, 'unix'))) { unix = _time(time).unix(); set(this, '__lastUnix', unix); set(this, 'unix', unix); } this.setState(); this.sendAction('onChange', { timestamp, unix }); }, updateTime(type, time, calendar) { time = _time.round(time, get(this, 'stateManager.selectRound')); calendar = _time.round(calendar, get(this, 'stateManager.selectRound')); if (type === MONTH_FLAG) { if (!isNone(calendar)) { setPrivate(this, 'calendar', calendar); this.setState(); } } else { setPrivate(this, 'timestamp', time); setPrivate(this, 'calendar', time); this.triggerDateChange(); } }, actions: { dateChange(time) { this.updateTime(DAY_FLAG, time); }, applyChange(evt, time) { this.updateTime(DAY_FLAG, time); this.send('closeAction'); }, update(section, time, calendar) { if (!this.get('stateChangeInProgress')) { this.set('stateChangeInProgress', true); this.updateTime(section, time, calendar); this.setActiveState({ section }); this.focusState(section); } }, stateChange(section) { if (!this.get('stateChangeInProgress')) { this.set('stateChangeInProgress', true); if (section === `m-${HOUR_FLAG}`) { section = HOUR_FLAG; } const isOpen = true; const isTop = this.shouldPickerOpenTop(); this.setActiveState({ section, isOpen, isTop }); } }, closeAction() { if (!this.get('lockOpen')) { this.setActiveState({ section: '', isOpen: false, isTop: false }); } }, focusAction(evt, section) { if (!this.get('stateChangeInProgress')) { this.set('stateChangeInProgress', true); if (section === `m-${HOUR_FLAG}`) { section = HOUR_FLAG; } const isOpen = true; const isTop = this.shouldPickerOpenTop(); this.setActiveState({ section, isOpen, isTop }); } }, tabAction(evt, handler) { this.sendAction('onTabKey', evt); if (evt.isDefaultPrevented() || evt.isPropagationStopped()) { return handler.preventDefault(); } else { this.send('closeAction'); return true; } } } }); function getPrivate(target, name) { return get(target, `__${name}`); } function setPrivate(target, name, value) { set(target, `__${name}`, value); } function findSectionIndex(target, type) { let format = getPrivate(target, 'format'); //et value = _time(getPrivate(target, 'timestamp')).format(format); //let f = splitFormat(format); //let v = splitFormat(value); let exp; if (type === HOUR_FLAG) { exp = _time.typeExp(`m-${type}`); if (!exp.test(format)) { exp = _time.typeExp(type); } } else { exp = _time.typeExp(type); } let idx = format.search(exp); if (idx < 0) { idx = 0; } return idx; }