summaryrefslogtreecommitdiffstats
path: root/toolkit/content/widgets/datepicker.js
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@gmail.com>2018-04-15 11:50:14 +0200
committerwolfbeast <mcwerewolf@gmail.com>2018-04-15 11:51:11 +0200
commitfca67bb95d4cda5046365e124e79ec583c336697 (patch)
treec511e5060d170f181c10155c0a5a3583fe66b756 /toolkit/content/widgets/datepicker.js
parentf8ad954f7d670ba7d4de0648f4b60d3507164dd2 (diff)
parentf4d1b4804a0dad0a616b01edc31187dace152165 (diff)
downloadUXP-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.js376
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);
+ }
+ };
+}