Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 1a1ef62

Browse files
committed
fix(ngModel): do not reset bound date objects
Previously, if you bound a `Date` object to `<input type="time">`, whenever you changed the time, the day, month, and year fields of the new resulting bound `Date` object would be reset. Now fields not modified by bound time input elements are copied to the new resulting object. Same for input types of `month`, `week`, etc. Closes #6666
1 parent 3e51b84 commit 1a1ef62

File tree

2 files changed

+113
-6
lines changed

2 files changed

+113
-6
lines changed

src/ng/directive/input.js

+35-6
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
10041004
};
10051005
}
10061006

1007-
function weekParser(isoWeek) {
1007+
function weekParser(isoWeek, existingDate) {
10081008
if (isDate(isoWeek)) {
10091009
return isoWeek;
10101010
}
@@ -1015,17 +1015,29 @@ function weekParser(isoWeek) {
10151015
if (parts) {
10161016
var year = +parts[1],
10171017
week = +parts[2],
1018+
hours = 0,
1019+
minutes = 0,
1020+
seconds = 0,
1021+
milliseconds = 0,
10181022
firstThurs = getFirstThursdayOfYear(year),
10191023
addDays = (week - 1) * 7;
1020-
return new Date(year, 0, firstThurs.getDate() + addDays);
1024+
1025+
if (existingDate) {
1026+
hours = existingDate.getHours();
1027+
minutes = existingDate.getMinutes();
1028+
seconds = existingDate.getSeconds();
1029+
milliseconds = existingDate.getMilliseconds();
1030+
}
1031+
1032+
return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
10211033
}
10221034
}
10231035

10241036
return NaN;
10251037
}
10261038

10271039
function createDateParser(regexp, mapping) {
1028-
return function(iso) {
1040+
return function(iso, date) {
10291041
var parts, map;
10301042

10311043
if (isDate(iso)) {
@@ -1047,14 +1059,26 @@ function createDateParser(regexp, mapping) {
10471059

10481060
if (parts) {
10491061
parts.shift();
1050-
map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0 };
1062+
if (date) {
1063+
map = {
1064+
yyyy: date.getFullYear(),
1065+
MM: date.getMonth() + 1,
1066+
dd: date.getDate(),
1067+
HH: date.getHours(),
1068+
mm: date.getMinutes(),
1069+
ss: date.getSeconds(),
1070+
sss: date.getMilliseconds()
1071+
};
1072+
} else {
1073+
map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
1074+
}
10511075

10521076
forEach(parts, function(part, index) {
10531077
if (index < mapping.length) {
10541078
map[mapping[index]] = +part;
10551079
}
10561080
});
1057-
return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0);
1081+
return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss || 0);
10581082
}
10591083
}
10601084

@@ -1072,7 +1096,12 @@ function createDateInputType(type, regexp, parseDate, format) {
10721096
ctrl.$parsers.push(function(value) {
10731097
if (ctrl.$isEmpty(value)) return null;
10741098
if (regexp.test(value)) {
1075-
var parsedDate = parseDate(value);
1099+
var previousDate = ctrl.$modelValue;
1100+
if (previousDate && timezone === 'UTC') {
1101+
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
1102+
previousDate = new Date(previousDate.getTime() + timezoneOffset);
1103+
}
1104+
var parsedDate = parseDate(value, previousDate);
10761105
if (timezone === 'UTC') {
10771106
parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
10781107
}

test/ng/directive/inputSpec.js

+78
Original file line numberDiff line numberDiff line change
@@ -2314,6 +2314,17 @@ describe('input', function() {
23142314
expect(scope.form.alias.$error.month).toBeTruthy();
23152315
});
23162316

2317+
it('should only change the month of a bound date', function() {
2318+
compileInput('<input type="month" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
2319+
2320+
scope.$apply(function() {
2321+
scope.value = new Date(Date.UTC(2013, 7, 1, 1, 0, 0, 0));
2322+
});
2323+
changeInputValueTo('2013-12');
2324+
expect(+scope.value).toBe(Date.UTC(2013, 11, 1, 1, 0, 0, 0));
2325+
expect(inputElm.val()).toBe('2013-12');
2326+
});
2327+
23172328
describe('min', function (){
23182329
var scope;
23192330
beforeEach(inject(function ($rootScope){
@@ -2406,6 +2417,18 @@ describe('input', function() {
24062417
expect(inputElm.val()).toBe('2013-W02');
24072418
});
24082419

2420+
it('should not affect the hours or minutes of a bound date', function (){
2421+
compileInput('<input type="week" ng-model="secondWeek"/>');
2422+
2423+
scope.$apply(function(){
2424+
scope.secondWeek = new Date(2013, 0, 11, 1, 0, 0, 0);
2425+
});
2426+
2427+
changeInputValueTo('2013-W03');
2428+
2429+
expect(+scope.secondWeek).toBe(+new Date(2013, 0, 17, 1, 0, 0, 0));
2430+
});
2431+
24092432
it('should set the model undefined if the input is an invalid week string', function () {
24102433
compileInput('<input type="week" ng-model="value"/>');
24112434

@@ -2934,6 +2957,17 @@ describe('input', function() {
29342957
expect(scope.form.alias.$error.time).toBeTruthy();
29352958
});
29362959

2960+
it('should only change hours and minute of a bound date', function() {
2961+
compileInput('<input type="time" ng-model="value"" />');
2962+
2963+
scope.$apply(function(){
2964+
scope.value = new Date(2013, 2, 3, 1, 0, 0);
2965+
});
2966+
2967+
changeInputValueTo('01:02');
2968+
expect(+scope.value).toBe(+new Date(2013, 2, 3, 1, 2, 0));
2969+
});
2970+
29372971
describe('min', function (){
29382972
var scope;
29392973
beforeEach(inject(function ($rootScope){
@@ -3141,6 +3175,50 @@ describe('input', function() {
31413175
expect(scope.form.alias.$error.date).toBeTruthy();
31423176
});
31433177

3178+
it('should work with multiple date types bound to the same model', function() {
3179+
formElm = jqLite('<form name="form"></form>');
3180+
3181+
var timeElm = jqLite('<input type="time" ng-model="val" />'),
3182+
monthElm = jqLite('<input type="month" ng-model="val" />'),
3183+
weekElm = jqLite('<input type="week" ng-model="val" />');
3184+
3185+
formElm.append(timeElm);
3186+
formElm.append(monthElm);
3187+
formElm.append(weekElm);
3188+
3189+
$compile(formElm)(scope);
3190+
3191+
scope.$apply(function() {
3192+
scope.val = new Date(2013, 1, 2, 3, 4, 5, 6);
3193+
});
3194+
3195+
expect(timeElm.val()).toBe('03:04:05');
3196+
expect(monthElm.val()).toBe('2013-02');
3197+
expect(weekElm.val()).toBe('2013-W05');
3198+
3199+
changeGivenInputTo(monthElm, '2012-02');
3200+
expect(monthElm.val()).toBe('2012-02');
3201+
expect(timeElm.val()).toBe('03:04:05');
3202+
expect(weekElm.val()).toBe('2012-W05');
3203+
3204+
changeGivenInputTo(timeElm, '04:05:06');
3205+
expect(monthElm.val()).toBe('2012-02');
3206+
expect(timeElm.val()).toBe('04:05:06');
3207+
expect(weekElm.val()).toBe('2012-W05');
3208+
3209+
changeGivenInputTo(weekElm, '2014-W01');
3210+
expect(monthElm.val()).toBe('2014-01');
3211+
expect(timeElm.val()).toBe('04:05:06');
3212+
expect(weekElm.val()).toBe('2014-W01');
3213+
3214+
expect(+scope.val).toBe(+new Date(2014, 0, 2, 4, 5, 6, 6));
3215+
3216+
function changeGivenInputTo(inputElm, value) {
3217+
inputElm.val(value);
3218+
browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
3219+
}
3220+
});
3221+
31443222
describe('min', function (){
31453223
beforeEach(function (){
31463224
compileInput('<input type="date" ng-model="value" name="alias" min="2000-01-01" />');

0 commit comments

Comments
 (0)