<?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/. --> <!DOCTYPE bindings [ <!ENTITY % datetimeboxDTD SYSTEM "chrome://global/locale/datetimebox.dtd"> %datetimeboxDTD; ]> <!-- TODO Bug 1446342: Input type="date" not working if the other form elements has name="document" Any alternative solution: document === window.document document === this.ownerDocument --> <bindings id="datetimeboxBindings" 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="date-input" extends="chrome://global/content/bindings/datetimebox.xml#datetime-input-base"> <resources> <stylesheet src="chrome://global/content/textbox.css"/> <stylesheet src="chrome://global/skin/textbox.css"/> <stylesheet src="chrome://global/content/bindings/datetimebox.css"/> </resources> <implementation> <constructor> <![CDATA[ /* eslint-disable no-multi-spaces */ this.mYearPlaceHolder = ]]>"&date.year.placeholder;"<![CDATA[; this.mMonthPlaceHolder = ]]>"&date.month.placeholder;"<![CDATA[; this.mDayPlaceHolder = ]]>"&date.day.placeholder;"<![CDATA[; this.mSeparatorText = "/"; /* eslint-enable no-multi-spaces */ this.mMinMonth = 1; this.mMaxMonth = 12; this.mMinDay = 1; this.mMaxDay = 31; this.mMinYear = 1; // Maximum year limited by ECMAScript date object range, year <= 275760. this.mMaxYear = 275760; this.mMonthDayLength = 2; this.mYearLength = 4; this.mMonthPageUpDownInterval = 3; this.mDayPageUpDownInterval = 7; this.mYearPageUpDownInterval = 10; // Default to en-US, month-day-year order. this.mMonthField = window.document.getAnonymousElementByAttribute(this, "anonid", "input-one"); this.mDayField = window.document.getAnonymousElementByAttribute(this, "anonid", "input-two"); this.mYearField = window.document.getAnonymousElementByAttribute(this, "anonid", "input-three"); this.mYearField.size = this.mYearLength; this.mYearField.maxLength = this.mMaxYear.toString().length; this.mMonthField.placeholder = this.mMonthPlaceHolder; this.mDayField.placeholder = this.mDayPlaceHolder; this.mYearField.placeholder = this.mYearPlaceHolder; this.mMonthField.setAttribute("min", this.mMinMonth); this.mMonthField.setAttribute("max", this.mMaxMonth); this.mMonthField.setAttribute("pginterval", this.mMonthPageUpDownInterval); this.mDayField.setAttribute("min", this.mMinDay); this.mDayField.setAttribute("max", this.mMaxDay); this.mDayField.setAttribute("pginterval", this.mDayPageUpDownInterval); this.mYearField.setAttribute("min", this.mMinYear); this.mYearField.setAttribute("max", this.mMaxYear); this.mYearField.setAttribute("pginterval", this.mYearPageUpDownInterval); this.mDaySeparator = window.document.getAnonymousElementByAttribute(this, "anonid", "sep-first"); this.mDaySeparator.textContent = this.mSeparatorText; this.mYearSeparator = window.document.getAnonymousElementByAttribute(this, "anonid", "sep-second"); this.mYearSeparator.textContent = this.mSeparatorText; if (this.mInputElement.value) { this.setFieldsFromInputValue(); } this.updateResetButtonVisibility(); ]]> </constructor> <method name="clearInputFields"> <parameter name="aFromInputElement"/> <body> <![CDATA[ this.log("clearInputFields"); if (this.isDisabled() || this.isReadonly()) { return; } if (this.mMonthField && !this.mMonthField.disabled && !this.mMonthField.readOnly) { this.mMonthField.value = ""; this.mMonthField.setAttribute("typeBuffer", ""); } if (this.mDayField && !this.mDayField.disabled && !this.mDayField.readOnly) { this.mDayField.value = ""; this.mDayField.setAttribute("typeBuffer", ""); } if (this.mYearField && !this.mYearField.disabled && !this.mYearField.readOnly) { this.mYearField.value = ""; this.mYearField.setAttribute("typeBuffer", ""); } if (!aFromInputElement && this.mInputElement.value) { this.mInputElement.setUserInput(""); } this.updateResetButtonVisibility(); ]]> </body> </method> <method name="setFieldsFromInputValue"> <body> <![CDATA[ let value = this.mInputElement.value; if (!value) { this.clearInputFields(true); return; } this.log("setFieldsFromInputValue: " + value); let [year, month, day] = value.split("-"); this.setFieldValue(this.mYearField, year); this.setFieldValue(this.mMonthField, month); this.setFieldValue(this.mDayField, day); this.notifyPicker(); ]]> </body> </method> <method name="getDaysInMonth"> <parameter name="aMonth"/> <parameter name="aYear"/> <body> <![CDATA[ // Javascript's month is 0-based, so this means last day of the // previous month. return new Date(aYear, aMonth, 0).getDate(); ]]> </body> </method> <method name="isFieldInvalid"> <parameter name="aField"/> <body> <![CDATA[ if (this.isEmpty(aField.value)) { return true; } let min = Number(aField.getAttribute("min")); let max = Number(aField.getAttribute("max")); if (Number(aField.value) < min || Number(aField.value) > max) { return true; } return false; ]]> </body> </method> <method name="setInputValueFromFields"> <body> <![CDATA[ if (!this.isAnyValueAvailable(false) && this.mInputElement.value) { // Values in the input box was cleared, clear the input element's // value if not empty. this.mInputElement.setUserInput(""); return; } if (this.isFieldInvalid(this.mYearField) || this.isFieldInvalid(this.mMonthField) || this.isFieldInvalid(this.mDayField)) { // We still need to notify picker in case any of the field has // changed. If we can set input element value, then notifyPicker // will be called in setFieldsFromInputValue(). this.notifyPicker(); return; } let year = this.mYearField.value; let month = this.mMonthField.value; let day = this.mDayField.value; if (day > this.getDaysInMonth(month, year)) { // Don't set invalid date, otherwise input element's value will be // set to empty. return; } let date = [year, month, day].join("-"); if (date == this.mInputElement.value) { return; } this.log("setInputValueFromFields: " + date); this.mInputElement.setUserInput(date); ]]> </body> </method> <method name="setFieldsFromPicker"> <parameter name="aValue"/> <body> <![CDATA[ let year = aValue.year; let month = aValue.month; let day = aValue.day; if (!this.isEmpty(year)) { this.setFieldValue(this.mYearField, year); } if (!this.isEmpty(month)) { this.setFieldValue(this.mMonthField, month); } if (!this.isEmpty(day)) { this.setFieldValue(this.mDayField, day); } // Update input element's .value if needed. this.setInputValueFromFields(); ]]> </body> </method> <method name="handleKeypress"> <parameter name="aEvent"/> <body> <![CDATA[ if (this.isDisabled() || this.isReadonly()) { return; } let targetField = aEvent.originalTarget; let key = aEvent.key; if (targetField.classList.contains("numeric") && key.match(/[0-9]/)) { let buffer = targetField.getAttribute("typeBuffer") || ""; buffer = buffer.concat(key); this.setFieldValue(targetField, buffer); targetField.select(); let n = Number(buffer); let max = targetField.getAttribute("max"); if (buffer.length >= targetField.maxLength || n * 10 > max) { buffer = ""; this.advanceToNextField(); } targetField.setAttribute("typeBuffer", buffer); } ]]> </body> </method> <method name="incrementFieldValue"> <parameter name="aTargetField"/> <parameter name="aTimes"/> <body> <![CDATA[ let value; // Use current date if field is empty. if (this.isEmpty(aTargetField.value)) { let now = new Date(); if (aTargetField == this.mYearField) { value = now.getFullYear(); } else if (aTargetField == this.mMonthField) { value = now.getMonth() + 1; } else if (aTargetField == this.mDayField) { value = now.getDate(); } else { this.log("Field not supported in incrementFieldValue."); return; } } else { value = Number(aTargetField.value); } let min = Number(aTargetField.getAttribute("min")); let max = Number(aTargetField.getAttribute("max")); value += Number(aTimes); if (value > max) { value -= (max - min + 1); } else if (value < min) { value += (max - min + 1); } this.setFieldValue(aTargetField, value); aTargetField.select(); ]]> </body> </method> <method name="handleKeyboardNav"> <parameter name="aEvent"/> <body> <![CDATA[ if (this.isDisabled() || this.isReadonly()) { return; } let targetField = aEvent.originalTarget; let key = aEvent.key; // Home/End key does nothing on year field. if (targetField == this.mYearField && (key == "Home" || key == "End")) { return; } switch (key) { case "ArrowUp": this.incrementFieldValue(targetField, 1); break; case "ArrowDown": this.incrementFieldValue(targetField, -1); break; case "PageUp": { let interval = targetField.getAttribute("pginterval"); this.incrementFieldValue(targetField, interval); break; } case "PageDown": { let interval = targetField.getAttribute("pginterval"); this.incrementFieldValue(targetField, 0 - interval); break; } case "Home": let min = targetField.getAttribute("min"); this.setFieldValue(targetField, min); targetField.select(); break; case "End": let max = targetField.getAttribute("max"); this.setFieldValue(targetField, max); targetField.select(); break; } this.setInputValueFromFields(); ]]> </body> </method> <method name="getCurrentValue"> <body> <![CDATA[ let year; if (!this.isEmpty(this.mYearField.value)) { year = Number(this.mYearField.value); } let month; if (!this.isEmpty(this.mMonthField.value)) { month = Number(this.mMonthField.value); } let day; if (!this.isEmpty(this.mDayField.value)) { day = Number(this.mDayField.value); } let date = { year, month, day }; this.log("getCurrentValue: " + JSON.stringify(date)); return date; ]]> </body> </method> <method name="setFieldValue"> <parameter name="aField"/> <parameter name="aValue"/> <body> <![CDATA[ let value = Number(aValue); if (isNaN(value)) { this.log("NaN on setFieldValue!"); return; } if (aValue.length == aField.maxLength) { let min = Number(aField.getAttribute("min")); let max = Number(aField.getAttribute("max")); if (aValue < min) { value = min; } else if (aValue > max) { value = max; } } if (aField == this.mMonthField || aField == this.mDayField) { // prepend zero if (value < 10) { value = "0" + value; } } else { // prepend zeroes if (value < 10) { value = "000" + value; } else if (value < 100) { value = "00" + value; } else if (value < 1000) { value = "0" + value; } if (value.toString().length > this.mYearLength && value.toString().length <= this.mMaxYear.toString().length) { this.mYearField.size = value.toString().length; } } aField.value = value; this.updateResetButtonVisibility(); ]]> </body> </method> <method name="isAnyValueAvailable"> <parameter name="aForPicker"/> <body> <![CDATA[ return !this.isEmpty(this.mMonthField.value) || !this.isEmpty(this.mDayField.value) || !this.isEmpty(this.mYearField.value); ]]> </body> </method> </implementation> </binding> <binding id="time-input" extends="chrome://global/content/bindings/datetimebox.xml#datetime-input-base"> <resources> <stylesheet src="chrome://global/content/textbox.css"/> <stylesheet src="chrome://global/skin/textbox.css"/> <stylesheet src="chrome://global/content/bindings/datetimebox.css"/> </resources> <implementation> <constructor> <![CDATA[ // TODO: Bug 1301312 - localization for input type=time input. this.mHour12 = true; this.mAMIndicator = "AM"; this.mPMIndicator = "PM"; this.mPlaceHolder = "--"; this.mSeparatorText = ":"; this.mMillisecSeparatorText = "."; this.mMaxLength = 2; this.mMillisecMaxLength = 3; this.mDefaultStep = 60 * 1000; // in milliseconds this.mMinHourInHour12 = 1; this.mMaxHourInHour12 = 12; this.mMinMinute = 0; this.mMaxMinute = 59; this.mMinSecond = 0; this.mMaxSecond = 59; this.mMinMillisecond = 0; this.mMaxMillisecond = 999; this.mHourPageUpDownInterval = 3; this.mMinSecPageUpDownInterval = 10; this.mHourField = window.document.getAnonymousElementByAttribute(this, "anonid", "input-one"); this.mHourField.setAttribute("typeBuffer", ""); this.mMinuteField = window.document.getAnonymousElementByAttribute(this, "anonid", "input-two"); this.mMinuteField.setAttribute("typeBuffer", ""); this.mDayPeriodField = window.document.getAnonymousElementByAttribute(this, "anonid", "input-three"); this.mDayPeriodField.classList.remove("numeric"); this.mHourField.placeholder = this.mPlaceHolder; this.mMinuteField.placeholder = this.mPlaceHolder; this.mDayPeriodField.placeholder = this.mPlaceHolder; this.mHourField.setAttribute("min", this.mMinHourInHour12); this.mHourField.setAttribute("max", this.mMaxHourInHour12); this.mMinuteField.setAttribute("min", this.mMinMinute); this.mMinuteField.setAttribute("max", this.mMaxMinute); this.mMinuteSeparator = window.document.getAnonymousElementByAttribute(this, "anonid", "sep-first"); this.mMinuteSeparator.textContent = this.mSeparatorText; this.mSpaceSeparator = window.document.getAnonymousElementByAttribute(this, "anonid", "sep-second"); // space between time and am/pm field this.mSpaceSeparator.textContent = " "; this.mSecondSeparator = null; this.mSecondField = null; this.mMillisecSeparator = null; this.mMillisecField = null; if (this.mInputElement.value) { this.setFieldsFromInputValue(); } this.updateResetButtonVisibility(); ]]> </constructor> <method name="insertSeparator"> <parameter name="aSeparatorText"/> <body> <![CDATA[ let container = this.mHourField.parentNode; const HTML_NS = "http://www.w3.org/1999/xhtml"; let separator = document.createElementNS(HTML_NS, "span"); separator.textContent = aSeparatorText; separator.setAttribute("class", "datetime-separator"); container.insertBefore(separator, this.mSpaceSeparator); return separator; ]]> </body> </method> <method name="insertAdditionalField"> <parameter name="aPlaceHolder"/> <parameter name="aMin"/> <parameter name="aMax"/> <parameter name="aSize"/> <parameter name="aMaxLength"/> <body> <![CDATA[ let container = this.mHourField.parentNode; const HTML_NS = "http://www.w3.org/1999/xhtml"; let field = document.createElementNS(HTML_NS, "input"); field.classList.add("textbox-input", "datetime-input", "numeric"); field.setAttribute("size", aSize); field.setAttribute("maxlength", aMaxLength); field.setAttribute("min", aMin); field.setAttribute("max", aMax); field.setAttribute("typeBuffer", ""); field.disabled = this.mInputElement.disabled; field.readOnly = this.mInputElement.readOnly; field.tabIndex = this.mInputElement.tabIndex; field.placeholder = aPlaceHolder; container.insertBefore(field, this.mSpaceSeparator); return field; ]]> </body> </method> <method name="setFieldsFromInputValue"> <body> <![CDATA[ let value = this.mInputElement.value; if (!value) { this.clearInputFields(true); return; } this.log("setFieldsFromInputValue: " + value); let [hour, minute, second] = value.split(":"); this.setFieldValue(this.mHourField, hour); this.setFieldValue(this.mMinuteField, minute); if (this.mHour12) { this.mDayPeriodField.value = (hour >= this.mMaxHourInHour12) ? this.mPMIndicator : this.mAMIndicator; } if (!this.isEmpty(second)) { let index = second.indexOf("."); let millisecond; if (index != -1) { millisecond = second.substring(index + 1); second = second.substring(0, index); } if (!this.mSecondField) { this.mSecondSeparator = this.insertSeparator(this.mSeparatorText); this.mSecondField = this.insertAdditionalField(this.mPlaceHolder, this.mMinSecond, this.mMaxSecond, this.mMaxLength, this.mMaxLength); } this.setFieldValue(this.mSecondField, second); if (!this.isEmpty(millisecond)) { if (!this.mMillisecField) { this.mMillisecSeparator = this.insertSeparator( this.mMillisecSeparatorText); this.mMillisecField = this.insertAdditionalField( this.mPlaceHolder, this.mMinMillisecond, this.mMaxMillisecond, this.mMillisecMaxLength, this.mMillisecMaxLength); } this.setFieldValue(this.mMillisecField, millisecond); } else if (this.mMillisecField) { this.mMillisecField.remove(); this.mMillisecField = null; this.mMillisecSeparator.remove(); this.mMillisecSeparator = null; } } else { if (this.mSecondField) { this.mSecondField.remove(); this.mSecondField = null; this.mSecondSeparator.remove(); this.mSecondSeparator = null; } if (this.mMillisecField) { this.mMillisecField.remove(); this.mMillisecField = null; this.mMillisecSeparator.remove(); this.mMillisecSeparator = null; } } this.notifyPicker(); ]]> </body> </method> <method name="setInputValueFromFields"> <body> <![CDATA[ if (!this.isAnyValueAvailable(false) && this.mInputElement.value) { // Values in the input box was cleared, clear the input element's // value if not empty. this.mInputElement.setUserInput(""); return; } if (this.isEmpty(this.mHourField.value) || this.isEmpty(this.mMinuteField.value) || (this.mDayPeriodField && this.isEmpty(this.mDayPeriodField.value)) || (this.mSecondField && this.isEmpty(this.mSecondField.value)) || (this.mMillisecField && this.isEmpty(this.mMillisecField.value))) { // We still need to notify picker in case any of the field has // changed. If we can set input element value, then notifyPicker // will be called in setFieldsFromInputValue(). this.notifyPicker(); return; } let hour = Number(this.mHourField.value); if (this.mHour12) { let dayPeriod = this.mDayPeriodField.value; if (dayPeriod == this.mPMIndicator && hour < this.mMaxHourInHour12) { hour += this.mMaxHourInHour12; } else if (dayPeriod == this.mAMIndicator && hour == this.mMaxHourInHour12) { hour = 0; } } hour = (hour < 10) ? ("0" + hour) : hour; let time = hour + ":" + this.mMinuteField.value; if (this.mSecondField) { time += ":" + this.mSecondField.value; } if (this.mMillisecField) { time += "." + this.mMillisecField.value; } if (time == this.mInputElement.value) { return; } this.log("setInputValueFromFields: " + time); this.mInputElement.setUserInput(time); ]]> </body> </method> <method name="setFieldsFromPicker"> <parameter name="aValue"/> <body> <![CDATA[ let hour = aValue.hour; let minute = aValue.minute; this.log("setFieldsFromPicker: " + hour + ":" + minute); if (!this.isEmpty(hour)) { this.setFieldValue(this.mHourField, hour); if (this.mHour12) { this.mDayPeriodField.value = (hour >= this.mMaxHourInHour12) ? this.mPMIndicator : this.mAMIndicator; } } if (!this.isEmpty(minute)) { this.setFieldValue(this.mMinuteField, minute); } // Update input element's .value if needed. this.setInputValueFromFields(); ]]> </body> </method> <method name="clearInputFields"> <parameter name="aFromInputElement"/> <body> <![CDATA[ this.log("clearInputFields"); if (this.isDisabled() || this.isReadonly()) { return; } if (this.mHourField && !this.mHourField.disabled && !this.mHourField.readOnly) { this.mHourField.value = ""; this.mHourField.setAttribute("typeBuffer", ""); } if (this.mMinuteField && !this.mMinuteField.disabled && !this.mMinuteField.readOnly) { this.mMinuteField.value = ""; this.mMinuteField.setAttribute("typeBuffer", ""); } if (this.mSecondField && !this.mSecondField.disabled && !this.mSecondField.readOnly) { this.mSecondField.value = ""; this.mSecondField.setAttribute("typeBuffer", ""); } if (this.mMillisecField && !this.mMillisecField.disabled && !this.mMillisecField.readOnly) { this.mMillisecField.value = ""; this.mMillisecField.setAttribute("typeBuffer", ""); } if (this.mDayPeriodField && !this.mDayPeriodField.disabled && !this.mDayPeriodField.readOnly) { this.mDayPeriodField.value = ""; } if (!aFromInputElement && this.mInputElement.value) { this.mInputElement.setUserInput(""); } this.updateResetButtonVisibility(); ]]> </body> </method> <method name="incrementFieldValue"> <parameter name="aTargetField"/> <parameter name="aTimes"/> <body> <![CDATA[ let value; // Use current time if field is empty. if (this.isEmpty(aTargetField.value)) { let now = new Date(); if (aTargetField == this.mHourField) { value = now.getHours() % this.mMaxHourInHour12 || this.mMaxHourInHour12; } else if (aTargetField == this.mMinuteField) { value = now.getMinutes(); } else if (aTargetField == this.mSecondField) { value = now.getSeconds(); } else if (aTargetField == this.mMillisecField) { value = now.getMilliseconds(); } else { this.log("Field not supported in incrementFieldValue."); return; } } else { value = Number(aTargetField.value); } let min = aTargetField.getAttribute("min"); let max = aTargetField.getAttribute("max"); value += aTimes; if (value > max) { value -= (max - min + 1); } else if (value < min) { value += (max - min + 1); } this.setFieldValue(aTargetField, value); aTargetField.select(); ]]> </body> </method> <method name="handleKeyboardNav"> <parameter name="aEvent"/> <body> <![CDATA[ if (this.isDisabled() || this.isReadonly()) { return; } let targetField = aEvent.originalTarget; let key = aEvent.key; if (this.mDayPeriodField && targetField == this.mDayPeriodField) { // Home/End key does nothing on AM/PM field. if (key == "Home" || key == "End") { return; } this.mDayPeriodField.value = this.mDayPeriodField.value == this.mAMIndicator ? this.mPMIndicator : this.mAMIndicator; this.mDayPeriodField.select(); this.updateResetButtonVisibility(); this.setInputValueFromFields(); return; } switch (key) { case "ArrowUp": this.incrementFieldValue(targetField, 1); break; case "ArrowDown": this.incrementFieldValue(targetField, -1); break; case "PageUp": this.incrementFieldValue(targetField, targetField == this.mHourField ? this.mHourPageUpDownInterval : this.mMinSecPageUpDownInterval); break; case "PageDown": this.incrementFieldValue(targetField, targetField == this.mHourField ? (0 - this.mHourPageUpDownInterval) : (0 - this.mMinSecPageUpDownInterval)); break; case "Home": let min = targetField.getAttribute("min"); this.setFieldValue(targetField, min); targetField.select(); break; case "End": let max = targetField.getAttribute("max"); this.setFieldValue(targetField, max); targetField.select(); break; } this.setInputValueFromFields(); ]]> </body> </method> <method name="handleKeypress"> <parameter name="aEvent"/> <body> <![CDATA[ if (this.isDisabled() || this.isReadonly()) { return; } let targetField = aEvent.originalTarget; let key = aEvent.key; if (this.mDayPeriodField && targetField == this.mDayPeriodField) { if (key == "a" || key == "A") { this.mDayPeriodField.value = this.mAMIndicator; this.mDayPeriodField.select(); } else if (key == "p" || key == "P") { this.mDayPeriodField.value = this.mPMIndicator; this.mDayPeriodField.select(); } this.updateResetButtonVisibility(); return; } if (targetField.classList.contains("numeric") && key.match(/[0-9]/)) { let buffer = targetField.getAttribute("typeBuffer") || ""; buffer = buffer.concat(key); this.setFieldValue(targetField, buffer); targetField.select(); let n = Number(buffer); let max = targetField.getAttribute("max"); if (buffer.length >= targetField.maxLength || n * 10 > max) { buffer = ""; this.advanceToNextField(); } targetField.setAttribute("typeBuffer", buffer); } ]]> </body> </method> <method name="setFieldValue"> <parameter name="aField"/> <parameter name="aValue"/> <body> <![CDATA[ let value = Number(aValue); if (isNaN(value)) { this.log("NaN on setFieldValue!"); return; } if (aField.maxLength == this.mMaxLength) { // For hour, minute and second if (aField == this.mHourField && this.mHour12) { value = (value > this.mMaxHourInHour12) ? value - this.mMaxHourInHour12 : value; if (aValue == "00") { value = this.mMaxHourInHour12; } } // prepend zero if (value < 10) { value = "0" + value; } } else if (aField.maxLength == this.mMillisecMaxLength) { // prepend zeroes if (value < 10) { value = "00" + value; } else if (value < 100) { value = "0" + value; } } aField.value = value; this.updateResetButtonVisibility(); ]]> </body> </method> <method name="isAnyValueAvailable"> <parameter name="aForPicker"/> <body> <![CDATA[ let available = !this.isEmpty(this.mHourField.value) || !this.isEmpty(this.mMinuteField.value); if (available) { return true; } // Picker only cares about hour:minute. if (aForPicker) { return false; } return (this.mDayPeriodField && !this.isEmpty(this.mDayPeriodField.value)) || (this.mSecondField && !this.isEmpty(this.mSecondField.value)) || (this.mMillisecField && !this.isEmpty(this.mMillisecField.value)); ]]> </body> </method> <method name="getCurrentValue"> <body> <![CDATA[ let hour; if (!this.isEmpty(this.mHourField.value)) { hour = Number(this.mHourField.value); if (this.mHour12) { let dayPeriod = this.mDayPeriodField.value; if (dayPeriod == this.mPMIndicator && hour < this.mMaxHourInHour12) { hour += this.mMaxHourInHour12; } else if (dayPeriod == this.mAMIndicator && hour == this.mMaxHourInHour12) { hour = 0; } } } let minute; if (!this.isEmpty(this.mMinuteField.value)) { minute = Number(this.mMinuteField.value); } // Picker only needs hour/minute. let time = { hour, minute }; this.log("getCurrentValue: " + JSON.stringify(time)); return time; ]]> </body> </method> </implementation> </binding> <binding id="datetime-input-base"> <resources> <stylesheet src="chrome://global/content/textbox.css"/> <stylesheet src="chrome://global/skin/textbox.css"/> <stylesheet src="chrome://global/content/bindings/datetimebox.css"/> </resources> <content> <html:div class="datetime-input-box-wrapper" xbl:inherits="context,disabled,readonly"> <html:span class="datetime-input-edit-wrapper" anonid="edit-wrapper"> <html:input anonid="input-one" class="textbox-input datetime-input numeric" size="2" maxlength="2" xbl:inherits="disabled,readonly,tabindex"/> <html:span anonid="sep-first" class="datetime-separator"></html:span> <html:input anonid="input-two" class="textbox-input datetime-input numeric" size="2" maxlength="2" xbl:inherits="disabled,readonly,tabindex"/> <html:span anonid="sep-second" class="datetime-separator"></html:span> <html:input anonid="input-three" class="textbox-input datetime-input numeric" size="2" maxlength="2" xbl:inherits="disabled,readonly,tabindex"/> </html:span> <html:button class="datetime-reset-button" anonid="reset-button" tabindex="-1" xbl:inherits="disabled"/> </html:div> </content> <implementation implements="nsIDateTimeInputArea"> <constructor> <![CDATA[ this.DEBUG = false; this.mInputElement = this.parentNode; this.mMin = this.mInputElement.min; this.mMax = this.mInputElement.max; this.mStep = this.mInputElement.step; this.mIsPickerOpen = false; this.mResetButton = window.document.getAnonymousElementByAttribute(this, "anonid", "reset-button"); this.EVENTS.forEach((eventName) => { this.addEventListener(eventName, this, { mozSystemGroup: true }); }); // Handle keypress separately since we need to catch it on capturing. this.addEventListener("keypress", this, { capture: true, mozSystemGroup: true }); // This is to open the picker when input element is clicked (this // includes padding area). this.mInputElement.addEventListener("click", this, { mozSystemGroup: true }); ]]> </constructor> <destructor> <![CDATA[ this.EVENTS.forEach((eventName) => { this.removeEventListener(eventName, this, { mozSystemGroup: true }); }); this.removeEventListener("keypress", this, { capture: true, mozSystemGroup: true }); this.mInputElement.removeEventListener("click", this, { mozSystemGroup: true }); this.mInputElement = null; ]]> </destructor> <property name="EVENTS" readonly="true"> <getter> <![CDATA[ return ["focus", "blur", "copy", "cut", "paste", "mousedown"]; ]]> </getter> </property> <method name="log"> <parameter name="aMsg"/> <body> <![CDATA[ if (this.DEBUG) { dump("[DateTimeBox] " + aMsg + "\n"); } ]]> </body> </method> <method name="updateResetButtonVisibility"> <body> <![CDATA[ if (this.isAnyValueAvailable(false)) { this.mResetButton.style.visibility = "visible"; } else { this.mResetButton.style.visibility = "hidden"; } ]]> </body> </method> <method name="focusInnerTextBox"> <body> <![CDATA[ this.log("focusInnerTextBox"); window.document.getAnonymousElementByAttribute(this, "anonid", "input-one").focus(); ]]> </body> </method> <method name="blurInnerTextBox"> <body> <![CDATA[ this.log("blurInnerTextBox"); if (this.mLastFocusedField) { this.mLastFocusedField.blur(); } ]]> </body> </method> <method name="notifyInputElementValueChanged"> <body> <![CDATA[ this.log("inputElementValueChanged"); this.setFieldsFromInputValue(); ]]> </body> </method> <method name="setValueFromPicker"> <parameter name="aValue"/> <body> <![CDATA[ this.setFieldsFromPicker(aValue); ]]> </body> </method> <method name="advanceToNextField"> <parameter name="aReverse"/> <body> <![CDATA[ this.log("advanceToNextField"); let focusedInput = this.mLastFocusedField; let next = aReverse ? focusedInput.previousElementSibling : focusedInput.nextElementSibling; if (!next && !aReverse) { this.setInputValueFromFields(); return; } while (next) { if (next.type == "text" && !next.disabled) { next.focus(); break; } next = aReverse ? next.previousElementSibling : next.nextElementSibling; } ]]> </body> </method> <method name="setPickerState"> <parameter name="aIsOpen"/> <body> <![CDATA[ this.log("picker is now " + (aIsOpen ? "opened" : "closed")); this.mIsPickerOpen = aIsOpen; ]]> </body> </method> <method name="isEmpty"> <parameter name="aValue"/> <body> return (aValue == undefined || 0 === aValue.length); </body> </method> <method name="clearInputFields"> <body> throw Components.results.NS_ERROR_NOT_IMPLEMENTED; </body> </method> <method name="setFieldsFromInputValue"> <body> throw Components.results.NS_ERROR_NOT_IMPLEMENTED; </body> </method> <method name="setInputValueFromFields"> <body> throw Components.results.NS_ERROR_NOT_IMPLEMENTED; </body> </method> <method name="setFieldsFromPicker"> <body> throw Components.results.NS_ERROR_NOT_IMPLEMENTED; </body> </method> <method name="handleKeypress"> <body> throw Components.results.NS_ERROR_NOT_IMPLEMENTED; </body> </method> <method name="handleKeyboardNav"> <body> throw Components.results.NS_ERROR_NOT_IMPLEMENTED; </body> </method> <method name="getCurrentValue"> <body> throw Components.results.NS_ERROR_NOT_IMPLEMENTED; </body> </method> <method name="isAnyValueAvailable"> <body> throw Components.results.NS_ERROR_NOT_IMPLEMENTED; </body> </method> <method name="notifyPicker"> <body> <![CDATA[ if (this.mIsPickerOpen && this.isAnyValueAvailable(true)) { this.mInputElement.updateDateTimePicker(this.getCurrentValue()); } ]]> </body> </method> <method name="isDisabled"> <body> <![CDATA[ return this.hasAttribute("disabled"); ]]> </body> </method> <method name="isReadonly"> <body> <![CDATA[ return this.hasAttribute("readonly"); ]]> </body> </method> <method name="handleEvent"> <parameter name="aEvent"/> <body> <![CDATA[ this.log("handleEvent: " + aEvent.type); switch (aEvent.type) { case "keypress": { this.onKeyPress(aEvent); break; } case "click": { this.onClick(aEvent); break; } case "focus": { this.onFocus(aEvent); break; } case "blur": { this.onBlur(aEvent); break; } case "mousedown": { if (aEvent.originalTarget == this.mResetButton) { aEvent.preventDefault(); } break; } case "copy": case "cut": case "paste": { aEvent.preventDefault(); break; } default: break; } ]]> </body> </method> <method name="onFocus"> <parameter name="aEvent"/> <body> <![CDATA[ this.log("onFocus originalTarget: " + aEvent.originalTarget); let target = aEvent.originalTarget; if ((target instanceof HTMLInputElement) && target.type == "text") { this.mLastFocusedField = target; target.select(); } ]]> </body> </method> <method name="onBlur"> <parameter name="aEvent"/> <body> <![CDATA[ this.log("onBlur originalTarget: " + aEvent.originalTarget + " target: " + aEvent.target); let target = aEvent.originalTarget; target.setAttribute("typeBuffer", ""); this.setInputValueFromFields(); ]]> </body> </method> <method name="onKeyPress"> <parameter name="aEvent"/> <body> <![CDATA[ this.log("onKeyPress key: " + aEvent.key); switch (aEvent.key) { // Close picker on Enter, Escape or Space key. case "Enter": case "Escape": case " ": { if (this.mIsPickerOpen) { this.mInputElement.closeDateTimePicker(); aEvent.preventDefault(); } break; } case "Backspace": { let targetField = aEvent.originalTarget; targetField.value = ""; targetField.setAttribute("typeBuffer", ""); this.updateResetButtonVisibility(); this.setInputValueFromFields(); aEvent.preventDefault(); break; } case "ArrowRight": case "ArrowLeft": { this.advanceToNextField(aEvent.key == "ArrowRight" ? false : true); aEvent.preventDefault(); break; } case "ArrowUp": case "ArrowDown": case "PageUp": case "PageDown": case "Home": case "End": { this.handleKeyboardNav(aEvent); aEvent.preventDefault(); break; } default: { // printable characters if (aEvent.keyCode == 0 && !(aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey)) { this.handleKeypress(aEvent); aEvent.preventDefault(); } break; } } ]]> </body> </method> <method name="onClick"> <parameter name="aEvent"/> <body> <![CDATA[ this.log("onClick originalTarget: " + aEvent.originalTarget + " target: " + aEvent.target); if (aEvent.defaultPrevented || this.isDisabled() || this.isReadonly()) { return; } if (aEvent.originalTarget == this.mResetButton) { this.clearInputFields(false); } else if (!this.mIsPickerOpen) { this.mInputElement.openDateTimePicker(this.getCurrentValue()); } ]]> </body> </method> </implementation> </binding> </bindings>