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
This commit is contained in:
Brian Ford
2014-08-29 13:44:08 -07:00
parent 3e51b84bc1
commit 1a1ef62903
2 changed files with 113 additions and 6 deletions

View File

@@ -1004,7 +1004,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
};
}
function weekParser(isoWeek) {
function weekParser(isoWeek, existingDate) {
if (isDate(isoWeek)) {
return isoWeek;
}
@@ -1015,9 +1015,21 @@ function weekParser(isoWeek) {
if (parts) {
var year = +parts[1],
week = +parts[2],
hours = 0,
minutes = 0,
seconds = 0,
milliseconds = 0,
firstThurs = getFirstThursdayOfYear(year),
addDays = (week - 1) * 7;
return new Date(year, 0, firstThurs.getDate() + addDays);
if (existingDate) {
hours = existingDate.getHours();
minutes = existingDate.getMinutes();
seconds = existingDate.getSeconds();
milliseconds = existingDate.getMilliseconds();
}
return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
}
}
@@ -1025,7 +1037,7 @@ function weekParser(isoWeek) {
}
function createDateParser(regexp, mapping) {
return function(iso) {
return function(iso, date) {
var parts, map;
if (isDate(iso)) {
@@ -1047,14 +1059,26 @@ function createDateParser(regexp, mapping) {
if (parts) {
parts.shift();
map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0 };
if (date) {
map = {
yyyy: date.getFullYear(),
MM: date.getMonth() + 1,
dd: date.getDate(),
HH: date.getHours(),
mm: date.getMinutes(),
ss: date.getSeconds(),
sss: date.getMilliseconds()
};
} else {
map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
}
forEach(parts, function(part, index) {
if (index < mapping.length) {
map[mapping[index]] = +part;
}
});
return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0);
return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss || 0);
}
}
@@ -1072,7 +1096,12 @@ function createDateInputType(type, regexp, parseDate, format) {
ctrl.$parsers.push(function(value) {
if (ctrl.$isEmpty(value)) return null;
if (regexp.test(value)) {
var parsedDate = parseDate(value);
var previousDate = ctrl.$modelValue;
if (previousDate && timezone === 'UTC') {
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
previousDate = new Date(previousDate.getTime() + timezoneOffset);
}
var parsedDate = parseDate(value, previousDate);
if (timezone === 'UTC') {
parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
}

View File

@@ -2314,6 +2314,17 @@ describe('input', function() {
expect(scope.form.alias.$error.month).toBeTruthy();
});
it('should only change the month of a bound date', function() {
compileInput('<input type="month" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
scope.$apply(function() {
scope.value = new Date(Date.UTC(2013, 7, 1, 1, 0, 0, 0));
});
changeInputValueTo('2013-12');
expect(+scope.value).toBe(Date.UTC(2013, 11, 1, 1, 0, 0, 0));
expect(inputElm.val()).toBe('2013-12');
});
describe('min', function (){
var scope;
beforeEach(inject(function ($rootScope){
@@ -2406,6 +2417,18 @@ describe('input', function() {
expect(inputElm.val()).toBe('2013-W02');
});
it('should not affect the hours or minutes of a bound date', function (){
compileInput('<input type="week" ng-model="secondWeek"/>');
scope.$apply(function(){
scope.secondWeek = new Date(2013, 0, 11, 1, 0, 0, 0);
});
changeInputValueTo('2013-W03');
expect(+scope.secondWeek).toBe(+new Date(2013, 0, 17, 1, 0, 0, 0));
});
it('should set the model undefined if the input is an invalid week string', function () {
compileInput('<input type="week" ng-model="value"/>');
@@ -2934,6 +2957,17 @@ describe('input', function() {
expect(scope.form.alias.$error.time).toBeTruthy();
});
it('should only change hours and minute of a bound date', function() {
compileInput('<input type="time" ng-model="value"" />');
scope.$apply(function(){
scope.value = new Date(2013, 2, 3, 1, 0, 0);
});
changeInputValueTo('01:02');
expect(+scope.value).toBe(+new Date(2013, 2, 3, 1, 2, 0));
});
describe('min', function (){
var scope;
beforeEach(inject(function ($rootScope){
@@ -3141,6 +3175,50 @@ describe('input', function() {
expect(scope.form.alias.$error.date).toBeTruthy();
});
it('should work with multiple date types bound to the same model', function() {
formElm = jqLite('<form name="form"></form>');
var timeElm = jqLite('<input type="time" ng-model="val" />'),
monthElm = jqLite('<input type="month" ng-model="val" />'),
weekElm = jqLite('<input type="week" ng-model="val" />');
formElm.append(timeElm);
formElm.append(monthElm);
formElm.append(weekElm);
$compile(formElm)(scope);
scope.$apply(function() {
scope.val = new Date(2013, 1, 2, 3, 4, 5, 6);
});
expect(timeElm.val()).toBe('03:04:05');
expect(monthElm.val()).toBe('2013-02');
expect(weekElm.val()).toBe('2013-W05');
changeGivenInputTo(monthElm, '2012-02');
expect(monthElm.val()).toBe('2012-02');
expect(timeElm.val()).toBe('03:04:05');
expect(weekElm.val()).toBe('2012-W05');
changeGivenInputTo(timeElm, '04:05:06');
expect(monthElm.val()).toBe('2012-02');
expect(timeElm.val()).toBe('04:05:06');
expect(weekElm.val()).toBe('2012-W05');
changeGivenInputTo(weekElm, '2014-W01');
expect(monthElm.val()).toBe('2014-01');
expect(timeElm.val()).toBe('04:05:06');
expect(weekElm.val()).toBe('2014-W01');
expect(+scope.val).toBe(+new Date(2014, 0, 2, 4, 5, 6, 6));
function changeGivenInputTo(inputElm, value) {
inputElm.val(value);
browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
}
});
describe('min', function (){
beforeEach(function (){
compileInput('<input type="date" ng-model="value" name="alias" min="2000-01-01" />');