feat($animate): provide support for DOM callbacks

This commit is contained in:
Matias Niemelä 2014-01-13 21:51:08 -05:00
parent 4ae3184c59
commit dde1b29497
2 changed files with 100 additions and 3 deletions

View file

@ -317,6 +317,10 @@ angular.module('ngAnimate', ['ng'])
return classNameFilter.test(className);
};
function async(fn) {
return $timeout(fn, 0, false);
}
function lookup(name) {
if (name) {
var matches = [],
@ -608,6 +612,8 @@ angular.module('ngAnimate', ['ng'])
//best to catch this early on to prevent any animation operations from occurring
if(!node || !isAnimatableClassName(classes)) {
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
closeAnimation();
return;
}
@ -627,6 +633,8 @@ angular.module('ngAnimate', ['ng'])
//NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found.
if (animationsDisabled(element, parentElement) || matches.length === 0) {
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
closeAnimation();
return;
}
@ -665,6 +673,8 @@ angular.module('ngAnimate', ['ng'])
//animation do it's thing and close this one early
if(animations.length === 0) {
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
fireDoneCallbackAsync();
return;
}
@ -718,6 +728,8 @@ angular.module('ngAnimate', ['ng'])
if((animationEvent == 'addClass' && futureClassName.indexOf(classNameToken) >= 0) ||
(animationEvent == 'removeClass' && futureClassName.indexOf(classNameToken) == -1)) {
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
fireDoneCallbackAsync();
return;
}
@ -758,6 +770,10 @@ angular.module('ngAnimate', ['ng'])
}
function invokeRegisteredAnimationFns(animations, phase, allAnimationFnsComplete) {
phase == 'after' ?
fireAfterCallbackAsync() :
fireBeforeCallbackAsync();
var endFnName = phase + 'End';
forEach(animations, function(animation, index) {
var animationPhaseCompleted = function() {
@ -794,8 +810,27 @@ angular.module('ngAnimate', ['ng'])
}
}
function fireDOMCallback(animationPhase) {
element.triggerHandler('$animate:' + animationPhase, {
event : animationEvent,
className : className
});
}
function fireBeforeCallbackAsync() {
async(function() {
fireDOMCallback('before');
});
}
function fireAfterCallbackAsync() {
async(function() {
fireDOMCallback('after');
});
}
function fireDoneCallbackAsync() {
doneCallback && $timeout(doneCallback, 0, false);
doneCallback && async(doneCallback);
}
//it is less complicated to use a flag than managing and cancelling
@ -819,9 +854,9 @@ angular.module('ngAnimate', ['ng'])
if(isClassBased) {
cleanup(element);
} else {
data.closeAnimationTimeout = $timeout(function() {
data.closeAnimationTimeout = async(function() {
cleanup(element);
}, 0, false);
});
element.data(NG_ANIMATE_STATE, data);
}
}

View file

@ -1496,6 +1496,68 @@ describe("ngAnimate", function() {
expect(signature).toBe('AB');
}));
it('should fire DOM callbacks on the element being animated',
inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) {
if(!$sniffer.transitions) return;
$animate.enabled(true);
ss.addRule('.klass-add', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
var element = jqLite('<div></div>');
$rootElement.append(element);
body.append($rootElement);
var steps = [];
element.on('$animate:before', function(e, data) {
steps.push(['before', data.className, data.event]);
});
element.on('$animate:after', function(e, data) {
steps.push(['after', data.className, data.event]);
});
$animate.addClass(element, 'klass');
$timeout.flush(1);
expect(steps.pop()).toEqual(['before', 'klass', 'addClass']);
$animate.triggerReflow();
$timeout.flush(1);
expect(steps.pop()).toEqual(['after', 'klass', 'addClass']);
}));
it('should fire the DOM callbacks even if no animation is rendered',
inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) {
$animate.enabled(true);
var parent = jqLite('<div></div>');
var element = jqLite('<div></div>');
$rootElement.append(parent);
body.append($rootElement);
var steps = [];
element.on('$animate:before', function(e, data) {
steps.push(['before', data.className, data.event]);
});
element.on('$animate:after', function(e, data) {
steps.push(['after', data.className, data.event]);
});
$animate.enter(element, parent);
$rootScope.$digest();
$timeout.flush(1);
expect(steps.shift()).toEqual(['before', 'ng-enter', 'enter']);
expect(steps.shift()).toEqual(['after', 'ng-enter', 'enter']);
}));
it("should fire a done callback when provided with no animation",
inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) {