summaryrefslogtreecommitdiffstats
path: root/toolkit/modules/DateTimePickerPanel.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/modules/DateTimePickerPanel.jsm')
-rw-r--r--toolkit/modules/DateTimePickerPanel.jsm340
1 files changed, 340 insertions, 0 deletions
diff --git a/toolkit/modules/DateTimePickerPanel.jsm b/toolkit/modules/DateTimePickerPanel.jsm
new file mode 100644
index 0000000000..06d1d87f5a
--- /dev/null
+++ b/toolkit/modules/DateTimePickerPanel.jsm
@@ -0,0 +1,340 @@
+/* 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";
+
+var EXPORTED_SYMBOLS = ["DateTimePickerPanel"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var DateTimePickerPanel = class {
+ constructor(element) {
+ this.element = element;
+
+ this.TIME_PICKER_WIDTH = "12em";
+ this.TIME_PICKER_HEIGHT = "21em";
+ this.DATE_PICKER_WIDTH = "23.1em";
+ this.DATE_PICKER_HEIGHT = "20.7em";
+ }
+
+ get dateTimePopupFrame() {
+ let frame = this.element.querySelector("#dateTimePopupFrame");
+ if (!frame) {
+ frame = this.element.ownerDocument.createXULElement("iframe");
+ frame.id = "dateTimePopupFrame";
+ this.element.appendChild(frame);
+ }
+ return frame;
+ }
+
+ openPicker(type, rect, detail) {
+ this.type = type;
+ this.pickerState = {};
+ // TODO: Resize picker according to content zoom level
+ this.element.style.fontSize = "10px";
+ switch (type) {
+ case "time": {
+ this.detail = detail;
+ this.dateTimePopupFrame.addEventListener("load", this, true);
+ this.dateTimePopupFrame.setAttribute(
+ "src",
+ "chrome://global/content/timepicker.xhtml"
+ );
+ this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
+ this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
+ break;
+ }
+ case "date": {
+ this.detail = detail;
+ this.dateTimePopupFrame.addEventListener("load", this, true);
+ this.dateTimePopupFrame.setAttribute(
+ "src",
+ "chrome://global/content/datepicker.xhtml"
+ );
+ this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH;
+ this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT;
+ break;
+ }
+ }
+ this.element.hidden = false;
+ this.element.openPopupAtScreenRect(
+ "after_start",
+ rect.left,
+ rect.top,
+ rect.width,
+ rect.height,
+ false,
+ false
+ );
+ }
+
+ closePicker() {
+ this.setInputBoxValue(true);
+ this.pickerState = {};
+ this.type = undefined;
+ this.dateTimePopupFrame.removeEventListener("load", this, true);
+ this.dateTimePopupFrame.contentDocument.removeEventListener(
+ "message",
+ this
+ );
+ this.dateTimePopupFrame.setAttribute("src", "");
+ this.element.hidden = true;
+ }
+
+ setPopupValue(data) {
+ switch (this.type) {
+ case "time": {
+ this.postMessageToPicker({
+ name: "PickerSetValue",
+ detail: data.value,
+ });
+ break;
+ }
+ case "date": {
+ const { year, month, day } = data.value;
+ this.postMessageToPicker({
+ name: "PickerSetValue",
+ detail: {
+ year,
+ // Month value from input box starts from 1 instead of 0
+ month: month == undefined ? undefined : month - 1,
+ day,
+ },
+ });
+ break;
+ }
+ }
+ }
+
+ initPicker(detail) {
+ let locale = new Services.intl.Locale(
+ Services.locale.webExposedLocales[0],
+ {
+ calendar: "gregory",
+ }
+ ).toString();
+
+ // Workaround for bug 1418061, while we wait for resolution of
+ // http://bugs.icu-project.org/trac/ticket/13592: drop the PT region code,
+ // because it results in "abbreviated" day names that are too long;
+ // the region-less "pt" locale has shorter forms that are better here.
+ locale = locale.replace(/^pt-PT/i, "pt");
+
+ const dir = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
+
+ switch (this.type) {
+ case "time": {
+ const { hour, minute } = detail.value;
+ const format = detail.format || "12";
+
+ this.postMessageToPicker({
+ name: "PickerInit",
+ detail: {
+ hour,
+ minute,
+ format,
+ locale,
+ min: detail.min,
+ max: detail.max,
+ step: detail.step,
+ },
+ });
+ break;
+ }
+ case "date": {
+ const { year, month, day } = detail.value;
+ const { firstDayOfWeek, weekends } = this.getCalendarInfo(locale);
+ const monthStrings = this.getDisplayNames(
+ locale,
+ [
+ "dates/gregorian/months/january",
+ "dates/gregorian/months/february",
+ "dates/gregorian/months/march",
+ "dates/gregorian/months/april",
+ "dates/gregorian/months/may",
+ "dates/gregorian/months/june",
+ "dates/gregorian/months/july",
+ "dates/gregorian/months/august",
+ "dates/gregorian/months/september",
+ "dates/gregorian/months/october",
+ "dates/gregorian/months/november",
+ "dates/gregorian/months/december",
+ ],
+ "short"
+ );
+ const weekdayStrings = this.getDisplayNames(
+ locale,
+ [
+ "dates/gregorian/weekdays/sunday",
+ "dates/gregorian/weekdays/monday",
+ "dates/gregorian/weekdays/tuesday",
+ "dates/gregorian/weekdays/wednesday",
+ "dates/gregorian/weekdays/thursday",
+ "dates/gregorian/weekdays/friday",
+ "dates/gregorian/weekdays/saturday",
+ ],
+ "short"
+ );
+
+ this.postMessageToPicker({
+ name: "PickerInit",
+ detail: {
+ year,
+ // Month value from input box starts from 1 instead of 0
+ month: month == undefined ? undefined : month - 1,
+ day,
+ firstDayOfWeek,
+ weekends,
+ monthStrings,
+ weekdayStrings,
+ locale,
+ dir,
+ min: detail.min,
+ max: detail.max,
+ step: detail.step,
+ stepBase: detail.stepBase,
+ },
+ });
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param {Boolean} passAllValues: Pass spinner values regardless if they've been set/changed or not
+ */
+ setInputBoxValue(passAllValues) {
+ switch (this.type) {
+ case "time": {
+ const {
+ hour,
+ minute,
+ isHourSet,
+ isMinuteSet,
+ isDayPeriodSet,
+ } = this.pickerState;
+ const isAnyValueSet = isHourSet || isMinuteSet || isDayPeriodSet;
+ if (passAllValues && isAnyValueSet) {
+ this.sendPickerValueChanged({ hour, minute });
+ } else {
+ this.sendPickerValueChanged({
+ hour: isHourSet || isDayPeriodSet ? hour : undefined,
+ minute: isMinuteSet ? minute : undefined,
+ });
+ }
+ break;
+ }
+ case "date": {
+ this.sendPickerValueChanged(this.pickerState);
+ break;
+ }
+ }
+ }
+
+ sendPickerValueChanged(value) {
+ switch (this.type) {
+ case "time": {
+ this.element.dispatchEvent(
+ new CustomEvent("DateTimePickerValueChanged", {
+ detail: {
+ hour: value.hour,
+ minute: value.minute,
+ },
+ })
+ );
+ break;
+ }
+ case "date": {
+ this.element.dispatchEvent(
+ new CustomEvent("DateTimePickerValueChanged", {
+ detail: {
+ year: value.year,
+ // Month value from input box starts from 1 instead of 0
+ month: value.month == undefined ? undefined : value.month + 1,
+ day: value.day,
+ },
+ })
+ );
+ break;
+ }
+ }
+ }
+
+ getCalendarInfo(locale) {
+ const calendarInfo = Services.intl.getCalendarInfo(locale);
+
+ // Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
+ // so they need to be mapped to JavaScript convention with 0 as Sunday
+ // and 6 as Saturday
+ let firstDayOfWeek = calendarInfo.firstDayOfWeek - 1,
+ weekendStart = calendarInfo.weekendStart - 1,
+ weekendEnd = calendarInfo.weekendEnd - 1;
+
+ let weekends = [];
+
+ // Make sure weekendEnd is greater than weekendStart
+ if (weekendEnd < weekendStart) {
+ weekendEnd += 7;
+ }
+
+ // We get the weekends by incrementing weekendStart up to weekendEnd.
+ // If the start and end is the same day, then weekends only has one day.
+ for (let day = weekendStart; day <= weekendEnd; day++) {
+ weekends.push(day % 7);
+ }
+
+ return {
+ firstDayOfWeek,
+ weekends,
+ };
+ }
+
+ getDisplayNames(locale, keys, style) {
+ const displayNames = Services.intl.getDisplayNames(locale, { keys, style });
+ return keys.map(key => displayNames.values[key]);
+ }
+
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "load": {
+ this.initPicker(this.detail);
+ this.dateTimePopupFrame.contentWindow.addEventListener("message", this);
+ break;
+ }
+ case "message": {
+ this.handleMessage(aEvent);
+ break;
+ }
+ }
+ }
+
+ handleMessage(aEvent) {
+ if (
+ !this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal
+ ) {
+ return;
+ }
+
+ switch (aEvent.data.name) {
+ case "PickerPopupChanged": {
+ this.pickerState = aEvent.data.detail;
+ this.setInputBoxValue();
+ break;
+ }
+ case "ClosePopup": {
+ this.element.hidePopup();
+ this.closePicker();
+ break;
+ }
+ }
+ }
+
+ postMessageToPicker(data) {
+ if (
+ this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal
+ ) {
+ this.dateTimePopupFrame.contentWindow.postMessage(data, "*");
+ }
+ }
+};