|
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