aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/ngAnimate/animate.js61
-rw-r--r--test/ngAnimate/animateSpec.js53
2 files changed, 95 insertions, 19 deletions
diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js
index 18796ba9..8ff7b429 100644
--- a/src/ngAnimate/animate.js
+++ b/src/ngAnimate/animate.js
@@ -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) {
@@ -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) {
@@ -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);
@@ -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;
@@ -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;
}
@@ -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) {
@@ -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) {
@@ -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 = '';
@@ -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;
@@ -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);
}
@@ -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]);
}
@@ -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');
diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js
index b3068470..44b623b4 100644
--- a/test/ngAnimate/animateSpec.js
+++ b/test/ngAnimate/animateSpec.js
@@ -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');
+ });
+ });
});
});