feat(ngAnimate): provide configuration support to match specific className values to trigger animations

Closes #5357
Closes #5283
This commit is contained in:
Matias Niemelä 2013-12-16 15:45:57 -05:00
parent 937caab647
commit cef084ade9
3 changed files with 86 additions and 4 deletions

View file

@ -61,6 +61,28 @@ var $AnimateProvider = ['$provide', function($provide) {
$provide.factory(key, factory); $provide.factory(key, factory);
}; };
/**
* @ngdoc function
* @name ng.$animateProvider#classNameFilter
* @methodOf ng.$animateProvider
*
* @description
* Sets and/or returns the CSS class regular expression that is checked when performing
* an animation. Upon bootstrap the classNameFilter value is not set at all and will
* therefore enable $animate to attempt to perform an animation on any element.
* When setting the classNameFilter value, animations will only be performed on elements
* that successfully match the filter expression. This in turn can boost performance
* for low-powered devices as well as applications containing a lot of structural operations.
* @param {RegExp=} expression The className expression which will be checked against all animations
* @return {RegExp} The current CSS className expression value. If null then there is no expression value
*/
this.classNameFilter = function(expression) {
if(arguments.length === 1) {
this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
}
return this.$$classNameFilter;
};
this.$get = ['$timeout', function($timeout) { this.$get = ['$timeout', function($timeout) {
/** /**

View file

@ -288,6 +288,13 @@ angular.module('ngAnimate', ['ng'])
}); });
}); });
var classNameFilter = $animateProvider.classNameFilter();
var isAnimatableClassName = !classNameFilter
? function() { return true; }
: function(className) {
return classNameFilter.test(className);
};
function lookup(name) { function lookup(name) {
if (name) { if (name) {
var matches = [], var matches = [],
@ -569,17 +576,20 @@ angular.module('ngAnimate', ['ng'])
and the onComplete callback will be fired once the animation is fully complete. 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, doneCallback) {
var node = extractElementNode(element); var currentClassName, classes, node = extractElementNode(element);
if(node) {
currentClassName = node.className;
classes = currentClassName + ' ' + className;
}
//transcluded directives may sometimes fire an animation using only comment nodes //transcluded directives may sometimes fire an animation using only comment nodes
//best to catch this early on to prevent any animation operations from occurring //best to catch this early on to prevent any animation operations from occurring
if(!node) { if(!node || !isAnimatableClassName(classes)) {
fireDOMOperation(); fireDOMOperation();
closeAnimation(); closeAnimation();
return; return;
} }
var currentClassName = node.className;
var classes = currentClassName + ' ' + className;
var animationLookup = (' ' + classes).replace(/\s+/g,'.'); var animationLookup = (' ' + classes).replace(/\s+/g,'.');
if (!parentElement) { if (!parentElement) {
parentElement = afterElement ? afterElement.parent() : element.parent(); parentElement = afterElement ? afterElement.parent() : element.parent();

View file

@ -2947,5 +2947,55 @@ describe("ngAnimate", function() {
expect(capturedAnimation).toBe('leave'); expect(capturedAnimation).toBe('leave');
}); });
}); });
it('should animate only the specified CSS className', function() {
var captures = {};
module(function($animateProvider) {
$animateProvider.classNameFilter(/prefixed-animation/);
$animateProvider.register('.capture', function() {
return {
enter : buildFn('enter'),
leave : buildFn('leave')
};
function buildFn(key) {
return function(element, className, done) {
captures[key] = true;
(done || className)();
}
}
});
});
inject(function($rootScope, $compile, $rootElement, $document, $timeout, $templateCache, $sniffer, $animate) {
if(!$sniffer.transitions) return;
var element = $compile('<div class="capture"></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
var enterDone = false;
$animate.enter(element, $rootElement, null, function() {
enterDone = true;
});
$rootScope.$digest();
$timeout.flush();
expect(captures['enter']).toBeUndefined();
expect(enterDone).toBe(true);
element.addClass('prefixed-animation');
var leaveDone = false;
$animate.leave(element, function() {
leaveDone = true;
});
$rootScope.$digest();
$timeout.flush();
expect(captures['leave']).toBe(true);
expect(leaveDone).toBe(true);
});
});
}); });
}); });