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

Commit 2021884

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 for filtering animations, such as allowing specific events only, or enabling animations on specific subtrees of the DOM etc. Fixes #14891
1 parent aea62af commit 2021884

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)