Skip to content

Commit 8ef1b59

Browse files
matskobullgare
authored andcommitted
chore($animate): enable temporary classes to be applied during an animation
Closes angular#9493
1 parent 131b164 commit 8ef1b59

File tree

3 files changed

+119
-15
lines changed

3 files changed

+119
-15
lines changed

src/ngAnimate/animate.js

+46-15
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ angular.module('ngAnimate', ['ng'])
377377
var forEach = angular.forEach;
378378
var selectors = $animateProvider.$$selectors;
379379
var isArray = angular.isArray;
380+
var isString = angular.isString;
380381

381382
var ELEMENT_NODE = 1;
382383
var NG_ANIMATE_STATE = '$$ngAnimateState';
@@ -467,6 +468,14 @@ angular.module('ngAnimate', ['ng'])
467468
return defer.promise;
468469
}
469470

471+
function parseAnimateOptions(options) {
472+
// some plugin code may still be passing in the callback
473+
// function as the last param for the $animate methods so
474+
// it's best to only allow string or array values for now
475+
if (isArray(options)) return options;
476+
if (isString(options)) return [options];
477+
}
478+
470479
function resolveElementClasses(element, cache, runningAnimations) {
471480
runningAnimations = runningAnimations || {};
472481

@@ -784,15 +793,16 @@ angular.module('ngAnimate', ['ng'])
784793
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
785794
* @return {Promise} the animation callback promise
786795
*/
787-
enter : function(element, parentElement, afterElement) {
796+
enter : function(element, parentElement, afterElement, options) {
797+
options = parseAnimateOptions(options);
788798
element = angular.element(element);
789799
parentElement = prepareElement(parentElement);
790800
afterElement = prepareElement(afterElement);
791801

792802
classBasedAnimationsBlocked(element, true);
793803
$delegate.enter(element, parentElement, afterElement);
794804
return runAnimationPostDigest(function(done) {
795-
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
805+
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
796806
});
797807
},
798808

@@ -826,15 +836,16 @@ angular.module('ngAnimate', ['ng'])
826836
* @param {DOMElement} element the element that will be the focus of the leave animation
827837
* @return {Promise} the animation callback promise
828838
*/
829-
leave : function(element) {
839+
leave : function(element, options) {
840+
options = parseAnimateOptions(options);
830841
element = angular.element(element);
831842

832843
cancelChildAnimations(element);
833844
classBasedAnimationsBlocked(element, true);
834845
return runAnimationPostDigest(function(done) {
835846
return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
836847
$delegate.leave(element);
837-
}, done);
848+
}, options, done);
838849
});
839850
},
840851

@@ -871,7 +882,8 @@ angular.module('ngAnimate', ['ng'])
871882
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
872883
* @return {Promise} the animation callback promise
873884
*/
874-
move : function(element, parentElement, afterElement) {
885+
move : function(element, parentElement, afterElement, options) {
886+
options = parseAnimateOptions(options);
875887
element = angular.element(element);
876888
parentElement = prepareElement(parentElement);
877889
afterElement = prepareElement(afterElement);
@@ -880,7 +892,7 @@ angular.module('ngAnimate', ['ng'])
880892
classBasedAnimationsBlocked(element, true);
881893
$delegate.move(element, parentElement, afterElement);
882894
return runAnimationPostDigest(function(done) {
883-
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
895+
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
884896
});
885897
},
886898

@@ -913,8 +925,8 @@ angular.module('ngAnimate', ['ng'])
913925
* @param {string} className the CSS class that will be added to the element and then animated
914926
* @return {Promise} the animation callback promise
915927
*/
916-
addClass : function(element, className) {
917-
return this.setClass(element, className, []);
928+
addClass : function(element, className, options) {
929+
return this.setClass(element, className, [], options);
918930
},
919931

920932
/**
@@ -946,8 +958,8 @@ angular.module('ngAnimate', ['ng'])
946958
* @param {string} className the CSS class that will be animated and then removed from the element
947959
* @return {Promise} the animation callback promise
948960
*/
949-
removeClass : function(element, className) {
950-
return this.setClass(element, [], className);
961+
removeClass : function(element, className, options) {
962+
return this.setClass(element, [], className, options);
951963
},
952964

953965
/**
@@ -977,7 +989,9 @@ angular.module('ngAnimate', ['ng'])
977989
* CSS classes have been set on the element
978990
* @return {Promise} the animation callback promise
979991
*/
980-
setClass : function(element, add, remove) {
992+
setClass : function(element, add, remove, options) {
993+
options = parseAnimateOptions(options);
994+
981995
var STORAGE_KEY = '$$animateClasses';
982996
element = angular.element(element);
983997
element = stripCommentsFromElement(element);
@@ -1014,11 +1028,16 @@ angular.module('ngAnimate', ['ng'])
10141028
});
10151029

10161030
if (hasCache) {
1031+
if (options && cache.options) {
1032+
cache.options = cache.options.concat(options);
1033+
}
1034+
10171035
//the digest cycle will combine all the animations into one function
10181036
return cache.promise;
10191037
} else {
10201038
element.data(STORAGE_KEY, cache = {
1021-
classes : classes
1039+
classes : classes,
1040+
options : options
10221041
});
10231042
}
10241043

@@ -1042,7 +1061,7 @@ angular.module('ngAnimate', ['ng'])
10421061
: performAnimation('setClass', classes, element, parentElement, null, function() {
10431062
if (classes[0]) $delegate.$$addClassImmediately(element, classes[0]);
10441063
if (classes[1]) $delegate.$$removeClassImmediately(element, classes[1]);
1045-
}, done);
1064+
}, cache.options, done);
10461065
});
10471066
},
10481067

@@ -1104,7 +1123,7 @@ angular.module('ngAnimate', ['ng'])
11041123
CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
11051124
and the onComplete callback will be fired once the animation is fully complete.
11061125
*/
1107-
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
1126+
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) {
11081127

11091128
var noopCancel = noop;
11101129
var runner = animationRunner(element, animationEvent, className);
@@ -1212,6 +1231,11 @@ angular.module('ngAnimate', ['ng'])
12121231
//the ng-animate class does nothing, but it's here to allow for
12131232
//parent animations to find and cancel child animations when needed
12141233
element.addClass(NG_ANIMATE_CLASS_NAME);
1234+
if (isArray(options)) {
1235+
forEach(options, function(className) {
1236+
element.addClass(className);
1237+
});
1238+
}
12151239

12161240
var localAnimationCount = globalAnimationCounter++;
12171241
totalActiveAnimations++;
@@ -1281,8 +1305,15 @@ angular.module('ngAnimate', ['ng'])
12811305
function closeAnimation() {
12821306
if (!closeAnimation.hasBeenRun) {
12831307
closeAnimation.hasBeenRun = true;
1308+
if (isArray(options)) {
1309+
forEach(options, function(className) {
1310+
element.removeClass(className);
1311+
});
1312+
}
1313+
12841314
var data = element.data(NG_ANIMATE_STATE);
12851315
if (data) {
1316+
12861317
/* only structural animations wait for reflow before removing an
12871318
animation, but class-based animations don't. An example of this
12881319
failing would be when a parent HTML tag has a ng-class attribute
@@ -1547,7 +1578,7 @@ angular.module('ngAnimate', ['ng'])
15471578

15481579
function parseMaxTime(str) {
15491580
var maxValue = 0;
1550-
var values = angular.isString(str) ?
1581+
var values = isString(str) ?
15511582
str.split(/\s*,\s*/) :
15521583
[];
15531584
forEach(values, function(value) {

src/ngMock/angular-mocks.js

+1
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
809809
animate.queue.push({
810810
event : method,
811811
element : arguments[0],
812+
options : arguments[arguments.length-1],
812813
args : arguments
813814
});
814815
return $delegate[method].apply($delegate, arguments);

test/ngAnimate/animateSpec.js

+72
Original file line numberDiff line numberDiff line change
@@ -2456,6 +2456,78 @@ describe("ngAnimate", function() {
24562456
}));
24572457
});
24582458

2459+
describe("options", function() {
2460+
2461+
it('should add and remove the temporary className value is provided', function() {
2462+
var captures = {};
2463+
module(function($animateProvider) {
2464+
$animateProvider.register('.capture', function() {
2465+
return {
2466+
enter : capture('enter'),
2467+
leave : capture('leave'),
2468+
move : capture('move'),
2469+
addClass : capture('addClass'),
2470+
removeClass : capture('removeClass'),
2471+
setClass : capture('setClass')
2472+
};
2473+
2474+
function capture(event) {
2475+
return function(element, add, remove, done) {
2476+
//some animations only have one extra param
2477+
done = done || remove || add;
2478+
captures[event]=done;
2479+
};
2480+
}
2481+
});
2482+
});
2483+
inject(function($animate, $rootScope, $compile, $rootElement, $document) {
2484+
var container = jqLite('<div class="container"></div>');
2485+
var container2 = jqLite('<div class="container2"></div>');
2486+
var element = jqLite('<div class="capture"></div>');
2487+
$rootElement.append(container);
2488+
$rootElement.append(container2);
2489+
angular.element($document[0].body).append($rootElement);
2490+
2491+
$compile(element)($rootScope);
2492+
2493+
assertTempClass('enter', 'temp-enter', function() {
2494+
$animate.enter(element, container, null, 'temp-enter');
2495+
});
2496+
2497+
assertTempClass('move', 'temp-move', function() {
2498+
$animate.move(element, null, container2, 'temp-move');
2499+
});
2500+
2501+
assertTempClass('addClass', 'temp-add', function() {
2502+
$animate.addClass(element, 'add', 'temp-add');
2503+
});
2504+
2505+
assertTempClass('removeClass', 'temp-remove', function() {
2506+
$animate.removeClass(element, 'add', 'temp-remove');
2507+
});
2508+
2509+
element.addClass('remove');
2510+
assertTempClass('setClass', 'temp-set', function() {
2511+
$animate.setClass(element, 'add', 'remove', 'temp-set');
2512+
});
2513+
2514+
assertTempClass('leave', 'temp-leave', function() {
2515+
$animate.leave(element, 'temp-leave');
2516+
});
2517+
2518+
function assertTempClass(event, className, animationOperation) {
2519+
expect(element).not.toHaveClass(className);
2520+
animationOperation();
2521+
$rootScope.$digest();
2522+
expect(element).toHaveClass(className);
2523+
$animate.triggerReflow();
2524+
captures[event]();
2525+
$animate.triggerCallbacks();
2526+
expect(element).not.toHaveClass(className);
2527+
}
2528+
});
2529+
});
2530+
});
24592531

24602532
describe("addClass / removeClass", function() {
24612533

0 commit comments

Comments
 (0)