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

Commit

Permalink
feat(datepicker): predicate function to allow fine-grained control ov…
Browse files Browse the repository at this point in the history
…er pickable dates

Fixes #4538. Closes #5475.
  • Loading branch information
FDIM authored and ThomasBurleson committed Nov 5, 2015
1 parent 2a76887 commit 9522148
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 10 deletions.
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 @@ -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));
}
}
};

Expand All @@ -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);
Expand All @@ -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;
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 Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
});
});
});

Expand Down Expand Up @@ -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() {
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;
}
});

0 comments on commit 9522148

Please sign in to comment.