Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

fix(ngAnimate): ensure that all jqLite elements are deconstructed properly #11760

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/ngAnimate/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"packageStyles": false,
"removeFromArray": false,
"stripCommentsFromElement": false,
"extractElementNode": false
"extractElementNode": false,
"getDomNode": false
}
}
4 changes: 2 additions & 2 deletions src/ngAnimate/animateCss.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
return stagger || {};
}

var bod = $document[0].body;
var bod = getDomNode($document).body;
var cancelLastRAFRequest;
var rafWaitQueue = [];
function waitUntilQuiet(callback) {
Expand Down Expand Up @@ -521,7 +521,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
}

function init(element, options) {
var node = element[0];
var node = getDomNode(element);
options = prepareAnimationOptions(options);

var temporaryStyles = [];
Expand Down
8 changes: 4 additions & 4 deletions src/ngAnimate/animateCssDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
// only browsers that support these properties can render animations
if (!$sniffer.animations && !$sniffer.transitions) return noop;

var bodyNode = $document[0].body;
var rootNode = $rootElement[0];
var bodyNode = getDomNode($document).body;
var rootNode = getDomNode($rootElement);

var rootBodyElement = jqLite(bodyNode.parentNode === rootNode ? bodyNode : rootNode);

Expand All @@ -44,7 +44,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
}

function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
var clone = jqLite(outAnchor[0].cloneNode(true));
var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
var startingClasses = filterCssClasses(clone.attr('class') || '');
var anchorClasses = pendClasses(classes, NG_ANIMATE_ANCHOR_SUFFIX);

Expand Down Expand Up @@ -113,7 +113,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
function calculateAnchorStyles(anchor) {
var styles = {};

var coords = anchor[0].getBoundingClientRect();
var coords = getDomNode(anchor).getBoundingClientRect();

// we iterate directly since safari messes up and doesn't return
// all the keys for the coods object when iterated
Expand Down
54 changes: 26 additions & 28 deletions src/ngAnimate/animateQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}

function findCallbacks(element, event) {
var targetNode = element[0];
var targetNode = getDomNode(element);

var matches = [];
var entries = callbackRegistry[event];
Expand Down Expand Up @@ -198,7 +198,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
// (bool) - Global setter
bool = animationsEnabled = !!element;
} else {
var node = element.length ? element[0] : element;
var node = getDomNode(element);
var recordExists = disabledElementsLookup.get(node);

if (argCount === 1) {
Expand All @@ -224,7 +224,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
var node, parent;
element = stripCommentsFromElement(element);
if (element) {
node = element[0];
node = getDomNode(element);
parent = element.parent();
}

Expand Down Expand Up @@ -411,7 +411,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
close(!status);
var animationDetails = activeAnimationsLookup.get(node);
if (animationDetails && animationDetails.counter === counter) {
clearElementAnimationState(element);
clearElementAnimationState(getDomNode(element));
}
notifyProgress(runner, event, 'close', {});
});
Expand All @@ -438,7 +438,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}

function closeChildAnimations(element) {
var node = element[0];
var node = getDomNode(element);
var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
forEach(children, function(child) {
var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
Expand All @@ -457,19 +457,17 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}

function clearElementAnimationState(element) {
element = element.length ? element[0] : element;
element.removeAttribute(NG_ANIMATE_ATTR_NAME);
activeAnimationsLookup.remove(element);
var node = getDomNode(element);
node.removeAttribute(NG_ANIMATE_ATTR_NAME);
activeAnimationsLookup.remove(node);
}

function isMatchingElement(a,b) {
a = a.length ? a[0] : a;
b = b.length ? b[0] : b;
return a === b;
function isMatchingElement(nodeOrElmA, nodeOrElmB) {
return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
}

function closeParentClassBasedAnimations(startingElement) {
var parentNode = startingElement[0];
var parentNode = getDomNode(startingElement);
do {
if (!parentNode || parentNode.nodeType !== ELEMENT_NODE) break;

Expand All @@ -495,25 +493,25 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
}

function areAnimationsAllowed(element, parent, event) {
function areAnimationsAllowed(element, parentElement, event) {
var bodyElementDetected = false;
var rootElementDetected = false;
var parentAnimationDetected = false;
var animateChildren;

var parentHost = element.data(NG_ANIMATE_PIN_DATA);
if (parentHost) {
parent = parentHost;
parentElement = parentHost;
}

while (parent && parent.length) {
while (parentElement && parentElement.length) {
if (!rootElementDetected) {
// angular doesn't want to attempt to animate elements outside of the application
// therefore we need to ensure that the rootElement is an ancestor of the current element
rootElementDetected = isMatchingElement(parent, $rootElement);
rootElementDetected = isMatchingElement(parentElement, $rootElement);
}

var parentNode = parent[0];
var parentNode = parentElement[0];
if (parentNode.nodeType !== ELEMENT_NODE) {
// no point in inspecting the #document element
break;
Expand All @@ -528,7 +526,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}

if (isUndefined(animateChildren) || animateChildren === true) {
var value = parent.data(NG_ANIMATE_CHILDREN_DATA);
var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);
if (isDefined(value)) {
animateChildren = value;
}
Expand All @@ -540,22 +538,22 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
if (!rootElementDetected) {
// angular doesn't want to attempt to animate elements outside of the application
// therefore we need to ensure that the rootElement is an ancestor of the current element
rootElementDetected = isMatchingElement(parent, $rootElement);
rootElementDetected = isMatchingElement(parentElement, $rootElement);
if (!rootElementDetected) {
parentHost = parent.data(NG_ANIMATE_PIN_DATA);
parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
if (parentHost) {
parent = parentHost;
parentElement = parentHost;
}
}
}

if (!bodyElementDetected) {
// we also need to ensure that the element is or will be apart of the body element
// otherwise it is pointless to even issue an animation to be rendered
bodyElementDetected = isMatchingElement(parent, bodyElement);
bodyElementDetected = isMatchingElement(parentElement, bodyElement);
}

parent = parent.parent();
parentElement = parentElement.parent();
}

var allowAnimation = !parentAnimationDetected || animateChildren;
Expand All @@ -566,14 +564,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
details = details || {};
details.state = state;

element = element.length ? element[0] : element;
element.setAttribute(NG_ANIMATE_ATTR_NAME, state);
var node = getDomNode(element);
node.setAttribute(NG_ANIMATE_ATTR_NAME, state);

var oldValue = activeAnimationsLookup.get(element);
var oldValue = activeAnimationsLookup.get(node);
var newValue = oldValue
? extend(oldValue, details)
: details;
activeAnimationsLookup.put(element, newValue);
activeAnimationsLookup.put(node, newValue);
}
}];
}];
2 changes: 1 addition & 1 deletion src/ngAnimate/animation.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
var refLookup = {};
forEach(animations, function(animation, index) {
var element = animation.element;
var node = element[0];
var node = getDomNode(element);
var event = animation.event;
var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;
var anchorNodes = animation.structural ? getAnchorNodes(node) : [];
Expand Down
4 changes: 4 additions & 0 deletions src/ngAnimate/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,7 @@ function resolveElementClasses(existing, toAdd, toRemove) {

return classes;
}

function getDomNode(element) {
return (element instanceof angular.element) ? element[0] : element;
}
26 changes: 26 additions & 0 deletions test/ngAnimate/animateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,32 @@ describe("animations", function() {
expect(itsOver).toBe(true);
}));

it('should immediately end a parent class-based form animation if a structural child is active',
inject(function($rootScope, $animate, $rootElement, $$rAF, $$AnimateRunner) {

parent.remove();
element.remove();

parent = jqLite('<form></form>');
$rootElement.append(parent);

element = jqLite('<input type="text" name="myInput" />');

$animate.addClass(parent, 'abc');
$rootScope.$digest();

// we do this since the old runner was already closed
overriddenAnimationRunner = new $$AnimateRunner();

$animate.enter(element, parent);
$rootScope.$digest();

$$rAF.flush();

expect(parent.attr('data-ng-animate')).toBeFalsy();
expect(element.attr('data-ng-animate')).toBeTruthy();
}));

it('should not end a pre-digest parent animation if it does not have any classes to add/remove',
inject(function($rootScope, $animate, $$rAF) {

Expand Down