diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2018-04-15 11:50:14 +0200 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2018-04-15 11:51:11 +0200 |
commit | fca67bb95d4cda5046365e124e79ec583c336697 (patch) | |
tree | c511e5060d170f181c10155c0a5a3583fe66b756 /toolkit/content/widgets/datepicker.js | |
parent | f8ad954f7d670ba7d4de0648f4b60d3507164dd2 (diff) | |
parent | f4d1b4804a0dad0a616b01edc31187dace152165 (diff) | |
download | UXP-fca67bb95d4cda5046365e124e79ec583c336697.tar UXP-fca67bb95d4cda5046365e124e79ec583c336697.tar.gz UXP-fca67bb95d4cda5046365e124e79ec583c336697.tar.lz UXP-fca67bb95d4cda5046365e124e79ec583c336697.tar.xz UXP-fca67bb95d4cda5046365e124e79ec583c336697.zip |
Merge branch 'html_input_datetime_1'
This is almost everything needed for #162.
Diffstat (limited to 'toolkit/content/widgets/datepicker.js')
-rw-r--r-- | toolkit/content/widgets/datepicker.js | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/toolkit/content/widgets/datepicker.js b/toolkit/content/widgets/datepicker.js new file mode 100644 index 000000000..0e9c9a6e6 --- /dev/null +++ b/toolkit/content/widgets/datepicker.js @@ -0,0 +1,376 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +function DatePicker(context) { + this.context = context; + this._attachEventListeners(); +} + +{ + const CAL_VIEW_SIZE = 42; + + DatePicker.prototype = { + /** + * Initializes the date picker. Set the default states and properties. + * @param {Object} props + * { + * {Number} year [optional] + * {Number} month [optional] + * {Number} date [optional] + * {Number} min + * {Number} max + * {Number} step + * {Number} stepBase + * {Number} firstDayOfWeek + * {Array<Number>} weekends + * {Array<String>} monthStrings + * {Array<String>} weekdayStrings + * {String} locale [optional]: User preferred locale + * } + */ + init(props = {}) { + this.props = props; + this._setDefaultState(); + this._createComponents(); + this._update(); + document.dispatchEvent(new CustomEvent("PickerReady")); + }, + + /* + * Set initial date picker states. + */ + _setDefaultState() { + const { year, month, day, min, max, step, stepBase, firstDayOfWeek, weekends, + monthStrings, weekdayStrings, locale } = this.props; + const dateKeeper = new DateKeeper({ + year, month, day, min, max, step, stepBase, firstDayOfWeek, weekends, + calViewSize: CAL_VIEW_SIZE + }); + + this.state = { + dateKeeper, + locale, + isMonthPickerVisible: false, + getDayString: day => day ? new Intl.NumberFormat(locale).format(day) : "", + getWeekHeaderString: weekday => weekdayStrings[weekday], + getMonthString: month => monthStrings[month], + setSelection: date => { + dateKeeper.setSelection({ + year: date.getUTCFullYear(), + month: date.getUTCMonth(), + day: date.getUTCDate(), + }); + this._update(); + this._dispatchState(); + this._closePopup(); + }, + setYear: year => { + dateKeeper.setYear(year); + dateKeeper.setSelection({ + year, + month: dateKeeper.selection.month, + day: dateKeeper.selection.day, + }); + this._update(); + this._dispatchState(); + }, + setMonth: month => { + dateKeeper.setMonth(month); + dateKeeper.setSelection({ + year: dateKeeper.selection.year, + month, + day: dateKeeper.selection.day, + }); + this._update(); + this._dispatchState(); + }, + toggleMonthPicker: () => { + this.state.isMonthPickerVisible = !this.state.isMonthPickerVisible; + this._update(); + } + }; + }, + + /** + * Initalize the date picker components. + */ + _createComponents() { + this.components = { + calendar: new Calendar({ + calViewSize: CAL_VIEW_SIZE, + locale: this.state.locale, + setSelection: this.state.setSelection, + getDayString: this.state.getDayString, + getWeekHeaderString: this.state.getWeekHeaderString + }, { + weekHeader: this.context.weekHeader, + daysView: this.context.daysView + }), + monthYear: new MonthYear({ + setYear: this.state.setYear, + setMonth: this.state.setMonth, + getMonthString: this.state.getMonthString, + locale: this.state.locale + }, { + monthYear: this.context.monthYear, + monthYearView: this.context.monthYearView + }) + }; + }, + + /** + * Update date picker and its components. + */ + _update(options = {}) { + const { dateKeeper, isMonthPickerVisible } = this.state; + + if (isMonthPickerVisible) { + this.state.months = dateKeeper.getMonths(); + this.state.years = dateKeeper.getYears(); + } else { + this.state.days = dateKeeper.getDays(); + } + + this.components.monthYear.setProps({ + isVisible: isMonthPickerVisible, + dateObj: dateKeeper.state.dateObj, + months: this.state.months, + years: this.state.years, + toggleMonthPicker: this.state.toggleMonthPicker, + noSmoothScroll: options.noSmoothScroll + }); + this.components.calendar.setProps({ + isVisible: !isMonthPickerVisible, + days: this.state.days, + weekHeaders: dateKeeper.state.weekHeaders + }); + + isMonthPickerVisible ? + this.context.monthYearView.classList.remove("hidden") : + this.context.monthYearView.classList.add("hidden"); + }, + + /** + * Use postMessage to close the picker. + */ + _closePopup() { + window.postMessage({ + name: "ClosePopup" + }, "*"); + }, + + /** + * Use postMessage to pass the state of picker to the panel. + */ + _dispatchState() { + const { year, month, day } = this.state.dateKeeper.selection; + // The panel is listening to window for postMessage event, so we + // do postMessage to itself to send data to input boxes. + window.postMessage({ + name: "PickerPopupChanged", + detail: { + year, + month, + day, + } + }, "*"); + }, + + /** + * Attach event listeners + */ + _attachEventListeners() { + window.addEventListener("message", this); + document.addEventListener("mouseup", this, { passive: true }); + document.addEventListener("mousedown", this); + }, + + /** + * Handle events. + * + * @param {Event} event + */ + handleEvent(event) { + switch (event.type) { + case "message": { + this.handleMessage(event); + break; + } + case "mousedown": { + // Use preventDefault to keep focus on input boxes + event.preventDefault(); + event.target.setCapture(); + + if (event.target == this.context.buttonLeft) { + event.target.classList.add("active"); + this.state.dateKeeper.setMonthByOffset(-1); + this._update(); + } else if (event.target == this.context.buttonRight) { + event.target.classList.add("active"); + this.state.dateKeeper.setMonthByOffset(1); + this._update(); + } + break; + } + case "mouseup": { + if (event.target == this.context.buttonLeft || event.target == this.context.buttonRight) { + event.target.classList.remove("active"); + } + + } + } + }, + + /** + * Handle postMessage events. + * + * @param {Event} event + */ + handleMessage(event) { + switch (event.data.name) { + case "PickerSetValue": { + this.set(event.data.detail); + break; + } + case "PickerInit": { + this.init(event.data.detail); + break; + } + } + }, + + /** + * Set the date state and update the components with the new state. + * + * @param {Object} dateState + * { + * {Number} year [optional] + * {Number} month [optional] + * {Number} date [optional] + * } + */ + set({ year, month, day }) { + const { dateKeeper } = this.state; + + dateKeeper.setCalendarMonth({ + year, month + }); + dateKeeper.setSelection({ + year, month, day + }); + this._update({ noSmoothScroll: true }); + } + }; + + /** + * MonthYear is a component that handles the month & year spinners + * + * @param {Object} options + * { + * {String} locale + * {Function} setYear + * {Function} setMonth + * {Function} getMonthString + * } + * @param {DOMElement} context + */ + function MonthYear(options, context) { + const spinnerSize = 5; + const yearFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric", + timeZone: "UTC" }).format; + const dateFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric", + month: "long", + timeZone: "UTC" }).format; + this.context = context; + this.state = { dateFormat }; + this.props = {}; + this.components = { + month: new Spinner({ + setValue: month => { + this.state.isMonthSet = true; + options.setMonth(month); + }, + getDisplayString: options.getMonthString, + viewportSize: spinnerSize + }, context.monthYearView), + year: new Spinner({ + setValue: year => { + this.state.isYearSet = true; + options.setYear(year); + }, + getDisplayString: year => yearFormat(new Date(new Date(0).setUTCFullYear(year))), + viewportSize: spinnerSize + }, context.monthYearView) + }; + + this._attachEventListeners(); + } + + MonthYear.prototype = { + + /** + * Set new properties and pass them to components + * + * @param {Object} props + * { + * {Boolean} isVisible + * {Date} dateObj + * {Array<Object>} months + * {Array<Object>} years + * {Function} toggleMonthPicker + * } + */ + setProps(props) { + this.context.monthYear.textContent = this.state.dateFormat(props.dateObj); + + if (props.isVisible) { + this.context.monthYear.classList.add("active"); + this.components.month.setState({ + value: props.dateObj.getUTCMonth(), + items: props.months, + isInfiniteScroll: true, + isValueSet: this.state.isMonthSet, + smoothScroll: !(this.state.firstOpened || props.noSmoothScroll) + }); + this.components.year.setState({ + value: props.dateObj.getUTCFullYear(), + items: props.years, + isInfiniteScroll: false, + isValueSet: this.state.isYearSet, + smoothScroll: !(this.state.firstOpened || props.noSmoothScroll) + }); + this.state.firstOpened = false; + } else { + this.context.monthYear.classList.remove("active"); + this.state.isMonthSet = false; + this.state.isYearSet = false; + this.state.firstOpened = true; + } + + this.props = Object.assign(this.props, props); + }, + + /** + * Handle events + * @param {DOMEvent} event + */ + handleEvent(event) { + switch (event.type) { + case "click": { + this.props.toggleMonthPicker(); + break; + } + } + }, + + /** + * Attach event listener to monthYear button + */ + _attachEventListeners() { + this.context.monthYear.addEventListener("click", this); + } + }; +} |