chore($animate): enable temporary classes to be applied during an animation

Closes #9493
This commit is contained in:
Matias Niemelä
2014-10-08 14:29:57 +03:00
committed by Igor Minar
parent d5445c601f
commit 035ffb82c4
3 changed files with 119 additions and 15 deletions

View File

@@ -377,6 +377,7 @@ angular.module('ngAnimate', ['ng'])
var forEach = angular.forEach;
var selectors = $animateProvider.$$selectors;
var isArray = angular.isArray;
var isString = angular.isString;
var ELEMENT_NODE = 1;
var NG_ANIMATE_STATE = '$$ngAnimateState';
@@ -467,6 +468,14 @@ angular.module('ngAnimate', ['ng'])
return defer.promise;
}
function parseAnimateOptions(options) {
// some plugin code may still be passing in the callback
// function as the last param for the $animate methods so
// it's best to only allow string or array values for now
if (isArray(options)) return options;
if (isString(options)) return [options];
}
function resolveElementClasses(element, cache, runningAnimations) {
runningAnimations = runningAnimations || {};
@@ -784,7 +793,8 @@ angular.module('ngAnimate', ['ng'])
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
* @return {Promise} the animation callback promise
*/
enter : function(element, parentElement, afterElement) {
enter : function(element, parentElement, afterElement, options) {
options = parseAnimateOptions(options);
element = angular.element(element);
parentElement = prepareElement(parentElement);
afterElement = prepareElement(afterElement);
@@ -792,7 +802,7 @@ angular.module('ngAnimate', ['ng'])
classBasedAnimationsBlocked(element, true);
$delegate.enter(element, parentElement, afterElement);
return runAnimationPostDigest(function(done) {
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
});
},
@@ -826,7 +836,8 @@ angular.module('ngAnimate', ['ng'])
* @param {DOMElement} element the element that will be the focus of the leave animation
* @return {Promise} the animation callback promise
*/
leave : function(element) {
leave : function(element, options) {
options = parseAnimateOptions(options);
element = angular.element(element);
cancelChildAnimations(element);
@@ -834,7 +845,7 @@ angular.module('ngAnimate', ['ng'])
return runAnimationPostDigest(function(done) {
return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
$delegate.leave(element);
}, done);
}, options, done);
});
},
@@ -871,7 +882,8 @@ angular.module('ngAnimate', ['ng'])
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
* @return {Promise} the animation callback promise
*/
move : function(element, parentElement, afterElement) {
move : function(element, parentElement, afterElement, options) {
options = parseAnimateOptions(options);
element = angular.element(element);
parentElement = prepareElement(parentElement);
afterElement = prepareElement(afterElement);
@@ -880,7 +892,7 @@ angular.module('ngAnimate', ['ng'])
classBasedAnimationsBlocked(element, true);
$delegate.move(element, parentElement, afterElement);
return runAnimationPostDigest(function(done) {
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
});
},
@@ -913,8 +925,8 @@ angular.module('ngAnimate', ['ng'])
* @param {string} className the CSS class that will be added to the element and then animated
* @return {Promise} the animation callback promise
*/
addClass : function(element, className) {
return this.setClass(element, className, []);
addClass : function(element, className, options) {
return this.setClass(element, className, [], options);
},
/**
@@ -946,8 +958,8 @@ angular.module('ngAnimate', ['ng'])
* @param {string} className the CSS class that will be animated and then removed from the element
* @return {Promise} the animation callback promise
*/
removeClass : function(element, className) {
return this.setClass(element, [], className);
removeClass : function(element, className, options) {
return this.setClass(element, [], className, options);
},
/**
@@ -977,7 +989,9 @@ angular.module('ngAnimate', ['ng'])
* CSS classes have been set on the element
* @return {Promise} the animation callback promise
*/
setClass : function(element, add, remove) {
setClass : function(element, add, remove, options) {
options = parseAnimateOptions(options);
var STORAGE_KEY = '$$animateClasses';
element = angular.element(element);
element = stripCommentsFromElement(element);
@@ -1014,11 +1028,16 @@ angular.module('ngAnimate', ['ng'])
});
if (hasCache) {
if (options && cache.options) {
cache.options = cache.options.concat(options);
}
//the digest cycle will combine all the animations into one function
return cache.promise;
} else {
element.data(STORAGE_KEY, cache = {
classes : classes
classes : classes,
options : options
});
}
@@ -1042,7 +1061,7 @@ angular.module('ngAnimate', ['ng'])
: performAnimation('setClass', classes, element, parentElement, null, function() {
if (classes[0]) $delegate.$$addClassImmediately(element, classes[0]);
if (classes[1]) $delegate.$$removeClassImmediately(element, classes[1]);
}, done);
}, cache.options, done);
});
},
@@ -1104,7 +1123,7 @@ angular.module('ngAnimate', ['ng'])
CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
and the onComplete callback will be fired once the animation is fully complete.
*/
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) {
var noopCancel = noop;
var runner = animationRunner(element, animationEvent, className);
@@ -1212,6 +1231,11 @@ angular.module('ngAnimate', ['ng'])
//the ng-animate class does nothing, but it's here to allow for
//parent animations to find and cancel child animations when needed
element.addClass(NG_ANIMATE_CLASS_NAME);
if (isArray(options)) {
forEach(options, function(className) {
element.addClass(className);
});
}
var localAnimationCount = globalAnimationCounter++;
totalActiveAnimations++;
@@ -1281,8 +1305,15 @@ angular.module('ngAnimate', ['ng'])
function closeAnimation() {
if (!closeAnimation.hasBeenRun) {
closeAnimation.hasBeenRun = true;
if (isArray(options)) {
forEach(options, function(className) {
element.removeClass(className);
});
}
var data = element.data(NG_ANIMATE_STATE);
if (data) {
/* only structural animations wait for reflow before removing an
animation, but class-based animations don't. An example of this
failing would be when a parent HTML tag has a ng-class attribute
@@ -1547,7 +1578,7 @@ angular.module('ngAnimate', ['ng'])
function parseMaxTime(str) {
var maxValue = 0;
var values = angular.isString(str) ?
var values = isString(str) ?
str.split(/\s*,\s*/) :
[];
forEach(values, function(value) {

View File

@@ -809,6 +809,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
animate.queue.push({
event : method,
element : arguments[0],
options : arguments[arguments.length-1],
args : arguments
});
return $delegate[method].apply($delegate, arguments);

View File

@@ -2456,6 +2456,78 @@ describe("ngAnimate", function() {
}));
});
describe("options", function() {
it('should add and remove the temporary className value is provided', function() {
var captures = {};
module(function($animateProvider) {
$animateProvider.register('.capture', function() {
return {
enter : capture('enter'),
leave : capture('leave'),
move : capture('move'),
addClass : capture('addClass'),
removeClass : capture('removeClass'),
setClass : capture('setClass')
};
function capture(event) {
return function(element, add, remove, done) {
//some animations only have one extra param
done = done || remove || add;
captures[event]=done;
};
}
});
});
inject(function($animate, $rootScope, $compile, $rootElement, $document) {
var container = jqLite('<div class="container"></div>');
var container2 = jqLite('<div class="container2"></div>');
var element = jqLite('<div class="capture"></div>');
$rootElement.append(container);
$rootElement.append(container2);
angular.element($document[0].body).append($rootElement);
$compile(element)($rootScope);
assertTempClass('enter', 'temp-enter', function() {
$animate.enter(element, container, null, 'temp-enter');
});
assertTempClass('move', 'temp-move', function() {
$animate.move(element, null, container2, 'temp-move');
});
assertTempClass('addClass', 'temp-add', function() {
$animate.addClass(element, 'add', 'temp-add');
});
assertTempClass('removeClass', 'temp-remove', function() {
$animate.removeClass(element, 'add', 'temp-remove');
});
element.addClass('remove');
assertTempClass('setClass', 'temp-set', function() {
$animate.setClass(element, 'add', 'remove', 'temp-set');
});
assertTempClass('leave', 'temp-leave', function() {
$animate.leave(element, 'temp-leave');
});
function assertTempClass(event, className, animationOperation) {
expect(element).not.toHaveClass(className);
animationOperation();
$rootScope.$digest();
expect(element).toHaveClass(className);
$animate.triggerReflow();
captures[event]();
$animate.triggerCallbacks();
expect(element).not.toHaveClass(className);
}
});
});
});
describe("addClass / removeClass", function() {