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

Commit 1064443

Browse files
committedOct 2, 2014
fix(input): register builtin parsers/formatters before anyone else
Previously, builtin parsers/formatters for e.g. `input[date]` or `input[number]` were added in the post linking phase to `ngModelController`, which in most cases was after a custom formatter/parser was registered. This commit registers builtin parsers/formatters already in the pre linking phase. With that builtin parsers run first, and builtin formatters run last. Closes #9218 Closes #9358
1 parent a0bfdd0 commit 1064443

File tree

2 files changed

+84
-9
lines changed

2 files changed

+84
-9
lines changed
 

‎src/ng/directive/input.js

+21-9
Original file line numberDiff line numberDiff line change
@@ -1091,16 +1091,15 @@ function createDateInputType(type, regexp, parseDate, format) {
10911091
badInputChecker(scope, element, attr, ctrl);
10921092
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
10931093
var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
1094+
var previousDate;
10941095

10951096
ctrl.$$parserName = type;
10961097
ctrl.$parsers.push(function(value) {
10971098
if (ctrl.$isEmpty(value)) return null;
10981099
if (regexp.test(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-
}
1100+
// Note: We cannot read ctrl.$modelValue, as there might be a different
1101+
// parser/formatter in the processing chain so that the model
1102+
// contains some different data format!
11041103
var parsedDate = parseDate(value, previousDate);
11051104
if (timezone === 'UTC') {
11061105
parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
@@ -1115,7 +1114,14 @@ function createDateInputType(type, regexp, parseDate, format) {
11151114
if (!isDate(value)) {
11161115
throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
11171116
}
1117+
previousDate = value;
1118+
if (previousDate && timezone === 'UTC') {
1119+
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
1120+
previousDate = new Date(previousDate.getTime() + timezoneOffset);
1121+
}
11181122
return $filter('date')(value, format, timezone);
1123+
} else {
1124+
previousDate = null;
11191125
}
11201126
return '';
11211127
});
@@ -1460,10 +1466,12 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
14601466
return {
14611467
restrict: 'E',
14621468
require: ['?ngModel'],
1463-
link: function(scope, element, attr, ctrls) {
1464-
if (ctrls[0]) {
1465-
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
1466-
$browser, $filter, $parse);
1469+
link: {
1470+
pre: function(scope, element, attr, ctrls) {
1471+
if (ctrls[0]) {
1472+
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
1473+
$browser, $filter, $parse);
1474+
}
14671475
}
14681476
}
14691477
};
@@ -2383,6 +2391,10 @@ var ngModelDirective = function() {
23832391
restrict: 'A',
23842392
require: ['ngModel', '^?form', '^?ngModelOptions'],
23852393
controller: NgModelController,
2394+
// Prelink needs to run before any input directive
2395+
// so that we can set the NgModelOptions in NgModelController
2396+
// before anyone else uses it.
2397+
priority: 1,
Has conversations. Original line has conversations.
23862398
compile: function ngModelCompile(element) {
23872399
// Setup initial state of the control
23882400
element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);

‎test/ng/directive/inputSpec.js

+63
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,69 @@ describe('ngModel', function() {
946946
dealoc(element);
947947
}));
948948

949+
describe('custom formatter and parser that are added by a directive in post linking', function() {
950+
var inputElm, scope;
951+
beforeEach(module(function($compileProvider) {
952+
$compileProvider.directive('customFormat', function() {
953+
return {
954+
require: 'ngModel',
955+
link: function(scope, element, attrs, ngModelCtrl) {
956+
ngModelCtrl.$formatters.push(function(value) {
957+
return value.part;
958+
});
959+
ngModelCtrl.$parsers.push(function(value) {
960+
return {part: value};
961+
});
962+
}
963+
};
964+
});
965+
}));
966+
967+
afterEach(function() {
968+
dealoc(inputElm);
969+
});
970+
971+
function createInput(type) {
972+
inject(function($compile, $rootScope) {
973+
scope = $rootScope;
974+
inputElm = $compile('<input type="'+type+'" ng-model="val" custom-format/>')($rootScope);
975+
});
976+
}
977+
978+
it('should use them after the builtin ones for text inputs', function() {
979+
createInput('text');
980+
scope.$apply('val = {part: "a"}');
981+
expect(inputElm.val()).toBe('a');
982+
983+
inputElm.val('b');
984+
browserTrigger(inputElm, 'change');
985+
expect(scope.val).toEqual({part: 'b'});
986+
});
987+
988+
it('should use them after the builtin ones for number inputs', function() {
989+
createInput('number');
990+
scope.$apply('val = {part: 1}');
991+
expect(inputElm.val()).toBe('1');
992+
993+
inputElm.val('2');
994+
browserTrigger(inputElm, 'change');
995+
expect(scope.val).toEqual({part: 2});
996+
});
997+
998+
it('should use them after the builtin ones for date inputs', function() {
999+
createInput('date');
1000+
scope.$apply(function() {
1001+
scope.val = {part: new Date(2000, 10, 8)};
1002+
});
1003+
expect(inputElm.val()).toBe('2000-11-08');
1004+
1005+
inputElm.val('2001-12-09');
1006+
browserTrigger(inputElm, 'change');
1007+
expect(scope.val).toEqual({part: new Date(2001, 11, 9)});
1008+
});
1009+
});
1010+
1011+
9491012
it('should always format the viewValue as a string for a blank input type when the value is present',
9501013
inject(function($compile, $rootScope, $sniffer) {
9511014

0 commit comments

Comments
 (0)
This repository has been archived.