|
58 | 58 | * <ANY class="slide" ng-include="..."></ANY>
|
59 | 59 | * ```
|
60 | 60 | *
|
61 |
| - * Keep in mind that if an animation is running, any child elements cannot be animated until the parent element's |
62 |
| - * animation has completed. |
| 61 | + * Keep in mind that, by default, if an animation is running, any child elements cannot be animated |
| 62 | + * until the parent element's animation has completed. This blocking feature can be overridden by |
| 63 | + * placing the `ng-animate-children` attribute on a parent container tag. |
| 64 | + * |
| 65 | + * ```html |
| 66 | + * <div class="slide-animation" ng-if="on" ng-animate-children> |
| 67 | + * <div class="fade-animation" ng-if="on"> |
| 68 | + * <div class="explode-animation" ng-if="on"> |
| 69 | + * ... |
| 70 | + * </div> |
| 71 | + * </div> |
| 72 | + * </div> |
| 73 | + * ``` |
| 74 | + * |
| 75 | + * When the `on` expression value changes and an animation is triggered then each of the elements within |
| 76 | + * will all animate without the block being applied to child elements. |
63 | 77 | *
|
64 | 78 | * <h2>CSS-defined Animations</h2>
|
65 | 79 | * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
|
@@ -249,6 +263,19 @@ angular.module('ngAnimate', ['ng'])
|
249 | 263 | * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
|
250 | 264 | *
|
251 | 265 | */
|
| 266 | + .directive('ngAnimateChildren', function() { |
| 267 | + var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren'; |
| 268 | + return function(scope, element, attrs) { |
| 269 | + var val = attrs.ngAnimateChildren; |
| 270 | + if(angular.isString(val) && val.length === 0) { //empty attribute |
| 271 | + element.data(NG_ANIMATE_CHILDREN, true); |
| 272 | + } else { |
| 273 | + scope.$watch(val, function(value) { |
| 274 | + element.data(NG_ANIMATE_CHILDREN, !!value); |
| 275 | + }); |
| 276 | + } |
| 277 | + }; |
| 278 | + }) |
252 | 279 |
|
253 | 280 | //this private service is only used within CSS-enabled animations
|
254 | 281 | //IE8 + IE9 do not support rAF natively, but that is fine since they
|
@@ -277,6 +304,7 @@ angular.module('ngAnimate', ['ng'])
|
277 | 304 |
|
278 | 305 | var ELEMENT_NODE = 1;
|
279 | 306 | var NG_ANIMATE_STATE = '$$ngAnimateState';
|
| 307 | + var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren'; |
280 | 308 | var NG_ANIMATE_CLASS_NAME = 'ng-animate';
|
281 | 309 | var rootAnimateState = {running: true};
|
282 | 310 |
|
@@ -326,6 +354,12 @@ angular.module('ngAnimate', ['ng'])
|
326 | 354 | return classNameFilter.test(className);
|
327 | 355 | };
|
328 | 356 |
|
| 357 | + function blockElementAnimations(element) { |
| 358 | + var data = element.data(NG_ANIMATE_STATE) || {}; |
| 359 | + data.running = true; |
| 360 | + element.data(NG_ANIMATE_STATE, data); |
| 361 | + } |
| 362 | + |
329 | 363 | function lookup(name) {
|
330 | 364 | if (name) {
|
331 | 365 | var matches = [],
|
@@ -552,7 +586,7 @@ angular.module('ngAnimate', ['ng'])
|
552 | 586 | parentElement = prepareElement(parentElement);
|
553 | 587 | afterElement = prepareElement(afterElement);
|
554 | 588 |
|
555 |
| - this.enabled(false, element); |
| 589 | + blockElementAnimations(element); |
556 | 590 | $delegate.enter(element, parentElement, afterElement);
|
557 | 591 | $rootScope.$$postDigest(function() {
|
558 | 592 | element = stripCommentsFromElement(element);
|
@@ -590,7 +624,7 @@ angular.module('ngAnimate', ['ng'])
|
590 | 624 | leave : function(element, doneCallback) {
|
591 | 625 | element = angular.element(element);
|
592 | 626 | cancelChildAnimations(element);
|
593 |
| - this.enabled(false, element); |
| 627 | + blockElementAnimations(element); |
594 | 628 | $rootScope.$$postDigest(function() {
|
595 | 629 | performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
|
596 | 630 | $delegate.leave(element);
|
@@ -634,7 +668,7 @@ angular.module('ngAnimate', ['ng'])
|
634 | 668 | afterElement = prepareElement(afterElement);
|
635 | 669 |
|
636 | 670 | cancelChildAnimations(element);
|
637 |
| - this.enabled(false, element); |
| 671 | + blockElementAnimations(element); |
638 | 672 | $delegate.move(element, parentElement, afterElement);
|
639 | 673 | $rootScope.$$postDigest(function() {
|
640 | 674 | element = stripCommentsFromElement(element);
|
@@ -808,9 +842,12 @@ angular.module('ngAnimate', ['ng'])
|
808 | 842 |
|
809 | 843 | //only allow animations if the currently running animation is not structural
|
810 | 844 | //or if there is no animation running at all
|
811 |
| - var skipAnimations = runner.isClassBased ? |
812 |
| - ngAnimateState.disabled || (lastAnimation && !lastAnimation.isClassBased) : |
813 |
| - false; |
| 845 | + var skipAnimations; |
| 846 | + if (runner.isClassBased) { |
| 847 | + skipAnimations = ngAnimateState.running || |
| 848 | + ngAnimateState.disabled || |
| 849 | + (lastAnimation && !lastAnimation.isClassBased); |
| 850 | + } |
814 | 851 |
|
815 | 852 | //skip the animation if animations are disabled, a parent is already being animated,
|
816 | 853 | //the element is not currently attached to the document body or then completely close
|
@@ -1027,30 +1064,49 @@ angular.module('ngAnimate', ['ng'])
|
1027 | 1064 | }
|
1028 | 1065 |
|
1029 | 1066 | function animationsDisabled(element, parentElement) {
|
1030 |
| - if (rootAnimateState.disabled) return true; |
| 1067 | + if (rootAnimateState.disabled) { |
| 1068 | + return true; |
| 1069 | + } |
1031 | 1070 |
|
1032 |
| - if(isMatchingElement(element, $rootElement)) { |
1033 |
| - return rootAnimateState.disabled || rootAnimateState.running; |
| 1071 | + if (isMatchingElement(element, $rootElement)) { |
| 1072 | + return rootAnimateState.running; |
1034 | 1073 | }
|
1035 | 1074 |
|
| 1075 | + var allowChildAnimations, parentRunningAnimation, hasParent; |
1036 | 1076 | do {
|
1037 | 1077 | //the element did not reach the root element which means that it
|
1038 | 1078 | //is not apart of the DOM. Therefore there is no reason to do
|
1039 | 1079 | //any animations on it
|
1040 |
| - if(parentElement.length === 0) break; |
| 1080 | + if (parentElement.length === 0) break; |
1041 | 1081 |
|
1042 | 1082 | var isRoot = isMatchingElement(parentElement, $rootElement);
|
1043 |
| - var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE); |
1044 |
| - var result = state && (!!state.disabled || state.running || state.totalActive > 0); |
1045 |
| - if(isRoot || result) { |
1046 |
| - return result; |
| 1083 | + var state = isRoot ? rootAnimateState : (parentElement.data(NG_ANIMATE_STATE) || {}); |
| 1084 | + if (state.disabled) { |
| 1085 | + return true; |
| 1086 | + } |
| 1087 | + |
| 1088 | + //no matter what, for an animation to work it must reach the root element |
| 1089 | + //this implies that the element is attached to the DOM when the animation is run |
| 1090 | + if (isRoot) { |
| 1091 | + hasParent = true; |
1047 | 1092 | }
|
1048 | 1093 |
|
1049 |
| - if(isRoot) return true; |
| 1094 | + //once a flag is found that is strictly false then everything before |
| 1095 | + //it will be discarded and all child animations will be restricted |
| 1096 | + if (allowChildAnimations !== false) { |
| 1097 | + var animateChildrenFlag = parentElement.data(NG_ANIMATE_CHILDREN); |
| 1098 | + if(angular.isDefined(animateChildrenFlag)) { |
| 1099 | + allowChildAnimations = animateChildrenFlag; |
| 1100 | + } |
| 1101 | + } |
| 1102 | + |
| 1103 | + parentRunningAnimation = parentRunningAnimation || |
| 1104 | + state.running || |
| 1105 | + (state.last && !state.last.isClassBased); |
1050 | 1106 | }
|
1051 | 1107 | while(parentElement = parentElement.parent());
|
1052 | 1108 |
|
1053 |
| - return true; |
| 1109 | + return !hasParent || (!allowChildAnimations && parentRunningAnimation); |
1054 | 1110 | }
|
1055 | 1111 | }]);
|
1056 | 1112 |
|
|
0 commit comments