Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

Commit dd99f35

Browse files
bekospkozlowski-opensource
authored andcommitted
fix(datepicker): use $setViewValue for inner changes
Closes #855
1 parent 1f89fd4 commit dd99f35

File tree

2 files changed

+98
-37
lines changed

2 files changed

+98
-37
lines changed

src/datepicker/datepicker.js

+44-34
Original file line numberDiff line numberDiff line change
@@ -272,23 +272,6 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
272272
scope.$destroy();
273273
});
274274

275-
function formatDate(value) {
276-
return (value) ? dateFilter(value, dateFormat) : null;
277-
}
278-
ngModel.$formatters.push(formatDate);
279-
280-
// TODO: reverse from dateFilter string to Date object
281-
function parseDate(value) {
282-
if ( value ) {
283-
var date = new Date(value);
284-
if (!isNaN(date)) {
285-
return date;
286-
}
287-
}
288-
return value;
289-
}
290-
ngModel.$parsers.push(parseDate);
291-
292275
var getIsOpen, setIsOpen;
293276
if ( attrs.isOpen ) {
294277
getIsOpen = $parse(attrs.isOpen);
@@ -333,33 +316,58 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
333316
datepickerEl.attr(angular.extend({}, originalScope.$eval(attrs.datepickerOptions)));
334317
}
335318

336-
var $setModelValue = $parse(attrs.ngModel).assign;
319+
// TODO: reverse from dateFilter string to Date object
320+
function parseDate(viewValue) {
321+
if (!viewValue) {
322+
ngModel.$setValidity('date', true);
323+
return null;
324+
} else if (angular.isDate(viewValue)) {
325+
ngModel.$setValidity('date', true);
326+
return viewValue;
327+
} else if (angular.isString(viewValue)) {
328+
var date = new Date(viewValue);
329+
if (isNaN(date)) {
330+
ngModel.$setValidity('date', false);
331+
return undefined;
332+
} else {
333+
ngModel.$setValidity('date', true);
334+
return date;
335+
}
336+
} else {
337+
ngModel.$setValidity('date', false);
338+
return undefined;
339+
}
340+
}
341+
ngModel.$parsers.unshift(parseDate);
337342

338343
// Inner change
339344
scope.dateSelection = function() {
340-
$setModelValue(originalScope, scope.date);
345+
ngModel.$setViewValue(scope.date);
346+
ngModel.$render();
347+
341348
if (closeOnDateSelection) {
342349
setOpen( false );
343350
}
344351
};
345352

353+
element.bind('input change keyup', function() {
354+
scope.$apply(function() {
355+
updateCalendar();
356+
});
357+
});
358+
346359
// Outter change
347-
scope.$watch(function() {
348-
return ngModel.$modelValue;
349-
}, function(value) {
350-
if (angular.isString(value)) {
351-
var date = parseDate(value);
352-
353-
if (value && !date) {
354-
$setModelValue(originalScope, null);
355-
throw new Error(value + ' cannot be parsed to a date object.');
356-
} else {
357-
value = date;
358-
}
359-
}
360-
scope.date = value;
360+
ngModel.$render = function() {
361+
var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : '';
362+
element.val(date);
363+
364+
updateCalendar();
365+
};
366+
367+
function updateCalendar() {
368+
scope.date = ngModel.$modelValue;
361369
updatePosition();
362-
});
370+
}
363371

364372
function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) {
365373
if (attribute) {
@@ -409,6 +417,8 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
409417
}
410418
});
411419

420+
var $setModelValue = $parse(attrs.ngModel).assign;
421+
412422
scope.today = function() {
413423
$setModelValue(originalScope, new Date());
414424
};

src/datepicker/test/datepicker.spec.js

+54-3
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,12 @@ describe('datepicker directive', function () {
10001000
expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));
10011001
});
10021002

1003+
it('should mark the input field dirty when a day is clicked', function() {
1004+
expect(inputEl).toHaveClass('ng-pristine');
1005+
clickOption(2, 3);
1006+
expect(inputEl).toHaveClass('ng-dirty');
1007+
});
1008+
10031009
it('updates the input correctly when model changes', function() {
10041010
$rootScope.date = new Date("January 10, 1983 10:00:00");
10051011
$rootScope.$digest();
@@ -1014,11 +1020,22 @@ describe('datepicker directive', function () {
10141020
expect(dropdownEl.css('display')).toBe('none');
10151021
});
10161022

1017-
it('updates the model when input value changes', function() {
1023+
it('updates the model & calendar when input value changes', function() {
10181024
changeInputValueTo(inputEl, 'March 5, 1980');
1025+
10191026
expect($rootScope.date.getFullYear()).toEqual(1980);
10201027
expect($rootScope.date.getMonth()).toEqual(2);
10211028
expect($rootScope.date.getDate()).toEqual(5);
1029+
1030+
expect(getOptions()).toEqual([
1031+
['24', '25', '26', '27', '28', '29', '01'],
1032+
['02', '03', '04', '05', '06', '07', '08'],
1033+
['09', '10', '11', '12', '13', '14', '15'],
1034+
['16', '17', '18', '19', '20', '21', '22'],
1035+
['23', '24', '25', '26', '27', '28', '29'],
1036+
['30', '31', '01', '02', '03', '04', '05']
1037+
]);
1038+
expectSelectedElement( 1, 3 );
10221039
});
10231040

10241041
it('closes when click outside of calendar', function() {
@@ -1074,7 +1091,7 @@ describe('datepicker directive', function () {
10741091
});
10751092
});
10761093

1077-
describe('use with ng-required directive', function() {
1094+
describe('use with `ng-required` directive', function() {
10781095
beforeEach(inject(function() {
10791096
$rootScope.date = '';
10801097
var wrapElement = $compile('<div><input ng-model="date" datepicker-popup ng-required="true"><div>')($rootScope);
@@ -1092,8 +1109,42 @@ describe('datepicker directive', function () {
10921109
});
10931110
});
10941111

1095-
});
1112+
describe('use with `ng-change` directive', function() {
1113+
beforeEach(inject(function() {
1114+
$rootScope.changeHandler = jasmine.createSpy('changeHandler');
1115+
$rootScope.date = new Date();
1116+
var wrapElement = $compile('<div><input ng-model="date" datepicker-popup ng-required="true" ng-change="changeHandler()"><div>')($rootScope);
1117+
$rootScope.$digest();
1118+
assignElements(wrapElement);
1119+
}));
10961120

1121+
it('should not be called initially', function() {
1122+
expect($rootScope.changeHandler).not.toHaveBeenCalled();
1123+
});
1124+
1125+
it('should be called when a day is clicked', function() {
1126+
clickOption(2, 3);
1127+
expect($rootScope.changeHandler).toHaveBeenCalled();
1128+
});
1129+
1130+
it('should not be called when model changes programatically', function() {
1131+
$rootScope.date = new Date();
1132+
$rootScope.$digest();
1133+
expect($rootScope.changeHandler).not.toHaveBeenCalled();
1134+
});
1135+
});
1136+
1137+
describe('to invalid input', function() {
1138+
it('sets `ng-invalid`', function() {
1139+
changeInputValueTo(inputEl, 'pizza');
1140+
1141+
expect(inputEl).toHaveClass('ng-invalid');
1142+
expect(inputEl).toHaveClass('ng-invalid-date');
1143+
expect($rootScope.date).toBeUndefined();
1144+
expect(inputEl.val()).toBe('pizza');
1145+
});
1146+
});
1147+
});
10971148
});
10981149

10991150
describe('datepicker directive with empty initial state', function () {

0 commit comments

Comments
 (0)