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

feat($animate): allow directives to cancel animation events #7722

Closed
wants to merge 1 commit into from
Closed
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
13 changes: 9 additions & 4 deletions src/ng/animate.js
Original file line number Diff line number Diff line change
@@ -127,6 +127,7 @@ var $AnimateProvider = ['$provide', function($provide) {
? after.after(element)
: parent.prepend(element);
async(done);
return noop;
},

/**
@@ -143,6 +144,7 @@ var $AnimateProvider = ['$provide', function($provide) {
leave : function(element, done) {
element.remove();
async(done);
return noop;
},

/**
@@ -166,7 +168,7 @@ var $AnimateProvider = ['$provide', function($provide) {
move : function(element, parent, after, done) {
// Do not remove element before insert. Removing will cause data associated with the
// element to be dropped. Insert will implicitly do the remove.
this.enter(element, parent, after, done);
return this.enter(element, parent, after, done);
},

/**
@@ -183,13 +185,14 @@ var $AnimateProvider = ['$provide', function($provide) {
* className value has been added to the element
*/
addClass : function(element, className, done) {
className = isString(className) ?
className :
isArray(className) ? className.join(' ') : '';
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making it more consistent with the way that we code ternary statements.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the ? at the end 🍤

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matsko ok. I was just surprised that you negated it and swapped the branches.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to have the shorter line at the bottom :P

forEach(element, function (element) {
jqLiteAddClass(element, className);
});
async(done);
return noop;
},

/**
@@ -213,6 +216,7 @@ var $AnimateProvider = ['$provide', function($provide) {
jqLiteRemoveClass(element, className);
});
async(done);
return noop;
},

/**
@@ -235,6 +239,7 @@ var $AnimateProvider = ['$provide', function($provide) {
jqLiteRemoveClass(element, remove);
});
async(done);
return noop;
},

enabled : noop
49 changes: 34 additions & 15 deletions src/ngAnimate/animate.js
Original file line number Diff line number Diff line change
@@ -425,6 +425,16 @@ angular.module('ngAnimate', ['ng'])
element.data(NG_ANIMATE_STATE, data);
}

function runAnimationPostDigest(fn) {
var cancelFn;
$rootScope.$$postDigest(function() {
cancelFn = fn();
});
return function() {
cancelFn && cancelFn();
};
}

function lookup(name) {
if (name) {
var matches = [],
@@ -648,6 +658,7 @@ angular.module('ngAnimate', ['ng'])
* @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
* @return {function} the animation cancellation function
*/
enter : function(element, parentElement, afterElement, doneCallback) {
element = angular.element(element);
@@ -656,9 +667,8 @@ angular.module('ngAnimate', ['ng'])

blockElementAnimations(element);
$delegate.enter(element, parentElement, afterElement);
$rootScope.$$postDigest(function() {
element = stripCommentsFromElement(element);
performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
return runAnimationPostDigest(function() {
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, doneCallback);
});
},

@@ -691,13 +701,16 @@ angular.module('ngAnimate', ['ng'])
*
* @param {DOMElement} element the element that will be the focus of the leave animation
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
* @return {function} the animation cancellation function
*/
leave : function(element, doneCallback) {
element = angular.element(element);

cancelChildAnimations(element);
blockElementAnimations(element);
$rootScope.$$postDigest(function() {
performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
this.enabled(false, element);
return runAnimationPostDigest(function() {
return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
$delegate.leave(element);
}, doneCallback);
});
@@ -735,6 +748,7 @@ angular.module('ngAnimate', ['ng'])
* @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
* @return {function} the animation cancellation function
*/
move : function(element, parentElement, afterElement, doneCallback) {
element = angular.element(element);
@@ -744,9 +758,8 @@ angular.module('ngAnimate', ['ng'])
cancelChildAnimations(element);
blockElementAnimations(element);
$delegate.move(element, parentElement, afterElement);
$rootScope.$$postDigest(function() {
element = stripCommentsFromElement(element);
performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
return runAnimationPostDigest(function() {
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, doneCallback);
});
},

@@ -778,11 +791,12 @@ angular.module('ngAnimate', ['ng'])
* @param {DOMElement} element the element that will be animated
* @param {string} className the CSS class that will be added to the element and then animated
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
* @return {function} the animation cancellation function
*/
addClass : function(element, className, doneCallback) {
element = angular.element(element);
element = stripCommentsFromElement(element);
performAnimation('addClass', className, element, null, null, function() {
return performAnimation('addClass', className, element, null, null, function() {
$delegate.addClass(element, className);
}, doneCallback);
},
@@ -815,11 +829,12 @@ angular.module('ngAnimate', ['ng'])
* @param {DOMElement} element the element that will be animated
* @param {string} className the CSS class that will be animated and then removed from the element
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
* @return {function} the animation cancellation function
*/
removeClass : function(element, className, doneCallback) {
element = angular.element(element);
element = stripCommentsFromElement(element);
performAnimation('removeClass', className, element, null, null, function() {
return performAnimation('removeClass', className, element, null, null, function() {
$delegate.removeClass(element, className);
}, doneCallback);
},
@@ -848,13 +863,14 @@ angular.module('ngAnimate', ['ng'])
* removed from it
* @param {string} add the CSS classes which will be added to the element
* @param {string} remove the CSS class which will be removed from the element
* @param {Function=} done the callback function (if provided) that will be fired after the
* @param {function=} done the callback function (if provided) that will be fired after the
* CSS classes have been set on the element
* @return {function} the animation cancellation function
*/
setClass : function(element, add, remove, doneCallback) {
element = angular.element(element);
element = stripCommentsFromElement(element);
performAnimation('setClass', [add, remove], element, null, null, function() {
return performAnimation('setClass', [add, remove], element, null, null, function() {
$delegate.setClass(element, add, remove);
}, doneCallback);
},
@@ -905,13 +921,14 @@ angular.module('ngAnimate', ['ng'])
*/
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {

var noopCancel = noop;
var runner = animationRunner(element, animationEvent, className);
if(!runner) {
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
closeAnimation();
return;
return noopCancel;
}

className = runner.className;
@@ -945,7 +962,7 @@ angular.module('ngAnimate', ['ng'])
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
closeAnimation();
return;
return noopCancel;
}

var skipAnimation = false;
@@ -993,7 +1010,7 @@ angular.module('ngAnimate', ['ng'])
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
fireDoneCallbackAsync();
return;
return noopCancel;
}

if(animationEvent == 'leave') {
@@ -1046,6 +1063,8 @@ angular.module('ngAnimate', ['ng'])
}
});

return runner.cancel;

function fireDOMCallback(animationPhase) {
var eventName = '$animate:' + animationPhase;
if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
2 changes: 1 addition & 1 deletion src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
@@ -791,7 +791,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
element : arguments[0],
args : arguments
});
$delegate[method].apply($delegate, arguments);
return $delegate[method].apply($delegate, arguments);
};
});

14 changes: 14 additions & 0 deletions test/ng/animateSpec.js
Original file line number Diff line number Diff line change
@@ -57,6 +57,20 @@ describe("$animate", function() {
expect(element).toBeHidden();
}));

it("should run each method and return a noop function", inject(function($animate, $document) {
var element = jqLite('<div></div>');
var move = jqLite('<div></div>');
var parent = jqLite($document[0].body);
parent.append(move);

expect($animate.enter(element, parent)).toBe(noop);
expect($animate.move(element, move)).toBe(noop);
expect($animate.addClass(element, 'on')).toBe(noop);
expect($animate.addClass(element, 'off')).toBe(noop);
expect($animate.setClass(element, 'on', 'off')).toBe(noop);
expect($animate.leave(element)).toBe(noop);
}));

it("should add and remove classes on SVG elements", inject(function($animate) {
if (!window.SVGElement) return;
var svg = jqLite('<svg><rect></rect></svg>');
93 changes: 93 additions & 0 deletions test/ngAnimate/animateSpec.js
Original file line number Diff line number Diff line change
@@ -590,6 +590,99 @@ describe("ngAnimate", function() {
}));


it("should trigger a cancellation when the return function is called upon any animation", function() {
var captures = {};

module(function($animateProvider) {
$animateProvider.register('.track-me', function() {
return {
enter : track('enter'),
leave : track('leave'),
move : track('move'),
addClass : track('addClass'),
removeClass : track('removeClass'),
setClass : track('setClass')
};

function track(type) {
return function(element, add, remove, done) {
done = done || remove || add;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the purpose of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setClass = 4 params (done is the last one)
addClass / removeClass = 3 params (done is the one)
enter / leave / move = 2 params (done is the last one)

So it's basically finding the done function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok :)

return function(cancelled) {
captures[type]=cancelled;
};
};
}
});
});
inject(function($animate, $sniffer, $rootScope, $timeout) {

var fn;
$animate.enabled(true);
$rootScope.$digest();
element[0].removeChild(child[0]);
child.addClass('track-me');

//enter
fn = $animate.enter(child, element);
$rootScope.$digest();
$animate.triggerReflow();

expect(captures.enter).toBeUndefined();
fn();
expect(captures.enter).toBeTruthy();
$animate.triggerCallbacks();

//move
element.append(after);
fn = $animate.move(child, element, after);
$rootScope.$digest();
$animate.triggerReflow();

expect(captures.move).toBeUndefined();
fn();
expect(captures.move).toBeTruthy();
$animate.triggerCallbacks();

//addClass
fn = $animate.addClass(child, 'ng-hide');
$animate.triggerReflow();

expect(captures.addClass).toBeUndefined();
fn();
expect(captures.addClass).toBeTruthy();
$animate.triggerCallbacks();

//removeClass
fn = $animate.removeClass(child, 'ng-hide');
$animate.triggerReflow();

expect(captures.removeClass).toBeUndefined();
fn();
expect(captures.removeClass).toBeTruthy();
$animate.triggerCallbacks();

//setClass
child.addClass('red');
fn = $animate.setClass(child, 'blue', 'red');
$animate.triggerReflow();

expect(captures.setClass).toBeUndefined();
fn();
expect(captures.setClass).toBeTruthy();
$animate.triggerCallbacks();

//leave
fn = $animate.leave(child);
$rootScope.$digest();

expect(captures.leave).toBeUndefined();
fn();
expect(captures.leave).toBeTruthy();
$animate.triggerCallbacks();
});
});


it("should not run if animations are disabled",
inject(function($animate, $rootScope, $timeout, $sniffer) {