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

Commit 522d428

Browse files
crisbetojelbourn
authored andcommitted
feat(datepicker): configurable start/end dates, consistency improvements (#9309)
* Allows for the datepicker's first and last renderable dates to be configured via the $mdDateLocaleProvider. This lets developers specify when the calendar begins and ends. * Makes the month and year views consistent by having the same start and end dates. Previously the month view started from 1933, whereas the year view started from 1816. * Simplifies the logic for determining the start and end dates. * Fixes not being able to use the keyboard to navigate to the first item in the year view, if a minimum date is specified. Fixes #9269.
1 parent 0f424db commit 522d428

File tree

7 files changed

+80
-69
lines changed

7 files changed

+80
-69
lines changed

src/components/datepicker/js/calendar.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
* @ngInject @constructor
9090
*/
9191
function CalendarCtrl($element, $scope, $$mdDateUtil, $mdUtil,
92-
$mdConstant, $mdTheming, $$rAF, $attrs) {
92+
$mdConstant, $mdTheming, $$rAF, $attrs, $mdDateLocale) {
9393

9494
$mdTheming($element);
9595

@@ -155,6 +155,20 @@
155155
*/
156156
this.selectedDate = null;
157157

158+
/**
159+
* The first date that can be rendered by the calendar. The default is taken
160+
* from the mdDateLocale provider and is limited by the mdMinDate.
161+
* @type {Date}
162+
*/
163+
this.firstRenderableDate = null;
164+
165+
/**
166+
* The last date that can be rendered by the calendar. The default comes
167+
* from the mdDateLocale provider and is limited by the maxDate.
168+
* @type {Date}
169+
*/
170+
this.lastRenderableDate = null;
171+
158172
/**
159173
* Used to toggle initialize the root element in the next digest.
160174
* @type {Boolean}
@@ -190,6 +204,18 @@
190204
$scope.$on('$destroy', function() {
191205
angular.element(document.body).off('keydown', boundKeyHandler);
192206
});
207+
208+
if (this.minDate && this.minDate > $mdDateLocale.firstRenderableDate) {
209+
this.firstRenderableDate = this.minDate;
210+
} else {
211+
this.firstRenderableDate = $mdDateLocale.firstRenderableDate;
212+
}
213+
214+
if (this.maxDate && this.maxDate < $mdDateLocale.lastRenderableDate) {
215+
this.lastRenderableDate = this.maxDate;
216+
} else {
217+
this.lastRenderableDate = $mdDateLocale.lastRenderableDate;
218+
}
193219
}
194220

195221
/**

src/components/datepicker/js/calendar.spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -647,16 +647,16 @@ describe('md-calendar', function() {
647647
selectedDate.focus();
648648

649649
dispatchKeyEvent(keyCodes.UP_ARROW);
650-
expect(getFocusedDateElement().textContent).toBe('Feb');
650+
expect(getFocusedDateElement().textContent).toBe('Jan');
651651
expect(getGroupLabelForDateCell(getFocusedDateElement())).toBe('2014');
652652

653653
dispatchKeyEvent(keyCodes.LEFT_ARROW);
654-
expect(getFocusedDateElement().textContent).toBe('Feb');
654+
expect(getFocusedDateElement().textContent).toBe('Jan');
655655
expect(getGroupLabelForDateCell(getFocusedDateElement())).toBe('2014');
656656

657657
dispatchKeyEvent(keyCodes.DOWN_ARROW);
658658
dispatchKeyEvent(keyCodes.RIGHT_ARROW);
659-
expect(getFocusedDateElement().textContent).toBe('Sep');
659+
expect(getFocusedDateElement().textContent).toBe('Aug');
660660
expect(getGroupLabelForDateCell(getFocusedDateElement())).toBe('2014');
661661

662662
dispatchKeyEvent(keyCodes.RIGHT_ARROW);

src/components/datepicker/js/calendarMonth.js

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,6 @@
7676
/** @final {HTMLElement} */
7777
this.calendarScroller = $element[0].querySelector('.md-virtual-repeat-scroller');
7878

79-
/** @type {Date} */
80-
this.firstRenderableDate = null;
81-
8279
/** @type {boolean} */
8380
this.isInitialized = false;
8481

@@ -116,37 +113,23 @@
116113
* setting up the object that will be iterated by the virtual repeater.
117114
*/
118115
CalendarMonthCtrl.prototype.initialize = function(calendarCtrl) {
119-
var minDate = calendarCtrl.minDate;
120-
var maxDate = calendarCtrl.maxDate;
121-
this.calendarCtrl = calendarCtrl;
122-
123116
/**
124117
* Dummy array-like object for virtual-repeat to iterate over. The length is the total
125-
* number of months that can be viewed. This is shorter than ideal because of (potential)
126-
* Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=1181658.
118+
* number of months that can be viewed. We add 2 months: one to include the current month
119+
* and one for the last dummy month.
120+
*
121+
* This is shorter than ideal because of a (potential) Firefox bug
122+
* https://bugzilla.mozilla.org/show_bug.cgi?id=1181658.
127123
*/
128-
this.items = { length: 2000 };
129-
130-
if (maxDate && minDate) {
131-
// Limit the number of months if min and max dates are set.
132-
var numMonths = this.dateUtil.getMonthDistance(minDate, maxDate) + 1;
133-
numMonths = Math.max(numMonths, 1);
134-
// Add an additional month as the final dummy month for rendering purposes.
135-
numMonths += 1;
136-
this.items.length = numMonths;
137-
}
138-
139-
this.firstRenderableDate = this.dateUtil.incrementMonths(calendarCtrl.today, -this.items.length / 2);
140124

141-
if (minDate && minDate > this.firstRenderableDate) {
142-
this.firstRenderableDate = minDate;
143-
} else if (maxDate) {
144-
// Calculate the difference between the start date and max date.
145-
// Subtract 1 because it's an inclusive difference and 1 for the final dummy month.
146-
var monthDifference = this.items.length - 2;
147-
this.firstRenderableDate = this.dateUtil.incrementMonths(maxDate, -(this.items.length - 2));
148-
}
125+
this.items = {
126+
length: this.dateUtil.getMonthDistance(
127+
calendarCtrl.firstRenderableDate,
128+
calendarCtrl.lastRenderableDate
129+
) + 2
130+
};
149131

132+
this.calendarCtrl = calendarCtrl;
150133
this.attachScopeListeners();
151134
calendarCtrl.updateVirtualRepeat();
152135

@@ -160,8 +143,11 @@
160143
*/
161144
CalendarMonthCtrl.prototype.getSelectedMonthIndex = function() {
162145
var calendarCtrl = this.calendarCtrl;
163-
return this.dateUtil.getMonthDistance(this.firstRenderableDate,
164-
calendarCtrl.displayDate || calendarCtrl.selectedDate || calendarCtrl.today);
146+
147+
return this.dateUtil.getMonthDistance(
148+
calendarCtrl.firstRenderableDate,
149+
calendarCtrl.displayDate || calendarCtrl.selectedDate || calendarCtrl.today
150+
);
165151
};
166152

167153
/**
@@ -238,7 +224,7 @@
238224
*/
239225
CalendarMonthCtrl.prototype.animateDateChange = function(date) {
240226
if (this.dateUtil.isValidDate(date)) {
241-
var monthDistance = this.dateUtil.getMonthDistance(this.firstRenderableDate, date);
227+
var monthDistance = this.dateUtil.getMonthDistance(this.calendarCtrl.firstRenderableDate, date);
242228
this.calendarScroller.scrollTop = monthDistance * TBODY_HEIGHT;
243229
}
244230

src/components/datepicker/js/calendarMonthBody.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878

7979
/** Generate and append the content for this month to the directive element. */
8080
CalendarMonthBodyCtrl.prototype.generateContent = function() {
81-
var date = this.dateUtil.incrementMonths(this.monthCtrl.firstRenderableDate, this.offset);
81+
var date = this.dateUtil.incrementMonths(this.calendarCtrl.firstRenderableDate, this.offset);
8282

8383
this.$element
8484
.empty()

src/components/datepicker/js/calendarYear.js

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,6 @@
6363
/** @final {HTMLElement} */
6464
this.calendarScroller = $element[0].querySelector('.md-virtual-repeat-scroller');
6565

66-
/** @type {Date} */
67-
this.firstRenderableDate = null;
68-
6966
/** @type {boolean} */
7067
this.isInitialized = false;
7168

@@ -89,33 +86,18 @@
8986
* setting up the object that will be iterated by the virtual repeater.
9087
*/
9188
CalendarYearCtrl.prototype.initialize = function(calendarCtrl) {
92-
var minDate = calendarCtrl.minDate;
93-
var maxDate = calendarCtrl.maxDate;
94-
this.calendarCtrl = calendarCtrl;
95-
9689
/**
9790
* Dummy array-like object for virtual-repeat to iterate over. The length is the total
98-
* number of months that can be viewed. This is shorter than ideal because of (potential)
99-
* Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=1181658.
91+
* number of years that can be viewed. We add 1 extra in order to include the current year.
10092
*/
101-
this.items = { length: 400 };
102-
103-
this.firstRenderableDate = this.dateUtil.incrementYears(calendarCtrl.today, - (this.items.length / 2));
104-
105-
if (minDate && minDate > this.firstRenderableDate) {
106-
this.firstRenderableDate = minDate;
107-
} else if (maxDate) {
108-
// Calculate the year difference between the start date and max date.
109-
// Subtract 1 because it's an inclusive difference.
110-
this.firstRenderableDate = this.dateUtil.incrementMonths(maxDate, - (this.items.length - 1));
111-
}
112-
113-
if (maxDate && minDate) {
114-
// Limit the number of years if min and max dates are set.
115-
var numYears = this.dateUtil.getYearDistance(this.firstRenderableDate, maxDate) + 1;
116-
this.items.length = Math.max(numYears, 1);
117-
}
93+
this.items = {
94+
length: this.dateUtil.getYearDistance(
95+
calendarCtrl.firstRenderableDate,
96+
calendarCtrl.lastRenderableDate
97+
) + 1
98+
};
11899

100+
this.calendarCtrl = calendarCtrl;
119101
this.attachScopeListeners();
120102
calendarCtrl.updateVirtualRepeat();
121103

@@ -129,8 +111,11 @@
129111
*/
130112
CalendarYearCtrl.prototype.getFocusedYearIndex = function() {
131113
var calendarCtrl = this.calendarCtrl;
132-
return this.dateUtil.getYearDistance(this.firstRenderableDate,
133-
calendarCtrl.displayDate || calendarCtrl.selectedDate || calendarCtrl.today);
114+
115+
return this.dateUtil.getYearDistance(
116+
calendarCtrl.firstRenderableDate,
117+
calendarCtrl.displayDate || calendarCtrl.selectedDate || calendarCtrl.today
118+
);
134119
};
135120

136121
/**
@@ -164,7 +149,7 @@
164149
*/
165150
CalendarYearCtrl.prototype.animateDateChange = function(date) {
166151
if (this.dateUtil.isValidDate(date)) {
167-
var monthDistance = this.dateUtil.getYearDistance(this.firstRenderableDate, date);
152+
var monthDistance = this.dateUtil.getYearDistance(this.calendarCtrl.firstRenderableDate, date);
168153
this.calendarScroller.scrollTop = monthDistance * TBODY_HEIGHT;
169154
}
170155

@@ -198,7 +183,7 @@
198183
}
199184

200185
if (date) {
201-
var min = calendarCtrl.minDate ? dateUtil.incrementMonths(dateUtil.getFirstDateOfMonth(calendarCtrl.minDate), 1) : null;
186+
var min = calendarCtrl.minDate ? dateUtil.getFirstDateOfMonth(calendarCtrl.minDate) : null;
202187
var max = calendarCtrl.maxDate ? dateUtil.getFirstDateOfMonth(calendarCtrl.maxDate) : null;
203188
date = dateUtil.getFirstDateOfMonth(this.dateUtil.clampDate(date, min, max));
204189

src/components/datepicker/js/calendarYearBody.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868

6969
/** Generate and append the content for this year to the directive element. */
7070
CalendarYearBodyCtrl.prototype.generateContent = function() {
71-
var date = this.dateUtil.incrementYears(this.yearCtrl.firstRenderableDate, this.offset);
71+
var date = this.dateUtil.incrementYears(this.calendarCtrl.firstRenderableDate, this.offset);
7272

7373
this.$element
7474
.empty()

src/components/datepicker/js/dateLocaleProvider.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
* @property {(string)=} msgCalendar Translation of the label "Calendar" for the current locale.
3232
* @property {(string)=} msgOpenCalendar Translation of the button label "Open calendar" for the
3333
* current locale.
34+
* @property {Date=} firstRenderableDate The date from which the datepicker calendar will begin
35+
* rendering. Note that this will be ignored if a minimum date is set. Defaults to January 1st 1880.
36+
* @property {Date=} lastRenderableDate The last date that will be rendered by the datepicker
37+
* calendar. Note that this will be ignored if a maximum date is set. Defaults to January 1st 2130.
3438
*
3539
* @usage
3640
* <hljs lang="js">
@@ -72,6 +76,10 @@
7276
*
7377
* $mdDateLocaleProvider.msgCalendar = 'Calendrier';
7478
* $mdDateLocaleProvider.msgOpenCalendar = 'Ouvrir le calendrier';
79+
*
80+
* // You can also set when your calendar begins and ends.
81+
* $mdDateLocaleProvider.firstRenderableDate = new Date(1776, 6, 4);
82+
* $mdDateLocaleProvider.lastRenderableDate = new Date(2012, 11, 21);
7583
* });
7684
* </hljs>
7785
*
@@ -260,6 +268,10 @@
260268
var defaultMsgCalendar = 'Calendar';
261269
var defaultMsgOpenCalendar = 'Open calendar';
262270

271+
// Default start/end dates that are rendered in the calendar.
272+
var defaultFirstRenderableDate = new Date(1880, 0, 1);
273+
var defaultLastRendereableDate = new Date(defaultFirstRenderableDate.getFullYear() + 250, 0, 1);
274+
263275
var service = {
264276
months: this.months || $locale.DATETIME_FORMATS.MONTH,
265277
shortMonths: this.shortMonths || $locale.DATETIME_FORMATS.SHORTMONTH,
@@ -275,7 +287,9 @@
275287
weekNumberFormatter: this.weekNumberFormatter || defaultWeekNumberFormatter,
276288
longDateFormatter: this.longDateFormatter || defaultLongDateFormatter,
277289
msgCalendar: this.msgCalendar || defaultMsgCalendar,
278-
msgOpenCalendar: this.msgOpenCalendar || defaultMsgOpenCalendar
290+
msgOpenCalendar: this.msgOpenCalendar || defaultMsgOpenCalendar,
291+
firstRenderableDate: this.firstRenderableDate || defaultFirstRenderableDate,
292+
lastRenderableDate: this.lastRenderableDate || defaultLastRendereableDate
279293
};
280294

281295
return service;

0 commit comments

Comments
 (0)