mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-01-12 22:45:52 +08:00
fix(ngEventDirs): execute blur and focus expression using scope.$evalAsync
BREAKING CHANGE: The `blur` and `focus` event fire synchronously, also during DOM operations that remove elements. This lead to errors as the Angular model was not in a consistent state. See this [fiddle](http://jsfiddle.net/fq1dq5yb/) for a demo. This change executes the expression of those events using `scope.$evalAsync` if an `$apply` is in progress, otherwise keeps the old behavior. Fixes #4979 Fixes #5945 Closes #8803 Closes #6910 Closes #5402
This commit is contained in:
@@ -37,6 +37,14 @@
|
||||
* Events that are handled via these handler are always configured not to propagate further.
|
||||
*/
|
||||
var ngEventDirectives = {};
|
||||
|
||||
// For events that might fire synchronously during DOM manipulation
|
||||
// we need to execute their event handlers asynchronously using $evalAsync,
|
||||
// so that they are not executed in an inconsistent state.
|
||||
var forceAsyncEvents = {
|
||||
'blur': true,
|
||||
'focus': true
|
||||
};
|
||||
forEach(
|
||||
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
|
||||
function(name) {
|
||||
@@ -47,10 +55,16 @@ forEach(
|
||||
compile: function($element, attr) {
|
||||
var fn = $parse(attr[directiveName]);
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(lowercase(name), function(event) {
|
||||
scope.$apply(function() {
|
||||
var eventName = lowercase(name);
|
||||
element.on(eventName, function(event) {
|
||||
var callback = function() {
|
||||
fn(scope, {$event:event});
|
||||
});
|
||||
};
|
||||
if (forceAsyncEvents[eventName] && scope.$$phase) {
|
||||
scope.$evalAsync(callback);
|
||||
} else {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -367,6 +381,10 @@ forEach(
|
||||
* @description
|
||||
* Specify custom behavior on focus event.
|
||||
*
|
||||
* Note: As the `focus` event is executed synchronously when calling `input.focus()`
|
||||
* AngularJS executes the expression using `scope.$evalAsync` if the event is fired
|
||||
* during an `$apply` to ensure a consistent state.
|
||||
*
|
||||
* @element window, input, select, textarea, a
|
||||
* @priority 0
|
||||
* @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
|
||||
@@ -383,6 +401,11 @@ forEach(
|
||||
* @description
|
||||
* Specify custom behavior on blur event.
|
||||
*
|
||||
* Note: As the `blur` event is executed synchronously also during DOM manipulations
|
||||
* (e.g. removing a focussed input),
|
||||
* AngularJS executes the expression using `scope.$evalAsync` if the event is fired
|
||||
* during an `$apply` to ensure a consistent state.
|
||||
*
|
||||
* @element window, input, select, textarea, a
|
||||
* @priority 0
|
||||
* @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
|
||||
|
||||
@@ -39,4 +39,64 @@ describe('event directives', function() {
|
||||
expect($rootScope.formSubmitted).toEqual('foo');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('focus', function() {
|
||||
|
||||
it('should call the listener asynchronously during $apply',
|
||||
inject(function($rootScope, $compile) {
|
||||
element = $compile('<input type="text" ng-focus="focus()">')($rootScope);
|
||||
$rootScope.focus = jasmine.createSpy('focus');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
element.triggerHandler('focus');
|
||||
expect($rootScope.focus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect($rootScope.focus).toHaveBeenCalledOnce();
|
||||
}));
|
||||
|
||||
it('should call the listener synchronously inside of $apply if outside of $apply',
|
||||
inject(function($rootScope, $compile) {
|
||||
element = $compile('<input type="text" ng-focus="focus()" ng-model="value">')($rootScope);
|
||||
$rootScope.focus = jasmine.createSpy('focus').andCallFake(function() {
|
||||
$rootScope.value = 'newValue';
|
||||
});
|
||||
|
||||
element.triggerHandler('focus');
|
||||
|
||||
expect($rootScope.focus).toHaveBeenCalledOnce();
|
||||
expect(element.val()).toBe('newValue');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('blur', function() {
|
||||
|
||||
it('should call the listener asynchronously during $apply',
|
||||
inject(function($rootScope, $compile) {
|
||||
element = $compile('<input type="text" ng-blur="blur()">')($rootScope);
|
||||
$rootScope.blur = jasmine.createSpy('blur');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
element.triggerHandler('blur');
|
||||
expect($rootScope.blur).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect($rootScope.blur).toHaveBeenCalledOnce();
|
||||
}));
|
||||
|
||||
it('should call the listener synchronously inside of $apply if outside of $apply',
|
||||
inject(function($rootScope, $compile) {
|
||||
element = $compile('<input type="text" ng-blur="blur()" ng-model="value">')($rootScope);
|
||||
$rootScope.blur = jasmine.createSpy('blur').andCallFake(function() {
|
||||
$rootScope.value = 'newValue';
|
||||
});
|
||||
|
||||
element.triggerHandler('blur');
|
||||
|
||||
expect($rootScope.blur).toHaveBeenCalledOnce();
|
||||
expect(element.val()).toBe('newValue');
|
||||
}));
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,23 +34,5 @@ describe('ngKeyup and ngKeydown directives', function() {
|
||||
expect($rootScope.touched).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should get called on focus', inject(function($rootScope, $compile) {
|
||||
element = $compile('<input ng-focus="touched = true">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.touched).toBeFalsy();
|
||||
|
||||
browserTrigger(element, 'focus');
|
||||
expect($rootScope.touched).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should get called on blur', inject(function($rootScope, $compile) {
|
||||
element = $compile('<input ng-blur="touched = true">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.touched).toBeFalsy();
|
||||
|
||||
browserTrigger(element, 'blur');
|
||||
expect($rootScope.touched).toEqual(true);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user