Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit ca75279

Browse files
committed
feat($animate): allow directives to cancel animation events
Closes #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

+9-4
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

+34-15
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

+1-1
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

+14
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

+93
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)