From f170e894d88312e2d23ac016ec0a574f40b12815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Mon, 13 Jan 2014 21:51:08 -0500 Subject: [PATCH] feat($animate): provide support for DOM callbacks --- src/ngAnimate/animate.js | 29 ++++++++++++-- test/ngAnimate/animateSpec.js | 73 +++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js index 64ed302981a9..ded188c45b3d 100644 --- a/src/ngAnimate/animate.js +++ b/src/ngAnimate/animate.js @@ -295,6 +295,20 @@ angular.module('ngAnimate', ['ng']) return classNameFilter.test(className); }; + function async(fn) { + return fn && $timeout(fn, 0, false); + }; + + function triggerDOMCallback(element, animationPhase, eventData, isSync) { + eventData = eventData || {}; + var event = '$animate:' + animationPhase; + isSync ? trigger() : async(trigger); + + function trigger() { + element.triggerHandler(event, eventData); + }; + }; + function lookup(name) { if (name) { var matches = [], @@ -724,6 +738,11 @@ angular.module('ngAnimate', ['ng']) } function invokeRegisteredAnimationFns(animations, phase, allAnimationFnsComplete) { + triggerDOMCallback(element, phase, { + event : animationEvent, + className : className + }); + var endFnName = phase + 'End'; forEach(animations, function(animation, index) { var animationPhaseCompleted = function() { @@ -761,7 +780,7 @@ angular.module('ngAnimate', ['ng']) } function fireDoneCallbackAsync() { - doneCallback && $timeout(doneCallback, 0, false); + async(doneCallback); } //it is less complicated to use a flag than managing and cancelling @@ -785,13 +804,17 @@ 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); } } fireDoneCallbackAsync(); + triggerDOMCallback(element, 'close', { + event : animationEvent, + className : className + }); } } } diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index 028c021f3106..0b976d156368 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -1439,6 +1439,79 @@ 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) { + + $animate.enabled(true); + + ss.addRule('.klass-add', '-webkit-transition:1s linear all;' + + 'transition:1s linear all;'); + + var element = jqLite('
'); + $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]); + }); + + element.on('$animate:close', function(e, data) { + steps.push(['close', data.className, data.event]); + }); + + $animate.addClass(element, 'klass'); + + $timeout.flush(1); + + expect(steps.pop()).toEqual(['before', 'klass', 'addClass']); + + $timeout.flush(10); + + expect(steps.pop()).toEqual(['after', 'klass', 'addClass']); + + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); + $timeout.flush(1); + + expect(steps.pop()).toEqual(['close', '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('
'); + var element = jqLite('
'); + $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]); + }); + + element.on('$animate:close', function(e, data) { + steps.push(['close', 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']); + expect(steps.shift()).toEqual(['close', 'ng-enter', 'enter']); + })); it("should fire a done callback when provided with no animation", inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) {