diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js index 05784947d0..121fb9b5d0 100644 --- a/src/datepicker/datepicker.js +++ b/src/datepicker/datepicker.js @@ -440,7 +440,10 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi currentText: '@', clearText: '@', closeText: '@', - dateDisabled: '&' + dateDisabled: '&', + minDate: '@', + maxDate: '@' + }, link: function(scope, element, attrs, ngModel) { var dateFormat, @@ -500,7 +503,62 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi if (attrs.dateDisabled) { datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); } + function compareDates(date1, date2) { + if (isNaN(date1) || isNaN(date2)) { + return undefined; + } + else { + return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()) ); + } + } + function getDateLimitToCheck(limitName){ + var watchDate=scope.watchData[limitName]; + if (!watchDate) { + return null; + } else { + return new Date(watchDate); + } + } + + function isDateLimitMet(limitName, dateToCheck, viewValue){ + var dateCompare = compareDates(new Date(viewValue), dateToCheck); + if (limitName=='minDate'){ + return !dateCompare || dateCompare>0; + } else if (limitName=='maxDate'){ + return !dateCompare || dateCompare<0; + } + } + function dateLimitFormat(limitName, viewValue){ + var dateLimit=getDateLimitToCheck(limitName); + if (dateLimit) { + ngModel.$setValidity(limitName, isDateLimitMet(limitName, dateLimit , viewValue)); + } + return viewValue; + } + function dateLimitParse(limitName, viewValue){ + var dateLimit=getDateLimitToCheck(limitName); + if (dateLimit) { + var isMet = isDateLimitMet(limitName, dateLimit , viewValue); + ngModel.$setValidity(limitName, isMet ); + return isMet ? viewValue : undefined; + } else { + return viewValue; + } + } + + function minLimitParse(viewValue) { + return dateLimitParse('minDate', viewValue); + } + function minLimitFormat(viewValue) { + return dateLimitFormat('minDate', viewValue); + } + function maxLimitParse(viewValue) { + return dateLimitParse('maxDate', viewValue); + } + function maxLimitFormat(viewValue) { + return dateLimitFormat('maxDate', viewValue); + } function parseDate(viewValue) { if (!viewValue) { ngModel.$setValidity('date', true); @@ -523,6 +581,10 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi } } ngModel.$parsers.unshift(parseDate); + ngModel.$parsers.unshift(minLimitParse); + ngModel.$formatters.unshift(minLimitFormat); + ngModel.$parsers.unshift(maxLimitParse); + ngModel.$formatters.unshift(maxLimitFormat); // Inner change scope.dateSelection = function(dt) { @@ -544,7 +606,7 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi }); }); - // Outter change + // Outer change ngModel.$render = function() { var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; element.val(date); diff --git a/src/datepicker/test/datepicker.spec.js b/src/datepicker/test/datepicker.spec.js index b673d162da..075952d0ac 100644 --- a/src/datepicker/test/datepicker.spec.js +++ b/src/datepicker/test/datepicker.spec.js @@ -1123,11 +1123,19 @@ describe('datepicker directive', function () { $sniffer = _$sniffer_; $rootScope.isopen = true; $rootScope.date = new Date('September 30, 2010 15:30:00'); - var wrapElement = $compile('
')($rootScope); + $rootScope.minimumDate = new Date('January 1, 1970'); + $rootScope.maximumDate = new Date('January 1, 2020'); + $rootScope.dateDisabledHandler = jasmine.createSpy('dateDisabledHandler'); + var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); + })); + it('executes the dateDisabled expression for each visible day plus one for validation', function() { + expect($rootScope.dateDisabledHandler.calls.length).toEqual(42+1); + }); + it('datepicker is displayed', function() { expect(dropdownEl).not.toBeHidden(); }); @@ -1161,6 +1169,41 @@ describe('datepicker directive', function () { $rootScope.date = new Date('January 10, 1983 10:00:00'); $rootScope.$digest(); expect(inputEl.val()).toBe('1983-01-10'); + expect(inputEl).not.toHaveClass('ng-invalid'); + expect(inputEl).not.toHaveClass('ng-invalid-min-date'); + expect(inputEl).not.toHaveClass('ng-invalid-max-date'); + }); + + it('updates the input correctly when model changes to a date prior to minimum date', function() { + $rootScope.date = new Date('January 10, 1963 10:00:00'); + $rootScope.$digest(); + expect(inputEl.val()).toBe('1963-01-10'); + expect(inputEl).toHaveClass('ng-invalid'); + expect(inputEl).toHaveClass('ng-invalid-min-date'); + expect(inputEl).not.toHaveClass('ng-invalid-max-date'); + }); + + it('input is not marked with invalid minimum date when minimum is not a valid date', function() { + expect(inputEl).not.toHaveClass('ng-invalid-min-date'); + $rootScope.minimumDate = 'pizza'; + $rootScope.$digest(); + expect(inputEl).not.toHaveClass('ng-invalid-min-date'); + }); + + it('updates the input correctly when model changes to a date past the maximum date', function() { + $rootScope.date = new Date('January 10, 2023 10:00:00'); + $rootScope.$digest(); + expect(inputEl.val()).toBe('2023-01-10'); + expect(inputEl).toHaveClass('ng-invalid'); + expect(inputEl).toHaveClass('ng-invalid-max-date'); + expect(inputEl).not.toHaveClass('ng-invalid-min-date'); + }); + + it('input is not marked with invalid maximum date when maximum is not a valid date', function() { + expect(inputEl).not.toHaveClass('ng-invalid-max-date'); + $rootScope.maximumDate = new Date('pizza'); + $rootScope.$digest(); + expect(inputEl).not.toHaveClass('ng-invalid-max-date'); }); it('closes the dropdown when a day is clicked', function() { @@ -1214,7 +1257,27 @@ describe('datepicker directive', function () { expect(inputEl).not.toHaveClass('ng-invalid-date'); }); - describe('focus', function () { + it('sets `ng-invalid` for date prior to minimum date', function() { + changeInputValueTo(inputEl, '1960-12-01'); + + expect(inputEl).toHaveClass('ng-invalid'); + expect(inputEl).toHaveClass('ng-invalid-min-date'); + expect(inputEl).not.toHaveClass('ng-invalid-max-date'); + expect($rootScope.date).toBeNull(); + expect(inputEl.val()).toBe('1960-12-01'); + }); + + it('sets `ng-invalid` for date past the maximum date', function() { + changeInputValueTo(inputEl, '2050-12-01'); + + expect(inputEl).toHaveClass('ng-invalid'); + expect(inputEl).not.toHaveClass('ng-invalid-min-date'); + expect(inputEl).toHaveClass('ng-invalid-max-date'); + expect($rootScope.date).toBeNull(); + expect(inputEl.val()).toBe('2050-12-01'); + }); + + describe('focus', function () { beforeEach(function() { var body = $document.find('body'); body.append(inputEl);