From dd99f35d936f5427666b100e4e6eed4c96ba937e Mon Sep 17 00:00:00 2001 From: Tasos Bekos Date: Tue, 3 Sep 2013 18:36:19 +0300 Subject: [PATCH] fix(datepicker): use $setViewValue for inner changes Closes #855 --- src/datepicker/datepicker.js | 78 +++++++++++++++----------- src/datepicker/test/datepicker.spec.js | 57 ++++++++++++++++++- 2 files changed, 98 insertions(+), 37 deletions(-) diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js index 8c0af8eeb8..130ada20d4 100644 --- a/src/datepicker/datepicker.js +++ b/src/datepicker/datepicker.js @@ -272,23 +272,6 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon scope.$destroy(); }); - function formatDate(value) { - return (value) ? dateFilter(value, dateFormat) : null; - } - ngModel.$formatters.push(formatDate); - - // TODO: reverse from dateFilter string to Date object - function parseDate(value) { - if ( value ) { - var date = new Date(value); - if (!isNaN(date)) { - return date; - } - } - return value; - } - ngModel.$parsers.push(parseDate); - var getIsOpen, setIsOpen; if ( attrs.isOpen ) { getIsOpen = $parse(attrs.isOpen); @@ -333,33 +316,58 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon datepickerEl.attr(angular.extend({}, originalScope.$eval(attrs.datepickerOptions))); } - var $setModelValue = $parse(attrs.ngModel).assign; + // TODO: reverse from dateFilter string to Date object + function parseDate(viewValue) { + if (!viewValue) { + ngModel.$setValidity('date', true); + return null; + } else if (angular.isDate(viewValue)) { + ngModel.$setValidity('date', true); + return viewValue; + } else if (angular.isString(viewValue)) { + var date = new Date(viewValue); + if (isNaN(date)) { + ngModel.$setValidity('date', false); + return undefined; + } else { + ngModel.$setValidity('date', true); + return date; + } + } else { + ngModel.$setValidity('date', false); + return undefined; + } + } + ngModel.$parsers.unshift(parseDate); // Inner change scope.dateSelection = function() { - $setModelValue(originalScope, scope.date); + ngModel.$setViewValue(scope.date); + ngModel.$render(); + if (closeOnDateSelection) { setOpen( false ); } }; + element.bind('input change keyup', function() { + scope.$apply(function() { + updateCalendar(); + }); + }); + // Outter change - scope.$watch(function() { - return ngModel.$modelValue; - }, function(value) { - if (angular.isString(value)) { - var date = parseDate(value); - - if (value && !date) { - $setModelValue(originalScope, null); - throw new Error(value + ' cannot be parsed to a date object.'); - } else { - value = date; - } - } - scope.date = value; + ngModel.$render = function() { + var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; + element.val(date); + + updateCalendar(); + }; + + function updateCalendar() { + scope.date = ngModel.$modelValue; updatePosition(); - }); + } function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) { if (attribute) { @@ -409,6 +417,8 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon } }); + var $setModelValue = $parse(attrs.ngModel).assign; + scope.today = function() { $setModelValue(originalScope, new Date()); }; diff --git a/src/datepicker/test/datepicker.spec.js b/src/datepicker/test/datepicker.spec.js index 221c85ad04..2e81632d5f 100644 --- a/src/datepicker/test/datepicker.spec.js +++ b/src/datepicker/test/datepicker.spec.js @@ -1000,6 +1000,12 @@ describe('datepicker directive', function () { expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00')); }); + it('should mark the input field dirty when a day is clicked', function() { + expect(inputEl).toHaveClass('ng-pristine'); + clickOption(2, 3); + expect(inputEl).toHaveClass('ng-dirty'); + }); + it('updates the input correctly when model changes', function() { $rootScope.date = new Date("January 10, 1983 10:00:00"); $rootScope.$digest(); @@ -1014,11 +1020,22 @@ describe('datepicker directive', function () { expect(dropdownEl.css('display')).toBe('none'); }); - it('updates the model when input value changes', function() { + it('updates the model & calendar when input value changes', function() { changeInputValueTo(inputEl, 'March 5, 1980'); + expect($rootScope.date.getFullYear()).toEqual(1980); expect($rootScope.date.getMonth()).toEqual(2); expect($rootScope.date.getDate()).toEqual(5); + + expect(getOptions()).toEqual([ + ['24', '25', '26', '27', '28', '29', '01'], + ['02', '03', '04', '05', '06', '07', '08'], + ['09', '10', '11', '12', '13', '14', '15'], + ['16', '17', '18', '19', '20', '21', '22'], + ['23', '24', '25', '26', '27', '28', '29'], + ['30', '31', '01', '02', '03', '04', '05'] + ]); + expectSelectedElement( 1, 3 ); }); it('closes when click outside of calendar', function() { @@ -1074,7 +1091,7 @@ describe('datepicker directive', function () { }); }); - describe('use with ng-required directive', function() { + describe('use with `ng-required` directive', function() { beforeEach(inject(function() { $rootScope.date = ''; var wrapElement = $compile('
')($rootScope); @@ -1092,8 +1109,42 @@ describe('datepicker directive', function () { }); }); - }); + describe('use with `ng-change` directive', function() { + beforeEach(inject(function() { + $rootScope.changeHandler = jasmine.createSpy('changeHandler'); + $rootScope.date = new Date(); + var wrapElement = $compile('
')($rootScope); + $rootScope.$digest(); + assignElements(wrapElement); + })); + it('should not be called initially', function() { + expect($rootScope.changeHandler).not.toHaveBeenCalled(); + }); + + it('should be called when a day is clicked', function() { + clickOption(2, 3); + expect($rootScope.changeHandler).toHaveBeenCalled(); + }); + + it('should not be called when model changes programatically', function() { + $rootScope.date = new Date(); + $rootScope.$digest(); + expect($rootScope.changeHandler).not.toHaveBeenCalled(); + }); + }); + + describe('to invalid input', function() { + it('sets `ng-invalid`', function() { + changeInputValueTo(inputEl, 'pizza'); + + expect(inputEl).toHaveClass('ng-invalid'); + expect(inputEl).toHaveClass('ng-invalid-date'); + expect($rootScope.date).toBeUndefined(); + expect(inputEl.val()).toBe('pizza'); + }); + }); + }); }); describe('datepicker directive with empty initial state', function () {