blob: 8796694922b5ff7f55cdabaff6311911424b57ea [file] [log] [blame]
"use strict";
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// FIXME:
// - Touch event
/**
* CSS class names.
*
* @enum {string}
*/
var ClassNames = {
Available: "available",
CancelButton: "cancel-button",
ClearButton: "clear-button",
Day: "day",
DayLabel: "day-label",
DayLabelContainer: "day-label-container",
DaysArea: "days-area",
DaysAreaContainer: "days-area-container",
Monday: "monday",
MonthMode: "month-mode",
MonthSelector: "month-selector",
MonthSelectorBox: "month-selector-box",
MonthSelectorPopup: "month-selector-popup",
MonthSelectorPopupContents: "month-selector-popup-contents",
MonthSelectorPopupEntry: "month-selector-popup-entry",
MonthSelectorWall: "month-selector-wall",
NoFocusRing: "no-focus-ring",
NotThisMonth: "not-this-month",
Selected: "day-selected",
SelectedMonthYear: "selected-month-year",
Sunday: "sunday",
TodayButton: "today-button",
TodayClearArea: "today-clear-area",
Unavailable: "unavailable",
WeekContainer: "week-container",
WeekColumn: "week-column",
WeekMode: "week-mode",
YearMonthArea: "year-month-area",
YearMonthButton: "year-month-button",
YearMonthButtonLeft: "year-month-button-left",
YearMonthButtonRight: "year-month-button-right",
YearMonthUpper: "year-month-upper"
};
/**
* @type {Object}
*/
var global = {
argumentsReceived: false,
params: null,
picker: null
};
// ----------------------------------------------------------------
// Utility functions
/**
* @return {!string} lowercase locale name. e.g. "en-us"
*/
function getLocale() {
return (global.params.locale || "en-us").toLowerCase();
}
/**
* @return {!string} lowercase language code. e.g. "en"
*/
function getLanguage() {
var locale = getLocale();
var result = locale.match(/^([a-z]+)/);
if (!result)
return "en";
return result[1];
}
/**
* @param {!number} number
* @return {!string}
*/
function localizeNumber(number) {
return window.pagePopupController.localizeNumberString(number);
}
/**
* @const
* @type {number}
*/
var ImperialEraLimit = 2087;
/**
* @param {!number} year
* @param {!number} month
* @return {!string}
*/
function formatJapaneseImperialEra(year, month) {
// We don't show an imperial era if it is greater than 99 becase of space
// limitation.
if (year > ImperialEraLimit)
return "";
if (year > 1989)
return "(平成" + localizeNumber(year - 1988) + "年)";
if (year == 1989)
return "(平成元年)";
if (year >= 1927)
return "(昭和" + localizeNumber(year - 1925) + "年)";
if (year > 1912)
return "(大正" + localizeNumber(year - 1911) + "年)";
if (year == 1912 && month >= 7)
return "(大正元年)";
if (year > 1868)
return "(明治" + localizeNumber(year - 1867) + "年)";
if (year == 1868)
return "(明治元年)";
return "";
}
/**
* @return {!string}
*/
Month.prototype.toLocaleString = function() {
if (isNaN(this.year) || isNaN(this.year))
return "Invalid Month";
if (getLanguage() == "ja")
return "" + this.year + "年" + formatJapaneseImperialEra(this.year, this.month) + " " + (this.month + 1) + "月";
return window.pagePopupController.formatMonth(this.year, this.month);
};
function createUTCDate(year, month, date) {
var newDate = new Date(0);
newDate.setUTCFullYear(year);
newDate.setUTCMonth(month);
newDate.setUTCDate(date);
return newDate;
};
/**
* @param {string} dateString
* @return {?Day|Week|Month}
*/
function parseDateString(dateString) {
var month = Month.parse(dateString);
if (month)
return month;
var week = Week.parse(dateString);
if (week)
return week;
return Day.parse(dateString);
}
/**
* @constructor
* @param {!number|Day} valueOrDayOrYear
* @param {!number=} month
* @param {!number=} date
*/
function Day(valueOrDayOrYear, month, date) {
var dateObject;
if (arguments.length == 3)
dateObject = createUTCDate(valueOrDayOrYear, month, date);
else if (valueOrDayOrYear instanceof Day)
dateObject = createUTCDate(valueOrDayOrYear.year, valueOrDayOrYear.month, valueOrDayOrYear.date);
else
dateObject = new Date(valueOrDayOrYear);
this.year = dateObject.getUTCFullYear();
this.month = dateObject.getUTCMonth();
this.date = dateObject.getUTCDate();
};
Day.ISOStringRegExp = /^(\d+)-(\d+)-(\d+)/;
/**
* @param {!string} str
* @return {?Month}
*/
Day.parse = function(str) {
var match = Day.ISOStringRegExp.exec(str);
if (!match)
return null;
var year = parseInt(match[1], 10);
var month = parseInt(match[2], 10) - 1;
var date = parseInt(match[3], 10);
return new Day(year, month, date);
};
/**
* @param {!Date} date
* @return {!Day}
*/
Day.createFromDate = function(date) {
return new Day(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
};
/**
* @return {!Day}
*/
Day.createFromToday = function() {
var now = new Date();
return new Day(now.getFullYear(), now.getMonth(), now.getDate());
};
/**
* @param {!Day} other
* @return {!bool}
*/
Day.prototype.equals = function(other) {
return this.year === other.year && this.month === other.month && this.date === other.date;
};
/**
* @return {!Day}
*/
Day.prototype.previous = function() {
return new Day(this.year, this.month, this.date - 1);
};
/**
* @return {!Day}
*/
Day.prototype.next = function() {
return new Day(this.year, this.month, this.date + 1);
};
/**
* @return {!Date}
*/
Day.prototype.startDate = function() {
return createUTCDate(this.year, this.month, this.date);
};
/**
* @return {!Date}
*/
Day.prototype.endDate = function() {
return createUTCDate(this.year, this.month, this.date + 1);
};
/**
* @return {!number}
*/
Day.prototype.valueOf = function() {
return this.startDate().getTime();
};
/**
* @return {!string}
*/
Day.prototype.toString = function() {
var yearString = String(this.year);
if (yearString.length < 4)
yearString = ("000" + yearString).substr(-4, 4);
return yearString + "-" + ("0" + (this.month + 1)).substr(-2, 2) + "-" + ("0" + this.date).substr(-2, 2);
};
// See WebCore/platform/DateComponents.h.
Day.Minimum = new Day(-62135596800000.0);
Day.Maximum = new Day(8640000000000000.0);
// See WebCore/html/DayInputType.cpp.
Day.DefaultStep = 86400000;
Day.DefaultStepBase = 0;
/**
* @constructor
* @param {!number|Week} valueOrWeekOrYear
* @param {!number=} week
*/
function Week(valueOrWeekOrYear, week) {
if (arguments.length === 2) {
this.year = valueOrWeekOrYear;
this.week = week;
// Number of years per year is either 52 or 53.
if (this.week < 1 || (this.week > 52 && this.week > Week.numberOfWeeksInYear(this.year))) {
var normalizedWeek = Week.createFromDate(this.startDate());
this.year = normalizedWeek.year;
this.week = normalizedWeek.week;
}
} else if (valueOrWeekOrYear instanceof Week) {
this.year = valueOrWeekOrYear.year;
this.week = valueOrWeekOrYear.week;
} else {
var week = Week.createFromDate(new Date(valueOrWeekOrYear));
this.year = week.year;
this.week = week.week;
}
}
Week.MillisecondsPerWeek = 7 * 24 * 60 * 60 * 1000;
Week.ISOStringRegExp = /^(\d+)-[wW](\d+)$/;
// See WebCore/platform/DateComponents.h.
Week.Minimum = new Week(1, 1);
Week.Maximum = new Week(275760, 37);
// See WebCore/html/WeekInputType.cpp.
Week.DefaultStep = 604800000;
Week.DefaultStepBase = -259200000;
/**
* @param {!string} str
* @return {?Week}
*/
Week.parse = function(str) {
var match = Week.ISOStringRegExp.exec(str);
if (!match)
return null;
var year = parseInt(match[1], 10);
var week = parseInt(match[2], 10);
return new Week(year, week);
};
/**
* @param {!Date} date
* @return {!Week}
*/
Week.createFromDate = function(date) {
var year = date.getUTCFullYear();
if (year <= Week.Maximum.year && Week.weekOneStartDateForYear(year + 1).getTime() <= date.getTime())
year++;
else if (year > 1 && Week.weekOneStartDateForYear(year).getTime() > date.getTime())
year--;
var week = 1 + Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), date);
return new Week(year, week);
};
/**
* @return {!Week}
*/
Week.createFromToday = function() {
var now = new Date();
return Week.createFromDate(createUTCDate(now.getFullYear(), now.getMonth(), now.getDate()));
};
/**
* @param {!number} year
* @return {!Date}
*/
Week.weekOneStartDateForYear = function(year) {
if (year < 1)
return createUTCDate(1, 0, 1);
// The week containing January 4th is week one.
var yearStartDay = createUTCDate(year, 0, 4).getUTCDay();
return createUTCDate(year, 0, 4 - (yearStartDay + 6) % 7);
};
/**
* @param {!number} year
* @return {!number}
*/
Week.numberOfWeeksInYear = function(year) {
if (year < 1 || year > Week.Maximum.year)
return 0;
else if (year === Week.Maximum.year)
return Week.Maximum.week;
return Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), Week.weekOneStartDateForYear(year + 1));
};
/**
* @param {!Date} baseDate
* @param {!Date} date
* @return {!number}
*/
Week._numberOfWeeksSinceDate = function(baseDate, date) {
return Math.floor((date.getTime() - baseDate.getTime()) / Week.MillisecondsPerWeek);
};
/**
* @param {!Week} other
* @return {!bool}
*/
Week.prototype.equals = function(other) {
return this.year === other.year && this.week === other.week;
};
/**
* @return {!Week}
*/
Week.prototype.previous = function() {
return new Week(this.year, this.week - 1);
};
/**
* @return {!Week}
*/
Week.prototype.next = function() {
return new Week(this.year, this.week + 1);
};
/**
* @return {!Date}
*/
Week.prototype.startDate = function() {
var weekStartDate = Week.weekOneStartDateForYear(this.year);
weekStartDate.setUTCDate(weekStartDate.getUTCDate() + (this.week - 1) * 7);
return weekStartDate;
};
/**
* @return {!Date}
*/
Week.prototype.endDate = function() {
if (this.equals(Week.Maximum))
return Day.Maximum.startDate();
return this.next().startDate();
};
/**
* @return {!number}
*/
Week.prototype.valueOf = function() {
return this.startDate().getTime() - createUTCDate(1970, 0, 1).getTime();
};
/**
* @return {!string}
*/
Week.prototype.toString = function() {
var yearString = String(this.year);
if (yearString.length < 4)
yearString = ("000" + yearString).substr(-4, 4);
return yearString + "-W" + ("0" + this.week).substr(-2, 2);
};
/**
* @constructor
* @param {!number|Month} valueOrMonthOrYear
* @param {!number=} month
*/
function Month(valueOrMonthOrYear, month) {
if (arguments.length == 2) {
this.year = valueOrMonthOrYear;
this.month = month;
} else if (valueOrMonthOrYear instanceof Month) {
this.year = valueOrMonthOrYear.year;
this.month = valueOrMonthOrYear.month;
} else {
this.year = 1970;
this.month = valueOrMonthOrYear;
}
this.year = this.year + Math.floor(this.month / 12);
this.month = this.month < 0 ? this.month % 12 + 12 : this.month % 12;
if (this.year <= 0 || Month.Maximum < this) {
this.year = NaN;
this.month = NaN;
}
};
Month.ISOStringRegExp = /^(\d+)-(\d+)$/;
// See WebCore/platform/DateComponents.h.
Month.Minimum = new Month(1, 0);
Month.Maximum = new Month(275760, 8);
// See WebCore/html/MonthInputType.cpp.
Month.DefaultStep = 1;
Month.DefaultStepBase = 0;
/**
* @param {!string} str
* @return {?Month}
*/
Month.parse = function(str) {
var match = Month.ISOStringRegExp.exec(str);
if (!match)
return null;
var year = parseInt(match[1], 10);
var month = parseInt(match[2], 10) - 1;
return new Month(year, month);
};
/**
* @param {!Date} date
* @return {!Month}
*/
Month.createFromDate = function(date) {
return new Month(date.getUTCFullYear(), date.getUTCMonth());
};
/**
* @return {!Month}
*/
Month.createFromToday = function() {
var now = new Date();
return new Month(now.getFullYear(), now.getMonth());
};
/**
* @param {!Month} other
* @return {!bool}
*/
Month.prototype.equals = function(other) {
return this.year === other.year && this.month === other.month;
};
/**
* @return {!Month}
*/
Month.prototype.previous = function() {
return new Month(this.year, this.month - 1);
};
/**
* @return {!Month}
*/
Month.prototype.next = function() {
return new Month(this.year, this.month + 1);
};
/**
* @return {!Date}
*/
Month.prototype.startDate = function() {
return createUTCDate(this.year, this.month, 1);
};
/**
* @return {!Date}
*/
Month.prototype.endDate = function() {
if (this.equals(Month.Maximum))
return Day.Maximum.startDate();
return this.next().startDate();
};
/**
* @return {!number}
*/
Month.prototype.valueOf = function() {
return (this.year - 1970) * 12 + this.month;
};
/**
* @return {!string}
*/
Month.prototype.toString = function() {
var yearString = String(this.year);
if (yearString.length < 4)
yearString = ("000" + yearString).substr(-4, 4);
return yearString + "-" + ("0" + (this.month + 1)).substr(-2, 2);
};
// ----------------------------------------------------------------
// Initialization
/**
* @param {Event} event
*/
function handleMessage(event) {
if (global.argumentsReceived)
return;
global.argumentsReceived = true;
initialize(JSON.parse(event.data));
}
function handleArgumentsTimeout() {
if (global.argumentsReceived)
return;
var args = {
dayLabels : ["d1", "d2", "d3", "d4", "d5", "d6", "d7"],
todayLabel : "Today",
clearLabel : "Clear",
cancelLabel : "Cancel",
currentValue : "",
weekStartDay : 0,
step : CalendarPicker.DefaultStepScaleFactor,
stepBase: CalendarPicker.DefaultStepBase
};
initialize(args);
}
/**
* @param {!Object} config
* @return {?string} An error message, or null if the argument has no errors.
*/
CalendarPicker.validateConfig = function(config) {
if (!config.dayLabels)
return "No dayLabels.";
if (config.dayLabels.length != 7)
return "dayLabels is not an array with 7 elements.";
if (!config.clearLabel)
return "No clearLabel.";
if (!config.todayLabel)
return "No todayLabel.";
if (config.weekStartDay) {
if (config.weekStartDay < 0 || config.weekStartDay > 6)
return "Invalid weekStartDay: " + config.weekStartDay;
}
return null;
}
/**
* @param {!Object} args
*/
function initialize(args) {
global.params = args;
var errorString = CalendarPicker.validateConfig(args);
if (args.suggestionValues)
errorString = errorString || SuggestionPicker.validateConfig(args)
if (errorString) {
var main = $("main");
main.textContent = "Internal error: " + errorString;
resizeWindow(main.offsetWidth, main.offsetHeight);
} else {
if (global.params.suggestionValues && global.params.suggestionValues.length)
openSuggestionPicker();
else
openCalendarPicker();
}
}
function closePicker() {
if (global.picker)
global.picker.cleanup();
var main = $("main");
main.innerHTML = "";
main.className = "";
};
function openSuggestionPicker() {
closePicker();
global.picker = new SuggestionPicker($("main"), global.params);
};
function openCalendarPicker() {
closePicker();
global.picker = new CalendarPicker($("main"), global.params);
};
/**
* @constructor
* @param {!Element} element
* @param {!Object} config
*/
function CalendarPicker(element, config) {
Picker.call(this, element, config);
if (this._config.mode === "month") {
this.selectionConstructor = Month;
this._daysTable = new MonthPickerDaysTable(this);
this._element.classList.add(ClassNames.MonthMode);
} else if (this._config.mode === "week") {
this.selectionConstructor = Week;
this._daysTable = new WeekPickerDaysTable(this);
this._element.classList.add(ClassNames.WeekMode);
} else {
this.selectionConstructor = Day;
this._daysTable = new DaysTable(this);
}
this._element.classList.add("calendar-picker");
this._element.classList.add("preparing");
this._handleWindowResizeBound = this._handleWindowResize.bind(this);
window.addEventListener("resize", this._handleWindowResizeBound, false);
// We assume this._config.min/max are valid dates or months.
var minimum = (typeof this._config.min !== "undefined") ? parseDateString(this._config.min) : this.selectionConstructor.Minimum;
var maximum = (typeof this._config.max !== "undefined") ? parseDateString(this._config.max) : this.selectionConstructor.Maximum;
this._minimumValue = minimum.valueOf();
this._maximumValue = maximum.valueOf();
this.step = (typeof this._config.step !== undefined) ? Number(this._config.step) : this.selectionConstructor.DefaultStep;
this.stepBase = (typeof this._config.stepBase !== "undefined") ? Number(this._config.stepBase) : this.selectionConstructor.DefaultStepBase;
this._minimumMonth = Month.createFromDate(minimum.startDate());
this.maximumMonth = Month.createFromDate(maximum.startDate());
this._currentMonth = new Month(NaN, NaN);
this._yearMonthController = new YearMonthController(this);
this._hadKeyEvent = false;
this._layout();
var initialSelection = parseDateString(this._config.currentValue);
if (!initialSelection)
initialSelection = this.selectionConstructor.createFromToday();
if (initialSelection.valueOf() < this._minimumValue)
initialSelection = new this.selectionConstructor(this._minimumValue);
else if (initialSelection.valueOf() > this._maximumValue)
initialSelection = new this.selectionConstructor(this._maximumValue);
this.showMonth(Month.createFromDate(initialSelection.startDate()));
this._daysTable.selectRangeAndShowEntireRange(initialSelection);
this.fixWindowSize();
this._handleBodyKeyDownBound = this._handleBodyKeyDown.bind(this);
document.body.addEventListener("keydown", this._handleBodyKeyDownBound, false);
}
CalendarPicker.prototype = Object.create(Picker.prototype);
CalendarPicker.NavigationBehaviour = {
None: 0,
Animate: 1 << 0,
KeepSelectionPosition: 1 << 1
};
CalendarPicker.prototype._handleWindowResize = function() {
this._element.classList.remove("preparing");
};
CalendarPicker.prototype.cleanup = function() {
document.body.removeEventListener("keydown", this._handleBodyKeyDownBound, false);
};
CalendarPicker.prototype._layout = function() {
if (this._config.isCalendarRTL)
this._element.classList.add("rtl");
this._yearMonthController.attachTo(this._element);
this._daysTable.attachTo(this._element);
this._layoutButtons();
// DaysTable will have focus but we don't want to show its focus ring until the first key event.
this._element.classList.add(ClassNames.NoFocusRing);
};
CalendarPicker.prototype.handleToday = function() {
var today = this.selectionConstructor.createFromToday();
this._daysTable.selectRangeAndShowEntireRange(today);
this.submitValue(today.toString());
};
CalendarPicker.prototype.handleClear = function() {
this.submitValue("");
};
CalendarPicker.prototype.fixWindowSize = function() {
var yearMonthRightElement = this._element.getElementsByClassName(ClassNames.YearMonthButtonRight)[0];
var daysAreaElement = this._element.getElementsByClassName(ClassNames.DaysArea)[0];
var headers = daysAreaElement.getElementsByClassName(ClassNames.DayLabel);
var maxCellWidth = 0;
for (var i = 0; i < headers.length; ++i) {
if (maxCellWidth < headers[i].offsetWidth)
maxCellWidth = headers[i].offsetWidth;
}
var DaysAreaContainerBorder = 1;
var yearMonthEnd;
var daysAreaEnd;
if (global.params.isCalendarRTL) {
var startOffset = this._element.offsetLeft + this._element.offsetWidth;
yearMonthEnd = startOffset - yearMonthRightElement.offsetLeft;
daysAreaEnd = startOffset - (daysAreaElement.offsetLeft + daysAreaElement.offsetWidth) + maxCellWidth * 7 + DaysAreaContainerBorder;
} else {
yearMonthEnd = yearMonthRightElement.offsetLeft + yearMonthRightElement.offsetWidth;
daysAreaEnd = daysAreaElement.offsetLeft + maxCellWidth * 7 + DaysAreaContainerBorder;
}
var maxEnd = Math.max(yearMonthEnd, daysAreaEnd);
var MainPadding = 6; // FIXME: Fix name.
var MainBorder = 1;
var desiredBodyWidth = maxEnd + MainPadding + MainBorder;
var elementHeight = this._element.offsetHeight;
this._element.style.width = "auto";
daysAreaElement.style.width = "100%";
daysAreaElement.style.tableLayout = "fixed";
this._element.getElementsByClassName(ClassNames.YearMonthUpper)[0].style.display = "-webkit-box";
this._element.getElementsByClassName(ClassNames.MonthSelectorBox)[0].style.display = "block";
resizeWindow(desiredBodyWidth, elementHeight);
};
CalendarPicker.prototype._layoutButtons = function() {
var container = createElement("div", ClassNames.TodayClearArea);
this.today = createElement("input", ClassNames.TodayButton);
this.today.disabled = !this.isValidDate(this.selectionConstructor.createFromToday());
this.today.type = "button";
this.today.value = this._config.todayLabel;
this.today.addEventListener("click", this.handleToday.bind(this), false);
container.appendChild(this.today);
this.clear = null;
if (!this._config.required) {
this.clear = createElement("input", ClassNames.ClearButton);
this.clear.type = "button";
this.clear.value = this._config.clearLabel;
this.clear.addEventListener("click", this.handleClear.bind(this), false);
container.appendChild(this.clear);
}
this._element.appendChild(container);
this.lastFocusableControl = this.clear || this.today;
};
/**
* @param {!Month} month
* @return {!bool}
*/
CalendarPicker.prototype.shouldShowMonth = function(month) {
return this._minimumMonth.valueOf() <= month.valueOf() && this.maximumMonth.valueOf() >= month.valueOf();
};
/**
* @param {!Month} month
* @param {!CalendarPicker.NavigationBehaviour=} navigationBehaviour
*/
CalendarPicker.prototype.showMonth = function(month, navigationBehaviour) {
if (this._currentMonth.equals(month))
return;
else if (month.valueOf() < this._minimumMonth.valueOf())
month = this._minimumMonth;
else if (month.valueOf() > this.maximumMonth.valueOf())
month = this.maximumMonth;
this._yearMonthController.setMonth(month);
this._daysTable.navigateToMonth(month, navigationBehaviour || CalendarPicker.NavigationBehaviour.None);
this._currentMonth = month;
};
/**
* @return {!Month}
*/
CalendarPicker.prototype.currentMonth = function() {
return this._currentMonth;
};
// ----------------------------------------------------------------
/**
* @constructor
* @param {!CalendarPicker} picker
*/
function YearMonthController(picker) {
this.picker = picker;
}
/**
* @param {!Element} element
*/
YearMonthController.prototype.attachTo = function(element) {
var outerContainer = createElement("div", ClassNames.YearMonthArea);
var innerContainer = createElement("div", ClassNames.YearMonthUpper);
outerContainer.appendChild(innerContainer);
this._attachLeftButtonsTo(innerContainer);
var box = createElement("div", ClassNames.MonthSelectorBox);
innerContainer.appendChild(box);
// We can't use <select> popup in PagePopup.
this._monthPopup = createElement("div", ClassNames.MonthSelectorPopup);
this._monthPopup.addEventListener("click", this._handleYearMonthChange.bind(this), false);
this._monthPopup.addEventListener("keydown", this._handleMonthPopupKey.bind(this), false);
this._monthPopup.addEventListener("mousemove", this._handleMouseMove.bind(this), false);
this._updateSelectionOnMouseMove = true;
this._monthPopup.tabIndex = 0;
this._monthPopupContents = createElement("div", ClassNames.MonthSelectorPopupContents);
this._monthPopup.appendChild(this._monthPopupContents);
box.appendChild(this._monthPopup);
this._month = createElement("div", ClassNames.MonthSelector);
this._month.addEventListener("click", this._showPopup.bind(this), false);
box.appendChild(this._month);
this._attachRightButtonsTo(innerContainer);
element.appendChild(outerContainer);
this._wall = createElement("div", ClassNames.MonthSelectorWall);
this._wall.addEventListener("click", this._closePopup.bind(this), false);
element.appendChild(this._wall);
var month = this.picker.maximumMonth;
var maxWidth = 0;
for (var m = 0; m < 12; ++m) {
this._month.textContent = month.toLocaleString();
maxWidth = Math.max(maxWidth, this._month.offsetWidth);
month = month.previous();
}
if (getLanguage() == "ja" && ImperialEraLimit < this.picker.maximumMonth.year) {
for (var m = 0; m < 12; ++m) {
this._month.textContent = new Month(ImperialEraLimit, m).toLocaleString();
maxWidth = Math.max(maxWidth, this._month.offsetWidth);
}
}
this._month.style.minWidth = maxWidth + 'px';
this.picker.firstFocusableControl = this._left2; // FIXME: Should it be this.month?
};
YearMonthController.addTenYearsButtons = false;
/**
* @param {!Element} parent
*/
YearMonthController.prototype._attachLeftButtonsTo = function(parent) {
var container = createElement("div", ClassNames.YearMonthButtonLeft);
parent.appendChild(container);
if (YearMonthController.addTenYearsButtons) {
this._left3 = createElement("input", ClassNames.YearMonthButton);
this._left3.type = "button";
this._left3.value = "<<<";
this._left3.addEventListener("click", this._handleButtonClick.bind(this), false);
container.appendChild(this._left3);
}
this._left2 = createElement("input", ClassNames.YearMonthButton);
this._left2.type = "button";
this._left2.value = "<<";
this._left2.addEventListener("click", this._handleButtonClick.bind(this), false);
container.appendChild(this._left2);
this._left1 = createElement("input", ClassNames.YearMonthButton);
this._left1.type = "button";
this._left1.value = "<";
this._left1.addEventListener("click", this._handleButtonClick.bind(this), false);
container.appendChild(this._left1);
};
/**
* @param {!Element} parent
*/
YearMonthController.prototype._attachRightButtonsTo = function(parent) {
var container = createElement("div", ClassNames.YearMonthButtonRight);
parent.appendChild(container);
this._right1 = createElement("input", ClassNames.YearMonthButton);
this._right1.type = "button";
this._right1.value = ">";
this._right1.addEventListener("click", this._handleButtonClick.bind(this), false);
container.appendChild(this._right1);
this._right2 = createElement("input", ClassNames.YearMonthButton);
this._right2.type = "button";
this._right2.value = ">>";
this._right2.addEventListener("click", this._handleButtonClick.bind(this), false);
container.appendChild(this._right2);
if (YearMonthController.addTenYearsButtons) {
this._right3 = createElement("input", ClassNames.YearMonthButton);
this._right3.type = "button";
this._right3.value = ">>>";
this._right3.addEventListener("click", this._handleButtonClick.bind(this), false);
container.appendChild(this._right3);
}
};
/**
* @param {!Month} month
*/
YearMonthController.prototype.setMonth = function(month) {
var monthValue = month.valueOf();
if (this._left3)
this._left3.disabled = !this.picker.shouldShowMonth(new Month(monthValue - 13));
this._left2.disabled = !this.picker.shouldShowMonth(new Month(monthValue - 2));
this._left1.disabled = !this.picker.shouldShowMonth(new Month(monthValue - 1));
this._right1.disabled = !this.picker.shouldShowMonth(new Month(monthValue + 1));
this._right2.disabled = !this.picker.shouldShowMonth(new Month(monthValue + 2));
if (this._right3)
this._left3.disabled = !this.picker.shouldShowMonth(new Month(monthValue + 13));
this._month.innerText = month.toLocaleString();
while (this._monthPopupContents.hasChildNodes())
this._monthPopupContents.removeChild(this._monthPopupContents.firstChild);
for (var m = monthValue - 6; m <= monthValue + 6; m++) {
var month = new Month(m);
if (!this.picker.shouldShowMonth(month))
continue;
var option = createElement("div", ClassNames.MonthSelectorPopupEntry, month.toLocaleString());
option.dataset.value = month.toString();
this._monthPopupContents.appendChild(option);
if (m == monthValue)
option.classList.add(ClassNames.SelectedMonthYear);
}
};
YearMonthController.prototype._showPopup = function() {
this._monthPopup.style.display = "block";
this._monthPopup.style.zIndex = "1000"; // Larger than the days area.
this._monthPopup.style.left = this._month.offsetLeft + (this._month.offsetWidth - this._monthPopup.offsetWidth) / 2 + "px";
this._monthPopup.style.top = this._month.offsetTop + this._month.offsetHeight + "px";
this._wall.style.display = "block";
this._wall.style.zIndex = "999"; // This should be smaller than the z-index of monthPopup.
var popupHeight = this._monthPopup.clientHeight;
var fullHeight = this._monthPopupContents.clientHeight;
if (fullHeight > popupHeight) {
var selected = this._getSelection();
if (selected) {
var bottom = selected.offsetTop + selected.clientHeight;
if (bottom > popupHeight)
this._monthPopup.scrollTop = bottom - popupHeight;
}
this._monthPopup.style.webkitPaddingEnd = getScrollbarWidth() + 'px';
}
this._monthPopup.focus();
};
YearMonthController.prototype._closePopup = function() {
this._monthPopup.style.display = "none";
this._wall.style.display = "none";
var container = document.querySelector("." + ClassNames.DaysAreaContainer);
container.focus();
};
/**
* @return {Element} Selected element in the month-year popup.
*/
YearMonthController.prototype._getSelection = function()
{
return document.querySelector("." + ClassNames.SelectedMonthYear);
}
/**
* @param {Event} event
*/
YearMonthController.prototype._handleMouseMove = function(event)
{
if (!this._updateSelectionOnMouseMove) {
// Selection update turned off while navigating with keyboard to prevent a mouse
// move trigged during a scroll from resetting the selection. Automatically
// rearm control to enable mouse-based selection.
this._updateSelectionOnMouseMove = true;
} else {
var target = event.target;
var selection = this._getSelection();
if (target && target != selection && target.classList.contains(ClassNames.MonthSelectorPopupEntry)) {
if (selection)
selection.classList.remove(ClassNames.SelectedMonthYear);
target.classList.add(ClassNames.SelectedMonthYear);
}
}
event.stopPropagation();
event.preventDefault();
}
/**
* @param {Event} event
*/
YearMonthController.prototype._handleMonthPopupKey = function(event)
{
var key = event.keyIdentifier;
if (key == "Down") {
var selected = this._getSelection();
if (selected) {
var next = selected.nextSibling;
if (next) {
selected.classList.remove(ClassNames.SelectedMonthYear);
next.classList.add(ClassNames.SelectedMonthYear);
var bottom = next.offsetTop + next.clientHeight;
if (bottom > this._monthPopup.scrollTop + this._monthPopup.clientHeight) {
this._updateSelectionOnMouseMove = false;
this._monthPopup.scrollTop = bottom - this._monthPopup.clientHeight;
}
}
}
event.stopPropagation();
event.preventDefault();
} else if (key == "Up") {
var selected = this._getSelection();
if (selected) {
var previous = selected.previousSibling;
if (previous) {
selected.classList.remove(ClassNames.SelectedMonthYear);
previous.classList.add(ClassNames.SelectedMonthYear);
if (previous.offsetTop < this._monthPopup.scrollTop) {
this._updateSelectionOnMouseMove = false;
this._monthPopup.scrollTop = previous.offsetTop;
}
}
}
event.stopPropagation();
event.preventDefault();
} else if (key == "U+001B") {
this._closePopup();
event.stopPropagation();
event.preventDefault();
} else if (key == "Enter") {
this._handleYearMonthChange();
event.stopPropagation();
event.preventDefault();
}
}
YearMonthController.prototype._handleYearMonthChange = function() {
this._closePopup();
var selection = this._getSelection();
if (!selection)
return;
this.picker.showMonth(Month.parse(selection.dataset.value));
};
/**
* @const
* @type {number}
*/
YearMonthController.PreviousTenYears = -120;
/**
* @const
* @type {number}
*/
YearMonthController.PreviousYear = -12;
/**
* @const
* @type {number}
*/
YearMonthController.PreviousMonth = -1;
/**
* @const
* @type {number}
*/
YearMonthController.NextMonth = 1;
/**
* @const
* @type {number}
*/
YearMonthController.NextYear = 12;
/**
* @const
* @type {number}
*/
YearMonthController.NextTenYears = 120;
/**
* @param {Event} event
*/
YearMonthController.prototype._handleButtonClick = function(event) {
if (event.target == this._left3)
this.moveRelatively(YearMonthController.PreviousTenYears);
else if (event.target == this._left2)
this.moveRelatively(YearMonthController.PreviousYear);
else if (event.target == this._left1)
this.moveRelatively(YearMonthController.PreviousMonth);
else if (event.target == this._right1)
this.moveRelatively(YearMonthController.NextMonth)
else if (event.target == this._right2)
this.moveRelatively(YearMonthController.NextYear);
else if (event.target == this._right3)
this.moveRelatively(YearMonthController.NextTenYears);
else
return;
};
/**
* @param {!number} amount
*/
YearMonthController.prototype.moveRelatively = function(amount) {
var current = this.picker.currentMonth().valueOf();
var updated = new Month(current + amount);
this.picker.showMonth(updated, CalendarPicker.NavigationBehaviour.Animate | CalendarPicker.NavigationBehaviour.KeepSelectionPosition);
};
// ----------------------------------------------------------------
/**
* @constructor
* @param {!CalendarPicker} picker
*/
function DaysTable(picker) {
this.picker = picker;
}
/**
* @return {!boolean}
*/
DaysTable.prototype._hasSelection = function() {
return !!this._firstNodeInSelectedRange();
}
/**
* The number of week lines in the screen.
* @const
* @type {number}
*/
DaysTable._Weeks = 6;
/**
* @param {!Element} element
*/
DaysTable.prototype.attachTo = function(element) {
this._daysContainer = createElement("table", ClassNames.DaysArea);
this._daysContainer.addEventListener("click", this._handleDayClick.bind(this), false);
this._daysContainer.addEventListener("mouseover", this._handleMouseOver.bind(this), false);
this._daysContainer.addEventListener("mouseout", this._handleMouseOut.bind(this), false);
this._daysContainer.addEventListener("webkitTransitionEnd", this._moveInDays.bind(this), false);
var container = createElement("tr", ClassNames.DayLabelContainer);
var weekStartDay = global.params.weekStartDay || 0;
container.appendChild(createElement("th", ClassNames.DayLabel + " " + ClassNames.WeekColumn, global.params.weekLabel));
for (var i = 0; i < 7; i++)
container.appendChild(createElement("th", ClassNames.DayLabel, global.params.dayLabels[(weekStartDay + i) % 7]));
this._daysContainer.appendChild(container);
this._days = [];
this._weekNumbers = [];
for (var w = 0; w < DaysTable._Weeks; w++) {
container = createElement("tr", ClassNames.WeekContainer);
var week = [];
var weekNumberNode = createElement("td", ClassNames.Day + " " + ClassNames.WeekColumn, " ");
weekNumberNode.dataset.positionX = -1;
weekNumberNode.dataset.positionY = w;
this._weekNumbers.push(weekNumberNode);
container.appendChild(weekNumberNode);
for (var d = 0; d < 7; d++) {
var day = createElement("td", ClassNames.Day, " ");
day.setAttribute("data-position-x", String(d));
day.setAttribute("data-position-y", String(w));
week.push(day);
container.appendChild(day);
}
this._days.push(week);
this._daysContainer.appendChild(container);
}
container = createElement("div", ClassNames.DaysAreaContainer);
container.appendChild(this._daysContainer);
container.tabIndex = 0;
container.addEventListener("keydown", this._handleKey.bind(this), false);
element.appendChild(container);
container.focus();
};
/**
* @param {!number} value
* @return {!boolean}
*/
CalendarPicker.prototype._stepMismatch = function(value) {
var nextAllowedValue = Math.ceil((value - this.stepBase) / this.step) * this.step + this.stepBase;
return nextAllowedValue >= value + this.selectionConstructor.DefaultStep
}
/**
* @param {!number} value
* @return {!boolean}
*/
CalendarPicker.prototype._outOfRange = function(value) {
return value < this._minimumValue || value > this._maximumValue;
}
/**
* @param {!Month|Day} range
* @return {!boolean}
*/
CalendarPicker.prototype.isValidDate = function(range) {
var value = range.valueOf();
return !this._outOfRange(value) && !this._stepMismatch(value);
}
/**
* @param {!Month} month
*/
DaysTable.prototype._renderMonth = function(month) {
var dayIterator = month.startDate();
var monthStartDay = dayIterator.getUTCDay();
var weekStartDay = global.params.weekStartDay || 0;
var startOffset = weekStartDay - monthStartDay;
if (startOffset >= 0)
startOffset -= 7;
dayIterator.setUTCDate(startOffset + 1);
var mondayOffset = (8 - weekStartDay) % 7;
var sundayOffset = weekStartDay % 7;
for (var w = 0; w < DaysTable._Weeks; w++) {
for (var d = 0; d < 7; d++) {
var iterMonth = Month.createFromDate(dayIterator);
var iterWeek = Week.createFromDate(dayIterator);
var time = dayIterator.getTime();
var element = this._days[w][d];
element.innerText = localizeNumber(dayIterator.getUTCDate());
element.className = ClassNames.Day;
element.dataset.submitValue = Day.createFromDate(dayIterator).toString();
element.dataset.weekValue = iterWeek.toString();
element.dataset.monthValue = iterMonth.toString();
if (isNaN(time)) {
element.innerText = "-";
element.classList.add(ClassNames.Unavailable);
} else if (!this.picker.isValidDate(this._rangeForNode(element)))
element.classList.add(ClassNames.Unavailable);
else if (!iterMonth.equals(month)) {
element.classList.add(ClassNames.Available);
element.classList.add(ClassNames.NotThisMonth);
} else
element.classList.add(ClassNames.Available);
if (d === mondayOffset) {
element.classList.add(ClassNames.Monday);
if (this._weekNumbers[w]) {
this._weekNumbers[w].dataset.weekValue = iterWeek.toString();
this._weekNumbers[w].innerText = localizeNumber(iterWeek.week);
if (element.classList.contains(ClassNames.Available))
this._weekNumbers[w].classList.add(ClassNames.Available);
else
this._weekNumbers[w].classList.add(ClassNames.Unavailable);
}
} else if (d === sundayOffset)
element.classList.add(ClassNames.Sunday);
dayIterator.setUTCDate(dayIterator.getUTCDate() + 1);
}
}
};
/**
* @param {!Month} month
* @param {!CalendarPicker.NavigationBehaviour} navigationBehaviour
*/
DaysTable.prototype.navigateToMonth = function(month, navigationBehaviour) {
var firstNodeInSelectedRange = this._firstNodeInSelectedRange();
if (navigationBehaviour & CalendarPicker.NavigationBehaviour.Animate)
this._startMoveInAnimation(month);
this._renderMonth(month);
if (navigationBehaviour & CalendarPicker.NavigationBehaviour.KeepSelectionPosition && firstNodeInSelectedRange) {
var x = parseInt(firstNodeInSelectedRange.dataset.positionX, 10);
var y = parseInt(firstNodeInSelectedRange.dataset.positionY, 10);
this._selectRangeAtPosition(x, y);
}
};
/**
* @param {!Month} month
*/
DaysTable.prototype._startMoveInAnimation = function(month) {
var daysStyle = this._daysContainer.style;
daysStyle.position = "relative";
daysStyle.webkitTransition = "left 0.1s ease";
daysStyle.left = (this.picker.currentMonth().valueOf() > month.valueOf() ? "" : "-") + this._daysContainer.offsetWidth + "px";
};
DaysTable.prototype._moveInDays = function() {
var daysStyle = this._daysContainer.style;
if (daysStyle.left == "0px")
return;
daysStyle.webkitTransition = "";
daysStyle.left = (daysStyle.left.charAt(0) == "-" ? "" : "-") + this._daysContainer.offsetWidth + "px";
this._daysContainer.offsetLeft; // Force to layout.
daysStyle.webkitTransition = "left 0.1s ease";
daysStyle.left = "0px";
};
/**
* @param {!Day} day
*/
DaysTable.prototype._markRangeAsSelected = function(day) {
var dateString = day.toString();
for (var w = 0; w < DaysTable._Weeks; w++) {
for (var d = 0; d < 7; d++) {
if (this._days[w][d].dataset.submitValue == dateString) {
this._days[w][d].classList.add(ClassNames.Selected);
break;
}
}
}
};
/**
* @param {!Day} day
*/
DaysTable.prototype.selectRange = function(day) {
this._deselect();
if (this.startDate() > day.startDate() || this.endDate() < day.endDate())
this.picker.showMonth(Month.createFromDate(day.startDate()), CalendarPicker.NavigationBehaviour.Animate);
this._markRangeAsSelected(day);
};
/**
* @param {!Day} day
*/
DaysTable.prototype.selectRangeAndShowEntireRange = function(day) {
this.selectRange(day);
};
/**
* @param {!Element} dayNode
*/
DaysTable.prototype._selectRangeContainingNode = function(dayNode) {
var range = this._rangeForNode(dayNode);
if (!range)
return;
this.selectRange(range);
};
/**
* @param {!Element} dayNode
* @return {?Day}
*/
DaysTable.prototype._rangeForNode = function(dayNode) {
if (!dayNode)
return null;
return Day.parse(dayNode.dataset.submitValue);
};
/**
* @return {!Date}
*/
DaysTable.prototype.startDate = function() {
return Day.parse(this._days[0][0].dataset.submitValue).startDate();
};
/**
* @return {!Date}
*/
DaysTable.prototype.endDate = function() {
return Day.parse(this._days[DaysTable._Weeks - 1][7 - 1].dataset.submitValue).endDate();
};
/**
* @param {!number} x
* @param {!number} y
*/
DaysTable.prototype._selectRangeAtPosition = function(x, y) {
var node = x === -1 ? this._weekNumbers[y] : this._days[y][x];
this._selectRangeContainingNode(node);
};
/**
* @return {!Element}
*/
DaysTable.prototype._firstNodeInSelectedRange = function() {
return this._daysContainer.getElementsByClassName(ClassNames.Selected)[0];
};
DaysTable.prototype._deselect = function() {
var selectedNodes = this._daysContainer.getElementsByClassName(ClassNames.Selected);
for (var node = selectedNodes[0]; node; node = selectedNodes[0])
node.classList.remove(ClassNames.Selected);
};
/**
* @param {!CalendarPicker.NavigationBehaviour=} navigationBehaviour
* @return {!boolean}
*/
DaysTable.prototype._maybeSetPreviousMonth = function(navigationBehaviour) {
if (typeof navigationBehaviour === "undefined")
navigationBehaviour = CalendarPicker.NavigationBehaviour.Animate;
var previousMonth = this.picker.currentMonth().previous();
if (!this.picker.shouldShowMonth(previousMonth))
return false;
this.picker.showMonth(previousMonth, navigationBehaviour);
return true;
};
/**
* @param {!CalendarPicker.NavigationBehaviour=} navigationBehaviour
* @return {!boolean}
*/
DaysTable.prototype._maybeSetNextMonth = function(navigationBehaviour) {
if (typeof navigationBehaviour === "undefined")
navigationBehaviour = CalendarPicker.NavigationBehaviour.Animate;
var nextMonth = this.picker.currentMonth().next();
if (!this.picker.shouldShowMonth(nextMonth))
return false;
this.picker.showMonth(nextMonth, navigationBehaviour);
return true;
};
/**
* @param {Event} event
*/
DaysTable.prototype._handleDayClick = function(event) {
if (event.target.classList.contains(ClassNames.Available))
this.picker.submitValue(this._rangeForNode(event.target).toString());
};
/**
* @param {Event} event
*/
DaysTable.prototype._handleMouseOver = function(event) {
var node = event.target;
if (node.classList.contains(ClassNames.Selected))
return;
this._selectRangeContainingNode(node);
};
/**
* @param {Event} event
*/
DaysTable.prototype._handleMouseOut = function(event) {
this._deselect();
};
/**
* @param {Event} event
*/
DaysTable.prototype._handleKey = function(event) {
this.picker.maybeUpdateFocusStyle();
var x = -1;
var y = -1;
var key = event.keyIdentifier;
var firstNodeInSelectedRange = this._firstNodeInSelectedRange();
if (firstNodeInSelectedRange) {
x = parseInt(firstNodeInSelectedRange.dataset.positionX, 10);
y = parseInt(firstNodeInSelectedRange.dataset.positionY, 10);
}
if (!this._hasSelection() && (key == "Left" || key == "Up" || key == "Right" || key == "Down")) {
// Put the selection on a center cell.
this.updateSelection(event, 3, Math.floor(DaysTable._Weeks / 2 - 1));
return;
}
if (key == (global.params.isCalendarRTL ? "Right" : "Left")) {
if (x == 0) {
if (y == 0) {
if (!this._maybeSetPreviousMonth())
return;
y = DaysTable._Weeks - 1;
} else
y--;
x = 6;
} else
x--;
this.updateSelection(event, x, y);
} else if (key == "Up") {
if (y == 0) {
if (!this._maybeSetPreviousMonth())
return;
y = DaysTable._Weeks - 1;
} else
y--;
this.updateSelection(event, x, y);
} else if (key == (global.params.isCalendarRTL ? "Left" : "Right")) {
if (x == 6) {
if (y == DaysTable._Weeks - 1) {
if (!this._maybeSetNextMonth())
return;
y = 0;
} else
y++;
x = 0;
} else
x++;
this.updateSelection(event, x, y);
} else if (key == "Down") {
if (y == DaysTable._Weeks - 1) {
if (!this._maybeSetNextMonth())
return;
y = 0;
} else
y++;
this.updateSelection(event, x, y);
} else if (key == "PageUp") {
if (!this._maybeSetPreviousMonth())
return;
this.updateSelection(event, x, y);
} else if (key == "PageDown") {
if (!this._maybeSetNextMonth())
return;
this.updateSelection(event, x, y);
} else if (this._hasSelection() && key == "Enter") {
var dayNode = this._days[y][x];
if (dayNode.classList.contains(ClassNames.Available)) {
this.picker.submitValue(dayNode.dataset.submitValue);
event.stopPropagation();
}
} else if (key == "U+0054") { // 't'
this.selectRangeAndShowEntireRange(Day.createFromToday());
event.stopPropagation();
event.preventDefault();
}
};
/**
* @param {Event} event
* @param {!number} x
* @param {!number} y
*/
DaysTable.prototype.updateSelection = function(event, x, y) {
this._selectRangeAtPosition(x, y);
event.stopPropagation();
event.preventDefault();
};
/**
* @constructor
* @param{!CalendarPicker} picker
*/
function MonthPickerDaysTable(picker) {
DaysTable.call(this, picker);
}
MonthPickerDaysTable.prototype = Object.create(DaysTable.prototype);
/**
* @param {!Month} month
* @param {!CalendarPicker.NavigationBehaviour} navigationBehaviour
*/
MonthPickerDaysTable.prototype.navigateToMonth = function(month, navigationBehaviour) {
var hadSelection = this._hasSelection();
if (navigationBehaviour & CalendarPicker.NavigationBehaviour.Animate)
this._startMoveInAnimation(month);
this._renderMonth(month);
if (navigationBehaviour & CalendarPicker.NavigationBehaviour.KeepSelectionPosition && hadSelection)
this.selectRange(month);
};
/**
* @param {!Month} month
*/
MonthPickerDaysTable.prototype._markRangeAsSelected = function(month) {
var monthString = month.toString();
for (var w = 0; w < DaysTable._Weeks; w++) {
for (var d = 0; d < 7; d++) {
if (this._days[w][d].dataset.monthValue == monthString) {
this._days[w][d].classList.add(ClassNames.Selected);
}
}
}
};
/**
* @param {!Month} month
*/
MonthPickerDaysTable.prototype.selectRange = function(month) {
this._deselect();
if (this.startDate() >= month.endDate() || this.endDate() <= month.startDate())
this.picker.showMonth(month, CalendarPicker.NavigationBehaviour.Animate);
this._markRangeAsSelected(month);
};
/**
* @param {!Month} month
*/
MonthPickerDaysTable.prototype.selectRangeAndShowEntireRange = function(month) {
this._deselect();
this.picker.showMonth(month, CalendarPicker.NavigationBehaviour.Animate);
this._markRangeAsSelected(month);
};
/**
* @param {!Element} dayNode
* @return {?Month}
*/
MonthPickerDaysTable.prototype._rangeForNode = function(dayNode) {
if (!dayNode)
return null;
return Month.parse(dayNode.dataset.monthValue);
};
/**
* @param {Event} event
*/
MonthPickerDaysTable.prototype._handleKey = function(event) {
this.picker.maybeUpdateFocusStyle();
var key = event.keyIdentifier;
var eventHandled = false;
var currentMonth = this.picker.currentMonth();
var firstNodeInSelectedRange = this._firstNodeInSelectedRange();
var selectedMonth = this._rangeForNode(firstNodeInSelectedRange);
if (!firstNodeInSelectedRange
&& (key == "Right" || key == "Left" || key == "Up" || key == "Down" || key == "PageUp" || key == "PageDown")) {
this.selectRange(currentMonth);
eventHandled = true;
} else if (key == (global.params.isCalendarRTL ? "Right" : "Left") || key == "Up" || key == "PageUp") {
if (selectedMonth.valueOf() > currentMonth.valueOf())
this.selectRangeAndShowEntireRange(currentMonth);
else
this.selectRangeAndShowEntireRange(currentMonth.previous());
eventHandled = true;
} else if (key == (global.params.isCalendarRTL ? "Left" : "Right") || key == "Down" || key == "PageDown") {
if (selectedMonth.valueOf() < currentMonth.valueOf())
this.selectRangeAndShowEntireRange(currentMonth);
else
this.selectRangeAndShowEntireRange(currentMonth.next());
eventHandled = true;
} else if (selectedMonth && key == "Enter") {
if (firstNodeInSelectedRange.classList.contains(ClassNames.Available)) {
this.picker.submitValue(selectedMonth.toString());
eventHandled = true;
}
} else if (key == "U+0054") { // 't'
this.selectRangeAndShowEntireRange(Month.createFromToday());
eventHandled = true;
}
if (eventHandled) {
event.stopPropagation();
event.preventDefault();
}
};
/**
* @constructor
* @param{!CalendarPicker} picker
*/
function WeekPickerDaysTable(picker) {
DaysTable.call(this, picker);
}
WeekPickerDaysTable.prototype = Object.create(DaysTable.prototype);
/**
* @param {!Week} week
*/
WeekPickerDaysTable.prototype._markRangeAsSelected = function(week) {
var weekString = week.toString();
for (var w = 0; w < DaysTable._Weeks; w++) {
for (var d = 0; d < 7; d++) {
if (this._days[w][d].dataset.weekValue == weekString) {
this._days[w][d].classList.add(ClassNames.Selected);
}
}
}
for (var i = 0; i < this._weekNumbers.length; ++i) {
if (this._weekNumbers[i].dataset.weekValue === weekString) {
this._weekNumbers[i].classList.add(ClassNames.Selected);
break;
}
}
};
/**
* @param {!Week} week
*/
WeekPickerDaysTable.prototype.selectRange = function(week) {
this._deselect();
var weekStartDate = week.startDate();
var weekEndDate = week.endDate();
if (this.startDate() >= weekEndDate)
this.picker.showMonth(Month.createFromDate(weekEndDate), CalendarPicker.NavigationBehaviour.Animate);
else if (this.endDate() <= weekStartDate)
this.picker.showMonth(Month.createFromDate(weekStartDate), CalendarPicker.NavigationBehaviour.Animate);
this._markRangeAsSelected(week);
};
/**
* @param {!Week} week
*/
WeekPickerDaysTable.prototype.selectRangeAndShowEntireRange = function(week) {
this._deselect();
var weekStartDate = week.startDate();
var weekEndDate = week.endDate();
if (this.startDate() > weekStartDate)
this.picker.showMonth(Month.createFromDate(weekStartDate), CalendarPicker.NavigationBehaviour.Animate);
else if (this.endDate() < weekEndDate)
this.picker.showMonth(Month.createFromDate(weekEndDate), CalendarPicker.NavigationBehaviour.Animate);
this._markRangeAsSelected(week);
};
/**
* @param {!Element} dayNode
* @return {?Week}
*/
WeekPickerDaysTable.prototype._rangeForNode = function(dayNode) {
if (!dayNode)
return null;
return Week.parse(dayNode.dataset.weekValue);
};
/**
* @param {!Event} event
*/
WeekPickerDaysTable.prototype._handleKey = function(event) {
this.picker.maybeUpdateFocusStyle();
var key = event.keyIdentifier;
var eventHandled = false;
var currentMonth = this.picker.currentMonth();
var firstNodeInSelectedRange = this._firstNodeInSelectedRange();
var selectedWeek = this._rangeForNode(firstNodeInSelectedRange);
if (!firstNodeInSelectedRange
&& (key == "Right" || key == "Left" || key == "Up" || key == "Down" || key == "PageUp" || key == "PageDown")) {
// Put the selection on a center cell.
this._selectRangeAtPosition(3, Math.floor(DaysTable._Weeks / 2 - 1));
} else if (key == (global.params.isCalendarRTL ? "Right" : "Left") || key == "Up") {
this.selectRangeAndShowEntireRange(selectedWeek.previous());
eventHandled = true;
} else if (key == (global.params.isCalendarRTL ? "Left" : "Right") || key == "Down") {
this.selectRangeAndShowEntireRange(selectedWeek.next());
eventHandled = true;
} else if (key == "PageUp") {
if (!this._maybeSetPreviousMonth(CalendarPicker.NavigationBehaviour.Animate | CalendarPicker.NavigationBehaviour.KeepSelectionPosition))
return;
eventHandled = true;
} else if (key == "PageDown") {
if (!this._maybeSetNextMonth(CalendarPicker.NavigationBehaviour.Animate | CalendarPicker.NavigationBehaviour.KeepSelectionPosition))
return;
eventHandled = true;
} else if (selectedWeek && key == "Enter") {
if (firstNodeInSelectedRange.classList.contains(ClassNames.Available)) {
this.picker.submitValue(selectedWeek.toString());
eventHandled = true;
}
} else if (key == "U+0054") { // 't'
this.selectRangeAndShowEntireRange(Week.createFromToday());
eventHandled = true;
}
if (eventHandled) {
event.stopPropagation();
event.preventDefault();
}
};
/**
* @param {!Event} event
*/
CalendarPicker.prototype._handleBodyKeyDown = function(event) {
this.maybeUpdateFocusStyle();
var key = event.keyIdentifier;
if (key == "U+0009") {
if (!event.shiftKey && document.activeElement == this.lastFocusableControl) {
event.stopPropagation();
event.preventDefault();
this.firstFocusableControl.focus();
} else if (event.shiftKey && document.activeElement == this.firstFocusableControl) {
event.stopPropagation();
event.preventDefault();
this.lastFocusableControl.focus();
}
} else if (key == "U+004D") { // 'm'
this._yearMonthController.moveRelatively(event.shiftKey ? YearMonthController.PreviousMonth : YearMonthController.NextMonth);
} else if (key == "U+0059") { // 'y'
this._yearMonthController.moveRelatively(event.shiftKey ? YearMonthController.PreviousYear : YearMonthController.NextYear);
} else if (key == "U+0044") { // 'd'
this._yearMonthController.moveRelatively(event.shiftKey ? YearMonthController.PreviousTenYears : YearMonthController.NextTenYears);
} else if (key == "U+001B") // ESC
this.handleCancel();
}
CalendarPicker.prototype.maybeUpdateFocusStyle = function() {
if (this._hadKeyEvent)
return;
this._hadKeyEvent = true;
this._element.classList.remove(ClassNames.NoFocusRing);
}
if (window.dialogArguments) {
initialize(dialogArguments);
} else {
window.addEventListener("message", handleMessage, false);
window.setTimeout(handleArgumentsTimeout, 1000);
}