Backed out changeset ee747c27b9bc (bug 1676068) Backed out changeset 0876924c66e8 (bug 1676068) Backed out changeset 30a8292a37ae (bug 1676068) Backed out changeset 19a0b7a0987c (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);
|
|
},
|
|
};
|
|
}
|