mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-04-28 11:56:03 +08:00
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
This commit is contained in:
@@ -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());
|
||||
@@ -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 '';
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
compile: function ngModelCompile(element) {
|
||||
// Setup initial state of the control
|
||||
element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user