3
* Copyright (C) 2012 Google Inc. All rights reserved.
5
* Redistribution and use in source and binary forms, with or without
6
* modification, are permitted provided that the following conditions are
9
* * Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer.
11
* * Redistributions in binary form must reproduce the above
12
* copyright notice, this list of conditions and the following disclaimer
13
* in the documentation and/or other materials provided with the
15
* * Neither the name of Google Inc. nor the names of its
16
* contributors may be used to endorse or promote products derived from
17
* this software without specific prior written permission.
19
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41
Available: "available",
42
CancelButton: "cancel-button",
43
ClearButton: "clear-button",
45
DayLabel: "day-label",
46
DayLabelContainer: "day-label-container",
47
DaysArea: "days-area",
48
DaysAreaContainer: "days-area-container",
50
MonthMode: "month-mode",
51
MonthSelector: "month-selector",
52
MonthSelectorBox: "month-selector-box",
53
MonthSelectorPopup: "month-selector-popup",
54
MonthSelectorPopupContents: "month-selector-popup-contents",
55
MonthSelectorPopupEntry: "month-selector-popup-entry",
56
MonthSelectorWall: "month-selector-wall",
57
NoFocusRing: "no-focus-ring",
58
NotThisMonth: "not-this-month",
59
Selected: "day-selected",
60
SelectedMonthYear: "selected-month-year",
62
TodayButton: "today-button",
63
TodayClearArea: "today-clear-area",
64
Unavailable: "unavailable",
65
WeekContainer: "week-container",
66
WeekColumn: "week-column",
67
WeekMode: "week-mode",
68
YearMonthArea: "year-month-area",
69
YearMonthButton: "year-month-button",
70
YearMonthButtonLeft: "year-month-button-left",
71
YearMonthButtonRight: "year-month-button-right",
72
YearMonthUpper: "year-month-upper"
79
argumentsReceived: false,
84
// ----------------------------------------------------------------
88
* @return {!string} lowercase locale name. e.g. "en-us"
90
function getLocale() {
91
return (global.params.locale || "en-us").toLowerCase();
95
* @return {!string} lowercase language code. e.g. "en"
97
function getLanguage() {
98
var locale = getLocale();
99
var result = locale.match(/^([a-z]+)/);
106
* @param {!number} number
109
function localizeNumber(number) {
110
return window.pagePopupController.localizeNumberString(number);
117
var ImperialEraLimit = 2087;
120
* @param {!number} year
121
* @param {!number} month
124
function formatJapaneseImperialEra(year, month) {
125
// We don't show an imperial era if it is greater than 99 becase of space
127
if (year > ImperialEraLimit)
130
return "(å¹³ę" + localizeNumber(year - 1988) + "幓)";
132
return "(å¹³ęå
幓)";
134
return "(ęå" + localizeNumber(year - 1925) + "幓)";
136
return "(大ę£" + localizeNumber(year - 1911) + "幓)";
137
if (year == 1912 && month >= 7)
138
return "(大ę£å
幓)";
140
return "(ęę²»" + localizeNumber(year - 1867) + "幓)";
142
return "(ęę²»å
幓)";
149
Month.prototype.toLocaleString = function() {
150
if (isNaN(this.year) || isNaN(this.year))
151
return "Invalid Month";
152
if (getLanguage() == "ja")
153
return "" + this.year + "幓" + formatJapaneseImperialEra(this.year, this.month) + " " + (this.month + 1) + "ę";
154
return window.pagePopupController.formatMonth(this.year, this.month);
157
function createUTCDate(year, month, date) {
158
var newDate = new Date(0);
159
newDate.setUTCFullYear(year);
160
newDate.setUTCMonth(month);
161
newDate.setUTCDate(date);
166
* @param {string} dateString
167
* @return {?Day|Week|Month}
169
function parseDateString(dateString) {
170
var month = Month.parse(dateString);
173
var week = Week.parse(dateString);
176
return Day.parse(dateString);
181
* @param {!number|Day} valueOrDayOrYear
182
* @param {!number=} month
183
* @param {!number=} date
185
function Day(valueOrDayOrYear, month, date) {
187
if (arguments.length == 3)
188
dateObject = createUTCDate(valueOrDayOrYear, month, date);
189
else if (valueOrDayOrYear instanceof Day)
190
dateObject = createUTCDate(valueOrDayOrYear.year, valueOrDayOrYear.month, valueOrDayOrYear.date);
192
dateObject = new Date(valueOrDayOrYear);
193
this.year = dateObject.getUTCFullYear();
194
this.month = dateObject.getUTCMonth();
195
this.date = dateObject.getUTCDate();
198
Day.ISOStringRegExp = /^(\d+)-(\d+)-(\d+)/;
201
* @param {!string} str
204
Day.parse = function(str) {
205
var match = Day.ISOStringRegExp.exec(str);
208
var year = parseInt(match[1], 10);
209
var month = parseInt(match[2], 10) - 1;
210
var date = parseInt(match[3], 10);
211
return new Day(year, month, date);
215
* @param {!Date} date
218
Day.createFromDate = function(date) {
219
return new Day(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
225
Day.createFromToday = function() {
226
var now = new Date();
227
return new Day(now.getFullYear(), now.getMonth(), now.getDate());
231
* @param {!Day} other
234
Day.prototype.equals = function(other) {
235
return this.year === other.year && this.month === other.month && this.date === other.date;
241
Day.prototype.previous = function() {
242
return new Day(this.year, this.month, this.date - 1);
248
Day.prototype.next = function() {
249
return new Day(this.year, this.month, this.date + 1);
255
Day.prototype.startDate = function() {
256
return createUTCDate(this.year, this.month, this.date);
262
Day.prototype.endDate = function() {
263
return createUTCDate(this.year, this.month, this.date + 1);
269
Day.prototype.valueOf = function() {
270
return this.startDate().getTime();
276
Day.prototype.toString = function() {
277
var yearString = String(this.year);
278
if (yearString.length < 4)
279
yearString = ("000" + yearString).substr(-4, 4);
280
return yearString + "-" + ("0" + (this.month + 1)).substr(-2, 2) + "-" + ("0" + this.date).substr(-2, 2);
283
// See WebCore/platform/DateComponents.h.
284
Day.Minimum = new Day(-62135596800000.0);
285
Day.Maximum = new Day(8640000000000000.0);
286
// See WebCore/html/DayInputType.cpp.
287
Day.DefaultStep = 86400000;
288
Day.DefaultStepBase = 0;
292
* @param {!number|Week} valueOrWeekOrYear
293
* @param {!number=} week
295
function Week(valueOrWeekOrYear, week) {
296
if (arguments.length === 2) {
297
this.year = valueOrWeekOrYear;
299
// Number of years per year is either 52 or 53.
300
if (this.week < 1 || (this.week > 52 && this.week > Week.numberOfWeeksInYear(this.year))) {
301
var normalizedWeek = Week.createFromDate(this.startDate());
302
this.year = normalizedWeek.year;
303
this.week = normalizedWeek.week;
305
} else if (valueOrWeekOrYear instanceof Week) {
306
this.year = valueOrWeekOrYear.year;
307
this.week = valueOrWeekOrYear.week;
309
var week = Week.createFromDate(new Date(valueOrWeekOrYear));
310
this.year = week.year;
311
this.week = week.week;
315
Week.MillisecondsPerWeek = 7 * 24 * 60 * 60 * 1000;
316
Week.ISOStringRegExp = /^(\d+)-[wW](\d+)$/;
317
// See WebCore/platform/DateComponents.h.
318
Week.Minimum = new Week(1, 1);
319
Week.Maximum = new Week(275760, 37);
320
// See WebCore/html/WeekInputType.cpp.
321
Week.DefaultStep = 604800000;
322
Week.DefaultStepBase = -259200000;
325
* @param {!string} str
328
Week.parse = function(str) {
329
var match = Week.ISOStringRegExp.exec(str);
332
var year = parseInt(match[1], 10);
333
var week = parseInt(match[2], 10);
334
return new Week(year, week);
338
* @param {!Date} date
341
Week.createFromDate = function(date) {
342
var year = date.getUTCFullYear();
343
if (year <= Week.Maximum.year && Week.weekOneStartDateForYear(year + 1).getTime() <= date.getTime())
345
else if (year > 1 && Week.weekOneStartDateForYear(year).getTime() > date.getTime())
347
var week = 1 + Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), date);
348
return new Week(year, week);
354
Week.createFromToday = function() {
355
var now = new Date();
356
return Week.createFromDate(createUTCDate(now.getFullYear(), now.getMonth(), now.getDate()));
360
* @param {!number} year
363
Week.weekOneStartDateForYear = function(year) {
365
return createUTCDate(1, 0, 1);
366
// The week containing January 4th is week one.
367
var yearStartDay = createUTCDate(year, 0, 4).getUTCDay();
368
return createUTCDate(year, 0, 4 - (yearStartDay + 6) % 7);
372
* @param {!number} year
375
Week.numberOfWeeksInYear = function(year) {
376
if (year < 1 || year > Week.Maximum.year)
378
else if (year === Week.Maximum.year)
379
return Week.Maximum.week;
380
return Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), Week.weekOneStartDateForYear(year + 1));
384
* @param {!Date} baseDate
385
* @param {!Date} date
388
Week._numberOfWeeksSinceDate = function(baseDate, date) {
389
return Math.floor((date.getTime() - baseDate.getTime()) / Week.MillisecondsPerWeek);
393
* @param {!Week} other
396
Week.prototype.equals = function(other) {
397
return this.year === other.year && this.week === other.week;
403
Week.prototype.previous = function() {
404
return new Week(this.year, this.week - 1);
410
Week.prototype.next = function() {
411
return new Week(this.year, this.week + 1);
417
Week.prototype.startDate = function() {
418
var weekStartDate = Week.weekOneStartDateForYear(this.year);
419
weekStartDate.setUTCDate(weekStartDate.getUTCDate() + (this.week - 1) * 7);
420
return weekStartDate;
426
Week.prototype.endDate = function() {
427
if (this.equals(Week.Maximum))
428
return Day.Maximum.startDate();
429
return this.next().startDate();
435
Week.prototype.valueOf = function() {
436
return this.startDate().getTime() - createUTCDate(1970, 0, 1).getTime();
442
Week.prototype.toString = function() {
443
var yearString = String(this.year);
444
if (yearString.length < 4)
445
yearString = ("000" + yearString).substr(-4, 4);
446
return yearString + "-W" + ("0" + this.week).substr(-2, 2);
451
* @param {!number|Month} valueOrMonthOrYear
452
* @param {!number=} month
454
function Month(valueOrMonthOrYear, month) {
455
if (arguments.length == 2) {
456
this.year = valueOrMonthOrYear;
458
} else if (valueOrMonthOrYear instanceof Month) {
459
this.year = valueOrMonthOrYear.year;
460
this.month = valueOrMonthOrYear.month;
463
this.month = valueOrMonthOrYear;
465
this.year = this.year + Math.floor(this.month / 12);
466
this.month = this.month < 0 ? this.month % 12 + 12 : this.month % 12;
467
if (this.year <= 0 || Month.Maximum < this) {
473
Month.ISOStringRegExp = /^(\d+)-(\d+)$/;
475
// See WebCore/platform/DateComponents.h.
476
Month.Minimum = new Month(1, 0);
477
Month.Maximum = new Month(275760, 8);
478
// See WebCore/html/MonthInputType.cpp.
479
Month.DefaultStep = 1;
480
Month.DefaultStepBase = 0;
483
* @param {!string} str
486
Month.parse = function(str) {
487
var match = Month.ISOStringRegExp.exec(str);
490
var year = parseInt(match[1], 10);
491
var month = parseInt(match[2], 10) - 1;
492
return new Month(year, month);
496
* @param {!Date} date
499
Month.createFromDate = function(date) {
500
return new Month(date.getUTCFullYear(), date.getUTCMonth());
506
Month.createFromToday = function() {
507
var now = new Date();
508
return new Month(now.getFullYear(), now.getMonth());
512
* @param {!Month} other
515
Month.prototype.equals = function(other) {
516
return this.year === other.year && this.month === other.month;
522
Month.prototype.previous = function() {
523
return new Month(this.year, this.month - 1);
529
Month.prototype.next = function() {
530
return new Month(this.year, this.month + 1);
536
Month.prototype.startDate = function() {
537
return createUTCDate(this.year, this.month, 1);
543
Month.prototype.endDate = function() {
544
if (this.equals(Month.Maximum))
545
return Day.Maximum.startDate();
546
return this.next().startDate();
552
Month.prototype.valueOf = function() {
553
return (this.year - 1970) * 12 + this.month;
559
Month.prototype.toString = function() {
560
var yearString = String(this.year);
561
if (yearString.length < 4)
562
yearString = ("000" + yearString).substr(-4, 4);
563
return yearString + "-" + ("0" + (this.month + 1)).substr(-2, 2);
566
// ----------------------------------------------------------------
570
* @param {Event} event
572
function handleMessage(event) {
573
if (global.argumentsReceived)
575
global.argumentsReceived = true;
576
initialize(JSON.parse(event.data));
579
function handleArgumentsTimeout() {
580
if (global.argumentsReceived)
583
dayLabels : ["d1", "d2", "d3", "d4", "d5", "d6", "d7"],
584
todayLabel : "Today",
585
clearLabel : "Clear",
586
cancelLabel : "Cancel",
589
step : CalendarPicker.DefaultStepScaleFactor,
590
stepBase: CalendarPicker.DefaultStepBase
596
* @param {!Object} config
597
* @return {?string} An error message, or null if the argument has no errors.
599
CalendarPicker.validateConfig = function(config) {
600
if (!config.dayLabels)
601
return "No dayLabels.";
602
if (config.dayLabels.length != 7)
603
return "dayLabels is not an array with 7 elements.";
604
if (!config.clearLabel)
605
return "No clearLabel.";
606
if (!config.todayLabel)
607
return "No todayLabel.";
608
if (config.weekStartDay) {
609
if (config.weekStartDay < 0 || config.weekStartDay > 6)
610
return "Invalid weekStartDay: " + config.weekStartDay;
616
* @param {!Object} args
618
function initialize(args) {
619
global.params = args;
620
var errorString = CalendarPicker.validateConfig(args);
621
if (args.suggestionValues)
622
errorString = errorString || SuggestionPicker.validateConfig(args)
624
var main = $("main");
625
main.textContent = "Internal error: " + errorString;
626
resizeWindow(main.offsetWidth, main.offsetHeight);
628
if (global.params.suggestionValues && global.params.suggestionValues.length)
629
openSuggestionPicker();
631
openCalendarPicker();
635
function closePicker() {
637
global.picker.cleanup();
638
var main = $("main");
643
function openSuggestionPicker() {
645
global.picker = new SuggestionPicker($("main"), global.params);
648
function openCalendarPicker() {
650
global.picker = new CalendarPicker($("main"), global.params);
655
* @param {!Element} element
656
* @param {!Object} config
658
function CalendarPicker(element, config) {
659
Picker.call(this, element, config);
660
if (this._config.mode === "month") {
661
this.selectionConstructor = Month;
662
this._daysTable = new MonthPickerDaysTable(this);
663
this._element.classList.add(ClassNames.MonthMode);
664
} else if (this._config.mode === "week") {
665
this.selectionConstructor = Week;
666
this._daysTable = new WeekPickerDaysTable(this);
667
this._element.classList.add(ClassNames.WeekMode);
669
this.selectionConstructor = Day;
670
this._daysTable = new DaysTable(this);
672
this._element.classList.add("calendar-picker");
673
this._element.classList.add("preparing");
674
this.isPreparing = true;
675
this._handleWindowResizeBound = this._handleWindowResize.bind(this);
676
window.addEventListener("resize", this._handleWindowResizeBound, false);
677
// We assume this._config.min/max are valid dates or months.
678
var minimum = (typeof this._config.min !== "undefined") ? parseDateString(this._config.min) : this.selectionConstructor.Minimum;
679
var maximum = (typeof this._config.max !== "undefined") ? parseDateString(this._config.max) : this.selectionConstructor.Maximum;
680
this._minimumValue = minimum.valueOf();
681
this._maximumValue = maximum.valueOf();
682
this.step = (typeof this._config.step !== undefined) ? Number(this._config.step) : this.selectionConstructor.DefaultStep;
683
this.stepBase = (typeof this._config.stepBase !== "undefined") ? Number(this._config.stepBase) : this.selectionConstructor.DefaultStepBase;
684
this._minimumMonth = Month.createFromDate(minimum.startDate());
685
this.maximumMonth = Month.createFromDate(maximum.startDate());
686
this._currentMonth = new Month(NaN, NaN);
687
this._yearMonthController = new YearMonthController(this);
688
this._hadKeyEvent = false;
690
var initialSelection = parseDateString(this._config.currentValue);
691
if (!initialSelection)
692
initialSelection = this.selectionConstructor.createFromToday();
693
if (initialSelection.valueOf() < this._minimumValue)
694
initialSelection = new this.selectionConstructor(this._minimumValue);
695
else if (initialSelection.valueOf() > this._maximumValue)
696
initialSelection = new this.selectionConstructor(this._maximumValue);
697
this.showMonth(Month.createFromDate(initialSelection.startDate()));
698
this._daysTable.selectRangeAndShowEntireRange(initialSelection);
699
this.fixWindowSize();
700
this._handleBodyKeyDownBound = this._handleBodyKeyDown.bind(this);
701
document.body.addEventListener("keydown", this._handleBodyKeyDownBound, false);
703
CalendarPicker.prototype = Object.create(Picker.prototype);
705
CalendarPicker.NavigationBehaviour = {
708
KeepSelectionPosition: 1 << 1
711
CalendarPicker.prototype._handleWindowResize = function() {
712
this._element.classList.remove("preparing");
713
this.isPreparing = false;
716
CalendarPicker.prototype.cleanup = function() {
717
document.body.removeEventListener("keydown", this._handleBodyKeyDownBound, false);
720
CalendarPicker.prototype._layout = function() {
721
if (this._config.isCalendarRTL)
722
this._element.classList.add("rtl");
723
this._yearMonthController.attachTo(this._element);
724
this._daysTable.attachTo(this._element);
725
this._layoutButtons();
726
// DaysTable will have focus but we don't want to show its focus ring until the first key event.
727
this._element.classList.add(ClassNames.NoFocusRing);
730
CalendarPicker.prototype.handleToday = function() {
731
var today = this.selectionConstructor.createFromToday();
732
this._daysTable.selectRangeAndShowEntireRange(today);
733
this.submitValue(today.toString());
736
CalendarPicker.prototype.handleClear = function() {
737
this.submitValue("");
740
CalendarPicker.prototype.fixWindowSize = function() {
741
var yearMonthRightElement = this._element.getElementsByClassName(ClassNames.YearMonthButtonRight)[0];
742
var daysAreaElement = this._element.getElementsByClassName(ClassNames.DaysArea)[0];
743
var headers = daysAreaElement.getElementsByClassName(ClassNames.DayLabel);
744
var maxCellWidth = 0;
745
for (var i = 1; i < headers.length; ++i) {
746
if (maxCellWidth < headers[i].offsetWidth)
747
maxCellWidth = headers[i].offsetWidth;
749
var weekColumnWidth = headers[0].offsetWidth;
750
if (maxCellWidth > weekColumnWidth)
751
weekColumnWidth = maxCellWidth;
752
headers[0].style.width = weekColumnWidth + "px";
753
var DaysAreaContainerBorder = 1;
756
if (global.params.isCalendarRTL) {
757
var startOffset = this._element.offsetLeft + this._element.offsetWidth;
758
yearMonthEnd = startOffset - yearMonthRightElement.offsetLeft;
759
daysAreaEnd = startOffset - (daysAreaElement.offsetLeft + daysAreaElement.offsetWidth) + weekColumnWidth + maxCellWidth * 7 + DaysAreaContainerBorder;
761
yearMonthEnd = yearMonthRightElement.offsetLeft + yearMonthRightElement.offsetWidth;
762
daysAreaEnd = daysAreaElement.offsetLeft + weekColumnWidth + maxCellWidth * 7 + DaysAreaContainerBorder;
764
var maxEnd = Math.max(yearMonthEnd, daysAreaEnd);
765
var MainPadding = 6; // FIXME: Fix name.
767
var desiredBodyWidth = maxEnd + MainPadding + MainBorder;
769
var elementHeight = this._element.offsetHeight;
770
this._element.style.width = "auto";
771
daysAreaElement.style.width = "100%";
772
daysAreaElement.style.tableLayout = "fixed";
773
this._element.getElementsByClassName(ClassNames.YearMonthUpper)[0].style.display = "-webkit-box";
774
this._element.getElementsByClassName(ClassNames.MonthSelectorBox)[0].style.display = "block";
775
resizeWindow(desiredBodyWidth, elementHeight);
778
CalendarPicker.prototype._layoutButtons = function() {
779
var container = createElement("div", ClassNames.TodayClearArea);
780
this.today = createElement("input", ClassNames.TodayButton);
781
this.today.disabled = !this.isValidDate(this.selectionConstructor.createFromToday());
782
this.today.type = "button";
783
this.today.value = this._config.todayLabel;
784
this.today.addEventListener("click", this.handleToday.bind(this), false);
785
container.appendChild(this.today);
787
if (!this._config.required) {
788
this.clear = createElement("input", ClassNames.ClearButton);
789
this.clear.type = "button";
790
this.clear.value = this._config.clearLabel;
791
this.clear.addEventListener("click", this.handleClear.bind(this), false);
792
container.appendChild(this.clear);
794
this._element.appendChild(container);
796
this.lastFocusableControl = this.clear || this.today;
800
* @param {!Month} month
803
CalendarPicker.prototype.shouldShowMonth = function(month) {
804
return this._minimumMonth.valueOf() <= month.valueOf() && this.maximumMonth.valueOf() >= month.valueOf();
808
* @param {!Month} month
809
* @param {!CalendarPicker.NavigationBehaviour=} navigationBehaviour
811
CalendarPicker.prototype.showMonth = function(month, navigationBehaviour) {
812
if (this._currentMonth.equals(month))
814
else if (month.valueOf() < this._minimumMonth.valueOf())
815
month = this._minimumMonth;
816
else if (month.valueOf() > this.maximumMonth.valueOf())
817
month = this.maximumMonth;
818
this._yearMonthController.setMonth(month);
819
this._daysTable.navigateToMonth(month, navigationBehaviour || CalendarPicker.NavigationBehaviour.None);
820
this._currentMonth = month;
826
CalendarPicker.prototype.currentMonth = function() {
827
return this._currentMonth;
830
// ----------------------------------------------------------------
834
* @param {!CalendarPicker} picker
836
function YearMonthController(picker) {
837
this.picker = picker;
841
* @param {!Element} element
843
YearMonthController.prototype.attachTo = function(element) {
844
var outerContainer = createElement("div", ClassNames.YearMonthArea);
846
var innerContainer = createElement("div", ClassNames.YearMonthUpper);
847
outerContainer.appendChild(innerContainer);
849
this._attachLeftButtonsTo(innerContainer);
851
var box = createElement("div", ClassNames.MonthSelectorBox);
852
innerContainer.appendChild(box);
853
// We can't use <select> popup in PagePopup.
854
this._monthPopup = createElement("div", ClassNames.MonthSelectorPopup);
855
this._monthPopup.addEventListener("click", this._handleYearMonthChange.bind(this), false);
856
this._monthPopup.addEventListener("keydown", this._handleMonthPopupKey.bind(this), false);
857
this._monthPopup.addEventListener("mousemove", this._handleMouseMove.bind(this), false);
858
this._updateSelectionOnMouseMove = true;
859
this._monthPopup.tabIndex = 0;
860
this._monthPopupContents = createElement("div", ClassNames.MonthSelectorPopupContents);
861
this._monthPopup.appendChild(this._monthPopupContents);
862
box.appendChild(this._monthPopup);
863
this._month = createElement("div", ClassNames.MonthSelector);
864
this._month.addEventListener("click", this._showPopup.bind(this), false);
865
box.appendChild(this._month);
867
this._attachRightButtonsTo(innerContainer);
868
element.appendChild(outerContainer);
870
this._wall = createElement("div", ClassNames.MonthSelectorWall);
871
this._wall.addEventListener("click", this._closePopup.bind(this), false);
872
element.appendChild(this._wall);
874
var month = this.picker.maximumMonth;
876
for (var m = 0; m < 12; ++m) {
877
this._month.textContent = month.toLocaleString();
878
maxWidth = Math.max(maxWidth, this._month.offsetWidth);
879
month = month.previous();
881
if (getLanguage() == "ja" && ImperialEraLimit < this.picker.maximumMonth.year) {
882
for (var m = 0; m < 12; ++m) {
883
this._month.textContent = new Month(ImperialEraLimit, m).toLocaleString();
884
maxWidth = Math.max(maxWidth, this._month.offsetWidth);
887
this._month.style.minWidth = maxWidth + 'px';
889
this.picker.firstFocusableControl = this._left2; // FIXME: Should it be this.month?
892
YearMonthController.addTenYearsButtons = false;
895
* @param {!Element} parent
897
YearMonthController.prototype._attachLeftButtonsTo = function(parent) {
898
var container = createElement("div", ClassNames.YearMonthButtonLeft);
899
parent.appendChild(container);
901
if (YearMonthController.addTenYearsButtons) {
902
this._left3 = createElement("input", ClassNames.YearMonthButton);
903
this._left3.type = "button";
904
this._left3.value = "<<<";
905
this._left3.addEventListener("click", this._handleButtonClick.bind(this), false);
906
container.appendChild(this._left3);
909
this._left2 = createElement("input", ClassNames.YearMonthButton);
910
this._left2.type = "button";
911
this._left2.value = "<<";
912
this._left2.addEventListener("click", this._handleButtonClick.bind(this), false);
913
container.appendChild(this._left2);
915
this._left1 = createElement("input", ClassNames.YearMonthButton);
916
this._left1.type = "button";
917
this._left1.value = "<";
918
this._left1.addEventListener("click", this._handleButtonClick.bind(this), false);
919
container.appendChild(this._left1);
923
* @param {!Element} parent
925
YearMonthController.prototype._attachRightButtonsTo = function(parent) {
926
var container = createElement("div", ClassNames.YearMonthButtonRight);
927
parent.appendChild(container);
928
this._right1 = createElement("input", ClassNames.YearMonthButton);
929
this._right1.type = "button";
930
this._right1.value = ">";
931
this._right1.addEventListener("click", this._handleButtonClick.bind(this), false);
932
container.appendChild(this._right1);
934
this._right2 = createElement("input", ClassNames.YearMonthButton);
935
this._right2.type = "button";
936
this._right2.value = ">>";
937
this._right2.addEventListener("click", this._handleButtonClick.bind(this), false);
938
container.appendChild(this._right2);
940
if (YearMonthController.addTenYearsButtons) {
941
this._right3 = createElement("input", ClassNames.YearMonthButton);
942
this._right3.type = "button";
943
this._right3.value = ">>>";
944
this._right3.addEventListener("click", this._handleButtonClick.bind(this), false);
945
container.appendChild(this._right3);
950
* @param {!Month} month
952
YearMonthController.prototype.setMonth = function(month) {
953
var monthValue = month.valueOf();
955
this._left3.disabled = !this.picker.shouldShowMonth(new Month(monthValue - 13));
956
this._left2.disabled = !this.picker.shouldShowMonth(new Month(monthValue - 2));
957
this._left1.disabled = !this.picker.shouldShowMonth(new Month(monthValue - 1));
958
this._right1.disabled = !this.picker.shouldShowMonth(new Month(monthValue + 1));
959
this._right2.disabled = !this.picker.shouldShowMonth(new Month(monthValue + 2));
961
this._left3.disabled = !this.picker.shouldShowMonth(new Month(monthValue + 13));
962
this._month.innerText = month.toLocaleString();
963
while (this._monthPopupContents.hasChildNodes())
964
this._monthPopupContents.removeChild(this._monthPopupContents.firstChild);
966
for (var m = monthValue - 6; m <= monthValue + 6; m++) {
967
var month = new Month(m);
968
if (!this.picker.shouldShowMonth(month))
970
var option = createElement("div", ClassNames.MonthSelectorPopupEntry, month.toLocaleString());
971
option.dataset.value = month.toString();
972
this._monthPopupContents.appendChild(option);
974
option.classList.add(ClassNames.SelectedMonthYear);
978
YearMonthController.prototype._showPopup = function() {
979
this._monthPopup.style.display = "block";
980
this._monthPopup.style.zIndex = "1000"; // Larger than the days area.
981
this._monthPopup.style.left = this._month.offsetLeft + (this._month.offsetWidth - this._monthPopup.offsetWidth) / 2 + "px";
982
this._monthPopup.style.top = this._month.offsetTop + this._month.offsetHeight + "px";
984
this._wall.style.display = "block";
985
this._wall.style.zIndex = "999"; // This should be smaller than the z-index of monthPopup.
987
var popupHeight = this._monthPopup.clientHeight;
988
var fullHeight = this._monthPopupContents.clientHeight;
989
if (fullHeight > popupHeight) {
990
var selected = this._getSelection();
992
var bottom = selected.offsetTop + selected.clientHeight;
993
if (bottom > popupHeight)
994
this._monthPopup.scrollTop = bottom - popupHeight;
996
this._monthPopup.style.webkitPaddingEnd = getScrollbarWidth() + 'px';
998
this._monthPopup.focus();
1001
YearMonthController.prototype._closePopup = function() {
1002
this._monthPopup.style.display = "none";
1003
this._wall.style.display = "none";
1004
var container = document.querySelector("." + ClassNames.DaysAreaContainer);
1009
* @return {Element} Selected element in the month-year popup.
1011
YearMonthController.prototype._getSelection = function()
1013
return document.querySelector("." + ClassNames.SelectedMonthYear);
1017
* @param {Event} event
1019
YearMonthController.prototype._handleMouseMove = function(event)
1021
if (!this._updateSelectionOnMouseMove) {
1022
// Selection update turned off while navigating with keyboard to prevent a mouse
1023
// move trigged during a scroll from resetting the selection. Automatically
1024
// rearm control to enable mouse-based selection.
1025
this._updateSelectionOnMouseMove = true;
1027
var target = event.target;
1028
var selection = this._getSelection();
1029
if (target && target != selection && target.classList.contains(ClassNames.MonthSelectorPopupEntry)) {
1031
selection.classList.remove(ClassNames.SelectedMonthYear);
1032
target.classList.add(ClassNames.SelectedMonthYear);
1035
event.stopPropagation();
1036
event.preventDefault();
1040
* @param {Event} event
1042
YearMonthController.prototype._handleMonthPopupKey = function(event)
1044
var key = event.keyIdentifier;
1045
if (key == "Down") {
1046
var selected = this._getSelection();
1048
var next = selected.nextSibling;
1050
selected.classList.remove(ClassNames.SelectedMonthYear);
1051
next.classList.add(ClassNames.SelectedMonthYear);
1052
var bottom = next.offsetTop + next.clientHeight;
1053
if (bottom > this._monthPopup.scrollTop + this._monthPopup.clientHeight) {
1054
this._updateSelectionOnMouseMove = false;
1055
this._monthPopup.scrollTop = bottom - this._monthPopup.clientHeight;
1059
event.stopPropagation();
1060
event.preventDefault();
1061
} else if (key == "Up") {
1062
var selected = this._getSelection();
1064
var previous = selected.previousSibling;
1066
selected.classList.remove(ClassNames.SelectedMonthYear);
1067
previous.classList.add(ClassNames.SelectedMonthYear);
1068
if (previous.offsetTop < this._monthPopup.scrollTop) {
1069
this._updateSelectionOnMouseMove = false;
1070
this._monthPopup.scrollTop = previous.offsetTop;
1074
event.stopPropagation();
1075
event.preventDefault();
1076
} else if (key == "U+001B") {
1078
event.stopPropagation();
1079
event.preventDefault();
1080
} else if (key == "Enter") {
1081
this._handleYearMonthChange();
1082
event.stopPropagation();
1083
event.preventDefault();
1087
YearMonthController.prototype._handleYearMonthChange = function() {
1089
var selection = this._getSelection();
1092
this.picker.showMonth(Month.parse(selection.dataset.value));
1099
YearMonthController.PreviousTenYears = -120;
1104
YearMonthController.PreviousYear = -12;
1109
YearMonthController.PreviousMonth = -1;
1114
YearMonthController.NextMonth = 1;
1119
YearMonthController.NextYear = 12;
1124
YearMonthController.NextTenYears = 120;
1127
* @param {Event} event
1129
YearMonthController.prototype._handleButtonClick = function(event) {
1130
if (event.target == this._left3)
1131
this.moveRelatively(YearMonthController.PreviousTenYears);
1132
else if (event.target == this._left2)
1133
this.moveRelatively(YearMonthController.PreviousYear);
1134
else if (event.target == this._left1)
1135
this.moveRelatively(YearMonthController.PreviousMonth);
1136
else if (event.target == this._right1)
1137
this.moveRelatively(YearMonthController.NextMonth)
1138
else if (event.target == this._right2)
1139
this.moveRelatively(YearMonthController.NextYear);
1140
else if (event.target == this._right3)
1141
this.moveRelatively(YearMonthController.NextTenYears);
1147
* @param {!number} amount
1149
YearMonthController.prototype.moveRelatively = function(amount) {
1150
var current = this.picker.currentMonth().valueOf();
1151
var updated = new Month(current + amount);
1152
this.picker.showMonth(updated, CalendarPicker.NavigationBehaviour.Animate | CalendarPicker.NavigationBehaviour.KeepSelectionPosition);
1155
// ----------------------------------------------------------------
1159
* @param {!CalendarPicker} picker
1161
function DaysTable(picker) {
1162
this.picker = picker;
1166
* @return {!boolean}
1168
DaysTable.prototype._hasSelection = function() {
1169
return !!this._firstNodeInSelectedRange();
1173
* The number of week lines in the screen.
1177
DaysTable._Weeks = 6;
1180
* @param {!Element} element
1182
DaysTable.prototype.attachTo = function(element) {
1183
this._daysContainer = createElement("table", ClassNames.DaysArea);
1184
this._daysContainer.addEventListener("click", this._handleDayClick.bind(this), false);
1185
this._daysContainer.addEventListener("mouseover", this._handleMouseOver.bind(this), false);
1186
this._daysContainer.addEventListener("mouseout", this._handleMouseOut.bind(this), false);
1187
this._daysContainer.addEventListener("webkitTransitionEnd", this._moveInDays.bind(this), false);
1188
var container = createElement("tr", ClassNames.DayLabelContainer);
1189
var weekStartDay = global.params.weekStartDay || 0;
1190
container.appendChild(createElement("th", ClassNames.DayLabel + " " + ClassNames.WeekColumn, global.params.weekLabel));
1191
for (var i = 0; i < 7; i++)
1192
container.appendChild(createElement("th", ClassNames.DayLabel, global.params.dayLabels[(weekStartDay + i) % 7]));
1193
this._daysContainer.appendChild(container);
1195
this._weekNumbers = [];
1196
for (var w = 0; w < DaysTable._Weeks; w++) {
1197
container = createElement("tr", ClassNames.WeekContainer);
1199
var weekNumberNode = createElement("td", ClassNames.Day + " " + ClassNames.WeekColumn, " ");
1200
weekNumberNode.dataset.positionX = -1;
1201
weekNumberNode.dataset.positionY = w;
1202
this._weekNumbers.push(weekNumberNode);
1203
container.appendChild(weekNumberNode);
1204
for (var d = 0; d < 7; d++) {
1205
var day = createElement("td", ClassNames.Day, " ");
1206
day.setAttribute("data-position-x", String(d));
1207
day.setAttribute("data-position-y", String(w));
1209
container.appendChild(day);
1211
this._days.push(week);
1212
this._daysContainer.appendChild(container);
1214
container = createElement("div", ClassNames.DaysAreaContainer);
1215
container.appendChild(this._daysContainer);
1216
container.tabIndex = 0;
1217
container.addEventListener("keydown", this._handleKey.bind(this), false);
1218
element.appendChild(container);
1224
* @param {!number} value
1225
* @return {!boolean}
1227
CalendarPicker.prototype._stepMismatch = function(value) {
1228
var nextAllowedValue = Math.ceil((value - this.stepBase) / this.step) * this.step + this.stepBase;
1229
return nextAllowedValue >= value + this.selectionConstructor.DefaultStep
1233
* @param {!number} value
1234
* @return {!boolean}
1236
CalendarPicker.prototype._outOfRange = function(value) {
1237
return value < this._minimumValue || value > this._maximumValue;
1241
* @param {!Month|Day} range
1242
* @return {!boolean}
1244
CalendarPicker.prototype.isValidDate = function(range) {
1245
var value = range.valueOf();
1246
return !this._outOfRange(value) && !this._stepMismatch(value);
1250
* @param {!Month} month
1252
DaysTable.prototype._renderMonth = function(month) {
1253
var dayIterator = month.startDate();
1254
var monthStartDay = dayIterator.getUTCDay();
1255
var weekStartDay = global.params.weekStartDay || 0;
1256
var startOffset = weekStartDay - monthStartDay;
1257
if (startOffset >= 0)
1259
dayIterator.setUTCDate(startOffset + 1);
1260
var mondayOffset = (8 - weekStartDay) % 7;
1261
var sundayOffset = weekStartDay % 7;
1262
for (var w = 0; w < DaysTable._Weeks; w++) {
1263
for (var d = 0; d < 7; d++) {
1264
var iterMonth = Month.createFromDate(dayIterator);
1265
var iterWeek = Week.createFromDate(dayIterator);
1266
var time = dayIterator.getTime();
1267
var element = this._days[w][d];
1268
element.innerText = localizeNumber(dayIterator.getUTCDate());
1269
element.className = ClassNames.Day;
1270
element.dataset.submitValue = Day.createFromDate(dayIterator).toString();
1271
element.dataset.weekValue = iterWeek.toString();
1272
element.dataset.monthValue = iterMonth.toString();
1274
element.innerText = "-";
1275
element.classList.add(ClassNames.Unavailable);
1276
} else if (!this.picker.isValidDate(this._rangeForNode(element)))
1277
element.classList.add(ClassNames.Unavailable);
1278
else if (!iterMonth.equals(month)) {
1279
element.classList.add(ClassNames.Available);
1280
element.classList.add(ClassNames.NotThisMonth);
1282
element.classList.add(ClassNames.Available);
1283
if (d === mondayOffset) {
1284
element.classList.add(ClassNames.Monday);
1285
if (this._weekNumbers[w]) {
1286
this._weekNumbers[w].dataset.weekValue = iterWeek.toString();
1287
this._weekNumbers[w].innerText = localizeNumber(iterWeek.week);
1288
if (element.classList.contains(ClassNames.Available))
1289
this._weekNumbers[w].classList.add(ClassNames.Available);
1291
this._weekNumbers[w].classList.add(ClassNames.Unavailable);
1293
} else if (d === sundayOffset)
1294
element.classList.add(ClassNames.Sunday);
1295
dayIterator.setUTCDate(dayIterator.getUTCDate() + 1);
1301
* @param {!Month} month
1302
* @param {!CalendarPicker.NavigationBehaviour} navigationBehaviour
1304
DaysTable.prototype.navigateToMonth = function(month, navigationBehaviour) {
1305
var firstNodeInSelectedRange = this._firstNodeInSelectedRange();
1306
if (navigationBehaviour & CalendarPicker.NavigationBehaviour.Animate)
1307
this._startMoveInAnimation(month);
1308
this._renderMonth(month);
1309
if (navigationBehaviour & CalendarPicker.NavigationBehaviour.KeepSelectionPosition && firstNodeInSelectedRange) {
1310
var x = parseInt(firstNodeInSelectedRange.dataset.positionX, 10);
1311
var y = parseInt(firstNodeInSelectedRange.dataset.positionY, 10);
1312
this._selectRangeAtPosition(x, y);
1317
* @param {!Month} month
1319
DaysTable.prototype._startMoveInAnimation = function(month) {
1320
if (this.picker.isPreparing)
1322
var daysStyle = this._daysContainer.style;
1323
daysStyle.position = "relative";
1324
daysStyle.webkitTransition = "left 0.1s ease";
1325
daysStyle.left = (this.picker.currentMonth().valueOf() > month.valueOf() ? "" : "-") + this._daysContainer.offsetWidth + "px";
1328
DaysTable.prototype._moveInDays = function() {
1329
var daysStyle = this._daysContainer.style;
1330
if (daysStyle.left == "0px")
1332
daysStyle.webkitTransition = "";
1333
daysStyle.left = (daysStyle.left.charAt(0) == "-" ? "" : "-") + this._daysContainer.offsetWidth + "px";
1334
this._daysContainer.offsetLeft; // Force to layout.
1335
daysStyle.webkitTransition = "left 0.1s ease";
1336
daysStyle.left = "0px";
1342
DaysTable.prototype._markRangeAsSelected = function(day) {
1343
var dateString = day.toString();
1344
for (var w = 0; w < DaysTable._Weeks; w++) {
1345
for (var d = 0; d < 7; d++) {
1346
if (this._days[w][d].dataset.submitValue == dateString) {
1347
this._days[w][d].classList.add(ClassNames.Selected);
1357
DaysTable.prototype.selectRange = function(day) {
1359
if (this.startDate() > day.startDate() || this.endDate() < day.endDate())
1360
this.picker.showMonth(Month.createFromDate(day.startDate()), CalendarPicker.NavigationBehaviour.Animate);
1361
this._markRangeAsSelected(day);
1367
DaysTable.prototype.selectRangeAndShowEntireRange = function(day) {
1368
this.selectRange(day);
1372
* @param {!Element} dayNode
1374
DaysTable.prototype._selectRangeContainingNode = function(dayNode) {
1375
var range = this._rangeForNode(dayNode);
1378
this.selectRange(range);
1382
* @param {!Element} dayNode
1385
DaysTable.prototype._rangeForNode = function(dayNode) {
1388
return Day.parse(dayNode.dataset.submitValue);
1394
DaysTable.prototype.startDate = function() {
1395
return Day.parse(this._days[0][0].dataset.submitValue).startDate();
1401
DaysTable.prototype.endDate = function() {
1402
return Day.parse(this._days[DaysTable._Weeks - 1][7 - 1].dataset.submitValue).endDate();
1406
* @param {!number} x
1407
* @param {!number} y
1409
DaysTable.prototype._selectRangeAtPosition = function(x, y) {
1410
var node = x === -1 ? this._weekNumbers[y] : this._days[y][x];
1411
this._selectRangeContainingNode(node);
1415
* @return {!Element}
1417
DaysTable.prototype._firstNodeInSelectedRange = function() {
1418
return this._daysContainer.getElementsByClassName(ClassNames.Selected)[0];
1421
DaysTable.prototype._deselect = function() {
1422
var selectedNodes = this._daysContainer.getElementsByClassName(ClassNames.Selected);
1423
for (var node = selectedNodes[0]; node; node = selectedNodes[0])
1424
node.classList.remove(ClassNames.Selected);
1428
* @param {!CalendarPicker.NavigationBehaviour=} navigationBehaviour
1429
* @return {!boolean}
1431
DaysTable.prototype._maybeSetPreviousMonth = function(navigationBehaviour) {
1432
if (typeof navigationBehaviour === "undefined")
1433
navigationBehaviour = CalendarPicker.NavigationBehaviour.Animate;
1434
var previousMonth = this.picker.currentMonth().previous();
1435
if (!this.picker.shouldShowMonth(previousMonth))
1437
this.picker.showMonth(previousMonth, navigationBehaviour);
1442
* @param {!CalendarPicker.NavigationBehaviour=} navigationBehaviour
1443
* @return {!boolean}
1445
DaysTable.prototype._maybeSetNextMonth = function(navigationBehaviour) {
1446
if (typeof navigationBehaviour === "undefined")
1447
navigationBehaviour = CalendarPicker.NavigationBehaviour.Animate;
1448
var nextMonth = this.picker.currentMonth().next();
1449
if (!this.picker.shouldShowMonth(nextMonth))
1451
this.picker.showMonth(nextMonth, navigationBehaviour);
1456
* @param {Event} event
1458
DaysTable.prototype._handleDayClick = function(event) {
1459
if (event.target.classList.contains(ClassNames.Available))
1460
this.picker.submitValue(this._rangeForNode(event.target).toString());
1464
* @param {Event} event
1466
DaysTable.prototype._handleMouseOver = function(event) {
1467
var node = event.target;
1468
if (node.classList.contains(ClassNames.Selected))
1470
this._selectRangeContainingNode(node);
1474
* @param {Event} event
1476
DaysTable.prototype._handleMouseOut = function(event) {
1481
* @param {Event} event
1483
DaysTable.prototype._handleKey = function(event) {
1484
this.picker.maybeUpdateFocusStyle();
1487
var key = event.keyIdentifier;
1488
var firstNodeInSelectedRange = this._firstNodeInSelectedRange();
1489
if (firstNodeInSelectedRange) {
1490
x = parseInt(firstNodeInSelectedRange.dataset.positionX, 10);
1491
y = parseInt(firstNodeInSelectedRange.dataset.positionY, 10);
1493
if (!this._hasSelection() && (key == "Left" || key == "Up" || key == "Right" || key == "Down")) {
1494
// Put the selection on a center cell.
1495
this.updateSelection(event, 3, Math.floor(DaysTable._Weeks / 2 - 1));
1499
if (key == (global.params.isCalendarRTL ? "Right" : "Left")) {
1502
if (!this._maybeSetPreviousMonth())
1504
y = DaysTable._Weeks - 1;
1510
this.updateSelection(event, x, y);
1512
} else if (key == "Up") {
1514
if (!this._maybeSetPreviousMonth())
1516
y = DaysTable._Weeks - 1;
1519
this.updateSelection(event, x, y);
1521
} else if (key == (global.params.isCalendarRTL ? "Left" : "Right")) {
1523
if (y == DaysTable._Weeks - 1) {
1524
if (!this._maybeSetNextMonth())
1532
this.updateSelection(event, x, y);
1534
} else if (key == "Down") {
1535
if (y == DaysTable._Weeks - 1) {
1536
if (!this._maybeSetNextMonth())
1541
this.updateSelection(event, x, y);
1543
} else if (key == "PageUp") {
1544
if (!this._maybeSetPreviousMonth())
1546
this.updateSelection(event, x, y);
1548
} else if (key == "PageDown") {
1549
if (!this._maybeSetNextMonth())
1551
this.updateSelection(event, x, y);
1553
} else if (this._hasSelection() && key == "Enter") {
1554
var dayNode = this._days[y][x];
1555
if (dayNode.classList.contains(ClassNames.Available)) {
1556
this.picker.submitValue(dayNode.dataset.submitValue);
1557
event.stopPropagation();
1560
} else if (key == "U+0054") { // 't'
1561
this.selectRangeAndShowEntireRange(Day.createFromToday());
1562
event.stopPropagation();
1563
event.preventDefault();
1568
* @param {Event} event
1569
* @param {!number} x
1570
* @param {!number} y
1572
DaysTable.prototype.updateSelection = function(event, x, y) {
1573
this._selectRangeAtPosition(x, y);
1574
event.stopPropagation();
1575
event.preventDefault();
1580
* @param{!CalendarPicker} picker
1582
function MonthPickerDaysTable(picker) {
1583
DaysTable.call(this, picker);
1585
MonthPickerDaysTable.prototype = Object.create(DaysTable.prototype);
1588
* @param {!Month} month
1589
* @param {!CalendarPicker.NavigationBehaviour} navigationBehaviour
1591
MonthPickerDaysTable.prototype.navigateToMonth = function(month, navigationBehaviour) {
1592
var hadSelection = this._hasSelection();
1593
if (navigationBehaviour & CalendarPicker.NavigationBehaviour.Animate)
1594
this._startMoveInAnimation(month);
1595
this._renderMonth(month);
1596
if (navigationBehaviour & CalendarPicker.NavigationBehaviour.KeepSelectionPosition && hadSelection)
1597
this.selectRange(month);
1601
* @param {!Month} month
1603
MonthPickerDaysTable.prototype._markRangeAsSelected = function(month) {
1604
var monthString = month.toString();
1605
for (var w = 0; w < DaysTable._Weeks; w++) {
1606
for (var d = 0; d < 7; d++) {
1607
if (this._days[w][d].dataset.monthValue == monthString) {
1608
this._days[w][d].classList.add(ClassNames.Selected);
1615
* @param {!Month} month
1617
MonthPickerDaysTable.prototype.selectRange = function(month) {
1619
if (this.startDate() >= month.endDate() || this.endDate() <= month.startDate())
1620
this.picker.showMonth(month, CalendarPicker.NavigationBehaviour.Animate);
1621
this._markRangeAsSelected(month);
1625
* @param {!Month} month
1627
MonthPickerDaysTable.prototype.selectRangeAndShowEntireRange = function(month) {
1629
this.picker.showMonth(month, CalendarPicker.NavigationBehaviour.Animate);
1630
this._markRangeAsSelected(month);
1634
* @param {!Element} dayNode
1637
MonthPickerDaysTable.prototype._rangeForNode = function(dayNode) {
1640
return Month.parse(dayNode.dataset.monthValue);
1644
* @param {Event} event
1646
MonthPickerDaysTable.prototype._handleKey = function(event) {
1647
this.picker.maybeUpdateFocusStyle();
1648
var key = event.keyIdentifier;
1649
var eventHandled = false;
1650
var currentMonth = this.picker.currentMonth();
1651
var firstNodeInSelectedRange = this._firstNodeInSelectedRange();
1652
var selectedMonth = this._rangeForNode(firstNodeInSelectedRange);
1653
if (!firstNodeInSelectedRange
1654
&& (key == "Right" || key == "Left" || key == "Up" || key == "Down" || key == "PageUp" || key == "PageDown")) {
1655
this.selectRange(currentMonth);
1656
eventHandled = true;
1657
} else if (key == (global.params.isCalendarRTL ? "Right" : "Left") || key == "Up" || key == "PageUp") {
1658
if (selectedMonth.valueOf() > currentMonth.valueOf())
1659
this.selectRangeAndShowEntireRange(currentMonth);
1661
this.selectRangeAndShowEntireRange(currentMonth.previous());
1662
eventHandled = true;
1663
} else if (key == (global.params.isCalendarRTL ? "Left" : "Right") || key == "Down" || key == "PageDown") {
1664
if (selectedMonth.valueOf() < currentMonth.valueOf())
1665
this.selectRangeAndShowEntireRange(currentMonth);
1667
this.selectRangeAndShowEntireRange(currentMonth.next());
1668
eventHandled = true;
1669
} else if (selectedMonth && key == "Enter") {
1670
if (firstNodeInSelectedRange.classList.contains(ClassNames.Available)) {
1671
this.picker.submitValue(selectedMonth.toString());
1672
eventHandled = true;
1674
} else if (key == "U+0054") { // 't'
1675
this.selectRangeAndShowEntireRange(Month.createFromToday());
1676
eventHandled = true;
1679
event.stopPropagation();
1680
event.preventDefault();
1686
* @param{!CalendarPicker} picker
1688
function WeekPickerDaysTable(picker) {
1689
DaysTable.call(this, picker);
1691
WeekPickerDaysTable.prototype = Object.create(DaysTable.prototype);
1694
* @param {!Week} week
1696
WeekPickerDaysTable.prototype._markRangeAsSelected = function(week) {
1697
var weekString = week.toString();
1698
for (var w = 0; w < DaysTable._Weeks; w++) {
1699
for (var d = 0; d < 7; d++) {
1700
if (this._days[w][d].dataset.weekValue == weekString) {
1701
this._days[w][d].classList.add(ClassNames.Selected);
1705
for (var i = 0; i < this._weekNumbers.length; ++i) {
1706
if (this._weekNumbers[i].dataset.weekValue === weekString) {
1707
this._weekNumbers[i].classList.add(ClassNames.Selected);
1714
* @param {!Week} week
1716
WeekPickerDaysTable.prototype.selectRange = function(week) {
1718
var weekStartDate = week.startDate();
1719
var weekEndDate = week.endDate();
1720
if (this.startDate() >= weekEndDate)
1721
this.picker.showMonth(Month.createFromDate(weekEndDate), CalendarPicker.NavigationBehaviour.Animate);
1722
else if (this.endDate() <= weekStartDate)
1723
this.picker.showMonth(Month.createFromDate(weekStartDate), CalendarPicker.NavigationBehaviour.Animate);
1724
this._markRangeAsSelected(week);
1728
* @param {!Week} week
1730
WeekPickerDaysTable.prototype.selectRangeAndShowEntireRange = function(week) {
1732
var weekStartDate = week.startDate();
1733
var weekEndDate = week.endDate();
1734
if (this.startDate() > weekStartDate)
1735
this.picker.showMonth(Month.createFromDate(weekStartDate), CalendarPicker.NavigationBehaviour.Animate);
1736
else if (this.endDate() < weekEndDate)
1737
this.picker.showMonth(Month.createFromDate(weekEndDate), CalendarPicker.NavigationBehaviour.Animate);
1738
this._markRangeAsSelected(week);
1742
* @param {!Element} dayNode
1745
WeekPickerDaysTable.prototype._rangeForNode = function(dayNode) {
1748
return Week.parse(dayNode.dataset.weekValue);
1752
* @param {!Event} event
1754
WeekPickerDaysTable.prototype._handleKey = function(event) {
1755
this.picker.maybeUpdateFocusStyle();
1756
var key = event.keyIdentifier;
1757
var eventHandled = false;
1758
var currentMonth = this.picker.currentMonth();
1759
var firstNodeInSelectedRange = this._firstNodeInSelectedRange();
1760
var selectedWeek = this._rangeForNode(firstNodeInSelectedRange);
1761
if (!firstNodeInSelectedRange
1762
&& (key == "Right" || key == "Left" || key == "Up" || key == "Down" || key == "PageUp" || key == "PageDown")) {
1763
// Put the selection on a center cell.
1764
this._selectRangeAtPosition(3, Math.floor(DaysTable._Weeks / 2 - 1));
1765
} else if (key == (global.params.isCalendarRTL ? "Right" : "Left") || key == "Up") {
1766
this.selectRangeAndShowEntireRange(selectedWeek.previous());
1767
eventHandled = true;
1768
} else if (key == (global.params.isCalendarRTL ? "Left" : "Right") || key == "Down") {
1769
this.selectRangeAndShowEntireRange(selectedWeek.next());
1770
eventHandled = true;
1771
} else if (key == "PageUp") {
1772
if (!this._maybeSetPreviousMonth(CalendarPicker.NavigationBehaviour.Animate | CalendarPicker.NavigationBehaviour.KeepSelectionPosition))
1774
eventHandled = true;
1775
} else if (key == "PageDown") {
1776
if (!this._maybeSetNextMonth(CalendarPicker.NavigationBehaviour.Animate | CalendarPicker.NavigationBehaviour.KeepSelectionPosition))
1778
eventHandled = true;
1779
} else if (selectedWeek && key == "Enter") {
1780
if (firstNodeInSelectedRange.classList.contains(ClassNames.Available)) {
1781
this.picker.submitValue(selectedWeek.toString());
1782
eventHandled = true;
1784
} else if (key == "U+0054") { // 't'
1785
this.selectRangeAndShowEntireRange(Week.createFromToday());
1786
eventHandled = true;
1789
event.stopPropagation();
1790
event.preventDefault();
1795
* @param {!Event} event
1797
CalendarPicker.prototype._handleBodyKeyDown = function(event) {
1798
this.maybeUpdateFocusStyle();
1799
var key = event.keyIdentifier;
1800
if (key == "U+0009") {
1801
if (!event.shiftKey && document.activeElement == this.lastFocusableControl) {
1802
event.stopPropagation();
1803
event.preventDefault();
1804
this.firstFocusableControl.focus();
1805
} else if (event.shiftKey && document.activeElement == this.firstFocusableControl) {
1806
event.stopPropagation();
1807
event.preventDefault();
1808
this.lastFocusableControl.focus();
1810
} else if (key == "U+004D") { // 'm'
1811
this._yearMonthController.moveRelatively(event.shiftKey ? YearMonthController.PreviousMonth : YearMonthController.NextMonth);
1812
} else if (key == "U+0059") { // 'y'
1813
this._yearMonthController.moveRelatively(event.shiftKey ? YearMonthController.PreviousYear : YearMonthController.NextYear);
1814
} else if (key == "U+0044") { // 'd'
1815
this._yearMonthController.moveRelatively(event.shiftKey ? YearMonthController.PreviousTenYears : YearMonthController.NextTenYears);
1816
} else if (key == "U+001B") // ESC
1817
this.handleCancel();
1820
CalendarPicker.prototype.maybeUpdateFocusStyle = function() {
1821
if (this._hadKeyEvent)
1823
this._hadKeyEvent = true;
1824
this._element.classList.remove(ClassNames.NoFocusRing);
1827
if (window.dialogArguments) {
1828
initialize(dialogArguments);
1830
window.addEventListener("message", handleMessage, false);
1831
window.setTimeout(handleArgumentsTimeout, 1000);