Backed out changeset d3c9e777a050 (bug 1676068) Backed out changeset 639c9661c850 (bug 1676068) Backed out changeset d06b6aa3b9a3 (bug 1676068) Backed out changeset 50bb7e9c6bcf (bug 1676068) Backed out changeset 234acd14548e (bug 1676068) Backed out changeset 04050cfd5e3f (bug 1676068) Backed out changeset a06081c85646 (bug 1676068)
451 lines
12 KiB
JavaScript
451 lines
12 KiB
JavaScript
/* 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/. */
|
|
|
|
/* import-globals-from datekeeper.js */
|
|
/* import-globals-from calendar.js */
|
|
/* import-globals-from spinner.js */
|
|
|
|
"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,
|
|
dir,
|
|
} = this.props;
|
|
const dateKeeper = new DateKeeper({
|
|
year,
|
|
month,
|
|
day,
|
|
min,
|
|
max,
|
|
step,
|
|
stepBase,
|
|
firstDayOfWeek,
|
|
weekends,
|
|
calViewSize: CAL_VIEW_SIZE,
|
|
});
|
|
|
|
document.dir = dir;
|
|
|
|
this.state = {
|
|
dateKeeper,
|
|
locale,
|
|
isMonthPickerVisible: false,
|
|
datetimeOrders: new Intl.DateTimeFormat(locale)
|
|
.formatToParts(new Date(0))
|
|
.map(part => part.type),
|
|
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,
|
|
datetimeOrders: this.state.datetimeOrders,
|
|
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.buttonPrev) {
|
|
event.target.classList.add("active");
|
|
this.state.dateKeeper.setMonthByOffset(-1);
|
|
this._update();
|
|
} else if (event.target == this.context.buttonNext) {
|
|
event.target.classList.add("active");
|
|
this.state.dateKeeper.setMonthByOffset(1);
|
|
this._update();
|
|
}
|
|
break;
|
|
}
|
|
case "mouseup": {
|
|
if (
|
|
event.target == this.context.buttonPrev ||
|
|
event.target == this.context.buttonNext
|
|
) {
|
|
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 }) {
|
|
if (!this.state) {
|
|
return;
|
|
}
|
|
|
|
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
|
|
* {Array<String>} datetimeOrders
|
|
* }
|
|
* @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;
|
|
const spinnerOrder =
|
|
options.datetimeOrders.indexOf("month") <
|
|
options.datetimeOrders.indexOf("year")
|
|
? "order-month-year"
|
|
: "order-year-month";
|
|
|
|
context.monthYearView.classList.add(spinnerOrder);
|
|
|
|
this.context = context;
|
|
this.state = { dateFormat };
|
|
this.props = {};
|
|
this.components = {
|
|
month: new Spinner(
|
|
{
|
|
id: "spinner-month",
|
|
setValue: month => {
|
|
this.state.isMonthSet = true;
|
|
options.setMonth(month);
|
|
},
|
|
getDisplayString: options.getMonthString,
|
|
viewportSize: spinnerSize,
|
|
},
|
|
context.monthYearView
|
|
),
|
|
year: new Spinner(
|
|
{
|
|
id: "spinner-year",
|
|
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);
|
|
},
|
|
};
|
|
}
|