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

feat(datepicker): predicate function to allow fine-grained control over pickable dates #5475

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/datepicker/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
scope: {
minDate: '=mdMinDate',
maxDate: '=mdMaxDate',
dateFilter: '=mdDateFilter',
},
require: ['ngModel', 'mdCalendar'],
controller: CalendarCtrl,
Expand Down
17 changes: 14 additions & 3 deletions src/components/datepicker/calendarMonth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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.
Expand Down
25 changes: 21 additions & 4 deletions src/components/datepicker/datePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -75,6 +76,7 @@
'<div class="md-datepicker-calendar">' +
'<md-calendar role="dialog" aria-label="{{::ctrl.dateLocale.msgCalendar}}" ' +
'md-min-date="ctrl.minDate" md-max-date="ctrl.maxDate"' +
'md-date-filter="ctrl.dateFilter"' +
'ng-model="ctrl.date" ng-if="ctrl.isCalendarOpen">' +
'</md-calendar>' +
'</div>' +
Expand All @@ -83,7 +85,8 @@
scope: {
minDate: '=mdMinDate',
maxDate: '=mdMaxDate',
placeholder: '@mdPlaceholder'
placeholder: '@mdPlaceholder',
dateFilter: '=mdDateFilter'
},
controller: DatePickerCtrl,
controllerAs: 'ctrl',
Expand Down Expand Up @@ -344,6 +347,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));
}
}
};

Expand All @@ -365,8 +372,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);
Expand All @@ -375,7 +382,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;
Expand Down
22 changes: 21 additions & 1 deletion src/components/datepicker/datePicker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe('md-date-picker', function() {
'<md-datepicker name="birthday" ' +
'md-max-date="maxDate" ' +
'md-min-date="minDate" ' +
'md-date-filter="dateFilter"' +
'ng-model="myDate" ' +
'ng-required="isRequired" ' +
'ng-disabled="isDisabled">' +
Expand All @@ -38,7 +39,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
Expand Down Expand Up @@ -149,6 +149,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();
});
});
});

Expand Down Expand Up @@ -186,6 +196,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() {
Expand Down
15 changes: 13 additions & 2 deletions src/components/datepicker/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,28 @@ <h4>Disabled date-picker</h4>
<h4>Date-picker with min date and max date</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-min-date="minDate" md-max-date="maxDate"></md-datepicker>

<h4>Only weekends are selectable</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-date-filter="onlyWeekendsPredicate"></md-datepicker>

<h4>Only weekends within given range are selectable</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-min-date="minDate" md-max-date="maxDate"
md-date-filter="onlyWeekendsPredicate"></md-datepicker>

<h4>With ngMessages</h4>
<form name="myForm">
<md-datepicker name="dateField" ng-model="myDate" md-placeholder="Enter date"
required md-min-date="minDate" md-max-date="maxDate"></md-datepicker>
required md-min-date="minDate" md-max-date="maxDate"
md-date-filter="onlyWeekendsPredicate"></md-datepicker>

<div class="validation-messages" ng-messages="myForm.dateField.$error">
<div ng-message="required">This date is required!</div>
<div ng-message="mindate">Date is too early!</div>
<div ng-message="maxdate">Date is too late!</div>
<div ng-message="filtered">Only weekends are allowed!</div>
</div>
</form>

</md-content>
</div>
5 changes: 5 additions & 0 deletions src/components/datepicker/demoBasicUsage/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
});