<?xml version="1.0"?> <!-- 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/. --> <bindings id="dateTimePopupBindings" xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xbl="http://www.mozilla.org/xbl"> <binding id="datetime-popup" extends="chrome://global/content/bindings/popup.xml#arrowpanel"> <resources> <stylesheet src="chrome://global/skin/datetimepopup.css"/> </resources> <implementation> <field name="dateTimePopupFrame"> this.querySelector("#dateTimePopupFrame"); </field> <field name="TIME_PICKER_WIDTH" readonly="true">"12em"</field> <field name="TIME_PICKER_HEIGHT" readonly="true">"21em"</field> <field name="DATE_PICKER_WIDTH" readonly="true">"23.1em"</field> <field name="DATE_PICKER_HEIGHT" readonly="true">"20.7em"</field> <constructor><![CDATA[ this.l10n = {}; const mozIntl = Components.classes["@mozilla.org/mozintl;1"] .getService(Components.interfaces.mozIMozIntl); mozIntl.addGetCalendarInfo(l10n); mozIntl.addGetDisplayNames(l10n); // Notify DateTimePickerHelper.jsm that binding is ready. this.dispatchEvent(new CustomEvent("DateTimePickerBindingReady")); ]]></constructor> <method name="openPicker"> <parameter name="type"/> <parameter name="anchor"/> <parameter name="detail"/> <body><![CDATA[ this.type = type; this.pickerState = {}; // TODO: Resize picker according to content zoom level this.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.hidden = false; this.openPopup(anchor, "after_start", 0, 0); ]]></body> </method> <method name="closePicker"> <body><![CDATA[ this.setInputBoxValue(true); this.pickerState = {}; this.type = undefined; this.dateTimePopupFrame.removeEventListener("load", this, true); this.dateTimePopupFrame.contentDocument.removeEventListener("message", this, false); this.dateTimePopupFrame.setAttribute("src", ""); this.hidden = true; ]]></body> </method> <method name="setPopupValue"> <parameter name="data"/> <body><![CDATA[ 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; } } ]]></body> </method> <method name="initPicker"> <parameter name="detail"/> <body><![CDATA[ const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global"); 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, min: detail.min, max: detail.max, step: detail.step, stepBase: detail.stepBase, } }); break; } } ]]></body> </method> <method name="setInputBoxValue"> <parameter name="passAllValues"/> <body><![CDATA[ /** * @param {Boolean} passAllValues: Pass spinner values regardless if they've been set/changed or not */ 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; } } ]]></body> </method> <method name="sendPickerValueChanged"> <parameter name="value"/> <body><![CDATA[ switch (this.type) { case "time": { this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", { detail: { hour: value.hour, minute: value.minute } })); break; } case "date": { this.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; } } ]]></body> </method> <method name="getCalendarInfo"> <parameter name="locale"/> <body><![CDATA[ const calendarInfo = this.l10n.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 } ]]></body> </method> <method name="getDisplayNames"> <parameter name="locale"/> <parameter name="keys"/> <parameter name="style"/> <body><![CDATA[ const displayNames = this.l10n.getDisplayNames(locale, {keys, style}); return keys.map(key => displayNames.values[key]); ]]></body> </method> <method name="handleEvent"> <parameter name="aEvent"/> <body><![CDATA[ switch (aEvent.type) { case "load": { this.initPicker(this.detail); this.dateTimePopupFrame.contentWindow.addEventListener("message", this, false); break; } case "message": { this.handleMessage(aEvent); break; } } ]]></body> </method> <method name="handleMessage"> <parameter name="aEvent"/> <body><![CDATA[ if (!this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) { return; } switch (aEvent.data.name) { case "PickerPopupChanged": { this.pickerState = aEvent.data.detail; this.setInputBoxValue(); break; } case "ClosePopup": { this.hidePopup(); this.closePicker(); break; } } ]]></body> </method> <method name="postMessageToPicker"> <parameter name="data"/> <body><![CDATA[ if (this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) { this.dateTimePopupFrame.contentWindow.postMessage(data, "*"); } ]]></body> </method> </implementation> </binding> </bindings>