addon/components/private/date-time-menu.js
/**
* @module Components
*
*/
import Component from '@ember/component';
import { get, set, computed, observer } from '@ember/object';
import { isEmpty } from '@ember/utils';
import _time from '@busy-web/ember-date-time/utils/time';
import { bind, unbind, isEventLocal } from '@busy-web/ember-date-time/utils/event';
import {
YEAR_FLAG,
MONTH_FLAG,
WEEKDAY_FLAG,
DAY_FLAG,
HOUR_FLAG,
MINUTE_FLAG,
MERIDIAN_FLAG
} from '@busy-web/ember-date-time/utils/constant';
import layout from '../../templates/components/private/date-time-menu';
/**
* `Component/CompinedPicker`
*
* @class CombinedPicker
* @namespace Components
* @extends Component
*/
export default Component.extend({
/**
* @private
* @property classNames
* @type String
* @default combined-picker
*/
classNames: ['busyweb', 'emberdatetime', 'p-date-time-menu'],
layout: layout,
stateManager: null,
/**
* timestamp that is passed in when using combined-picker
*
* @private
* @property timestamp
* @type Number
*/
timestamp: null,
/**
* can be passed in so a date before the minDate cannot be selected
*
* @private
* @property minDate
* @type Number
* @optional
*/
minDate: 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 as true or false, true sets timepicker to handle unix timestamp * 1000, false sets it to handle unix timestamp
*
* @private
* @property isMilliseconds
* @type boolean
* @optional
*/
isMilliseconds: null,
/**
* String as the current date of the timestamp
*
* @private
* @property currentDate
* @type String
*/
currentDate: computed('timestamp', function() {
return _time(get(this, 'timestamp')).format('MMM DD, YYYY');
}).readOnly(),
/**
* String as the current time of the timestamp
*
* @private
* @property currentTime
* @type String
*/
currentTime: computed('timestamp', function() {
return _time(get(this, 'timestamp')).format('hh:mm A');
}).readOnly(),
/**
* if they cancel the change this is the timestamp the picker will revert back to
*
* @private
* @property backupTimestamp
* @type Number
*/
backupTimestamp: null,
/**
* the last active section that was open in the pickers
*
* @private
* @property lastActiveSection
* @type String
*/
lastActiveState: null,
/**
* sets currentTime and currentDate, sets a timestamp to now if a timestamp wasnt passed in
* @private
* @method init
* @constructor
*/
init(...args) {
this._super(...args);
this.setupTime();
},
setupTime: observer('stateManager.timestamp', function() {
if (!this.get('isDestroyed')) {
set(this, 'minDate', get(this, 'stateManager.minDate'));
set(this, 'maxDate', get(this, 'stateManager.maxDate'));
set(this, 'timestamp', get(this, 'stateManager.timestamp'));
set(this, 'backupTimestamp', get(this, 'timestamp'));
}
}),
/**
* binds a click event that will call the provided
* action anytime a click occurs not within the component
*
* NOTE:
* The action name provided will get called everytime a click occurs so the
* action should only handle on thing like closing a drop down vs toggling a dropdown.
* this will prevent accidently opening the dialog when it is closed and a click occurs.
*
* @public
* @method bindClick
* @return {void}
*/
bindClick() {
// save this for later
const _this = this;
// get the components elementId
let elementId = get(this, 'elementId');
// make sure an elementId exists on the class
// using this mixin
if (!isEmpty(elementId)) {
// bind the click listener
bind(document, 'click', elementId, function(evt) {
// check if the evt.target is local to this elements main parent
let isLocal = isEventLocal(evt, elementId, '.emberdatetime');
// if the clicked element id does not match the components id and the clicked
// elements parents dont have an id that matches then call the action provided
//if (el.attr('id') !== elementId && el.parents(`#${elementId}`).length === 0) {
if (!isLocal) {
// send a call to the actionName
evt.stopPropagation();
evt.preventDefault();
// call handler
_this.unbindClick();
_this.send('close');
return false;
}
}, { capture: true, rebind: true });
}
},
bindKeyup() {
// save this for later
const _this = this;
// get the components elementId
let elementId = get(this, 'elementId');
// make sure an elementId exists on the class
// using this mixin
if (!isEmpty(elementId)) {
bind(document, 'keyup', elementId, function(evt) {
let key = evt.which;
if (key === 27) {
_this.closeHandler('cancel');
} else if (key === 13) {
_this.closeHandler('close');
}
}, { capture: true, rebind: true });
}
},
/**
* method to unbind a click event that may have been
* setup by this components
*
* @public
* @method unbindClick
*/
unbindClick() {
// get the components elementId
let elementId = get(this, 'elementId');
// make sure an elementId exists on the class
// using this mixin
if (!isEmpty(elementId)) {
// unbind any previous click listeners
unbind(document, 'click', elementId);
}
},
unbindKeyup() {
// get the components elementId
let elementId = get(this, 'elementId');
// make sure an elementId exists on the class
// using this mixin
if (!isEmpty(elementId)) {
// unbind any previous click listeners
unbind(document, 'keyup', elementId);
}
},
/**
* sets up the click handler to close the dialogs if anything outside is clicked
* @private
* @method didInsertElement
*/
didInsertElement(...args) {
this._super(...args);
this.bindHandler();
},
/**
* removes the click handler to close the dialogs if anything outside is clicked
* @private
* @method removeClick
*/
willDestroyElement(...args) {
this._super(...args);
this.closeHandler();
},
/**
* closes dialog if tabbed on last input
*
* @private
* @method observeCloseOnTab
*/
observeCloseOnTab: observer('stateManager.isOpen', function() {
if (get(this, 'stateManager.isOpen')) {
this.bindHandler();
} else {
this.closeHandler();
}
}),
isClock: computed('stateManager.section', function() {
let section = get(this, 'stateManager.section');
return (section === HOUR_FLAG || section === MINUTE_FLAG || section === MERIDIAN_FLAG);
}),
isCalendar: computed('stateManager.section', function() {
let section = get(this, 'stateManager.section');
return (section === YEAR_FLAG || section === MONTH_FLAG || section === DAY_FLAG || section === WEEKDAY_FLAG);
}),
bindHandler() {
this.bindClick();
this.bindKeyup();
},
closeHandler(type) {
this.unbindClick();
this.unbindKeyup();
if (!isEmpty(type)) {
this.send(type);
}
},
actions: {
update(focus, time, calendar) {
this.sendAction('onUpdate', focus, time, calendar);
},
change(focus, time, calendar) {
this.sendAction('onChange', focus, time, calendar);
},
/**
* changes dialog from clock to calendar and vice versa
*
* @event togglePicker
*/
togglePicker() {
let section = HOUR_FLAG;
if (get(this, 'isClock')) {
section = DAY_FLAG;
}
this.sendAction('onUpdate', section, get(this, 'timestamp'));
},
/**
* closes all dialogs
*
* @event togglePicker
*/
close() {
if (!get(this, 'isDestroyed')) {
set(this, 'backupTimestamp', get(this, 'timestamp'));
}
this.sendAction('onClose');
},
/**
* closes all dialogs and resets the timestamp
*
* @event togglePicker
*/
cancel() {
if (!get(this, 'isDestroyed')) {
set(this, 'timestamp', get(this, 'backupTimestamp'));
}
this.sendAction('onClose');
},
onHeaderSelect(type) {
//if (!get(this, 'isDestroyed')) {
// set(this, 'stateManager.section', type);
//}
this.sendAction('onUpdate', type, get(this, 'timestamp'));
}
}
});