Skip to content

Commit ca75279

Browse files
committed
feat($animate): allow directives to cancel animation events
Closes angular#7722
1 parent 8252b8b commit ca75279

File tree

5 files changed

+151
-20
lines changed

5 files changed

+151
-20
lines changed

src/ng/animate.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ var $AnimateProvider = ['$provide', function($provide) {
127127
? after.after(element)
128128
: parent.prepend(element);
129129
async(done);
130+
return noop;
130131
},
131132

132133
/**
@@ -143,6 +144,7 @@ var $AnimateProvider = ['$provide', function($provide) {
143144
leave : function(element, done) {
144145
element.remove();
145146
async(done);
147+
return noop;
146148
},
147149

148150
/**
@@ -166,7 +168,7 @@ var $AnimateProvider = ['$provide', function($provide) {
166168
move : function(element, parent, after, done) {
167169
// Do not remove element before insert. Removing will cause data associated with the
168170
// element to be dropped. Insert will implicitly do the remove.
169-
this.enter(element, parent, after, done);
171+
return this.enter(element, parent, after, done);
170172
},
171173

172174
/**
@@ -183,13 +185,14 @@ var $AnimateProvider = ['$provide', function($provide) {
183185
* className value has been added to the element
184186
*/
185187
addClass : function(element, className, done) {
186-
className = isString(className) ?
187-
className :
188-
isArray(className) ? className.join(' ') : '';
188+
className = !isString(className)
189+
? (isArray(className) ? className.join(' ') : '')
190+
: className;
189191
forEach(element, function (element) {
190192
jqLiteAddClass(element, className);
191193
});
192194
async(done);
195+
return noop;
193196
},
194197

195198
/**
@@ -213,6 +216,7 @@ var $AnimateProvider = ['$provide', function($provide) {
213216
jqLiteRemoveClass(element, className);
214217
});
215218
async(done);
219+
return noop;
216220
},
217221

218222
/**
@@ -235,6 +239,7 @@ var $AnimateProvider = ['$provide', function($provide) {
235239
jqLiteRemoveClass(element, remove);
236240
});
237241
async(done);
242+
return noop;
238243
},
239244

240245
enabled : noop

src/ngAnimate/animate.js

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,16 @@ angular.module('ngAnimate', ['ng'])
425425
element.data(NG_ANIMATE_STATE, data);
426426
}
427427

428+
function runAnimationPostDigest(fn) {
429+
var cancelFn;
430+
$rootScope.$$postDigest(function() {
431+
cancelFn = fn();
432+
});
433+
return function() {
434+
cancelFn && cancelFn();
435+
};
436+
}
437+
428438
function lookup(name) {
429439
if (name) {
430440
var matches = [],
@@ -648,6 +658,7 @@ angular.module('ngAnimate', ['ng'])
648658
* @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
649659
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
650660
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
661+
* @return {function} the animation cancellation function
651662
*/
652663
enter : function(element, parentElement, afterElement, doneCallback) {
653664
element = angular.element(element);
@@ -656,9 +667,8 @@ angular.module('ngAnimate', ['ng'])
656667

657668
blockElementAnimations(element);
658669
$delegate.enter(element, parentElement, afterElement);
659-
$rootScope.$$postDigest(function() {
660-
element = stripCommentsFromElement(element);
661-
performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
670+
return runAnimationPostDigest(function() {
671+
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, doneCallback);
662672
});
663673
},
664674

@@ -691,13 +701,16 @@ angular.module('ngAnimate', ['ng'])
691701
*
692702
* @param {DOMElement} element the element that will be the focus of the leave animation
693703
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
704+
* @return {function} the animation cancellation function
694705
*/
695706
leave : function(element, doneCallback) {
696707
element = angular.element(element);
708+
697709
cancelChildAnimations(element);
698710
blockElementAnimations(element);
699-
$rootScope.$$postDigest(function() {
700-
performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
711+
this.enabled(false, element);
712+
return runAnimationPostDigest(function() {
713+
return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
701714
$delegate.leave(element);
702715
}, doneCallback);
703716
});
@@ -735,6 +748,7 @@ angular.module('ngAnimate', ['ng'])
735748
* @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
736749
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
737750
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
751+
* @return {function} the animation cancellation function
738752
*/
739753
move : function(element, parentElement, afterElement, doneCallback) {
740754
element = angular.element(element);
@@ -744,9 +758,8 @@ angular.module('ngAnimate', ['ng'])
744758
cancelChildAnimations(element);
745759
blockElementAnimations(element);
746760
$delegate.move(element, parentElement, afterElement);
747-
$rootScope.$$postDigest(function() {
748-
element = stripCommentsFromElement(element);
749-
performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
761+
return runAnimationPostDigest(function() {
762+
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, doneCallback);
750763
});
751764
},
752765

@@ -778,11 +791,12 @@ angular.module('ngAnimate', ['ng'])
778791
* @param {DOMElement} element the element that will be animated
779792
* @param {string} className the CSS class that will be added to the element and then animated
780793
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
794+
* @return {function} the animation cancellation function
781795
*/
782796
addClass : function(element, className, doneCallback) {
783797
element = angular.element(element);
784798
element = stripCommentsFromElement(element);
785-
performAnimation('addClass', className, element, null, null, function() {
799+
return performAnimation('addClass', className, element, null, null, function() {
786800
$delegate.addClass(element, className);
787801
}, doneCallback);
788802
},
@@ -815,11 +829,12 @@ angular.module('ngAnimate', ['ng'])
815829
* @param {DOMElement} element the element that will be animated
816830
* @param {string} className the CSS class that will be animated and then removed from the element
817831
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
832+
* @return {function} the animation cancellation function
818833
*/
819834
removeClass : function(element, className, doneCallback) {
820835
element = angular.element(element);
821836
element = stripCommentsFromElement(element);
822-
performAnimation('removeClass', className, element, null, null, function() {
837+
return performAnimation('removeClass', className, element, null, null, function() {
823838
$delegate.removeClass(element, className);
824839
}, doneCallback);
825840
},
@@ -848,13 +863,14 @@ angular.module('ngAnimate', ['ng'])
848863
* removed from it
849864
* @param {string} add the CSS classes which will be added to the element
850865
* @param {string} remove the CSS class which will be removed from the element
851-
* @param {Function=} done the callback function (if provided) that will be fired after the
866+
* @param {function=} done the callback function (if provided) that will be fired after the
852867
* CSS classes have been set on the element
868+
* @return {function} the animation cancellation function
853869
*/
854870
setClass : function(element, add, remove, doneCallback) {
855871
element = angular.element(element);
856872
element = stripCommentsFromElement(element);
857-
performAnimation('setClass', [add, remove], element, null, null, function() {
873+
return performAnimation('setClass', [add, remove], element, null, null, function() {
858874
$delegate.setClass(element, add, remove);
859875
}, doneCallback);
860876
},
@@ -905,13 +921,14 @@ angular.module('ngAnimate', ['ng'])
905921
*/
906922
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
907923

924+
var noopCancel = noop;
908925
var runner = animationRunner(element, animationEvent, className);
909926
if(!runner) {
910927
fireDOMOperation();
911928
fireBeforeCallbackAsync();
912929
fireAfterCallbackAsync();
913930
closeAnimation();
914-
return;
931+
return noopCancel;
915932
}
916933

917934
className = runner.className;
@@ -945,7 +962,7 @@ angular.module('ngAnimate', ['ng'])
945962
fireBeforeCallbackAsync();
946963
fireAfterCallbackAsync();
947964
closeAnimation();
948-
return;
965+
return noopCancel;
949966
}
950967

951968
var skipAnimation = false;
@@ -993,7 +1010,7 @@ angular.module('ngAnimate', ['ng'])
9931010
fireBeforeCallbackAsync();
9941011
fireAfterCallbackAsync();
9951012
fireDoneCallbackAsync();
996-
return;
1013+
return noopCancel;
9971014
}
9981015

9991016
if(animationEvent == 'leave') {
@@ -1046,6 +1063,8 @@ angular.module('ngAnimate', ['ng'])
10461063
}
10471064
});
10481065

1066+
return runner.cancel;
1067+
10491068
function fireDOMCallback(animationPhase) {
10501069
var eventName = '$animate:' + animationPhase;
10511070
if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {

src/ngMock/angular-mocks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
791791
element : arguments[0],
792792
args : arguments
793793
});
794-
$delegate[method].apply($delegate, arguments);
794+
return $delegate[method].apply($delegate, arguments);
795795
};
796796
});
797797

test/ng/animateSpec.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ describe("$animate", function() {
5757
expect(element).toBeHidden();
5858
}));
5959

60+
it("should run each method and return a noop function", inject(function($animate, $document) {
61+
var element = jqLite('<div></div>');
62+
var move = jqLite('<div></div>');
63+
var parent = jqLite($document[0].body);
64+
parent.append(move);
65+
66+
expect($animate.enter(element, parent)).toBe(noop);
67+
expect($animate.move(element, move)).toBe(noop);
68+
expect($animate.addClass(element, 'on')).toBe(noop);
69+
expect($animate.addClass(element, 'off')).toBe(noop);
70+
expect($animate.setClass(element, 'on', 'off')).toBe(noop);
71+
expect($animate.leave(element)).toBe(noop);
72+
}));
73+
6074
it("should add and remove classes on SVG elements", inject(function($animate) {
6175
if (!window.SVGElement) return;
6276
var svg = jqLite('<svg><rect></rect></svg>');

test/ngAnimate/animateSpec.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,99 @@ describe("ngAnimate", function() {
590590
}));
591591

592592

593+
it("should trigger a cancellation when the return function is called upon any animation", function() {
594+
var captures = {};
595+
596+
module(function($animateProvider) {
597+
$animateProvider.register('.track-me', function() {
598+
return {
599+
enter : track('enter'),
600+
leave : track('leave'),
601+
move : track('move'),
602+
addClass : track('addClass'),
603+
removeClass : track('removeClass'),
604+
setClass : track('setClass')
605+
};
606+
607+
function track(type) {
608+
return function(element, add, remove, done) {
609+
done = done || remove || add;
610+
return function(cancelled) {
611+
captures[type]=cancelled;
612+
};
613+
};
614+
}
615+
});
616+
});
617+
inject(function($animate, $sniffer, $rootScope, $timeout) {
618+
619+
var fn;
620+
$animate.enabled(true);
621+
$rootScope.$digest();
622+
element[0].removeChild(child[0]);
623+
child.addClass('track-me');
624+
625+
//enter
626+
fn = $animate.enter(child, element);
627+
$rootScope.$digest();
628+
$animate.triggerReflow();
629+
630+
expect(captures.enter).toBeUndefined();
631+
fn();
632+
expect(captures.enter).toBeTruthy();
633+
$animate.triggerCallbacks();
634+
635+
//move
636+
element.append(after);
637+
fn = $animate.move(child, element, after);
638+
$rootScope.$digest();
639+
$animate.triggerReflow();
640+
641+
expect(captures.move).toBeUndefined();
642+
fn();
643+
expect(captures.move).toBeTruthy();
644+
$animate.triggerCallbacks();
645+
646+
//addClass
647+
fn = $animate.addClass(child, 'ng-hide');
648+
$animate.triggerReflow();
649+
650+
expect(captures.addClass).toBeUndefined();
651+
fn();
652+
expect(captures.addClass).toBeTruthy();
653+
$animate.triggerCallbacks();
654+
655+
//removeClass
656+
fn = $animate.removeClass(child, 'ng-hide');
657+
$animate.triggerReflow();
658+
659+
expect(captures.removeClass).toBeUndefined();
660+
fn();
661+
expect(captures.removeClass).toBeTruthy();
662+
$animate.triggerCallbacks();
663+
664+
//setClass
665+
child.addClass('red');
666+
fn = $animate.setClass(child, 'blue', 'red');
667+
$animate.triggerReflow();
668+
669+
expect(captures.setClass).toBeUndefined();
670+
fn();
671+
expect(captures.setClass).toBeTruthy();
672+
$animate.triggerCallbacks();
673+
674+
//leave
675+
fn = $animate.leave(child);
676+
$rootScope.$digest();
677+
678+
expect(captures.leave).toBeUndefined();
679+
fn();
680+
expect(captures.leave).toBeTruthy();
681+
$animate.triggerCallbacks();
682+
});
683+
});
684+
685+
593686
it("should not run if animations are disabled",
594687
inject(function($animate, $rootScope, $timeout, $sniffer) {
595688

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy