busy-web/ember-date-time

View on GitHub
addon/components/private/time-picker.js

Summary

Maintainability
B
4 hrs
Test Coverage
/**
 * @module Components
 *
 */
import Component from '@ember/component';
import { isNone } from '@ember/utils';
//import { assert } from '@ember/debug';
import { on } from '@ember/object/evented';
import { get, getWithDefault, set, computed, observer } from '@ember/object';
import _time from '@busy-web/ember-date-time/utils/time';
import { isInBounds, getDate, getHourMinute } from '@busy-web/ember-date-time/utils/clock/data';
import { createMetaFor, metaFor } from '@busy-web/ember-date-time/utils/clock';
import { numberToString } from '@busy-web/ember-date-time/utils/clock/string';
import layout from '../../templates/components/private/time-picker';
import {
    HOUR_FLAG,
    MINUTE_FLAG,
    MERIDIAN_FLAG
} from '@busy-web/ember-date-time/utils/constant';

/***/

/**
 * `Component/TimePicker`
 *
 * @class TimePicker
 * @namespace Components
 * @extends Component
 */
export default Component.extend({
    classNames: ['busyweb', 'emberdatetime', 'p-time-picker'],
    layout: layout,

    stateManager: null,

    /**
     * timestamp that is passed in when using date picker
     *
     * @public
     * @property timestamp
     * @type Number
     */
    timestamp: null,

    /**
     * format for displaying dates
     *
     * @public
     * @property format
     * @type string
     */
    format: 'MMM DD, YYYY',

    /**
     * can be passed in so a date before the minDate cannot be selected
     *
     * @public
     * @property minDate
     * @type Number
     * @optional
     */
    minDate: null,

    /**
     * can be passed in so a date after the maxDate cannot be selected
     *
     * @public
     * @property maxDate
     * @type Number
     * @optional
     */
    maxDate: null,

    round: 5,
    selectRound: 1,

    section: HOUR_FLAG,

    invalidTime: false,

    initialize: on('init', function() {
        this.timeChange();
        this.sectionChage();

        // TODO:
        // pass format from parent element
        //set(this, 'format', get(this, 'stateManager.format'));

        let min = get(this, 'stateManager.minDate');
        let max = get(this, 'stateManager.maxDate');

        set(this, 'minDate', min);
        set(this, 'maxDate', max);

        let selectRound = getWithDefault(this, 'stateManager.selectRound', 1);
        let round = getWithDefault(this, 'stateManager.round', 5);
        if (round < selectRound) {
            round = selectRound;
        }

        set(this, 'round', round);
        set(this, 'selectRound', selectRound);

        // create hours clock meta object
        createMetaFor(this, HOUR_FLAG, 1, 12, min, max, 1);

        // create minutes clock meta object
        createMetaFor(this, MINUTE_FLAG, 0, 59, min, max, round, selectRound);
    }),

    renderPicker: on('didInsertElement', function() {
        this.resetTimeElements();
    }),

    timeChange: observer('stateManager.timestamp', function() {
        if (get(this, 'timestamp') !== get(this, 'stateManager.timestamp')) {
            this.saveTimestamp(get(this, 'stateManager.timestamp'));
            this.setActiveTime();
        }
    }),

    sectionChage: observer('stateManager.section', function() {
        let section = get(this, 'stateManager.section');
        this.changeSection(section);
    }),

    /**
     * current hour of timestamp displayed in clock header
     *
     * @private
     * @property hours
     * @type String
     */
    hours: computed('timestamp', function() {
        return numberToString(this.getCurrentTimeByType(HOUR_FLAG));
    }).readOnly(),

    /**
     * current minute of timestamp displayed in clock header
     *
     * @private
     * @property minutes
     * @type String
     */
    minutes: computed('timestamp', function() {
        return numberToString(this.getCurrentTimeByType(MINUTE_FLAG));
    }).readOnly(),

    /**
     * current date of timestamp displayed in clock footer
     *
     * @private
     * @property currentDate
     * @type String
     */
    currentDate: computed('timestamp', 'format', function() {
        return _time(get(this, 'timestamp')).format(get(this, 'format'));
    }).readOnly(),

    meridian: computed('timestamp', function() {
        return _time(get(this, 'timestamp')).format('A');
    }).readOnly(),

    isHourPicker: computed('section', function() {
        return get(this, 'section') === HOUR_FLAG;
    }),

    isAM: computed('timestamp', function() {
        return _time(get(this, 'timestamp')).format('A') === 'AM';
    }),

    isPM: computed('timestamp', function() {
        return _time(get(this, 'timestamp')).format('A') === 'PM';
    }),

    getCurrentTimeByType(type) {
        return getHourMinute(type, get(this, 'timestamp'), getWithDefault(this, 'selectRounder', 1));
    },

    changeSection(section) {
        if (section === MERIDIAN_FLAG) {
            section = HOUR_FLAG;
        }

        if (section !== get(this, 'section') && (section === HOUR_FLAG || section === MINUTE_FLAG)) {
            set(this, 'lastActive', null);
            set(this, 'section', section);

            if (get(this, '_state') === 'inDOM') {
                this.resetTimeElements();
            }
        }
    },

    resetTimeElements() {
        this.renderClock();
        this.cleanClock();

        const time = this.getCurrentTimeByType(get(this, 'section'));
        if (get(this, 'lastActive') !== time) {
            this.setTimestamp(time);
        }

        this.bindClick();
    },

    /**
     * sets up the active time classes and removes the lst active times
     *
     * @private
     * @method setActiveTime
     */
    setActiveTime() {
        if (get(this, '_state') === 'inDOM') {
            const value = this.getCurrentTimeByType(get(this, 'section'));
            if (!isNone(value)) {
                const clock = metaFor(this, get(this, 'section'));
                if (!isNone(get(this, 'lastActive'))) {
                    clock.unselectPlot(get(this, 'lastActive'));
                }
                clock.selectPlot(value);
                set(this, `lastActive`, value);
                this.dragHandler(value);
            }
        }
    },

    /**
     * converts moment object to a unix timestamp, and sets that to the global timestamp
     *
     * @private
     * @method saveTimestamp
     * @param date {object} moment object that will be the new timestamp
     */
    saveTimestamp(date) {
        if (!get(this, 'isDestroyed') && get(this, 'timestamp') !== date.valueOf()) {
            const bounds = isInBounds(date, get(this, 'minDate'), get(this, 'maxDate'));
            if (bounds.isBefore || bounds.isAfter) {
                set(this, 'invalidTime', true);
                //this.setAvailableTimestamp(bounds);
            } else {
                set(this, 'invalidTime', false);
            }

            // save the current date as the timestamp
            set(this, 'timestamp', date.valueOf());
            //}
        }
    },

    //setAvailableTimestamp(bounds) {
    //    if (bounds.isBefore) {
    //        this.saveTimestamp(_time(get(this, 'minDate')));
    //    } else if (bounds.isAfter) {
    //        this.saveTimestamp(_time(get(this, 'maxDate')));
    //    } else {
    //        assert(`error trying to setAvailableTimestamp with bounds isBefore: ${bounds.isBefore} and isAfter: ${bounds.isAfter}`, false);
    //    }
    //},

    /**
     * sets the timestamp to be the passed minute
     *
     * @private
     * @method setTimestamp
     * @param minute {number} minute to be set to timestamp
     */
    setTimestamp(value) {
        const time = getDate(get(this, 'section'), value, get(this, 'timestamp'));
        this.saveTimestamp(time);
        this.setActiveTime();
    },

    renderClock() {
        if (get(this, '_state') === 'inDOM') {
            const clock = metaFor(this, get(this, 'section'));
            clock.render(get(this, 'timestamp'));
        }
    },

    /**
     * remove initial circles and lines for clock
     *
     * @private
     * @method cleanClock
     */
    cleanClock() {
        if (get(this, '_state') === 'inDOM') {
            const clock = metaFor(this, get(this, 'section'));
            clock.cleanup();
        }
    },


    /**
     * handles all the function events for dragging on the hours clock
     * dragHandler must contain start, move and stop functions within it
     *
     * @private
     * @method dragHandler
     * @param hour {string} hour thats being dragged
     * @event drag
     */
    dragHandler(value) {
        if (get(this, '_state') === 'inDOM') {
            const clock = metaFor(this, get(this, 'section'));
            clock.handleDrag(value, time => {
                // set the time according to the new angle
                this.setTimestamp(time);

                // notify listeners an update has occured
                this.sendAction('onUpdate', get(this, 'section'), get(this, 'timestamp'));
            });
        }
    },

    bindClick() {
        if (get(this, '_state') === 'inDOM') {
            // unregister hour clicks
            const hourClock = metaFor(this, HOUR_FLAG);
            hourClock.unclick();

            // unregister minute clicks
            const minClock = metaFor(this, MINUTE_FLAG);
            minClock.unclick();

            // bind new click handler
            const clock = metaFor(this, get(this, 'section'));
            clock.click(time => {
                // set the time according to the new angle
                this.setTimestamp(time);

                // notify listeners an update has occured
                this.sendAction('onUpdate', get(this, 'section'), get(this, 'timestamp'));
            });
        }
    },

    actions: {
        /**
         * handles clicking AM && PM, wont allow if it goes under min date
         *
         * @event meridianChange
         */
        meridianChange(type) {
            let time;
            if (type === 'AM' && get(this, MERIDIAN_FLAG) === 'PM') {
                time = _time(get(this, 'timestamp')).subtract(12, 'hours');
            } else if (type === 'PM' && get(this, MERIDIAN_FLAG) === 'AM') {
                time = _time(get(this, 'timestamp')).add(12, 'hours');
            }

            if (!isNone(time)) {
                // save new time
                this.saveTimestamp(time);

                // notify event listeners that an update has occurred
                this.sendAction('onUpdate', get(this, 'section'), get(this, 'timestamp'));
            }
        },

        /**
         * handles clicking the hour in the header
         *
         * @event hourHeaderClicked
         */
        hourHeaderClicked() {
            this.changeSection(HOUR_FLAG);

            // notify event listeners that an update has occurred
            this.sendAction('onUpdate', HOUR_FLAG, get(this, 'timestamp'));
        },

        /**
         * handles clicking the minute in the header
         *
         * @event minuteHeaderClicked
         */
        minuteHeaderClicked() {
            this.changeSection(MINUTE_FLAG);

            // notify event listeners that an update has occurred
            this.sendAction('onUpdate', MINUTE_FLAG, get(this, 'timestamp'));
        }
    }
});