diff options
| author | Matias Niemelä | 2013-10-10 09:16:19 -0400 | 
|---|---|---|
| committer | Misko Hevery | 2013-10-10 17:35:36 -0700 | 
| commit | 3f31a7c7691993893f0724076816f6558643bd91 (patch) | |
| tree | 710761f443eb304f5f4b68b6d698466a004dd363 | |
| parent | 079dd93991ac79b5f9af6efb7fe2b3600195f10c (diff) | |
| download | angular.js-3f31a7c7691993893f0724076816f6558643bd91.tar.bz2 | |
fix($animate): cancel any ongoing child animations during move and leave animations
| -rw-r--r-- | src/ngAnimate/animate.js | 38 | ||||
| -rw-r--r-- | test/ngAnimate/animateSpec.js | 84 | 
2 files changed, 110 insertions, 12 deletions
| diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js index ee637b3b..9b09cad2 100644 --- a/src/ngAnimate/animate.js +++ b/src/ngAnimate/animate.js @@ -200,6 +200,7 @@ angular.module('ngAnimate', ['ng'])      var selectors = $animateProvider.$$selectors;      var NG_ANIMATE_STATE = '$$ngAnimateState'; +    var NG_ANIMATE_CLASS_NAME = 'ng-animate';      var rootAnimateState = {running:true};      $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope',                              function($delegate,   $injector,   $sniffer,   $rootElement,   $timeout,   $rootScope) { @@ -320,6 +321,7 @@ angular.module('ngAnimate', ['ng'])           * @param {function()=} done callback function that will be called once the animation is complete          */          leave : function(element, done) { +          cancelChildAnimations(element);            $rootScope.$$postDigest(function() {              performAnimation('leave', 'ng-leave', element, null, null, function() {                $delegate.leave(element, done); @@ -358,6 +360,7 @@ angular.module('ngAnimate', ['ng'])           * @param {function()=} done callback function that will be called once the animation is complete          */          move : function(element, parent, after, done) { +          cancelChildAnimations(element);            $delegate.move(element, parent, after);            $rootScope.$$postDigest(function() {              performAnimation('move', 'ng-move', element, null, null, function() { @@ -503,6 +506,10 @@ angular.module('ngAnimate', ['ng'])            done:done          }); +        //the ng-animate class does nothing, but it's here to allow for +        //parent animations to find and cancel child animations when needed +        element.addClass(NG_ANIMATE_CLASS_NAME); +          forEach(animations, function(animation, index) {            var fn = function() {              progress(index); @@ -519,12 +526,6 @@ angular.module('ngAnimate', ['ng'])            }          }); -        function cancelAnimations(animations) { -          var isCancelledFlag = true; -          forEach(animations, function(animation) { -            (animation.endFn || noop)(isCancelledFlag); -          }); -        }          function progress(index) {            animations[index].done = true; @@ -538,11 +539,34 @@ angular.module('ngAnimate', ['ng'])          function done() {            if(!done.hasBeenRun) {              done.hasBeenRun = true; -            element.removeData(NG_ANIMATE_STATE); +            cleanup(element);              (onComplete || noop)();            }          }        } + +      function cancelChildAnimations(element) { +        angular.forEach(element[0].querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) { +          element = angular.element(element); +          var data = element.data(NG_ANIMATE_STATE); +          if(data) { +            cancelAnimations(data.animations); +            cleanup(element); +          } +        }); +      } + +      function cancelAnimations(animations) { +        var isCancelledFlag = true; +        forEach(animations, function(animation) { +          (animation.endFn || noop)(isCancelledFlag); +        }); +      } + +      function cleanup(element) { +        element.removeClass(NG_ANIMATE_CLASS_NAME); +        element.removeData(NG_ANIMATE_STATE); +      }      }]);      $animateProvider.register('', ['$window', '$sniffer', function($window, $sniffer) { diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index 90868ef6..5e53c585 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -696,8 +696,9 @@ describe("ngAnimate", function() {          $rootScope.$digest();          if ($sniffer.transitions) { -          expect(element.hasClass('abc ng-enter')).toBe(true); -          expect(element.hasClass('abc ng-enter ng-enter-active')).toBe(true); +          expect(element.hasClass('abc')).toBe(true); +          expect(element.hasClass('ng-enter')).toBe(true); +          expect(element.hasClass('ng-enter-active')).toBe(true);            browserTrigger(element,'transitionend', { timeStamp: Date.now() + 22000, elapsedTime: 22000 });          }          expect(element.hasClass('abc')).toBe(true); @@ -708,7 +709,8 @@ describe("ngAnimate", function() {          if ($sniffer.transitions) {            expect(element.hasClass('xyz')).toBe(true); -          expect(element.hasClass('xyz ng-enter ng-enter-active')).toBe(true); +          expect(element.hasClass('ng-enter')).toBe(true); +          expect(element.hasClass('ng-enter-active')).toBe(true);            browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000, elapsedTime: 11000 });          }          expect(element.hasClass('xyz')).toBe(true); @@ -732,8 +734,10 @@ describe("ngAnimate", function() {          $animate.enter(element, parent);          $rootScope.$digest();          if($sniffer.transitions) { -          expect(element.hasClass('one two ng-enter')).toBe(true); -          expect(element.hasClass('one two ng-enter ng-enter-active')).toBe(true); +          expect(element.hasClass('one')).toBe(true); +          expect(element.hasClass('two')).toBe(true); +          expect(element.hasClass('ng-enter')).toBe(true); +          expect(element.hasClass('ng-enter-active')).toBe(true);            expect(element.hasClass('one-active')).toBe(false);            expect(element.hasClass('two-active')).toBe(false);            browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000, elapsedTime: 3000 }); @@ -1574,4 +1578,74 @@ describe("ngAnimate", function() {      expect(element.contents().length).toBe(1);    })); +  it("should cancel all child animations when a leave or move animation is triggered on a parent element", function() { + +    var animationState; +    module(function($animateProvider) { +      $animateProvider.register('.animan', function($timeout) { +        return { +          enter : function(element, done) { +            animationState = 'enter'; +            $timeout(done, 0, false); +            return function() { +              animationState = 'enter-cancel'; +            } +          }, +          addClass : function(element, className, done) { +            animationState = 'addClass'; +            $timeout(done, 0, false); +            return function() { +              animationState = 'addClass-cancel'; +            } +          } +        }; +      }); +    }); + +    inject(function($animate, $compile, $rootScope, $timeout, $sniffer) { +      var element = html($compile('<div class="parent"></div>')($rootScope)); +      var container = html($compile('<div class="container"></div>')($rootScope)); +      var child   = html($compile('<div class="animan child"></div>')($rootScope)); + +      ss.addRule('.animan.ng-enter, .animan.something-add',  '-webkit-transition: width 1s, background 1s 1s;' + +                                                             'transition: width 1s, background 1s 1s;'); + +      $rootElement.append(element); +      jqLite(document.body).append($rootElement); + +      $animate.enter(child, element); +      $rootScope.$digest(); + +      expect(animationState).toBe('enter'); +      if($sniffer.transitions) { +        expect(child.hasClass('ng-enter')).toBe(true); +        expect(child.hasClass('ng-enter-active')).toBe(true); +      } + +      $animate.move(element, container); +      if($sniffer.transitions) { +        expect(child.hasClass('ng-enter')).toBe(false); +        expect(child.hasClass('ng-enter-active')).toBe(false); +      } + +      expect(animationState).toBe('enter-cancel'); +      $rootScope.$digest(); +      $timeout.flush(); + +      $animate.addClass(child, 'something'); +      expect(animationState).toBe('addClass'); +      if($sniffer.transitions) { +        expect(child.hasClass('something-add')).toBe(true); +        expect(child.hasClass('something-add-active')).toBe(true); +      } + +      $animate.leave(container); +      expect(animationState).toBe('addClass-cancel'); +      if($sniffer.transitions) { +        expect(child.hasClass('something-add')).toBe(false); +        expect(child.hasClass('something-add-active')).toBe(false); +      } +    }); +  }); +  }); | 
