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

Commit 4eb6dba

Browse files
committed
feat($animate): add support for customFilter
This commit adds a new `customFilter()` function on `$animateProvider` (similar to `classNameFilter()`), which can be used to filter animations (i.e. decide whether thay are allowed or not), based on the return value of a custom filter function. This allows to easily create arbitrarily complex rules filtering animations, such as allowing specific events only, or enabling animations on specific subtrees of the DOM. Fixes #14891
1 parent aea62af commit 4eb6dba

File tree

3 files changed

+150
-1
lines changed

3 files changed

+150
-1
lines changed

src/ng/animate.js

+25
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ var $$CoreAnimateQueueProvider = function() {
169169
*/
170170
var $AnimateProvider = ['$provide', function($provide) {
171171
var provider = this;
172+
var customFilter = null;
172173

173174
this.$$registeredAnimations = Object.create(null);
174175

@@ -249,6 +250,30 @@ var $AnimateProvider = ['$provide', function($provide) {
249250
return this.$$classNameFilter;
250251
};
251252

253+
/**
254+
* @ngdoc method
255+
* @name $animateProvider#customFilter
256+
*
257+
* @description
258+
* TODO(gkalpak)
259+
*
260+
* @param {Function=} filterFn - The filter function which will be used to filter all animations.
261+
* If a falsy value is returned, no animation will be performed. The function will be called
262+
* with the followijg arguments:
263+
* - **node** `{DOMElement}` - The DOM element to be animated.
264+
* - **event** `{String}` - The name of the animation event (e.g. `enter`, `leave`, `addClass`
265+
* etc).
266+
* - **options** `{Object}` - An collection of options/styles used for the animation.
267+
* @return {Function} The current filter function or `null` if there is none set.
268+
*/
269+
this.customFilter = function(filterFn) {
270+
if (arguments.length === 1) {
271+
customFilter = isFunction(filterFn) ? filterFn : null;
272+
}
273+
274+
return customFilter;
275+
};
276+
252277
this.$get = ['$$animateQueue', function($$animateQueue) {
253278
function domInsert(element, parentElement, afterElement) {
254279
// if for some reason the previous element was removed

src/ngAnimate/animateQueue.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
168168
: function(className) {
169169
return classNameFilter.test(className);
170170
};
171+
var isAnimatableByFilter = $animateProvider.customFilter() || function() { return true; };
171172

172173
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
173174

@@ -361,7 +362,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
361362
}
362363

363364
var className = [node.getAttribute('class'), options.addClass, options.removeClass].join(' ');
364-
if (!isAnimatableClassName(className)) {
365+
if (!isAnimatableClassName(className) || !isAnimatableByFilter(node, event, initialOptions)) {
365366
close();
366367
return runner;
367368
}

test/ngAnimate/animateSpec.js

+123
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,129 @@ describe("animations", function() {
292292
});
293293
});
294294

295+
describe('customFilter()', function() {
296+
it('should clear the `customFilter` if no function is passed',
297+
module(function($animateProvider) {
298+
expect($animateProvider.customFilter()).toBeNull();
299+
300+
$animateProvider.customFilter(angular.noop);
301+
expect($animateProvider.customFilter()).toEqual(jasmine.any(Function));
302+
303+
$animateProvider.customFilter({});
304+
expect($animateProvider.customFilter()).toBeNull();
305+
})
306+
);
307+
308+
it('should animate only elements that pass through `$animateProvider.customFilter()`',
309+
function() {
310+
var animationsAllowed = false;
311+
312+
module(function($animateProvider) {
313+
$animateProvider.customFilter(function() { return animationsAllowed; });
314+
});
315+
316+
inject(function($animate, $rootScope) {
317+
$animate.enter(element, parent);
318+
$rootScope.$digest();
319+
expect(capturedAnimation).toBeFalsy();
320+
321+
$animate.leave(element, parent);
322+
$rootScope.$digest();
323+
expect(capturedAnimation).toBeFalsy();
324+
325+
animationsAllowed = true;
326+
327+
$animate.enter(element, parent);
328+
$rootScope.$digest();
329+
expect(capturedAnimation).toBeTruthy();
330+
331+
capturedAnimation = null;
332+
333+
$animate.leave(element, parent);
334+
$rootScope.$digest();
335+
expect(capturedAnimation).toBeTruthy();
336+
});
337+
}
338+
);
339+
340+
it('should animate only elements that pass through `$animateProvider.customFilter()`',
341+
function() {
342+
var animationsAllowed = false;
343+
344+
module(function($animateProvider) {
345+
$animateProvider.customFilter(function() { return animationsAllowed; });
346+
});
347+
348+
inject(function($animate, $compile, $rootScope) {
349+
var svgElement = $compile('<svg class="element"></svg>')($rootScope);
350+
351+
$animate.enter(svgElement, parent);
352+
$rootScope.$digest();
353+
expect(capturedAnimation).toBeFalsy();
354+
355+
$animate.leave(svgElement, parent);
356+
$rootScope.$digest();
357+
expect(capturedAnimation).toBeFalsy();
358+
359+
animationsAllowed = true;
360+
361+
$animate.enter(svgElement, parent);
362+
$rootScope.$digest();
363+
expect(capturedAnimation).toBeTruthy();
364+
365+
capturedAnimation = null;
366+
367+
$animate.leave(svgElement, parent);
368+
$rootScope.$digest();
369+
expect(capturedAnimation).toBeTruthy();
370+
});
371+
}
372+
);
373+
374+
it('should pass the DOM element, event name and options to the filter function', function() {
375+
var filterFn = jasmine.createSpy('filterFn');
376+
var options = {};
377+
378+
module(function($animateProvider) {
379+
$animateProvider.customFilter(filterFn);
380+
});
381+
382+
inject(function($animate, $rootScope) {
383+
$animate.enter(element, parent, null, options);
384+
expect(filterFn).toHaveBeenCalledOnceWith(element[0], 'enter', options);
385+
386+
filterFn.calls.reset();
387+
388+
$animate.leave(element);
389+
expect(filterFn).toHaveBeenCalledOnceWith(element[0], 'leave', jasmine.any(Object));
390+
});
391+
});
392+
393+
it('should complete the DOM operation even if filtered out', function() {
394+
var filterFn = jasmine.createSpy('filterFn').and.returnValue(false);
395+
396+
module(function($animateProvider) {
397+
$animateProvider.customFilter(filterFn);
398+
});
399+
400+
inject(function($animate, $rootScope) {
401+
expect(element.parent()[0]).toBeUndefined();
402+
403+
$animate.enter(element, parent);
404+
$rootScope.$digest();
405+
406+
expect(capturedAnimation).toBeFalsy();
407+
expect(element.parent()[0]).toBe(parent[0]);
408+
409+
$animate.leave(element);
410+
$rootScope.$digest();
411+
412+
expect(capturedAnimation).toBeFalsy();
413+
expect(element.parent()[0]).toBeUndefined();
414+
});
415+
});
416+
});
417+
295418
describe('enabled()', function() {
296419
it("should work for all animations", inject(function($animate) {
297420

0 commit comments

Comments
 (0)