|
9 | 9 | angular.module('material.components.calendar', ['material.core'])
|
10 | 10 | .directive('mdCalendar', calendarDirective);
|
11 | 11 |
|
12 |
| - // TODO(jelbourn): i18n [month names, day names, days of month, date formatting] |
13 | 12 | // TODO(jelbourn): Date cell IDs need to be unique per-calendar.
|
14 |
| - |
15 |
| - // TODO(jelbourn): a11y (announcements and labels) |
| 13 | + // TODO(jelbourn): internationalize a11y announcements. |
16 | 14 |
|
17 | 15 | // TODO(jelbourn): Update the selected date on [click, tap, enter]
|
18 |
| - |
19 | 16 | // TODO(jelbourn): Shown month transition on [swipe, scroll, keyboard, ngModel change]
|
20 | 17 | // TODO(jelbourn): Introduce free scrolling that works w/ mobile momemtum scrolling (+snapping)
|
21 | 18 |
|
|
26 | 23 | // TODO(jelbourn): Minimum and maximum date
|
27 | 24 | // TODO(jelbourn): Make sure the *time* on the written date makes sense (probably midnight).
|
28 | 25 | // TODO(jelbourn): Refactor "sections" into separate files.
|
29 |
| - // TODO(jelbourn): Highlight today. |
30 | 26 | // TODO(jelbourn): Horizontal line between months (pending spec finalization)
|
31 | 27 | // TODO(jelbourn): Alt+down in date input to open calendar
|
32 | 28 | // TODO(jelbourn): Animations should use `.finally()` instead of `.then()`
|
33 |
| - |
34 |
| - var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; |
35 |
| - |
36 |
| - var fullMonths = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', |
37 |
| - 'September', 'October', 'November', 'December']; |
38 |
| - var fullDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; |
| 29 | + // TODO(jelbourn): improve default date parser in locale provider. |
| 30 | + // TODO(jelbourn): read-only state. |
39 | 31 |
|
40 | 32 | function calendarDirective() {
|
41 | 33 | return {
|
42 | 34 | template:
|
43 | 35 | '<div>' +
|
44 |
| - '<table class="md-calendar-day-header"><thead><tr>' + |
45 |
| - '<th>S</th><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th>' + |
46 |
| - '</tr></thead></table>' + |
| 36 | + '<table class="md-calendar-day-header"><thead></thead></table>' + |
47 | 37 | '<div class="md-calendar-container">' +
|
48 | 38 | '<table class="md-calendar"></table>' +
|
49 | 39 | '</div>' +
|
|
74 | 64 | DISTANT_PAST: 4
|
75 | 65 | };
|
76 | 66 |
|
77 |
| - // TODO(jelbourn): Refactor this to core and share with other components. |
78 |
| - /** @enum {number} */ |
79 |
| - var Keys = { |
80 |
| - ENTER: 13, |
81 |
| - PAGE_UP: 33, |
82 |
| - PAGE_DOWN: 34, |
83 |
| - END: 35, |
84 |
| - HOME: 36, |
85 |
| - LEFT: 37, |
86 |
| - UP: 38, |
87 |
| - RIGHT: 39, |
88 |
| - DOWN: 40 |
89 |
| - }; |
90 |
| - |
91 | 67 | /** Class applied to the selected date cell/. */
|
92 | 68 | var SELECTED_DATE_CLASS = 'md-calendar-selected-date';
|
93 | 69 |
|
|
108 | 84 | * Controller for the mdCalendar component.
|
109 | 85 | * @ngInject @constructor
|
110 | 86 | */
|
111 |
| - function CalendarCtrl($element, $scope, $animate, $q, $$mdDateUtil, $$mdDateLocale, $mdInkRipple, $mdUtil) { |
| 87 | + function CalendarCtrl($element, $scope, $animate, $q, $mdConstant, |
| 88 | + $$mdDateUtil, $$mdDateLocale, $mdInkRipple, $mdUtil) { |
| 89 | + |
112 | 90 | /** @final {!angular.$animate} */
|
113 | 91 | this.$animate = $animate;
|
114 | 92 |
|
|
121 | 99 | /** @final */
|
122 | 100 | this.$mdUtil = $mdUtil;
|
123 | 101 |
|
| 102 | + /** @final */ |
| 103 | + this.keyCode = $mdConstant.KEY_CODE; |
| 104 | + |
124 | 105 | /** @final */
|
125 | 106 | this.dateUtil = $$mdDateUtil;
|
126 | 107 |
|
| 108 | + /** @final */ |
| 109 | + this.dateLocale = $$mdDateLocale; |
| 110 | + |
127 | 111 | /** @final {!angular.JQLite} */
|
128 | 112 | this.$element = $element;
|
129 | 113 |
|
|
209 | 193 | * Initialization should occur after the ngModel value is known.
|
210 | 194 | */
|
211 | 195 | CalendarCtrl.prototype.buildInitialCalendarDisplay = function() {
|
| 196 | + this.buildWeekHeader(); |
| 197 | + |
212 | 198 | this.displayDate = this.selectedDate || new Date();
|
213 | 199 | var nextMonth = this.dateUtil.getDateInNextMonth(this.displayDate);
|
214 | 200 | this.calendarElement.appendChild(this.buildCalendarForMonth(this.displayDate));
|
|
250 | 236 | this.$scope.$apply(function() {
|
251 | 237 | // Handled key events fall into two categories: selection and navigation.
|
252 | 238 | // Start by checking if this is a selection event.
|
253 |
| - if (event.which === Keys.ENTER) { |
| 239 | + if (event.which === self.keyCode.ENTER) { |
254 | 240 | self.ngModelCtrl.$setViewValue(self.displayDate);
|
255 | 241 | self.ngModelCtrl.$render();
|
256 | 242 | event.preventDefault();
|
|
280 | 266 | */
|
281 | 267 | CalendarCtrl.prototype.getFocusDateFromKeyEvent = function(event) {
|
282 | 268 | var dateUtil = this.dateUtil;
|
| 269 | + var keyCode = this.keyCode; |
283 | 270 |
|
284 | 271 | switch (event.which) {
|
285 |
| - case Keys.RIGHT: return dateUtil.incrementDays(this.displayDate, 1); |
286 |
| - case Keys.LEFT: return dateUtil.incrementDays(this.displayDate, -1); |
287 |
| - case Keys.DOWN: return dateUtil.incrementDays(this.displayDate, 7); |
288 |
| - case Keys.UP: return dateUtil.incrementDays(this.displayDate, -7); |
289 |
| - case Keys.PAGE_DOWN: return dateUtil.incrementMonths(this.displayDate, 1); |
290 |
| - case Keys.PAGE_UP: return dateUtil.incrementMonths(this.displayDate, -1); |
291 |
| - case Keys.HOME: return dateUtil.getFirstDateOfMonth(this.displayDate); |
292 |
| - case Keys.END: return dateUtil.getLastDateOfMonth(this.displayDate); |
| 272 | + case keyCode.RIGHT_ARROW: return dateUtil.incrementDays(this.displayDate, 1); |
| 273 | + case keyCode.LEFT_ARROW: return dateUtil.incrementDays(this.displayDate, -1); |
| 274 | + case keyCode.DOWN_ARROW: return dateUtil.incrementDays(this.displayDate, 7); |
| 275 | + case keyCode.UP_ARROW: return dateUtil.incrementDays(this.displayDate, -7); |
| 276 | + case keyCode.PAGE_DOWN: return dateUtil.incrementMonths(this.displayDate, 1); |
| 277 | + case keyCode.PAGE_UP: return dateUtil.incrementMonths(this.displayDate, -1); |
| 278 | + case keyCode.HOME: return dateUtil.getFirstDateOfMonth(this.displayDate); |
| 279 | + case keyCode.END: return dateUtil.getLastDateOfMonth(this.displayDate); |
293 | 280 | default: return this.displayDate;
|
294 | 281 | }
|
295 | 282 | };
|
|
572 | 559 | var annoucement = '';
|
573 | 560 |
|
574 | 561 | if (!previousDate || !this.dateUtil.isSameMonthAndYear(previousDate, currentDate)) {
|
575 |
| - annoucement += currentDate.getFullYear() + '. ' + fullMonths[currentDate.getMonth()] + '. '; |
| 562 | + annoucement += currentDate.getFullYear() + |
| 563 | + '. ' + |
| 564 | + this.dateLocale.months[currentDate.getMonth()] + '. '; |
576 | 565 | }
|
577 | 566 |
|
578 | 567 | if (previousDate.getDate() !== currentDate.getDate()) {
|
579 |
| - annoucement += fullDays[currentDate.getDay()] + '. ' + currentDate.getDate() ; |
| 568 | + annoucement += this.dateLocale.days[currentDate.getDay()] + '. ' + currentDate.getDate() ; |
580 | 569 | }
|
581 | 570 |
|
582 | 571 | this.ariaLiveElement.textContent = annoucement;
|
|
585 | 574 |
|
586 | 575 | /*** Constructing the calendar table ***/
|
587 | 576 |
|
| 577 | + /** |
| 578 | + * Builds and appends a day-of-the-week header to the calendar. |
| 579 | + * This should only need to be called once during initialization. |
| 580 | + */ |
| 581 | + CalendarCtrl.prototype.buildWeekHeader = function() { |
| 582 | + var row = document.createElement('tr'); |
| 583 | + for (var i = 0; i < 7; i++) { |
| 584 | + var th = document.createElement('th'); |
| 585 | + th.textContent = this.dateLocale.shortDays[i]; |
| 586 | + row.appendChild(th); |
| 587 | + } |
| 588 | + |
| 589 | + this.$element.find('thead').append(row); |
| 590 | + }; |
| 591 | + |
588 | 592 | /**
|
589 | 593 | * Creates a single cell to contain a date in the calendar with all appropriate
|
590 | 594 | * attributes and classes added. If a date is given, the cell content will be set
|
|
601 | 605 | var selectionIndicator = document.createElement('span');
|
602 | 606 | cell.appendChild(selectionIndicator);
|
603 | 607 | selectionIndicator.classList.add('md-calendar-date-selection-indicator');
|
604 |
| - selectionIndicator.textContent = opt_date.getDate(); |
| 608 | + selectionIndicator.textContent = this.dateLocale.dates[opt_date.getDate()]; |
605 | 609 | //selectionIndicator.setAttribute('aria-label', '');
|
606 | 610 |
|
607 | 611 | cell.setAttribute('tabindex', '-1');
|
|
629 | 633 | // Store rows for the month in a document fragment so that we can append them all at once.
|
630 | 634 | var monthBody = document.createElement('tbody');
|
631 | 635 | monthBody.classList.add('md-calendar-month');
|
632 |
| - monthBody.setAttribute('aria-hidden', 'true') |
| 636 | + monthBody.setAttribute('aria-hidden', 'true'); |
633 | 637 |
|
634 | 638 | var row = document.createElement('tr');
|
635 | 639 | monthBody.appendChild(row);
|
|
642 | 646 | monthLabelCell.classList.add('md-calendar-month-label');
|
643 | 647 | if (firstDayOfTheWeek <= 1) {
|
644 | 648 | monthLabelCell.setAttribute('colspan', '7');
|
645 |
| - monthLabelCell.textContent = months[date.getMonth()]; |
| 649 | + monthLabelCell.textContent = this.dateLocale.shortMonths[date.getMonth()]; |
646 | 650 |
|
647 | 651 | var monthLabelRow = document.createElement('tr');
|
648 | 652 | monthLabelRow.appendChild(monthLabelCell);
|
649 | 653 | monthBody.insertBefore(monthLabelRow, row);
|
650 | 654 | } else {
|
651 | 655 | blankCellOffset = 2;
|
652 | 656 | monthLabelCell.setAttribute('colspan', '2');
|
653 |
| - monthLabelCell.textContent = months[date.getMonth()]; |
| 657 | + monthLabelCell.textContent = this.dateLocale.shortMonths[date.getMonth()]; |
654 | 658 |
|
655 | 659 | row.appendChild(monthLabelCell);
|
656 | 660 | }
|
|
0 commit comments