--- a/toolkit/content/widgets/datetimebox.xml
+++ b/toolkit/content/widgets/datetimebox.xml
@@ -701,18 +701,18 @@
(this.isEmpty(millisecond) && this.mMillisecField)) {
this.log("Edit fields need to be rebuilt.")
this.reBuildEditFields();
}
this.setFieldValue(this.mHourField, hour);
this.setFieldValue(this.mMinuteField, minute);
if (this.mHour12) {
- this.mDayPeriodField.value = (hour >= this.mMaxHour) ?
- this.mPMIndicator : this.mAMIndicator;
+ this.setDayPeriodValue(hour >= this.mMaxHour ? this.mPMIndicator
+ : this.mAMIndicator);
}
if (!this.isEmpty(second)) {
this.setFieldValue(this.mSecondField, second);
}
if (!this.isEmpty(millisecond)) {
this.setFieldValue(this.mMillisecField, millisecond);
}
@@ -737,36 +737,44 @@
(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);
+ let { hour, minute, second, millisecond } = this.getCurrentValue();
+
+ // Convert to a valid time string according to:
+ // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-time-string
if (this.mHour12) {
- let dayPeriod = this.mDayPeriodField.value;
+ let dayPeriod = this.getDayPeriodValue();
if (dayPeriod == this.mPMIndicator && hour < this.mMaxHour) {
hour += this.mMaxHour;
} else if (dayPeriod == this.mAMIndicator &&
hour == this.mMaxHour) {
hour = 0;
}
}
hour = (hour < 10) ? ("0" + hour) : hour;
+ minute = (minute < 10) ? ("0" + minute) : minute;
- let time = hour + ":" + this.mMinuteField.value;
+ let time = hour + ":" + minute;
if (this.mSecondField) {
- time += ":" + this.mSecondField.value;
+ second = (second < 10) ? ("0" + second) : second;
+ time += ":" + second;
}
if (this.mMillisecField) {
- time += "." + this.mMillisecField.value;
+ // Convert milliseconds to fraction of second.
+ millisecond = millisecond.toString().padStart(
+ this.mMillisecMaxLength, "0");
+ time += "." + millisecond;
}
this.log("setInputValueFromFields: " + time);
this.mInputElement.setUserInput(time);
]]>
</body>
</method>
@@ -776,19 +784,18 @@
<![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.mMaxHour) ? this.mPMIndicator
- : this.mAMIndicator;
+ this.setDayPeriodValue(hour >= this.mMaxHour ? this.mPMIndicator
+ : this.mAMIndicator);
}
}
if (!this.isEmpty(minute)) {
this.setFieldValue(this.mMinuteField, minute);
}
]]>
</body>
@@ -801,61 +808,55 @@
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", "");
+ this.clearFieldValue(this.mHourField);
}
if (this.mMinuteField && !this.mMinuteField.disabled &&
!this.mMinuteField.readOnly) {
- this.mMinuteField.value = "";
- this.mMinuteField.setAttribute("typeBuffer", "");
+ this.clearFieldValue(this.mMinuteField);
}
if (this.mSecondField && !this.mSecondField.disabled &&
!this.mSecondField.readOnly) {
- this.mSecondField.value = "";
- this.mSecondField.setAttribute("typeBuffer", "");
+ this.clearFieldValue(this.mSecondField);
}
if (this.mMillisecField && !this.mMillisecField.disabled &&
!this.mMillisecField.readOnly) {
- this.mMillisecField.value = "";
- this.mMillisecField.setAttribute("typeBuffer", "");
+ this.clearFieldValue(this.mMillisecField);
}
if (this.mDayPeriodField && !this.mDayPeriodField.disabled &&
!this.mDayPeriodField.readOnly) {
- this.mDayPeriodField.value = "";
+ this.clearFieldValue(this.mDayPeriodField);
}
if (!aFromInputElement) {
this.mInputElement.setUserInput("");
}
-
- this.updateResetButtonVisibility();
]]>
</body>
</method>
<method name="incrementFieldValue">
<parameter name="aTargetField"/>
<parameter name="aTimes"/>
<body>
<![CDATA[
- let value;
+ let value = this.getFieldValue(aTargetField);
// Use current time if field is empty.
- if (this.isEmpty(aTargetField.value)) {
+ if (this.isEmpty(value)) {
let now = new Date();
if (aTargetField == this.mHourField) {
value = now.getHours();
if (this.mHour12) {
value = (value % this.mMaxHour) || this.mMaxHour;
}
} else if (aTargetField == this.mMinuteField) {
@@ -863,31 +864,29 @@
} 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 += 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[
@@ -900,21 +899,19 @@
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.setDayPeriodValue(
+ this.getDayPeriodValue() == this.mAMIndicator ? this.mPMIndicator
+ : this.mAMIndicator);
this.setInputValueFromFields();
return;
}
switch (key) {
case "ArrowUp":
this.incrementFieldValue(targetField, 1);
break;
@@ -929,22 +926,20 @@
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="handleKeypress">
@@ -956,32 +951,28 @@
}
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();
+ this.setDayPeriodValue(this.mAMIndicator);
} else if (key == "p" || key == "P") {
- this.mDayPeriodField.value = this.mPMIndicator;
- this.mDayPeriodField.select();
+ this.setDayPeriodValue(this.mPMIndicator);
}
- 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);
@@ -990,102 +981,135 @@
</body>
</method>
<method name="setFieldValue">
<parameter name="aField"/>
<parameter name="aValue"/>
<body>
<![CDATA[
+ if (!aField || !aField.classList.contains("numeric")) {
+ return;
+ }
+
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) {
- if (this.mHour12) {
- // Try to change to 12hr format if user input is 0 or greater
- // than 12.
- if (value == 0 && aValue.length == aField.maxLength) {
- value = this.mMaxHour;
- } else {
- value = (value > this.mMaxHour) ? value % this.mMaxHour : value;
- }
- } else if (value > this.mMaxHour) {
+ if (aField == this.mHourField) {
+ if (this.mHour12) {
+ // Try to change to 12hr format if user input is 0 or greater
+ // than 12.
+ if (value == 0 && aValue.length == aField.maxLength) {
value = this.mMaxHour;
+ } else {
+ value = (value > this.mMaxHour) ? value % this.mMaxHour : value;
}
- }
- // 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;
+ } else if (value > this.mMaxHour) {
+ value = this.mMaxHour;
}
}
- aField.value = value;
+ aField.setAttribute("rawValue", value);
+
+ let minDigits = aField.size;
+ let formatted = value.toLocaleString(this.mLocales, {
+ minimumIntegerDigits: minDigits,
+ useGrouping: false
+ });
+
+ aField.value = formatted;
+ if (document.activeElement == this.mInputElement &&
+ this.mLastFocusedField && this.mLastFocusedField == aField) {
+ aField.select();
+ }
+ this.updateResetButtonVisibility();
+ ]]>
+ </body>
+ </method>
+
+ <method name="getDayPeriodValue">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ if (!this.mDayPeriodField) {
+ return "";
+ }
+
+ return this.mDayPeriodField.value;
+ ]]>
+ </body>
+ </method>
+
+ <method name="setDayPeriodValue">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ if (!this.mDayPeriodField) {
+ return;
+ }
+
+ this.mDayPeriodField.value = aValue;
+ if (document.activeElement == this.mInputElement &&
+ this.mLastFocusedField &&
+ this.mLastFocusedField == this.mDayPeriodField) {
+ this.mDayPeriodField.select();
+ }
this.updateResetButtonVisibility();
]]>
</body>
</method>
<method name="isAnyValueAvailable">
<parameter name="aForPicker"/>
<body>
<![CDATA[
- let available = !this.isEmpty(this.mHourField.value) ||
- !this.isEmpty(this.mMinuteField.value);
+ let { hour, minute, second, millisecond } = this.getCurrentValue();
+ let dayPeriod = this.getDayPeriodValue();
+ let available = !this.isEmpty(hour) || !this.isEmpty(minute);
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));
+ return (this.mDayPeriodField && !this.isEmpty(dayPeriod)) ||
+ (this.mSecondField && !this.isEmpty(second)) ||
+ (this.mMillisecField && !this.isEmpty(millisecond));
]]>
</body>
</method>
<method name="getCurrentValue">
<body>
<![CDATA[
- let hour;
- if (!this.isEmpty(this.mHourField.value)) {
- hour = Number(this.mHourField.value);
+ let hour = this.getFieldValue(this.mHourField);
+ if (!this.isEmpty(hour)) {
if (this.mHour12) {
- let dayPeriod = this.mDayPeriodField.value;
+ let dayPeriod = this.getDayPeriodValue();
if (dayPeriod == this.mPMIndicator && hour < this.mMaxHour) {
hour += this.mMaxHour;
} else if (dayPeriod == this.mAMIndicator &&
hour == this.mMaxHour) {
hour = 0;
}
}
- }
-
- let minute;
- if (!this.isEmpty(this.mMinuteField.value)) {
- minute = Number(this.mMinuteField.value);
}
- // Picker only needs hour/minute.
- let time = { hour, minute };
+ let minute = this.getFieldValue(this.mMinuteField);
+ let second = this.getFieldValue(this.mSecondField);
+ let millisecond = this.getFieldValue(this.mMillisecField);
+
+ let time = { hour, minute, second, millisecond };
this.log("getCurrentValue: " + JSON.stringify(time));
return time;
]]>
</body>
</method>
</implementation>
</binding>
@@ -1185,23 +1209,31 @@
field.classList.add("textbox-input", "datetime-input");
field.disabled = this.mInputElement.disabled;
field.readOnly = this.mInputElement.readOnly;
field.tabIndex = this.mInputElement.tabIndex;
field.placeholder = aPlaceHolder;
field.setAttribute("size", aLength);
field.setAttribute("maxlength", aLength);
- field.setAttribute("typeBuffer", "");
if (aIsNumeric) {
field.classList.add("numeric");
+ // Maximum value allowed.
field.setAttribute("min", aMin);
+ // Minumim value allowed.
field.setAttribute("max", aMax);
+ // Interval when pressing pageUp/pageDown key.
field.setAttribute("pginterval", aPageUpDownInterval);
+ // Used to store what the user has already typed in the field,
+ // cleared when value is cleared and when field is blurred.
+ field.setAttribute("typeBuffer", "");
+ // Used to store the non-formatted number, clered when value is
+ // cleared.
+ field.setAttribute("rawValue", "");
}
return field;
]]>
</body>
</method>
<method name="updateResetButtonVisibility">
@@ -1362,16 +1394,51 @@
let str = aString.replace(
/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g, "");
return str;
]]>
</body>
</method>
+ <method name="getFieldValue">
+ <parameter name="aField"/>
+ <body>
+ <![CDATA[
+ if (!aField || !aField.classList.contains("numeric")) {
+ return undefined;
+ }
+
+ let value = aField.getAttribute("rawValue");
+ // Avoid returning 0 when field is empty.
+ return (this.isEmpty(value) ? undefined : Number(value));
+ ]]>
+ </body>
+ </method>
+
+ <method name="clearFieldValue">
+ <parameter name="aField"/>
+ <body>
+ <![CDATA[
+ aField.value = "";
+ if (aField.classList.contains("numeric")) {
+ aField.setAttribute("typeBuffer", "");
+ aField.setAttribute("rawValue", "");
+ }
+ this.updateResetButtonVisibility();
+ ]]>
+ </body>
+ </method>
+
+ <method name="setFieldValue">
+ <body>
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ </body>
+ </method>
+
<method name="clearInputFields">
<body>
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
</body>
</method>
<method name="setFieldsFromInputValue">
<body>
@@ -1516,19 +1583,17 @@
case "Enter":
case " ": {
this.mInputElement.closeDateTimePicker();
aEvent.preventDefault();
break;
}
case "Backspace": {
let targetField = aEvent.originalTarget;
- targetField.value = "";
- targetField.setAttribute("typeBuffer", "");
- this.updateResetButtonVisibility();
+ this.clearFieldValue(targetField);
this.setInputValueFromFields();
aEvent.preventDefault();
break;
}
case "ArrowRight":
case "ArrowLeft": {
this.advanceToNextField(aEvent.key == "ArrowRight" ? false : true);
aEvent.preventDefault();