From 9522148b18059a299994386eb3b54bc36781545a Mon Sep 17 00:00:00 2001 From: Domas Trijonis Date: Sat, 31 Oct 2015 17:55:22 +0100 Subject: [PATCH] feat(datepicker): predicate function to allow fine-grained control over pickable dates Fixes #4538. Closes #5475. --- src/components/datepicker/calendar.js | 1 + src/components/datepicker/calendarMonth.js | 17 ++++++++++--- src/components/datepicker/datePicker.js | 25 ++++++++++++++++--- src/components/datepicker/datePicker.spec.js | 22 +++++++++++++++- .../datepicker/demoBasicUsage/index.html | 15 +++++++++-- .../datepicker/demoBasicUsage/script.js | 5 ++++ 6 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/components/datepicker/calendar.js b/src/components/datepicker/calendar.js index 289bbb245d0..21ebca062ea 100644 --- a/src/components/datepicker/calendar.js +++ b/src/components/datepicker/calendar.js @@ -58,6 +58,7 @@ scope: { minDate: '=mdMinDate', maxDate: '=mdMaxDate', + dateFilter: '=mdDateFilter', }, require: ['ngModel', 'mdCalendar'], controller: CalendarCtrl, diff --git a/src/components/datepicker/calendarMonth.js b/src/components/datepicker/calendarMonth.js index 8ab5854e434..a2b42e464e0 100644 --- a/src/components/datepicker/calendarMonth.js +++ b/src/components/datepicker/calendarMonth.js @@ -124,8 +124,7 @@ var cellText = this.dateLocale.dates[opt_date.getDate()]; - if (this.dateUtil.isDateWithinRange(opt_date, - this.calendarCtrl.minDate, this.calendarCtrl.maxDate)) { + if (this.isDateEnabled(opt_date)) { // Add a indicator for select, hover, and focus states. var selectionIndicator = document.createElement('span'); cell.appendChild(selectionIndicator); @@ -145,7 +144,19 @@ return cell; }; - + + /** + * Check whether date is in range and enabled + * @param {Date=} opt_date + * @return {boolean} Whether the date is enabled. + */ + CalendarMonthCtrl.prototype.isDateEnabled = function(opt_date) { + return this.dateUtil.isDateWithinRange(opt_date, + this.calendarCtrl.minDate, this.calendarCtrl.maxDate) && + (!angular.isFunction(this.calendarCtrl.dateFilter) + || this.calendarCtrl.dateFilter(opt_date)); + } + /** * Builds a `tr` element for the calendar grid. * @param rowNumber The week number within the month. diff --git a/src/components/datepicker/datePicker.js b/src/components/datepicker/datePicker.js index 1c5301f0ed4..6de3c7e6a86 100644 --- a/src/components/datepicker/datePicker.js +++ b/src/components/datepicker/datePicker.js @@ -24,6 +24,7 @@ * @param {expression=} ng-change Expression evaluated when the model value changes. * @param {Date=} md-min-date Expression representing a min date (inclusive). * @param {Date=} md-max-date Expression representing a max date (inclusive). + * @param {(function(Date): boolean)=} md-date-filter Function expecting a date and returning a boolean whether it can be selected or not. * @param {boolean=} disabled Whether the datepicker is disabled. * @param {boolean=} required Whether a value is required for the datepicker. * @@ -75,6 +76,7 @@ '
' + '' + '' + '
' + @@ -83,7 +85,8 @@ scope: { minDate: '=mdMinDate', maxDate: '=mdMaxDate', - placeholder: '@mdPlaceholder' + placeholder: '@mdPlaceholder', + dateFilter: '=mdDateFilter' }, controller: DatePickerCtrl, controllerAs: 'ctrl', @@ -356,6 +359,10 @@ if (this.dateUtil.isValidDate(this.maxDate)) { this.ngModelCtrl.$setValidity('maxdate', this.date <= this.maxDate); } + + if (angular.isFunction(this.dateFilter)) { + this.ngModelCtrl.$setValidity('filtered', this.dateFilter(this.date)); + } } }; @@ -377,8 +384,8 @@ this.date = null; this.inputContainer.classList.remove(INVALID_CLASS); } else if (this.dateUtil.isValidDate(parsedDate) && - this.dateLocale.isDateComplete(inputString) && - this.dateUtil.isDateWithinRange(parsedDate, this.minDate, this.maxDate)) { + this.dateLocale.isDateComplete(inputString) && + this.isDateEnabled(parsedDate)) { this.ngModelCtrl.$setViewValue(parsedDate); this.date = parsedDate; this.inputContainer.classList.remove(INVALID_CLASS); @@ -387,7 +394,17 @@ this.inputContainer.classList.toggle(INVALID_CLASS, inputString); } }; - + + /** + * Check whether date is in range and enabled + * @param {Date=} opt_date + * @return {boolean} Whether the date is enabled. + */ + DatePickerCtrl.prototype.isDateEnabled = function(opt_date) { + return this.dateUtil.isDateWithinRange(opt_date, this.minDate, this.maxDate) && + (!angular.isFunction(this.dateFilter) || this.dateFilter(opt_date)); + } + /** Position and attach the floating calendar to the document. */ DatePickerCtrl.prototype.attachCalendarPane = function() { var calendarPane = this.calendarPane; diff --git a/src/components/datepicker/datePicker.spec.js b/src/components/datepicker/datePicker.spec.js index 0777dde1e6e..fb3ee83501e 100644 --- a/src/components/datepicker/datePicker.spec.js +++ b/src/components/datepicker/datePicker.spec.js @@ -14,6 +14,7 @@ describe('md-date-picker', function() { '' + @@ -43,7 +44,6 @@ describe('md-date-picker', function() { createDatepickerInstance(DATEPICKER_TEMPLATE); controller.closeCalendarPane(); })); - /** * Compile and link the given template and store values for element, scope, and controller. * @param {string} template @@ -184,6 +184,16 @@ describe('md-date-picker', function() { expect(formCtrl.$error['maxdate']).toBeTruthy(); }); + + it('should set `filtered` $error flag on the form', function() { + pageScope.dateFilter = function(date) { + return date.getDay() === 1; + }; + populateInputElement('2016-01-03'); + controller.ngModelCtrl.$render(); + + expect(formCtrl.$error['filtered']).toBeTruthy(); + }); }); }); @@ -221,6 +231,16 @@ describe('md-date-picker', function() { populateInputElement('7'); expect(controller.inputContainer).toHaveClass('md-datepicker-invalid'); }); + + it('should not update the model when value is not enabled', function() { + pageScope.dateFilter = function(date) { + return date.getDay() === 1; + }; + pageScope.$apply(); + + populateInputElement('5/30/2014'); + expect(controller.ngModelCtrl.$modelValue).toEqual(initialDate); + }); }); describe('floating calendar pane', function() { diff --git a/src/components/datepicker/demoBasicUsage/index.html b/src/components/datepicker/demoBasicUsage/index.html index 33cfe334a72..21d5115c72d 100644 --- a/src/components/datepicker/demoBasicUsage/index.html +++ b/src/components/datepicker/demoBasicUsage/index.html @@ -10,17 +10,28 @@

Disabled date-picker

Date-picker with min date and max date

- +

Only weekends are selectable

+ + +

Only weekends within given range are selectable

+ +

With ngMessages

+ required md-min-date="minDate" md-max-date="maxDate" + md-date-filter="onlyWeekendsPredicate">
This date is required!
Date is too early!
Date is too late!
+
Only weekends are allowed!
+ diff --git a/src/components/datepicker/demoBasicUsage/script.js b/src/components/datepicker/demoBasicUsage/script.js index f0b99bd9ba8..c68cd823318 100644 --- a/src/components/datepicker/demoBasicUsage/script.js +++ b/src/components/datepicker/demoBasicUsage/script.js @@ -11,4 +11,9 @@ angular.module('datepickerBasicUsage', $scope.myDate.getFullYear(), $scope.myDate.getMonth() + 2, $scope.myDate.getDate()); + + $scope.onlyWeekendsPredicate = function(date) { + var day = date.getDay(); + return day === 0 || day === 6; + } });