Skip to content

Commit

Permalink
fix($animate): ensure animations work with directives that share a tr…
Browse files Browse the repository at this point in the history
…ansclusion

Closes angular#4716
Closes angular#4871
Closes angular#5021
Closes angular#5278
  • Loading branch information
matsko authored and jamesdaily committed Jan 27, 2014
1 parent 4a96702 commit ee6c8fb
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 19 deletions.
61 changes: 42 additions & 19 deletions src/ngAnimate/animate.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,19 @@ angular.module('ngAnimate', ['ng'])
var NG_ANIMATE_CLASS_NAME = 'ng-animate';
var rootAnimateState = {running: true};

function extractElementNode(element) {
for(var i = 0; i < element.length; i++) {
var elm = element[i];
if(elm.nodeType == ELEMENT_NODE) {
return elm;
}
}
}

function isMatchingElement(elm1, elm2) {
return extractElementNode(elm1) == extractElementNode(elm2);
}

$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) {

Expand Down Expand Up @@ -556,7 +569,16 @@ angular.module('ngAnimate', ['ng'])
and the onComplete callback will be fired once the animation is fully complete.
*/
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
var currentClassName = element.attr('class') || '';
var node = extractElementNode(element);
//transcluded directives may sometimes fire an animation using only comment nodes
//best to catch this early on to prevent any animation operations from occurring
if(!node) {
fireDOMOperation();
closeAnimation();
return;
}

var currentClassName = node.className;
var classes = currentClassName + ' ' + className;
var animationLookup = (' ' + classes).replace(/\s+/g,'.');
if (!parentElement) {
Expand Down Expand Up @@ -760,11 +782,7 @@ angular.module('ngAnimate', ['ng'])
}

function cancelChildAnimations(element) {
var node = element[0];
if(node.nodeType != ELEMENT_NODE) {
return;
}

var node = extractElementNode(element);
forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
element = angular.element(element);
var data = element.data(NG_ANIMATE_STATE);
Expand All @@ -788,7 +806,7 @@ angular.module('ngAnimate', ['ng'])
}

function cleanup(element) {
if(element[0] == $rootElement[0]) {
if(isMatchingElement(element, $rootElement)) {
if(!rootAnimateState.disabled) {
rootAnimateState.running = false;
rootAnimateState.structural = false;
Expand All @@ -802,7 +820,7 @@ angular.module('ngAnimate', ['ng'])
function animationsDisabled(element, parentElement) {
if (rootAnimateState.disabled) return true;

if(element[0] == $rootElement[0]) {
if(isMatchingElement(element, $rootElement)) {
return rootAnimateState.disabled || rootAnimateState.running;
}

Expand All @@ -812,7 +830,7 @@ angular.module('ngAnimate', ['ng'])
//any animations on it
if(parentElement.length === 0) break;

var isRoot = parentElement[0] == $rootElement[0];
var isRoot = isMatchingElement(parentElement, $rootElement);
var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
var result = state && (!!state.disabled || !!state.running);
if(isRoot || result) {
Expand Down Expand Up @@ -960,7 +978,7 @@ angular.module('ngAnimate', ['ng'])
parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
parentID = parentCounter;
}
return parentID + '-' + element[0].className;
return parentID + '-' + extractElementNode(element).className;
}

function animateSetup(element, className) {
Expand Down Expand Up @@ -995,7 +1013,6 @@ angular.module('ngAnimate', ['ng'])
return false;
}

var node = element[0];
//temporarily disable the transition so that the enter styles
//don't animate twice (this is here to avoid a bug in Chrome/FF).
var activeClassName = '';
Expand Down Expand Up @@ -1025,35 +1042,37 @@ angular.module('ngAnimate', ['ng'])
}

function blockTransitions(element) {
element[0].style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
}

function blockKeyframeAnimations(element) {
element[0].style[ANIMATION_PROP] = 'none 0s';
extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
}

function unblockTransitions(element) {
var node = element[0], prop = TRANSITION_PROP + PROPERTY_KEY;
var prop = TRANSITION_PROP + PROPERTY_KEY;
var node = extractElementNode(element);
if(node.style[prop] && node.style[prop].length > 0) {
node.style[prop] = '';
}
}

function unblockKeyframeAnimations(element) {
var node = element[0], prop = ANIMATION_PROP;
var prop = ANIMATION_PROP;
var node = extractElementNode(element);
if(node.style[prop] && node.style[prop].length > 0) {
element[0].style[prop] = '';
node.style[prop] = '';
}
}

function animateRun(element, className, activeAnimationComplete) {
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(!element.hasClass(className) || !data) {
var node = extractElementNode(element);
if(node.className.indexOf(className) == -1 || !data) {
activeAnimationComplete();
return;
}

var node = element[0];
var timings = data.timings;
var stagger = data.stagger;
var maxDuration = data.maxDuration;
Expand Down Expand Up @@ -1096,6 +1115,9 @@ angular.module('ngAnimate', ['ng'])
}

if(appliedStyles.length > 0) {
//the element being animated may sometimes contain comment nodes in
//the jqLite object, so we're safe to use a single variable to house
//the styles since there is always only one element being animated
var oldStyle = node.getAttribute('style') || '';
node.setAttribute('style', oldStyle + ' ' + style);
}
Expand All @@ -1110,6 +1132,7 @@ angular.module('ngAnimate', ['ng'])
element.off(css3AnimationEvents, onAnimationProgress);
element.removeClass(activeClassName);
animateClose(element, className);
var node = extractElementNode(element);
for (var i in appliedStyles) {
node.style.removeProperty(appliedStyles[i]);
}
Expand Down Expand Up @@ -1209,7 +1232,7 @@ angular.module('ngAnimate', ['ng'])
}

var parentElement = element.parent();
var clone = angular.element(element[0].cloneNode());
var clone = angular.element(extractElementNode(element).cloneNode());

//make the element super hidden and override any CSS style values
clone.attr('style','position:absolute; top:-9999px; left:-9999px');
Expand Down
53 changes: 53 additions & 0 deletions test/ngAnimate/animateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2873,5 +2873,58 @@ describe("ngAnimate", function() {

expect($rootElement.children().length).toBe(0);
}));

it('should properly animate elements with compound directives', function() {
var capturedAnimation;
module(function($animateProvider) {
$animateProvider.register('.special', function() {
return {
enter : function(element, done) {
capturedAnimation = 'enter';
done();
},
leave : function(element, done) {
capturedAnimation = 'leave';
done();
}
}
});
});
inject(function($rootScope, $compile, $rootElement, $document, $timeout, $templateCache, $sniffer) {
if(!$sniffer.transitions) return;

$templateCache.put('item-template', 'item: #{{ item }} ');
var element = $compile('<div>' +
' <div ng-repeat="item in items"' +
' ng-include="tpl"' +
' class="special"></div>' +
'</div>')($rootScope);

ss.addRule('.special', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');

$rootElement.append(element);
jqLite($document[0].body).append($rootElement);

$rootScope.tpl = 'item-template';
$rootScope.items = [1,2,3];
$rootScope.$digest();
$timeout.flush();

expect(capturedAnimation).toBe('enter');
expect(element.text()).toContain('item: #1');

forEach(element.children(), function(kid) {
browserTrigger(kid, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
});
$timeout.flush();

$rootScope.items = [];
$rootScope.$digest();
$timeout.flush();

expect(capturedAnimation).toBe('leave');
});
});
});
});

0 comments on commit ee6c8fb

Please sign in to comment.