|
56 | 56 | * <ANY class="slide" ng-include="..."></ANY>
|
57 | 57 | * ```
|
58 | 58 | *
|
59 |
| - * Keep in mind that if an animation is running, any child elements cannot be animated until the parent element's |
60 |
| - * animation has completed. |
| 59 | + * Keep in mind that, by default, if an animation is running, any child elements cannot be animated |
| 60 | + * until the parent element's animation has completed. This blocking feature can be overridden by |
| 61 | + * placing the `ng-animate-children` attribute on a parent container tag. |
| 62 | + * |
| 63 | + * ```html |
| 64 | + * <div class="slide-animation" ng-if="on" ng-animate-children> |
| 65 | + * <div class="fade-animation" ng-if="on"> |
| 66 | + * <div class="explode-animation" ng-if="on"> |
| 67 | + * ... |
| 68 | + * </div> |
| 69 | + * </div> |
| 70 | + * </div> |
| 71 | + * ``` |
| 72 | + * |
| 73 | + * When the `on` expression value changes and an animation is triggered then each of the elements within |
| 74 | + * will all animate without the block being applied to child elements. |
61 | 75 | *
|
62 | 76 | * <h2>CSS-defined Animations</h2>
|
63 | 77 | * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
|
@@ -314,6 +328,19 @@ angular.module('ngAnimate', ['ng'])
|
314 | 328 | * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
|
315 | 329 | *
|
316 | 330 | */
|
| 331 | + .directive('ngAnimateChildren', function() { |
| 332 | + var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren'; |
| 333 | + return function(scope, element, attrs) { |
| 334 | + var val = attrs.ngAnimateChildren; |
| 335 | + if(angular.isString(val) && val.length === 0) { //empty attribute |
| 336 | + element.data(NG_ANIMATE_CHILDREN, true); |
| 337 | + } else { |
| 338 | + scope.$watch(val, function(value) { |
| 339 | + element.data(NG_ANIMATE_CHILDREN, !!value); |
| 340 | + }); |
| 341 | + } |
| 342 | + }; |
| 343 | + }) |
317 | 344 |
|
318 | 345 | //this private service is only used within CSS-enabled animations
|
319 | 346 | //IE8 + IE9 do not support rAF natively, but that is fine since they
|
@@ -342,6 +369,7 @@ angular.module('ngAnimate', ['ng'])
|
342 | 369 |
|
343 | 370 | var ELEMENT_NODE = 1;
|
344 | 371 | var NG_ANIMATE_STATE = '$$ngAnimateState';
|
| 372 | + var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren'; |
345 | 373 | var NG_ANIMATE_CLASS_NAME = 'ng-animate';
|
346 | 374 | var rootAnimateState = {running: true};
|
347 | 375 |
|
@@ -391,6 +419,12 @@ angular.module('ngAnimate', ['ng'])
|
391 | 419 | return classNameFilter.test(className);
|
392 | 420 | };
|
393 | 421 |
|
| 422 | + function blockElementAnimations(element) { |
| 423 | + var data = element.data(NG_ANIMATE_STATE) || {}; |
| 424 | + data.running = true; |
| 425 | + element.data(NG_ANIMATE_STATE, data); |
| 426 | + } |
| 427 | + |
394 | 428 | function lookup(name) {
|
395 | 429 | if (name) {
|
396 | 430 | var matches = [],
|
@@ -620,7 +654,7 @@ angular.module('ngAnimate', ['ng'])
|
620 | 654 | parentElement = prepareElement(parentElement);
|
621 | 655 | afterElement = prepareElement(afterElement);
|
622 | 656 |
|
623 |
| - this.enabled(false, element); |
| 657 | + blockElementAnimations(element); |
624 | 658 | $delegate.enter(element, parentElement, afterElement);
|
625 | 659 | $rootScope.$$postDigest(function() {
|
626 | 660 | element = stripCommentsFromElement(element);
|
@@ -661,7 +695,7 @@ angular.module('ngAnimate', ['ng'])
|
661 | 695 | leave : function(element, doneCallback) {
|
662 | 696 | element = angular.element(element);
|
663 | 697 | cancelChildAnimations(element);
|
664 |
| - this.enabled(false, element); |
| 698 | + blockElementAnimations(element); |
665 | 699 | $rootScope.$$postDigest(function() {
|
666 | 700 | performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
|
667 | 701 | $delegate.leave(element);
|
@@ -708,7 +742,7 @@ angular.module('ngAnimate', ['ng'])
|
708 | 742 | afterElement = prepareElement(afterElement);
|
709 | 743 |
|
710 | 744 | cancelChildAnimations(element);
|
711 |
| - this.enabled(false, element); |
| 745 | + blockElementAnimations(element); |
712 | 746 | $delegate.move(element, parentElement, afterElement);
|
713 | 747 | $rootScope.$$postDigest(function() {
|
714 | 748 | element = stripCommentsFromElement(element);
|
@@ -895,9 +929,12 @@ angular.module('ngAnimate', ['ng'])
|
895 | 929 |
|
896 | 930 | //only allow animations if the currently running animation is not structural
|
897 | 931 | //or if there is no animation running at all
|
898 |
| - var skipAnimations = runner.isClassBased |
899 |
| - ? ngAnimateState.disabled || (lastAnimation && !lastAnimation.isClassBased) |
900 |
| - : false; |
| 932 | + var skipAnimations; |
| 933 | + if (runner.isClassBased) { |
| 934 | + skipAnimations = ngAnimateState.running || |
| 935 | + ngAnimateState.disabled || |
| 936 | + (lastAnimation && !lastAnimation.isClassBased); |
| 937 | + } |
901 | 938 |
|
902 | 939 | //skip the animation if animations are disabled, a parent is already being animated,
|
903 | 940 | //the element is not currently attached to the document body or then completely close
|
@@ -1114,33 +1151,49 @@ angular.module('ngAnimate', ['ng'])
|
1114 | 1151 | }
|
1115 | 1152 |
|
1116 | 1153 | function animationsDisabled(element, parentElement) {
|
1117 |
| - if (rootAnimateState.disabled) return true; |
| 1154 | + if (rootAnimateState.disabled) { |
| 1155 | + return true; |
| 1156 | + } |
1118 | 1157 |
|
1119 |
| - if(isMatchingElement(element, $rootElement)) { |
1120 |
| - return rootAnimateState.disabled || rootAnimateState.running; |
| 1158 | + if (isMatchingElement(element, $rootElement)) { |
| 1159 | + return rootAnimateState.running; |
1121 | 1160 | }
|
1122 | 1161 |
|
| 1162 | + var allowChildAnimations, parentRunningAnimation, hasParent; |
1123 | 1163 | do {
|
1124 | 1164 | //the element did not reach the root element which means that it
|
1125 | 1165 | //is not apart of the DOM. Therefore there is no reason to do
|
1126 | 1166 | //any animations on it
|
1127 |
| - if(parentElement.length === 0) break; |
| 1167 | + if (parentElement.length === 0) break; |
1128 | 1168 |
|
1129 | 1169 | var isRoot = isMatchingElement(parentElement, $rootElement);
|
1130 | 1170 | var state = isRoot ? rootAnimateState : (parentElement.data(NG_ANIMATE_STATE) || {});
|
1131 |
| - var result = state.disabled || state.running |
1132 |
| - ? true |
1133 |
| - : state.last && !state.last.isClassBased; |
| 1171 | + if (state.disabled) { |
| 1172 | + return true; |
| 1173 | + } |
1134 | 1174 |
|
1135 |
| - if(isRoot || result) { |
1136 |
| - return result; |
| 1175 | + //no matter what, for an animation to work it must reach the root element |
| 1176 | + //this implies that the element is attached to the DOM when the animation is run |
| 1177 | + if (isRoot) { |
| 1178 | + hasParent = true; |
1137 | 1179 | }
|
1138 | 1180 |
|
1139 |
| - if(isRoot) return true; |
| 1181 | + //once a flag is found that is strictly false then everything before |
| 1182 | + //it will be discarded and all child animations will be restricted |
| 1183 | + if (allowChildAnimations !== false) { |
| 1184 | + var animateChildrenFlag = parentElement.data(NG_ANIMATE_CHILDREN); |
| 1185 | + if(angular.isDefined(animateChildrenFlag)) { |
| 1186 | + allowChildAnimations = animateChildrenFlag; |
| 1187 | + } |
| 1188 | + } |
| 1189 | + |
| 1190 | + parentRunningAnimation = parentRunningAnimation || |
| 1191 | + state.running || |
| 1192 | + (state.last && !state.last.isClassBased); |
1140 | 1193 | }
|
1141 | 1194 | while(parentElement = parentElement.parent());
|
1142 | 1195 |
|
1143 |
| - return true; |
| 1196 | + return !hasParent || (!allowChildAnimations && parentRunningAnimation); |
1144 | 1197 | }
|
1145 | 1198 | }]);
|
1146 | 1199 |
|
|
0 commit comments