~ubuntu-branches/ubuntu/raring/qtwebkit-source/raring-proposed

« back to all changes in this revision

Viewing changes to Source/WebCore/Resources/pagepopups/calendarPicker.js

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2013-02-18 14:24:18 UTC
  • Revision ID: package-import@ubuntu.com-20130218142418-eon0jmjg3nj438uy
Tags: upstream-2.3
ImportĀ upstreamĀ versionĀ 2.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"use strict";
 
2
/*
 
3
 * Copyright (C) 2012 Google Inc. All rights reserved.
 
4
 *
 
5
 * Redistribution and use in source and binary forms, with or without
 
6
 * modification, are permitted provided that the following conditions are
 
7
 * met:
 
8
 *
 
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
 
14
 * distribution.
 
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.
 
18
 *
 
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.
 
30
 */
 
31
 
 
32
// FIXME:
 
33
//  - Touch event
 
34
 
 
35
/**
 
36
 * CSS class names.
 
37
 *
 
38
 * @enum {string}
 
39
 */
 
40
var ClassNames = {
 
41
    Available: "available",
 
42
    CancelButton: "cancel-button",
 
43
    ClearButton: "clear-button",
 
44
    Day: "day",
 
45
    DayLabel: "day-label",
 
46
    DayLabelContainer: "day-label-container",
 
47
    DaysArea: "days-area",
 
48
    DaysAreaContainer: "days-area-container",
 
49
    Monday: "monday",
 
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",
 
61
    Sunday: "sunday",
 
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"
 
73
};
 
74
 
 
75
/**
 
76
 * @type {Object}
 
77
 */
 
78
var global = {
 
79
    argumentsReceived: false,
 
80
    params: null,
 
81
    picker: null
 
82
};
 
83
 
 
84
// ----------------------------------------------------------------
 
85
// Utility functions
 
86
 
 
87
/**
 
88
 * @return {!string} lowercase locale name. e.g. "en-us"
 
89
 */
 
90
function getLocale() {
 
91
    return (global.params.locale || "en-us").toLowerCase();
 
92
}
 
93
 
 
94
/**
 
95
 * @return {!string} lowercase language code. e.g. "en"
 
96
 */
 
97
function getLanguage() {
 
98
    var locale = getLocale();
 
99
    var result = locale.match(/^([a-z]+)/);
 
100
    if (!result)
 
101
        return "en";
 
102
    return result[1];
 
103
}
 
104
 
 
105
/**
 
106
 * @param {!number} number
 
107
 * @return {!string}
 
108
 */
 
109
function localizeNumber(number) {
 
110
    return window.pagePopupController.localizeNumberString(number);
 
111
}
 
112
 
 
113
/**
 
114
 * @const
 
115
 * @type {number}
 
116
 */
 
117
var ImperialEraLimit = 2087;
 
118
 
 
119
/**
 
120
 * @param {!number} year
 
121
 * @param {!number} month
 
122
 * @return {!string}
 
123
 */
 
124
function formatJapaneseImperialEra(year, month) {
 
125
    // We don't show an imperial era if it is greater than 99 becase of space
 
126
    // limitation.
 
127
    if (year > ImperialEraLimit)
 
128
        return "";
 
129
    if (year > 1989)
 
130
        return "(å¹³ęˆ" + localizeNumber(year - 1988) + "幓)";
 
131
    if (year == 1989)
 
132
        return "(å¹³ęˆå…ƒå¹“)";
 
133
    if (year >= 1927)
 
134
        return "(ę˜­å’Œ" + localizeNumber(year - 1925) + "幓)";
 
135
    if (year > 1912)
 
136
        return "(å¤§ę­£" + localizeNumber(year - 1911) + "幓)";
 
137
    if (year == 1912 && month >= 7)
 
138
        return "(å¤§ę­£å…ƒå¹“)";
 
139
    if (year > 1868)
 
140
        return "(ę˜Žę²»" + localizeNumber(year - 1867) + "幓)";
 
141
    if (year == 1868)
 
142
        return "(ę˜Žę²»å…ƒå¹“)";
 
143
    return "";
 
144
}
 
145
 
 
146
/**
 
147
 * @return {!string}
 
148
 */
 
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);
 
155
};
 
156
 
 
157
function createUTCDate(year, month, date) {
 
158
    var newDate = new Date(0);
 
159
    newDate.setUTCFullYear(year);
 
160
    newDate.setUTCMonth(month);
 
161
    newDate.setUTCDate(date);
 
162
    return newDate;
 
163
};
 
164
 
 
165
/**
 
166
 * @param {string} dateString
 
167
 * @return {?Day|Week|Month}
 
168
 */
 
169
function parseDateString(dateString) {
 
170
    var month = Month.parse(dateString);
 
171
    if (month)
 
172
        return month;
 
173
    var week = Week.parse(dateString);
 
174
    if (week)
 
175
        return week;
 
176
    return Day.parse(dateString);
 
177
}
 
178
 
 
179
/**
 
180
 * @constructor
 
181
 * @param {!number|Day} valueOrDayOrYear
 
182
 * @param {!number=} month
 
183
 * @param {!number=} date
 
184
 */
 
185
function Day(valueOrDayOrYear, month, date) {
 
186
    var dateObject;
 
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);
 
191
    else
 
192
        dateObject = new Date(valueOrDayOrYear);
 
193
    this.year = dateObject.getUTCFullYear();    
 
194
    this.month = dateObject.getUTCMonth();
 
195
    this.date = dateObject.getUTCDate();
 
196
};
 
197
 
 
198
Day.ISOStringRegExp = /^(\d+)-(\d+)-(\d+)/;
 
199
 
 
200
/**
 
201
 * @param {!string} str
 
202
 * @return {?Month}
 
203
 */
 
204
Day.parse = function(str) {
 
205
    var match = Day.ISOStringRegExp.exec(str);
 
206
    if (!match)
 
207
        return null;
 
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);
 
212
};
 
213
 
 
214
/**
 
215
 * @param {!Date} date
 
216
 * @return {!Day}
 
217
 */
 
218
Day.createFromDate = function(date) {
 
219
    return new Day(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
 
220
};
 
221
 
 
222
/**
 
223
 * @return {!Day}
 
224
 */
 
225
Day.createFromToday = function() {
 
226
    var now = new Date();
 
227
    return new Day(now.getFullYear(), now.getMonth(), now.getDate());
 
228
};
 
229
 
 
230
/**
 
231
 * @param {!Day} other
 
232
 * @return {!bool}
 
233
 */
 
234
Day.prototype.equals = function(other) {
 
235
    return this.year === other.year && this.month === other.month && this.date === other.date;
 
236
};
 
237
 
 
238
/**
 
239
 * @return {!Day}
 
240
 */
 
241
Day.prototype.previous = function() {
 
242
    return new Day(this.year, this.month, this.date - 1);
 
243
};
 
244
 
 
245
/**
 
246
 * @return {!Day}
 
247
 */
 
248
Day.prototype.next = function() {
 
249
    return new Day(this.year, this.month, this.date + 1);
 
250
};
 
251
 
 
252
/**
 
253
 * @return {!Date}
 
254
 */
 
255
Day.prototype.startDate = function() {
 
256
    return createUTCDate(this.year, this.month, this.date);
 
257
};
 
258
 
 
259
/**
 
260
 * @return {!Date}
 
261
 */
 
262
Day.prototype.endDate = function() {
 
263
    return createUTCDate(this.year, this.month, this.date + 1);
 
264
};
 
265
 
 
266
/**
 
267
 * @return {!number}
 
268
 */
 
269
Day.prototype.valueOf = function() {
 
270
    return this.startDate().getTime();
 
271
};
 
272
 
 
273
/**
 
274
 * @return {!string}
 
275
 */
 
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);
 
281
};
 
282
 
 
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;
 
289
 
 
290
/**
 
291
 * @constructor
 
292
 * @param {!number|Week} valueOrWeekOrYear
 
293
 * @param {!number=} week
 
294
 */
 
295
function Week(valueOrWeekOrYear, week) {
 
296
    if (arguments.length === 2) {
 
297
        this.year = valueOrWeekOrYear;
 
298
        this.week = week;
 
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;
 
304
        }
 
305
    } else if (valueOrWeekOrYear instanceof Week) {
 
306
        this.year = valueOrWeekOrYear.year;
 
307
        this.week = valueOrWeekOrYear.week;
 
308
    } else {
 
309
        var week = Week.createFromDate(new Date(valueOrWeekOrYear));
 
310
        this.year = week.year;
 
311
        this.week = week.week;
 
312
    }
 
313
}
 
314
 
 
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;
 
323
 
 
324
/**
 
325
 * @param {!string} str
 
326
 * @return {?Week}
 
327
 */
 
328
Week.parse = function(str) {
 
329
    var match = Week.ISOStringRegExp.exec(str);
 
330
    if (!match)
 
331
        return null;
 
332
    var year = parseInt(match[1], 10);
 
333
    var week = parseInt(match[2], 10);
 
334
    return new Week(year, week);
 
335
};
 
336
 
 
337
/**
 
338
 * @param {!Date} date
 
339
 * @return {!Week}
 
340
 */
 
341
Week.createFromDate = function(date) {
 
342
    var year = date.getUTCFullYear();
 
343
    if (year <= Week.Maximum.year && Week.weekOneStartDateForYear(year + 1).getTime() <= date.getTime())
 
344
        year++;
 
345
    else if (year > 1 && Week.weekOneStartDateForYear(year).getTime() > date.getTime())
 
346
        year--;
 
347
    var week = 1 + Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), date);
 
348
    return new Week(year, week);
 
349
};
 
350
 
 
351
/**
 
352
 * @return {!Week}
 
353
 */
 
354
Week.createFromToday = function() {
 
355
    var now = new Date();
 
356
    return Week.createFromDate(createUTCDate(now.getFullYear(), now.getMonth(), now.getDate()));
 
357
};
 
358
 
 
359
/**
 
360
 * @param {!number} year
 
361
 * @return {!Date}
 
362
 */
 
363
Week.weekOneStartDateForYear = function(year) {
 
364
    if (year < 1)
 
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);
 
369
};
 
370
 
 
371
/**
 
372
 * @param {!number} year
 
373
 * @return {!number}
 
374
 */
 
375
Week.numberOfWeeksInYear = function(year) {
 
376
    if (year < 1 || year > Week.Maximum.year)
 
377
        return 0;
 
378
    else if (year === Week.Maximum.year)
 
379
        return Week.Maximum.week;
 
380
    return Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), Week.weekOneStartDateForYear(year + 1));
 
381
};
 
382
 
 
383
/**
 
384
 * @param {!Date} baseDate
 
385
 * @param {!Date} date
 
386
 * @return {!number}
 
387
 */
 
388
Week._numberOfWeeksSinceDate = function(baseDate, date) {
 
389
    return Math.floor((date.getTime() - baseDate.getTime()) / Week.MillisecondsPerWeek);
 
390
};
 
391
 
 
392
/**
 
393
 * @param {!Week} other
 
394
 * @return {!bool}
 
395
 */
 
396
Week.prototype.equals = function(other) {
 
397
    return this.year === other.year && this.week === other.week;
 
398
};
 
399
 
 
400
/**
 
401
 * @return {!Week}
 
402
 */
 
403
Week.prototype.previous = function() {
 
404
    return new Week(this.year, this.week - 1);
 
405
};
 
406
 
 
407
/**
 
408
 * @return {!Week}
 
409
 */
 
410
Week.prototype.next = function() {
 
411
    return new Week(this.year, this.week + 1);
 
412
};
 
413
 
 
414
/**
 
415
 * @return {!Date}
 
416
 */
 
417
Week.prototype.startDate = function() {
 
418
    var weekStartDate = Week.weekOneStartDateForYear(this.year);
 
419
    weekStartDate.setUTCDate(weekStartDate.getUTCDate() + (this.week - 1) * 7);
 
420
    return weekStartDate;
 
421
};
 
422
 
 
423
/**
 
424
 * @return {!Date}
 
425
 */
 
426
Week.prototype.endDate = function() {
 
427
    if (this.equals(Week.Maximum))
 
428
        return Day.Maximum.startDate();
 
429
    return this.next().startDate();
 
430
};
 
431
 
 
432
/**
 
433
 * @return {!number}
 
434
 */
 
435
Week.prototype.valueOf = function() {
 
436
    return this.startDate().getTime() - createUTCDate(1970, 0, 1).getTime();
 
437
};
 
438
 
 
439
/**
 
440
 * @return {!string}
 
441
 */
 
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);
 
447
};
 
448
 
 
449
/**
 
450
 * @constructor
 
451
 * @param {!number|Month} valueOrMonthOrYear
 
452
 * @param {!number=} month
 
453
 */
 
454
function Month(valueOrMonthOrYear, month) {
 
455
    if (arguments.length == 2) {
 
456
        this.year = valueOrMonthOrYear;
 
457
        this.month = month;
 
458
    } else if (valueOrMonthOrYear instanceof Month) {
 
459
        this.year = valueOrMonthOrYear.year;
 
460
        this.month = valueOrMonthOrYear.month;
 
461
    } else {
 
462
        this.year = 1970;
 
463
        this.month = valueOrMonthOrYear;
 
464
    }
 
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) {
 
468
        this.year = NaN;
 
469
        this.month = NaN;
 
470
    }
 
471
};
 
472
 
 
473
Month.ISOStringRegExp = /^(\d+)-(\d+)$/;
 
474
 
 
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;
 
481
 
 
482
/**
 
483
 * @param {!string} str
 
484
 * @return {?Month}
 
485
 */
 
486
Month.parse = function(str) {
 
487
    var match = Month.ISOStringRegExp.exec(str);
 
488
    if (!match)
 
489
        return null;
 
490
    var year = parseInt(match[1], 10);
 
491
    var month = parseInt(match[2], 10) - 1;
 
492
    return new Month(year, month);
 
493
};
 
494
 
 
495
/**
 
496
 * @param {!Date} date
 
497
 * @return {!Month}
 
498
 */
 
499
Month.createFromDate = function(date) {
 
500
    return new Month(date.getUTCFullYear(), date.getUTCMonth());
 
501
};
 
502
 
 
503
/**
 
504
 * @return {!Month}
 
505
 */
 
506
Month.createFromToday = function() {
 
507
    var now = new Date();
 
508
    return new Month(now.getFullYear(), now.getMonth());
 
509
};
 
510
 
 
511
/**
 
512
 * @param {!Month} other
 
513
 * @return {!bool}
 
514
 */
 
515
Month.prototype.equals = function(other) {
 
516
    return this.year === other.year && this.month === other.month;
 
517
};
 
518
 
 
519
/**
 
520
 * @return {!Month}
 
521
 */
 
522
Month.prototype.previous = function() {
 
523
    return new Month(this.year, this.month - 1);
 
524
};
 
525
 
 
526
/**
 
527
 * @return {!Month}
 
528
 */
 
529
Month.prototype.next = function() {
 
530
    return new Month(this.year, this.month + 1);
 
531
};
 
532
 
 
533
/**
 
534
 * @return {!Date}
 
535
 */
 
536
Month.prototype.startDate = function() {
 
537
    return createUTCDate(this.year, this.month, 1);
 
538
};
 
539
 
 
540
/**
 
541
 * @return {!Date}
 
542
 */
 
543
Month.prototype.endDate = function() {
 
544
    if (this.equals(Month.Maximum))
 
545
        return Day.Maximum.startDate();
 
546
    return this.next().startDate();
 
547
};
 
548
 
 
549
/**
 
550
 * @return {!number}
 
551
 */
 
552
Month.prototype.valueOf = function() {
 
553
    return (this.year - 1970) * 12 + this.month;
 
554
};
 
555
 
 
556
/**
 
557
 * @return {!string}
 
558
 */
 
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);
 
564
};
 
565
 
 
566
// ----------------------------------------------------------------
 
567
// Initialization
 
568
 
 
569
/**
 
570
 * @param {Event} event
 
571
 */
 
572
function handleMessage(event) {
 
573
    if (global.argumentsReceived)
 
574
        return;
 
575
    global.argumentsReceived = true;
 
576
    initialize(JSON.parse(event.data));
 
577
}
 
578
 
 
579
function handleArgumentsTimeout() {
 
580
    if (global.argumentsReceived)
 
581
        return;
 
582
    var args = {
 
583
        dayLabels : ["d1", "d2", "d3", "d4", "d5", "d6", "d7"],
 
584
        todayLabel : "Today",
 
585
        clearLabel : "Clear",
 
586
        cancelLabel : "Cancel",
 
587
        currentValue : "",
 
588
        weekStartDay : 0,
 
589
        step : CalendarPicker.DefaultStepScaleFactor,
 
590
        stepBase: CalendarPicker.DefaultStepBase
 
591
    };
 
592
    initialize(args);
 
593
}
 
594
 
 
595
/**
 
596
 * @param {!Object} config
 
597
 * @return {?string} An error message, or null if the argument has no errors.
 
598
 */
 
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;
 
611
    }
 
612
    return null;
 
613
}
 
614
 
 
615
/**
 
616
 * @param {!Object} args
 
617
 */
 
618
function initialize(args) { 
 
619
    global.params = args;
 
620
    var errorString = CalendarPicker.validateConfig(args);
 
621
    if (args.suggestionValues)
 
622
        errorString = errorString || SuggestionPicker.validateConfig(args)
 
623
    if (errorString) {
 
624
        var main = $("main");
 
625
        main.textContent = "Internal error: " + errorString;
 
626
        resizeWindow(main.offsetWidth, main.offsetHeight);
 
627
    } else {
 
628
        if (global.params.suggestionValues && global.params.suggestionValues.length)
 
629
            openSuggestionPicker();
 
630
        else
 
631
            openCalendarPicker();
 
632
    }
 
633
}
 
634
 
 
635
function closePicker() {
 
636
    if (global.picker)
 
637
        global.picker.cleanup();
 
638
    var main = $("main");
 
639
    main.innerHTML = "";
 
640
    main.className = "";
 
641
};
 
642
 
 
643
function openSuggestionPicker() {
 
644
    closePicker();
 
645
    global.picker = new SuggestionPicker($("main"), global.params);
 
646
};
 
647
 
 
648
function openCalendarPicker() {
 
649
    closePicker();
 
650
    global.picker = new CalendarPicker($("main"), global.params);
 
651
};
 
652
 
 
653
/**
 
654
 * @constructor
 
655
 * @param {!Element} element
 
656
 * @param {!Object} config
 
657
 */
 
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);
 
668
    } else {
 
669
        this.selectionConstructor = Day;
 
670
        this._daysTable = new DaysTable(this);
 
671
    }
 
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;
 
689
    this._layout();
 
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);
 
702
}
 
703
CalendarPicker.prototype = Object.create(Picker.prototype);
 
704
 
 
705
CalendarPicker.NavigationBehaviour = {
 
706
    None: 0,
 
707
    Animate: 1 << 0,
 
708
    KeepSelectionPosition: 1 << 1
 
709
};
 
710
 
 
711
CalendarPicker.prototype._handleWindowResize = function() {
 
712
    this._element.classList.remove("preparing");
 
713
    this.isPreparing = false;
 
714
};
 
715
 
 
716
CalendarPicker.prototype.cleanup = function() {
 
717
    document.body.removeEventListener("keydown", this._handleBodyKeyDownBound, false);
 
718
};
 
719
 
 
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);
 
728
};
 
729
 
 
730
CalendarPicker.prototype.handleToday = function() {
 
731
    var today = this.selectionConstructor.createFromToday();
 
732
    this._daysTable.selectRangeAndShowEntireRange(today);
 
733
    this.submitValue(today.toString());
 
734
};
 
735
 
 
736
CalendarPicker.prototype.handleClear = function() {
 
737
    this.submitValue("");
 
738
};
 
739
 
 
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;
 
748
    }
 
749
    var weekColumnWidth = headers[0].offsetWidth;
 
750
    if (maxCellWidth > weekColumnWidth)
 
751
        weekColumnWidth = maxCellWidth;
 
752
    headers[0].style.width = weekColumnWidth + "px";
 
753
    var DaysAreaContainerBorder = 1;
 
754
    var yearMonthEnd;
 
755
    var daysAreaEnd;
 
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;
 
760
    } else {
 
761
        yearMonthEnd = yearMonthRightElement.offsetLeft + yearMonthRightElement.offsetWidth;
 
762
        daysAreaEnd = daysAreaElement.offsetLeft + weekColumnWidth + maxCellWidth * 7 + DaysAreaContainerBorder;
 
763
    }
 
764
    var maxEnd = Math.max(yearMonthEnd, daysAreaEnd);
 
765
    var MainPadding = 6; // FIXME: Fix name.
 
766
    var MainBorder = 1;
 
767
    var desiredBodyWidth = maxEnd + MainPadding + MainBorder;
 
768
 
 
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);
 
776
};
 
777
 
 
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);
 
786
    this.clear = null;
 
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);
 
793
    }
 
794
    this._element.appendChild(container);
 
795
 
 
796
    this.lastFocusableControl = this.clear || this.today;
 
797
};
 
798
 
 
799
/**
 
800
 * @param {!Month} month
 
801
 * @return {!bool}
 
802
 */
 
803
CalendarPicker.prototype.shouldShowMonth = function(month) {
 
804
    return this._minimumMonth.valueOf() <= month.valueOf() && this.maximumMonth.valueOf() >= month.valueOf();
 
805
};
 
806
 
 
807
/**
 
808
 * @param {!Month} month
 
809
 * @param {!CalendarPicker.NavigationBehaviour=} navigationBehaviour
 
810
 */
 
811
CalendarPicker.prototype.showMonth = function(month, navigationBehaviour) {
 
812
    if (this._currentMonth.equals(month))
 
813
        return;
 
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;
 
821
};
 
822
 
 
823
/**
 
824
 * @return {!Month}
 
825
 */
 
826
CalendarPicker.prototype.currentMonth = function() {
 
827
    return this._currentMonth;
 
828
};
 
829
 
 
830
// ----------------------------------------------------------------
 
831
 
 
832
/**
 
833
 * @constructor
 
834
 * @param {!CalendarPicker} picker
 
835
 */
 
836
function YearMonthController(picker) {
 
837
    this.picker = picker;
 
838
}
 
839
 
 
840
/**
 
841
 * @param {!Element} element
 
842
 */
 
843
YearMonthController.prototype.attachTo = function(element) {
 
844
    var outerContainer = createElement("div", ClassNames.YearMonthArea);
 
845
 
 
846
    var innerContainer = createElement("div", ClassNames.YearMonthUpper);
 
847
    outerContainer.appendChild(innerContainer);
 
848
 
 
849
    this._attachLeftButtonsTo(innerContainer);
 
850
 
 
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);
 
866
 
 
867
    this._attachRightButtonsTo(innerContainer);
 
868
    element.appendChild(outerContainer);
 
869
 
 
870
    this._wall = createElement("div", ClassNames.MonthSelectorWall);
 
871
    this._wall.addEventListener("click", this._closePopup.bind(this), false);
 
872
    element.appendChild(this._wall);
 
873
 
 
874
    var month = this.picker.maximumMonth;
 
875
    var maxWidth = 0;
 
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();
 
880
    }
 
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);
 
885
        }
 
886
    }
 
887
    this._month.style.minWidth = maxWidth + 'px';
 
888
 
 
889
    this.picker.firstFocusableControl = this._left2; // FIXME: Should it be this.month?
 
890
};
 
891
 
 
892
YearMonthController.addTenYearsButtons = false;
 
893
 
 
894
/**
 
895
 * @param {!Element} parent
 
896
 */
 
897
YearMonthController.prototype._attachLeftButtonsTo = function(parent) {
 
898
    var container = createElement("div", ClassNames.YearMonthButtonLeft);
 
899
    parent.appendChild(container);
 
900
 
 
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);
 
907
    }
 
908
 
 
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);
 
914
 
 
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);
 
920
};
 
921
 
 
922
/**
 
923
 * @param {!Element} parent
 
924
 */
 
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);
 
933
 
 
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);
 
939
 
 
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);
 
946
    }
 
947
};
 
948
 
 
949
/**
 
950
 * @param {!Month} month
 
951
 */
 
952
YearMonthController.prototype.setMonth = function(month) {
 
953
    var monthValue = month.valueOf();
 
954
    if (this._left3)
 
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));
 
960
    if (this._right3)
 
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);
 
965
 
 
966
    for (var m = monthValue - 6; m <= monthValue + 6; m++) {
 
967
        var month = new Month(m);
 
968
        if (!this.picker.shouldShowMonth(month))
 
969
            continue;
 
970
        var option = createElement("div", ClassNames.MonthSelectorPopupEntry, month.toLocaleString());
 
971
        option.dataset.value = month.toString();
 
972
        this._monthPopupContents.appendChild(option);
 
973
        if (m == monthValue)
 
974
            option.classList.add(ClassNames.SelectedMonthYear);
 
975
    }
 
976
};
 
977
 
 
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";
 
983
 
 
984
    this._wall.style.display = "block";
 
985
    this._wall.style.zIndex = "999"; // This should be smaller than the z-index of monthPopup.
 
986
 
 
987
    var popupHeight = this._monthPopup.clientHeight;
 
988
    var fullHeight = this._monthPopupContents.clientHeight;
 
989
    if (fullHeight > popupHeight) {
 
990
        var selected = this._getSelection();
 
991
        if (selected) {
 
992
           var bottom = selected.offsetTop + selected.clientHeight;
 
993
           if (bottom > popupHeight)
 
994
               this._monthPopup.scrollTop = bottom - popupHeight;
 
995
        }
 
996
        this._monthPopup.style.webkitPaddingEnd = getScrollbarWidth() + 'px';
 
997
    }
 
998
    this._monthPopup.focus();
 
999
};
 
1000
 
 
1001
YearMonthController.prototype._closePopup = function() {
 
1002
    this._monthPopup.style.display = "none";
 
1003
    this._wall.style.display = "none";
 
1004
    var container = document.querySelector("." + ClassNames.DaysAreaContainer);
 
1005
    container.focus();
 
1006
};
 
1007
 
 
1008
/**
 
1009
 * @return {Element} Selected element in the month-year popup.
 
1010
 */
 
1011
YearMonthController.prototype._getSelection = function()
 
1012
{
 
1013
    return document.querySelector("." + ClassNames.SelectedMonthYear);
 
1014
}
 
1015
 
 
1016
/**
 
1017
 * @param {Event} event
 
1018
 */
 
1019
YearMonthController.prototype._handleMouseMove = function(event)
 
1020
{
 
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;
 
1026
    } else {
 
1027
        var target = event.target;
 
1028
        var selection = this._getSelection();
 
1029
        if (target && target != selection && target.classList.contains(ClassNames.MonthSelectorPopupEntry)) {
 
1030
            if (selection)
 
1031
                selection.classList.remove(ClassNames.SelectedMonthYear);
 
1032
            target.classList.add(ClassNames.SelectedMonthYear);
 
1033
        }
 
1034
    }
 
1035
    event.stopPropagation();
 
1036
    event.preventDefault();
 
1037
}
 
1038
 
 
1039
/**
 
1040
 * @param {Event} event
 
1041
 */
 
1042
YearMonthController.prototype._handleMonthPopupKey = function(event)
 
1043
{
 
1044
    var key = event.keyIdentifier;
 
1045
    if (key == "Down") {
 
1046
        var selected = this._getSelection();
 
1047
        if (selected) {
 
1048
            var next = selected.nextSibling;
 
1049
            if (next) {
 
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;
 
1056
                }
 
1057
            }
 
1058
        }
 
1059
        event.stopPropagation();
 
1060
        event.preventDefault();
 
1061
    } else if (key == "Up") {
 
1062
        var selected = this._getSelection();
 
1063
        if (selected) {
 
1064
            var previous = selected.previousSibling;
 
1065
            if (previous) {
 
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;
 
1071
                }
 
1072
            }
 
1073
        }
 
1074
        event.stopPropagation();
 
1075
        event.preventDefault();
 
1076
    } else if (key == "U+001B") {
 
1077
        this._closePopup();
 
1078
        event.stopPropagation();
 
1079
        event.preventDefault();
 
1080
    } else if (key == "Enter") {
 
1081
        this._handleYearMonthChange();
 
1082
        event.stopPropagation();
 
1083
        event.preventDefault();
 
1084
    }
 
1085
}
 
1086
 
 
1087
YearMonthController.prototype._handleYearMonthChange = function() {
 
1088
    this._closePopup();
 
1089
    var selection = this._getSelection();
 
1090
    if (!selection)
 
1091
        return;
 
1092
    this.picker.showMonth(Month.parse(selection.dataset.value));
 
1093
};
 
1094
 
 
1095
/**
 
1096
 * @const
 
1097
 * @type {number}
 
1098
 */
 
1099
YearMonthController.PreviousTenYears = -120;
 
1100
/**
 
1101
 * @const
 
1102
 * @type {number}
 
1103
 */
 
1104
YearMonthController.PreviousYear = -12;
 
1105
/**
 
1106
 * @const
 
1107
 * @type {number}
 
1108
 */
 
1109
YearMonthController.PreviousMonth = -1;
 
1110
/**
 
1111
 * @const
 
1112
 * @type {number}
 
1113
 */
 
1114
YearMonthController.NextMonth = 1;
 
1115
/**
 
1116
 * @const
 
1117
 * @type {number}
 
1118
 */
 
1119
YearMonthController.NextYear = 12;
 
1120
/**
 
1121
 * @const
 
1122
 * @type {number}
 
1123
 */
 
1124
YearMonthController.NextTenYears = 120;
 
1125
 
 
1126
/**
 
1127
 * @param {Event} event
 
1128
 */
 
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);
 
1142
    else
 
1143
        return;
 
1144
};
 
1145
 
 
1146
/**
 
1147
 * @param {!number} amount
 
1148
 */
 
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);
 
1153
};
 
1154
 
 
1155
// ----------------------------------------------------------------
 
1156
 
 
1157
/**
 
1158
 * @constructor
 
1159
 * @param {!CalendarPicker} picker
 
1160
 */
 
1161
function DaysTable(picker) {
 
1162
    this.picker = picker;
 
1163
}
 
1164
 
 
1165
/**
 
1166
 * @return {!boolean}
 
1167
 */
 
1168
DaysTable.prototype._hasSelection = function() {
 
1169
    return !!this._firstNodeInSelectedRange();
 
1170
}
 
1171
 
 
1172
/**
 
1173
 * The number of week lines in the screen.
 
1174
 * @const
 
1175
 * @type {number}
 
1176
 */
 
1177
DaysTable._Weeks = 6;
 
1178
 
 
1179
/**
 
1180
 * @param {!Element} element
 
1181
 */
 
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);
 
1194
    this._days = [];
 
1195
    this._weekNumbers = [];
 
1196
    for (var w = 0; w < DaysTable._Weeks; w++) {
 
1197
        container = createElement("tr", ClassNames.WeekContainer);
 
1198
        var week = [];
 
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));
 
1208
            week.push(day);
 
1209
            container.appendChild(day);
 
1210
        }
 
1211
        this._days.push(week);
 
1212
        this._daysContainer.appendChild(container);
 
1213
    }
 
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);
 
1219
 
 
1220
    container.focus();
 
1221
};
 
1222
 
 
1223
/**
 
1224
 * @param {!number} value
 
1225
 * @return {!boolean}
 
1226
 */
 
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
 
1230
}
 
1231
 
 
1232
/**
 
1233
 * @param {!number} value
 
1234
 * @return {!boolean}
 
1235
 */
 
1236
CalendarPicker.prototype._outOfRange = function(value) {
 
1237
    return value < this._minimumValue || value > this._maximumValue;
 
1238
}
 
1239
 
 
1240
/**
 
1241
 * @param {!Month|Day} range
 
1242
 * @return {!boolean}
 
1243
 */
 
1244
CalendarPicker.prototype.isValidDate = function(range) {
 
1245
    var value = range.valueOf();
 
1246
    return !this._outOfRange(value) && !this._stepMismatch(value);
 
1247
}
 
1248
 
 
1249
/**
 
1250
 * @param {!Month} month
 
1251
 */
 
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)
 
1258
        startOffset -= 7;
 
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();
 
1273
            if (isNaN(time)) {
 
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);
 
1281
            } else
 
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);
 
1290
                    else
 
1291
                        this._weekNumbers[w].classList.add(ClassNames.Unavailable);
 
1292
                }
 
1293
            } else if (d === sundayOffset)
 
1294
                element.classList.add(ClassNames.Sunday);
 
1295
            dayIterator.setUTCDate(dayIterator.getUTCDate() + 1);
 
1296
        }
 
1297
    }
 
1298
};
 
1299
 
 
1300
/**
 
1301
 * @param {!Month} month
 
1302
 * @param {!CalendarPicker.NavigationBehaviour} navigationBehaviour
 
1303
 */
 
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);
 
1313
    }
 
1314
};
 
1315
 
 
1316
/**
 
1317
 * @param {!Month} month
 
1318
 */
 
1319
DaysTable.prototype._startMoveInAnimation = function(month) {
 
1320
    if (this.picker.isPreparing)
 
1321
        return;
 
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";
 
1326
};
 
1327
 
 
1328
DaysTable.prototype._moveInDays = function() {
 
1329
    var daysStyle = this._daysContainer.style;
 
1330
    if (daysStyle.left == "0px")
 
1331
        return;
 
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";
 
1337
};
 
1338
 
 
1339
/**
 
1340
 * @param {!Day} day
 
1341
 */
 
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);
 
1348
                break;
 
1349
            }
 
1350
        }
 
1351
    }
 
1352
};
 
1353
 
 
1354
/**
 
1355
 * @param {!Day} day
 
1356
 */
 
1357
DaysTable.prototype.selectRange = function(day) {
 
1358
    this._deselect();
 
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);
 
1362
};
 
1363
 
 
1364
/**
 
1365
 * @param {!Day} day
 
1366
 */
 
1367
DaysTable.prototype.selectRangeAndShowEntireRange = function(day) {
 
1368
    this.selectRange(day);
 
1369
};
 
1370
 
 
1371
/**
 
1372
 * @param {!Element} dayNode
 
1373
 */
 
1374
DaysTable.prototype._selectRangeContainingNode = function(dayNode) {
 
1375
    var range = this._rangeForNode(dayNode);
 
1376
    if (!range)
 
1377
        return;
 
1378
    this.selectRange(range);
 
1379
};
 
1380
 
 
1381
/**
 
1382
 * @param {!Element} dayNode
 
1383
 * @return {?Day}
 
1384
 */
 
1385
DaysTable.prototype._rangeForNode = function(dayNode) {
 
1386
    if (!dayNode)
 
1387
        return null;
 
1388
    return Day.parse(dayNode.dataset.submitValue);
 
1389
};
 
1390
 
 
1391
/**
 
1392
 * @return {!Date}
 
1393
 */
 
1394
DaysTable.prototype.startDate = function() {
 
1395
    return Day.parse(this._days[0][0].dataset.submitValue).startDate();
 
1396
};
 
1397
 
 
1398
/**
 
1399
 * @return {!Date}
 
1400
 */
 
1401
DaysTable.prototype.endDate = function() {
 
1402
    return Day.parse(this._days[DaysTable._Weeks - 1][7 - 1].dataset.submitValue).endDate();
 
1403
};
 
1404
 
 
1405
/**
 
1406
 * @param {!number} x
 
1407
 * @param {!number} y
 
1408
 */
 
1409
DaysTable.prototype._selectRangeAtPosition = function(x, y) {
 
1410
    var node = x === -1 ? this._weekNumbers[y] : this._days[y][x];
 
1411
    this._selectRangeContainingNode(node);
 
1412
};
 
1413
 
 
1414
/**
 
1415
 * @return {!Element}
 
1416
 */
 
1417
DaysTable.prototype._firstNodeInSelectedRange = function() {
 
1418
    return this._daysContainer.getElementsByClassName(ClassNames.Selected)[0];
 
1419
};
 
1420
 
 
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);
 
1425
};
 
1426
 
 
1427
/**
 
1428
 * @param {!CalendarPicker.NavigationBehaviour=} navigationBehaviour
 
1429
 * @return {!boolean}
 
1430
 */
 
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))
 
1436
        return false;
 
1437
    this.picker.showMonth(previousMonth, navigationBehaviour);
 
1438
    return true;
 
1439
};
 
1440
 
 
1441
/**
 
1442
 * @param {!CalendarPicker.NavigationBehaviour=} navigationBehaviour
 
1443
 * @return {!boolean}
 
1444
 */
 
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))
 
1450
        return false;
 
1451
    this.picker.showMonth(nextMonth, navigationBehaviour);
 
1452
    return true;
 
1453
};
 
1454
 
 
1455
/**
 
1456
 * @param {Event} event
 
1457
 */
 
1458
DaysTable.prototype._handleDayClick = function(event) {
 
1459
    if (event.target.classList.contains(ClassNames.Available))
 
1460
        this.picker.submitValue(this._rangeForNode(event.target).toString());
 
1461
};
 
1462
 
 
1463
/**
 
1464
 * @param {Event} event
 
1465
 */
 
1466
DaysTable.prototype._handleMouseOver = function(event) {
 
1467
    var node = event.target;
 
1468
    if (node.classList.contains(ClassNames.Selected))
 
1469
        return;
 
1470
    this._selectRangeContainingNode(node);
 
1471
};
 
1472
 
 
1473
/**
 
1474
 * @param {Event} event
 
1475
 */
 
1476
DaysTable.prototype._handleMouseOut = function(event) {
 
1477
    this._deselect();
 
1478
};
 
1479
 
 
1480
/**
 
1481
 * @param {Event} event
 
1482
 */
 
1483
DaysTable.prototype._handleKey = function(event) {
 
1484
    this.picker.maybeUpdateFocusStyle();
 
1485
    var x = -1;
 
1486
    var y = -1;
 
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);
 
1492
    }
 
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));
 
1496
        return;
 
1497
    }
 
1498
 
 
1499
    if (key == (global.params.isCalendarRTL ? "Right" : "Left")) {
 
1500
        if (x == 0) {
 
1501
            if (y == 0) {
 
1502
                if (!this._maybeSetPreviousMonth())
 
1503
                    return;
 
1504
                y = DaysTable._Weeks - 1;
 
1505
            } else
 
1506
                y--;
 
1507
            x = 6;
 
1508
        } else
 
1509
            x--;
 
1510
        this.updateSelection(event, x, y);
 
1511
 
 
1512
    } else if (key == "Up") {
 
1513
        if (y == 0) {
 
1514
            if (!this._maybeSetPreviousMonth())
 
1515
                return;
 
1516
            y = DaysTable._Weeks - 1;
 
1517
        } else
 
1518
            y--;
 
1519
        this.updateSelection(event, x, y);
 
1520
 
 
1521
    } else if (key == (global.params.isCalendarRTL ? "Left" : "Right")) {
 
1522
        if (x == 6) {
 
1523
            if (y == DaysTable._Weeks - 1) {
 
1524
                if (!this._maybeSetNextMonth())
 
1525
                    return;
 
1526
                y = 0;
 
1527
            } else
 
1528
                y++;
 
1529
            x = 0;
 
1530
        } else
 
1531
            x++;
 
1532
        this.updateSelection(event, x, y);
 
1533
 
 
1534
    } else if (key == "Down") {
 
1535
        if (y == DaysTable._Weeks - 1) {
 
1536
            if (!this._maybeSetNextMonth())
 
1537
                return;
 
1538
            y = 0;
 
1539
        } else
 
1540
            y++;
 
1541
        this.updateSelection(event, x, y);
 
1542
 
 
1543
    } else if (key == "PageUp") {
 
1544
        if (!this._maybeSetPreviousMonth())
 
1545
            return;
 
1546
        this.updateSelection(event, x, y);
 
1547
 
 
1548
    } else if (key == "PageDown") {
 
1549
        if (!this._maybeSetNextMonth())
 
1550
            return;
 
1551
        this.updateSelection(event, x, y);
 
1552
 
 
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();
 
1558
        }
 
1559
 
 
1560
    } else if (key == "U+0054") { // 't'
 
1561
        this.selectRangeAndShowEntireRange(Day.createFromToday());
 
1562
        event.stopPropagation();
 
1563
        event.preventDefault();
 
1564
    }
 
1565
};
 
1566
 
 
1567
/**
 
1568
 * @param {Event} event
 
1569
 * @param {!number} x
 
1570
 * @param {!number} y
 
1571
 */
 
1572
DaysTable.prototype.updateSelection = function(event, x, y) {
 
1573
    this._selectRangeAtPosition(x, y);
 
1574
    event.stopPropagation();
 
1575
    event.preventDefault();
 
1576
};
 
1577
 
 
1578
/**
 
1579
 * @constructor
 
1580
 * @param{!CalendarPicker} picker
 
1581
 */
 
1582
function MonthPickerDaysTable(picker) {
 
1583
    DaysTable.call(this, picker);
 
1584
}
 
1585
MonthPickerDaysTable.prototype = Object.create(DaysTable.prototype);
 
1586
 
 
1587
/**
 
1588
 * @param {!Month} month
 
1589
 * @param {!CalendarPicker.NavigationBehaviour} navigationBehaviour
 
1590
 */
 
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);
 
1598
};
 
1599
 
 
1600
/**
 
1601
 * @param {!Month} month
 
1602
 */
 
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);
 
1609
            }
 
1610
        }
 
1611
    }
 
1612
};
 
1613
 
 
1614
/**
 
1615
 * @param {!Month} month
 
1616
 */
 
1617
MonthPickerDaysTable.prototype.selectRange = function(month) {
 
1618
    this._deselect();
 
1619
    if (this.startDate() >= month.endDate() || this.endDate() <= month.startDate())
 
1620
        this.picker.showMonth(month, CalendarPicker.NavigationBehaviour.Animate);
 
1621
    this._markRangeAsSelected(month);
 
1622
};
 
1623
 
 
1624
/**
 
1625
 * @param {!Month} month
 
1626
 */
 
1627
MonthPickerDaysTable.prototype.selectRangeAndShowEntireRange = function(month) {
 
1628
    this._deselect();
 
1629
    this.picker.showMonth(month, CalendarPicker.NavigationBehaviour.Animate);
 
1630
    this._markRangeAsSelected(month);
 
1631
};
 
1632
 
 
1633
/**
 
1634
 * @param {!Element} dayNode
 
1635
 * @return {?Month}
 
1636
 */
 
1637
MonthPickerDaysTable.prototype._rangeForNode = function(dayNode) {
 
1638
    if (!dayNode)
 
1639
        return null;
 
1640
    return Month.parse(dayNode.dataset.monthValue);
 
1641
};
 
1642
 
 
1643
/**
 
1644
 * @param {Event} event
 
1645
 */
 
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);
 
1660
        else
 
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);
 
1666
        else
 
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;
 
1673
        }
 
1674
    } else if (key == "U+0054") { // 't'
 
1675
        this.selectRangeAndShowEntireRange(Month.createFromToday());
 
1676
        eventHandled = true;
 
1677
    }
 
1678
    if (eventHandled) {
 
1679
        event.stopPropagation();
 
1680
        event.preventDefault();
 
1681
    }
 
1682
};
 
1683
 
 
1684
/**
 
1685
 * @constructor
 
1686
 * @param{!CalendarPicker} picker
 
1687
 */
 
1688
function WeekPickerDaysTable(picker) {
 
1689
    DaysTable.call(this, picker);
 
1690
}
 
1691
WeekPickerDaysTable.prototype = Object.create(DaysTable.prototype);
 
1692
 
 
1693
/**
 
1694
 * @param {!Week} week
 
1695
 */
 
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);
 
1702
            }
 
1703
        }
 
1704
    }
 
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);
 
1708
            break;
 
1709
        }
 
1710
    }
 
1711
};
 
1712
 
 
1713
/**
 
1714
 * @param {!Week} week
 
1715
 */
 
1716
WeekPickerDaysTable.prototype.selectRange = function(week) {
 
1717
    this._deselect();
 
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);
 
1725
};
 
1726
 
 
1727
/**
 
1728
 * @param {!Week} week
 
1729
 */
 
1730
WeekPickerDaysTable.prototype.selectRangeAndShowEntireRange = function(week) {
 
1731
    this._deselect();
 
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);
 
1739
};
 
1740
 
 
1741
/**
 
1742
 * @param {!Element} dayNode
 
1743
 * @return {?Week}
 
1744
 */
 
1745
WeekPickerDaysTable.prototype._rangeForNode = function(dayNode) {
 
1746
    if (!dayNode)
 
1747
        return null;
 
1748
    return Week.parse(dayNode.dataset.weekValue);
 
1749
};
 
1750
 
 
1751
/**
 
1752
 * @param {!Event} event
 
1753
 */
 
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))
 
1773
            return;
 
1774
        eventHandled = true;
 
1775
    } else if (key == "PageDown") {
 
1776
        if (!this._maybeSetNextMonth(CalendarPicker.NavigationBehaviour.Animate | CalendarPicker.NavigationBehaviour.KeepSelectionPosition))
 
1777
            return;
 
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;
 
1783
        }
 
1784
    } else if (key == "U+0054") { // 't'
 
1785
        this.selectRangeAndShowEntireRange(Week.createFromToday());
 
1786
        eventHandled = true;
 
1787
    }
 
1788
    if (eventHandled) {
 
1789
        event.stopPropagation();
 
1790
        event.preventDefault();
 
1791
    }
 
1792
};
 
1793
 
 
1794
/**
 
1795
 * @param {!Event} event
 
1796
 */
 
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();
 
1809
        }
 
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();
 
1818
}
 
1819
 
 
1820
CalendarPicker.prototype.maybeUpdateFocusStyle = function() {
 
1821
    if (this._hadKeyEvent)
 
1822
        return;
 
1823
    this._hadKeyEvent = true;
 
1824
    this._element.classList.remove(ClassNames.NoFocusRing);
 
1825
}
 
1826
 
 
1827
if (window.dialogArguments) {
 
1828
    initialize(dialogArguments);
 
1829
} else {
 
1830
    window.addEventListener("message", handleMessage, false);
 
1831
    window.setTimeout(handleArgumentsTimeout, 1000);
 
1832
}