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

Commit

Permalink
fix(input): register builtin parsers/formatters before anyone else
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tbosch committed Oct 2, 2014
1 parent a0bfdd0 commit 1064443
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 9 deletions.
30 changes: 21 additions & 9 deletions src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -1091,16 +1091,15 @@ function createDateInputType(type, regexp, parseDate, format) {
badInputChecker(scope, element, attr, ctrl);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
var previousDate;

ctrl.$$parserName = type;
ctrl.$parsers.push(function(value) {
if (ctrl.$isEmpty(value)) return null;
if (regexp.test(value)) {
var previousDate = ctrl.$modelValue;
if (previousDate && timezone === 'UTC') {
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
previousDate = new Date(previousDate.getTime() + timezoneOffset);
}
// Note: We cannot read ctrl.$modelValue, as there might be a different
// parser/formatter in the processing chain so that the model
// contains some different data format!
var parsedDate = parseDate(value, previousDate);
if (timezone === 'UTC') {
parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
Expand All @@ -1115,7 +1114,14 @@ function createDateInputType(type, regexp, parseDate, format) {
if (!isDate(value)) {
throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
}
previousDate = value;
if (previousDate && timezone === 'UTC') {
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
previousDate = new Date(previousDate.getTime() + timezoneOffset);
}
return $filter('date')(value, format, timezone);
} else {
previousDate = null;
}
return '';
});
Expand Down Expand Up @@ -1460,10 +1466,12 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
return {
restrict: 'E',
require: ['?ngModel'],
link: function(scope, element, attr, ctrls) {
if (ctrls[0]) {
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
$browser, $filter, $parse);
link: {
pre: function(scope, element, attr, ctrls) {
if (ctrls[0]) {
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
$browser, $filter, $parse);
}
}
}
};
Expand Down Expand Up @@ -2383,6 +2391,10 @@ var ngModelDirective = function() {
restrict: 'A',
require: ['ngModel', '^?form', '^?ngModelOptions'],
controller: NgModelController,
// Prelink needs to run before any input directive
// so that we can set the NgModelOptions in NgModelController
// before anyone else uses it.
priority: 1,

This comment has been minimized.

Copy link
@glepretre

glepretre Oct 15, 2014

Shouldn't this priority change be reported in the doc? This has affected the behavior of one of my directives after migrating to angular 1.3.0

This comment has been minimized.

Copy link
@tbosch

tbosch Oct 15, 2014

Author Contributor

Done, thanks for pointing this out.

compile: function ngModelCompile(element) {
// Setup initial state of the control
element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
Expand Down
63 changes: 63 additions & 0 deletions test/ng/directive/inputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,69 @@ describe('ngModel', function() {
dealoc(element);
}));

describe('custom formatter and parser that are added by a directive in post linking', function() {
var inputElm, scope;
beforeEach(module(function($compileProvider) {
$compileProvider.directive('customFormat', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$formatters.push(function(value) {
return value.part;
});
ngModelCtrl.$parsers.push(function(value) {
return {part: value};
});
}
};
});
}));

afterEach(function() {
dealoc(inputElm);
});

function createInput(type) {
inject(function($compile, $rootScope) {
scope = $rootScope;
inputElm = $compile('<input type="'+type+'" ng-model="val" custom-format/>')($rootScope);
});
}

it('should use them after the builtin ones for text inputs', function() {
createInput('text');
scope.$apply('val = {part: "a"}');
expect(inputElm.val()).toBe('a');

inputElm.val('b');
browserTrigger(inputElm, 'change');
expect(scope.val).toEqual({part: 'b'});
});

it('should use them after the builtin ones for number inputs', function() {
createInput('number');
scope.$apply('val = {part: 1}');
expect(inputElm.val()).toBe('1');

inputElm.val('2');
browserTrigger(inputElm, 'change');
expect(scope.val).toEqual({part: 2});
});

it('should use them after the builtin ones for date inputs', function() {
createInput('date');
scope.$apply(function() {
scope.val = {part: new Date(2000, 10, 8)};
});
expect(inputElm.val()).toBe('2000-11-08');

inputElm.val('2001-12-09');
browserTrigger(inputElm, 'change');
expect(scope.val).toEqual({part: new Date(2001, 11, 9)});
});
});


it('should always format the viewValue as a string for a blank input type when the value is present',
inject(function($compile, $rootScope, $sniffer) {

Expand Down

0 comments on commit 1064443

Please sign in to comment.