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

Commit 9522148

Browse files
FDIMThomasBurleson
authored andcommitted
feat(datepicker): predicate function to allow fine-grained control over pickable dates
Fixes #4538. Closes #5475.
1 parent 2a76887 commit 9522148

File tree

6 files changed

+75
-10
lines changed

6 files changed

+75
-10
lines changed

src/components/datepicker/calendar.js

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
scope: {
5959
minDate: '=mdMinDate',
6060
maxDate: '=mdMaxDate',
61+
dateFilter: '=mdDateFilter',
6162
},
6263
require: ['ngModel', 'mdCalendar'],
6364
controller: CalendarCtrl,

src/components/datepicker/calendarMonth.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,7 @@
124124

125125
var cellText = this.dateLocale.dates[opt_date.getDate()];
126126

127-
if (this.dateUtil.isDateWithinRange(opt_date,
128-
this.calendarCtrl.minDate, this.calendarCtrl.maxDate)) {
127+
if (this.isDateEnabled(opt_date)) {
129128
// Add a indicator for select, hover, and focus states.
130129
var selectionIndicator = document.createElement('span');
131130
cell.appendChild(selectionIndicator);
@@ -145,7 +144,19 @@
145144

146145
return cell;
147146
};
148-
147+
148+
/**
149+
* Check whether date is in range and enabled
150+
* @param {Date=} opt_date
151+
* @return {boolean} Whether the date is enabled.
152+
*/
153+
CalendarMonthCtrl.prototype.isDateEnabled = function(opt_date) {
154+
return this.dateUtil.isDateWithinRange(opt_date,
155+
this.calendarCtrl.minDate, this.calendarCtrl.maxDate) &&
156+
(!angular.isFunction(this.calendarCtrl.dateFilter)
157+
|| this.calendarCtrl.dateFilter(opt_date));
158+
}
159+
149160
/**
150161
* Builds a `tr` element for the calendar grid.
151162
* @param rowNumber The week number within the month.

src/components/datepicker/datePicker.js

+21-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* @param {expression=} ng-change Expression evaluated when the model value changes.
2525
* @param {Date=} md-min-date Expression representing a min date (inclusive).
2626
* @param {Date=} md-max-date Expression representing a max date (inclusive).
27+
* @param {(function(Date): boolean)=} md-date-filter Function expecting a date and returning a boolean whether it can be selected or not.
2728
* @param {boolean=} disabled Whether the datepicker is disabled.
2829
* @param {boolean=} required Whether a value is required for the datepicker.
2930
*
@@ -75,6 +76,7 @@
7576
'<div class="md-datepicker-calendar">' +
7677
'<md-calendar role="dialog" aria-label="{{::ctrl.dateLocale.msgCalendar}}" ' +
7778
'md-min-date="ctrl.minDate" md-max-date="ctrl.maxDate"' +
79+
'md-date-filter="ctrl.dateFilter"' +
7880
'ng-model="ctrl.date" ng-if="ctrl.isCalendarOpen">' +
7981
'</md-calendar>' +
8082
'</div>' +
@@ -83,7 +85,8 @@
8385
scope: {
8486
minDate: '=mdMinDate',
8587
maxDate: '=mdMaxDate',
86-
placeholder: '@mdPlaceholder'
88+
placeholder: '@mdPlaceholder',
89+
dateFilter: '=mdDateFilter'
8790
},
8891
controller: DatePickerCtrl,
8992
controllerAs: 'ctrl',
@@ -356,6 +359,10 @@
356359
if (this.dateUtil.isValidDate(this.maxDate)) {
357360
this.ngModelCtrl.$setValidity('maxdate', this.date <= this.maxDate);
358361
}
362+
363+
if (angular.isFunction(this.dateFilter)) {
364+
this.ngModelCtrl.$setValidity('filtered', this.dateFilter(this.date));
365+
}
359366
}
360367
};
361368

@@ -377,8 +384,8 @@
377384
this.date = null;
378385
this.inputContainer.classList.remove(INVALID_CLASS);
379386
} else if (this.dateUtil.isValidDate(parsedDate) &&
380-
this.dateLocale.isDateComplete(inputString) &&
381-
this.dateUtil.isDateWithinRange(parsedDate, this.minDate, this.maxDate)) {
387+
this.dateLocale.isDateComplete(inputString) &&
388+
this.isDateEnabled(parsedDate)) {
382389
this.ngModelCtrl.$setViewValue(parsedDate);
383390
this.date = parsedDate;
384391
this.inputContainer.classList.remove(INVALID_CLASS);
@@ -387,7 +394,17 @@
387394
this.inputContainer.classList.toggle(INVALID_CLASS, inputString);
388395
}
389396
};
390-
397+
398+
/**
399+
* Check whether date is in range and enabled
400+
* @param {Date=} opt_date
401+
* @return {boolean} Whether the date is enabled.
402+
*/
403+
DatePickerCtrl.prototype.isDateEnabled = function(opt_date) {
404+
return this.dateUtil.isDateWithinRange(opt_date, this.minDate, this.maxDate) &&
405+
(!angular.isFunction(this.dateFilter) || this.dateFilter(opt_date));
406+
}
407+
391408
/** Position and attach the floating calendar to the document. */
392409
DatePickerCtrl.prototype.attachCalendarPane = function() {
393410
var calendarPane = this.calendarPane;

src/components/datepicker/datePicker.spec.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ describe('md-date-picker', function() {
1414
'<md-datepicker name="birthday" ' +
1515
'md-max-date="maxDate" ' +
1616
'md-min-date="minDate" ' +
17+
'md-date-filter="dateFilter"' +
1718
'ng-model="myDate" ' +
1819
'ng-required="isRequired" ' +
1920
'ng-disabled="isDisabled">' +
@@ -43,7 +44,6 @@ describe('md-date-picker', function() {
4344
createDatepickerInstance(DATEPICKER_TEMPLATE);
4445
controller.closeCalendarPane();
4546
}));
46-
4747
/**
4848
* Compile and link the given template and store values for element, scope, and controller.
4949
* @param {string} template
@@ -184,6 +184,16 @@ describe('md-date-picker', function() {
184184

185185
expect(formCtrl.$error['maxdate']).toBeTruthy();
186186
});
187+
188+
it('should set `filtered` $error flag on the form', function() {
189+
pageScope.dateFilter = function(date) {
190+
return date.getDay() === 1;
191+
};
192+
populateInputElement('2016-01-03');
193+
controller.ngModelCtrl.$render();
194+
195+
expect(formCtrl.$error['filtered']).toBeTruthy();
196+
});
187197
});
188198
});
189199

@@ -221,6 +231,16 @@ describe('md-date-picker', function() {
221231
populateInputElement('7');
222232
expect(controller.inputContainer).toHaveClass('md-datepicker-invalid');
223233
});
234+
235+
it('should not update the model when value is not enabled', function() {
236+
pageScope.dateFilter = function(date) {
237+
return date.getDay() === 1;
238+
};
239+
pageScope.$apply();
240+
241+
populateInputElement('5/30/2014');
242+
expect(controller.ngModelCtrl.$modelValue).toEqual(initialDate);
243+
});
224244
});
225245

226246
describe('floating calendar pane', function() {

src/components/datepicker/demoBasicUsage/index.html

+13-2
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,28 @@ <h4>Disabled date-picker</h4>
1010
<h4>Date-picker with min date and max date</h4>
1111
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
1212
md-min-date="minDate" md-max-date="maxDate"></md-datepicker>
13-
13+
<h4>Only weekends are selectable</h4>
14+
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
15+
md-date-filter="onlyWeekendsPredicate"></md-datepicker>
16+
17+
<h4>Only weekends within given range are selectable</h4>
18+
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
19+
md-min-date="minDate" md-max-date="maxDate"
20+
md-date-filter="onlyWeekendsPredicate"></md-datepicker>
21+
1422
<h4>With ngMessages</h4>
1523
<form name="myForm">
1624
<md-datepicker name="dateField" ng-model="myDate" md-placeholder="Enter date"
17-
required md-min-date="minDate" md-max-date="maxDate"></md-datepicker>
25+
required md-min-date="minDate" md-max-date="maxDate"
26+
md-date-filter="onlyWeekendsPredicate"></md-datepicker>
1827

1928
<div class="validation-messages" ng-messages="myForm.dateField.$error">
2029
<div ng-message="required">This date is required!</div>
2130
<div ng-message="mindate">Date is too early!</div>
2231
<div ng-message="maxdate">Date is too late!</div>
32+
<div ng-message="filtered">Only weekends are allowed!</div>
2333
</div>
2434
</form>
35+
2536
</md-content>
2637
</div>

src/components/datepicker/demoBasicUsage/script.js

+5
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,9 @@ angular.module('datepickerBasicUsage',
1111
$scope.myDate.getFullYear(),
1212
$scope.myDate.getMonth() + 2,
1313
$scope.myDate.getDate());
14+
15+
$scope.onlyWeekendsPredicate = function(date) {
16+
var day = date.getDay();
17+
return day === 0 || day === 6;
18+
}
1419
});

0 commit comments

Comments
 (0)