Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit f3457b8

Browse files
committed
feat(calendar): fix up $$mdDateLocaleProvider and use in calendar.
1 parent 7fe7931 commit f3457b8

File tree

11 files changed

+246
-50
lines changed

11 files changed

+246
-50
lines changed

src/components/calendar/calendar.js

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@
99
angular.module('material.components.calendar', ['material.core'])
1010
.directive('mdCalendar', calendarDirective);
1111

12-
// TODO(jelbourn): i18n [month names, day names, days of month, date formatting]
1312
// 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.
1614

1715
// TODO(jelbourn): Update the selected date on [click, tap, enter]
18-
1916
// TODO(jelbourn): Shown month transition on [swipe, scroll, keyboard, ngModel change]
2017
// TODO(jelbourn): Introduce free scrolling that works w/ mobile momemtum scrolling (+snapping)
2118

@@ -26,24 +23,17 @@
2623
// TODO(jelbourn): Minimum and maximum date
2724
// TODO(jelbourn): Make sure the *time* on the written date makes sense (probably midnight).
2825
// TODO(jelbourn): Refactor "sections" into separate files.
29-
// TODO(jelbourn): Highlight today.
3026
// TODO(jelbourn): Horizontal line between months (pending spec finalization)
3127
// TODO(jelbourn): Alt+down in date input to open calendar
3228
// 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.
3931

4032
function calendarDirective() {
4133
return {
4234
template:
4335
'<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>' +
4737
'<div class="md-calendar-container">' +
4838
'<table class="md-calendar"></table>' +
4939
'</div>' +
@@ -74,20 +64,6 @@
7464
DISTANT_PAST: 4
7565
};
7666

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-
9167
/** Class applied to the selected date cell/. */
9268
var SELECTED_DATE_CLASS = 'md-calendar-selected-date';
9369

@@ -108,7 +84,9 @@
10884
* Controller for the mdCalendar component.
10985
* @ngInject @constructor
11086
*/
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+
11290
/** @final {!angular.$animate} */
11391
this.$animate = $animate;
11492

@@ -121,9 +99,15 @@
12199
/** @final */
122100
this.$mdUtil = $mdUtil;
123101

102+
/** @final */
103+
this.keyCode = $mdConstant.KEY_CODE;
104+
124105
/** @final */
125106
this.dateUtil = $$mdDateUtil;
126107

108+
/** @final */
109+
this.dateLocale = $$mdDateLocale;
110+
127111
/** @final {!angular.JQLite} */
128112
this.$element = $element;
129113

@@ -209,6 +193,8 @@
209193
* Initialization should occur after the ngModel value is known.
210194
*/
211195
CalendarCtrl.prototype.buildInitialCalendarDisplay = function() {
196+
this.buildWeekHeader();
197+
212198
this.displayDate = this.selectedDate || new Date();
213199
var nextMonth = this.dateUtil.getDateInNextMonth(this.displayDate);
214200
this.calendarElement.appendChild(this.buildCalendarForMonth(this.displayDate));
@@ -250,7 +236,7 @@
250236
this.$scope.$apply(function() {
251237
// Handled key events fall into two categories: selection and navigation.
252238
// Start by checking if this is a selection event.
253-
if (event.which === Keys.ENTER) {
239+
if (event.which === self.keyCode.ENTER) {
254240
self.ngModelCtrl.$setViewValue(self.displayDate);
255241
self.ngModelCtrl.$render();
256242
event.preventDefault();
@@ -280,16 +266,17 @@
280266
*/
281267
CalendarCtrl.prototype.getFocusDateFromKeyEvent = function(event) {
282268
var dateUtil = this.dateUtil;
269+
var keyCode = this.keyCode;
283270

284271
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);
293280
default: return this.displayDate;
294281
}
295282
};
@@ -572,11 +559,13 @@
572559
var annoucement = '';
573560

574561
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()] + '. ';
576565
}
577566

578567
if (previousDate.getDate() !== currentDate.getDate()) {
579-
annoucement += fullDays[currentDate.getDay()] + '. ' + currentDate.getDate() ;
568+
annoucement += this.dateLocale.days[currentDate.getDay()] + '. ' + currentDate.getDate() ;
580569
}
581570

582571
this.ariaLiveElement.textContent = annoucement;
@@ -585,6 +574,21 @@
585574

586575
/*** Constructing the calendar table ***/
587576

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+
588592
/**
589593
* Creates a single cell to contain a date in the calendar with all appropriate
590594
* attributes and classes added. If a date is given, the cell content will be set
@@ -601,7 +605,7 @@
601605
var selectionIndicator = document.createElement('span');
602606
cell.appendChild(selectionIndicator);
603607
selectionIndicator.classList.add('md-calendar-date-selection-indicator');
604-
selectionIndicator.textContent = opt_date.getDate();
608+
selectionIndicator.textContent = this.dateLocale.dates[opt_date.getDate()];
605609
//selectionIndicator.setAttribute('aria-label', '');
606610

607611
cell.setAttribute('tabindex', '-1');
@@ -629,7 +633,7 @@
629633
// Store rows for the month in a document fragment so that we can append them all at once.
630634
var monthBody = document.createElement('tbody');
631635
monthBody.classList.add('md-calendar-month');
632-
monthBody.setAttribute('aria-hidden', 'true')
636+
monthBody.setAttribute('aria-hidden', 'true');
633637

634638
var row = document.createElement('tr');
635639
monthBody.appendChild(row);
@@ -642,15 +646,15 @@
642646
monthLabelCell.classList.add('md-calendar-month-label');
643647
if (firstDayOfTheWeek <= 1) {
644648
monthLabelCell.setAttribute('colspan', '7');
645-
monthLabelCell.textContent = months[date.getMonth()];
649+
monthLabelCell.textContent = this.dateLocale.shortMonths[date.getMonth()];
646650

647651
var monthLabelRow = document.createElement('tr');
648652
monthLabelRow.appendChild(monthLabelCell);
649653
monthBody.insertBefore(monthLabelRow, row);
650654
} else {
651655
blankCellOffset = 2;
652656
monthLabelCell.setAttribute('colspan', '2');
653-
monthLabelCell.textContent = months[date.getMonth()];
657+
monthLabelCell.textContent = this.dateLocale.shortMonths[date.getMonth()];
654658

655659
row.appendChild(monthLabelCell);
656660
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
2+
describe('$$mdDateLocale', function() {
3+
var dateLocale, dateUtil;
4+
5+
// Fake $locale with made-up days and months.
6+
var $localeFake = {
7+
DATETIME_FORMATS: {
8+
DAY: ['Sundog', 'Mondog', 'Tuesdog', 'Wednesdog', 'Thursdog', 'Fridog', 'Saturdog'],
9+
MONTH: ['JanZ', 'FebZ', 'MarZ', 'AprZ', 'MayZ', 'JunZ', 'JulZ', 'AugZ', 'SeptZ',
10+
'OctZ', 'NovZ', 'DecZ'],
11+
SHORTMONTH: ['JZ', 'FZ', 'MZ', 'AZ', 'MZ', 'JZ', 'JZ', 'AZ', 'SZ', 'OZ', 'NZ', 'DZ']
12+
}
13+
};
14+
15+
beforeEach(module('material.components.calendar'));
16+
17+
describe('with default values', function() {
18+
beforeEach(module(function($provide) {
19+
$provide.value('$locale', $localeFake);
20+
}));
21+
22+
beforeEach(inject(function($$mdDateLocale, $$mdDateUtil) {
23+
dateLocale = $$mdDateLocale;
24+
dateUtil = $$mdDateUtil;
25+
}));
26+
27+
it('should expose default days, months, and dates', function() {
28+
expect(dateLocale.months).toEqual($localeFake.DATETIME_FORMATS.MONTH);
29+
expect(dateLocale.shortMonths).toEqual($localeFake.DATETIME_FORMATS.SHORTMONTH);
30+
expect(dateLocale.days).toEqual($localeFake.DATETIME_FORMATS.DAY);
31+
expect(dateLocale.shortDays).toEqual(['S', 'M', 'T', 'W', 'T', 'F', 'S']);
32+
expect(dateLocale.dates).toEqual([undefined, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
33+
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]);
34+
});
35+
36+
it('should have a default date formatter', function() {
37+
var date = new Date(2014, 2, 25);
38+
var dateStr = dateLocale.formatDate(date);
39+
expect(dateStr).toEqual(jasmine.any(String));
40+
expect(dateStr).toBeTruthy();
41+
});
42+
43+
it('should have a default date parser', function() {
44+
var dateStr = '2014-03-25';
45+
expect(dateLocale.parseDate(dateStr)).toEqual(jasmine.any(Date));
46+
});
47+
});
48+
49+
describe('with custom values', function() {
50+
var fakeMonths = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'];
51+
var fakeshortMonths = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'j', 'l'];
52+
var fakeDays = ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7'];
53+
var fakeShortDays = ['1', '2', '3', '4', '5', '6', '7'];
54+
var fakeDates = [undefined, 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9', 'X10', 'X11',
55+
'X12', 'X13', 'X14', 'X15', 'X16', 'X17', 'X18', 'X19', 'X20', 'X21', 'X22', 'X23', 'X24',
56+
'X25', 'X26', 'X27', 'X28', 'X29', 'X30', 'X31'];
57+
58+
beforeEach(module(function($$mdDateLocaleProvider) {
59+
$$mdDateLocaleProvider.months = fakeMonths;
60+
$$mdDateLocaleProvider.shortMonths = fakeshortMonths;
61+
$$mdDateLocaleProvider.days = fakeDays;
62+
$$mdDateLocaleProvider.shortDays = fakeShortDays;
63+
$$mdDateLocaleProvider.dates = fakeDates;
64+
$$mdDateLocaleProvider.formatDate = function() {
65+
return 'Your birthday!'
66+
};
67+
$$mdDateLocaleProvider.parseDate = function() {
68+
return new Date(1969, 6, 16);
69+
};
70+
}));
71+
72+
73+
beforeEach(inject(function($$mdDateLocale, $$mdDateUtil) {
74+
dateLocale = $$mdDateLocale;
75+
dateUtil = $$mdDateUtil;
76+
}));
77+
78+
it('should expose custom settings', function() {
79+
expect(dateLocale.months).toEqual(fakeMonths);
80+
expect(dateLocale.shortMonths).toEqual(fakeshortMonths);
81+
expect(dateLocale.days).toEqual(fakeDays);
82+
expect(dateLocale.shortDays).toEqual(fakeShortDays);
83+
expect(dateLocale.dates).toEqual(fakeDates);
84+
expect(dateLocale.formatDate(new Date())).toEqual('Your birthday!');
85+
expect(dateLocale.parseDate('2020-01-20')).toEqual(new Date(1969, 6, 16));
86+
});
87+
});
88+
});

src/components/calendar/dateLocaleProvider.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
* @param $locale
4545
* @returns {DateLocale}
4646
*/
47-
DateLocaleProvider.prototype.$get = function($locale, $filter) {
47+
DateLocaleProvider.prototype.$get = function($locale) {
4848
/**
4949
* Default date-to-string formatting function.
5050
* @param {Date} date
@@ -63,21 +63,28 @@
6363
return new Date(dateString);
6464
}
6565

66-
// The default "short" day strings are the first character of each day.
66+
// The default "short" day strings are the first character of each day,
67+
// e.g., "Monday" => "M".
6768
var defaultShortDays = $locale.DATETIME_FORMATS.DAY.map(function(day) {
6869
return day[0];
6970
});
7071

72+
// The default dates are simply the numbers 1 through 31.
73+
var defaultDates = Array(32);
74+
for (var i = 1; i <= 31; i++) {
75+
defaultDates[i] = i;
76+
}
77+
7178
window.$locale = $locale;
72-
window.$filter = $filter;
7379

74-
var dateLocale = {
80+
return {
7581
months: this.months || $locale.DATETIME_FORMATS.MONTH,
7682
shortMonths: this.shortMonths || $locale.DATETIME_FORMATS.SHORTMONTH,
7783
days: this.days || $locale.DATETIME_FORMATS.DAY,
7884
shortDays: this.shortDays || defaultShortDays,
79-
formatDate: defaultFormatDate,
80-
parseDate: defaultParseDate
85+
dates: this.dates || defaultDates,
86+
formatDate: this.formatDate || defaultFormatDate,
87+
parseDate: this.parseDate || defaultParseDate
8188
};
8289
};
8390

src/components/calendar/datePicker.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
(function() {
2+
'use strict';
3+
4+
angular.module('material.components.calendar')
5+
.directive('mdDatePicker', datePickerDirective);
6+
7+
function datePickerDirective() {
8+
return {
9+
template:
10+
'<div>' +
11+
'<input ng-model="textValue"> <br>' +
12+
'<md-calendar ng-model="dateValue"></md-calendar>' +
13+
'</div>',
14+
require: ['ngModel', 'mdDatePicker'],
15+
scope: {},
16+
controller: DatePickerCtrl,
17+
controllerAs: 'ctrl'
18+
};
19+
}
20+
21+
function DatePickerCtrl($$mdDateLocale) {
22+
/** @final */
23+
this.dateLocale = $$mdDateLocale;
24+
25+
/** @type {!angular.NgModelController} */
26+
this.ngModelCtrl = null;
27+
28+
/** @type {string} */
29+
this.textValue = '';
30+
31+
/** @type {Date} */
32+
this.dateValue = null;
33+
}
34+
35+
/**
36+
* Sets up the controller's reference to ngModelController.
37+
* @param {!angular.NgModelController} ngModelCtrl
38+
*/
39+
DatePickerCtrl.prototype.configureNgModel = function(ngModelCtrl) {
40+
this.ngModelCtrl = ngModelCtrl;
41+
42+
var self = this;
43+
ngModelCtrl.$render = function() {
44+
self.dateValue = self.ngModelCtrl.$viewValue
45+
self.textValue = self.dateLocale.format(self.ngModelCtrl.$viewValue);
46+
};
47+
}
48+
})();

0 commit comments

Comments
 (0)