'use strict'; // NOTE: this is a pseudo directive. /** * @ngdoc directive * @name ng.directive:ngAnimate * * @description * The `ngAnimate` directive works as an attribute that is attached alongside pre-existing directives. * It effects how the directive will perform DOM manipulation. This allows for complex animations to take place * without burdening the directive which uses the animation with animation details. The built in directives * `ngRepeat`, `ngInclude`, `ngSwitch`, `ngShow`, `ngHide` and `ngView` already accept `ngAnimate` directive. * Custom directives can take advantage of animation through {@link ng.$animator $animator service}. * * Below is a more detailed breakdown of the supported callback events provided by pre-exisitng ng directives: * * | Directive | Supported Animations | * |========================================================== |====================================================| * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move | * | {@link ng.directive:ngView#animations ngView} | enter and leave | * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave | * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave | * | {@link ng.directive:ngIf#animations ngIf} | enter and leave | * | {@link ng.directive:ngShow#animations ngShow & ngHide} | show and hide | * * You can find out more information about animations upon visiting each directive page. * * Below is an example of a directive that makes use of the ngAnimate attribute: * *
* ** * The `event1` and `event2` attributes refer to the animation events specific to the directive that has been assigned. * * Keep in mind that if an animation is running, no child element of such animation can also be animated. * ** * * * * * * * *
* * * ** * The following code below demonstrates how to perform animations using **CSS animations** with ngAnimate: * *
* * * ** * ngAnimate will first examine any CSS animation code and then fallback to using CSS transitions. * * Upon DOM mutation, the event class is added first, then the browser is allowed to reflow the content and then, * the active class is added to trigger the animation. The ngAnimate directive will automatically extract the duration * of the animation to determine when the animation ends. Once the animation is over then both CSS classes will be * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end * immediately resulting in a DOM element that is at it's final state. This final state is when the DOM element * has no CSS transition/animation classes surrounding it. * *
* var ngModule = angular.module('YourApp', []);
* ngModule.animation('animate-enter', function() {
* return {
* setup : function(element) {
* //prepare the element for animation
* element.css({ 'opacity': 0 });
* var memo = "..."; //this value is passed to the start function
* return memo;
* },
* start : function(element, done, memo) {
* //start the animation
* element.animate({
* 'opacity' : 1
* }, function() {
* //call when the animation is complete
* done()
* });
* }
* }
* });
*
*
* As you can see, the JavaScript code follows a similar template to the CSS3 animations. Once defined, the animation
* can be used in the same way with the ngAnimate attribute. Keep in mind that, when using JavaScript-enabled
* animations, ngAnimate will also add in the same CSS classes that CSS-enabled animations do (even if you're not using
* CSS animations) to animated the element, but it will not attempt to find any CSS3 transition or animation duration/delay values.
* It will instead close off the animation once the provided done function is executed. So it's important that you
* make sure your animations remember to fire off the done function once the animations are complete.
*
* @param {expression} ngAnimate Used to configure the DOM manipulation animations.
*
*/
var $AnimatorProvider = function() {
var NG_ANIMATE_CONTROLLER = '$ngAnimateController';
var rootAnimateController = {running:true};
this.$get = ['$animation', '$window', '$sniffer', '$rootElement', '$rootScope',
function($animation, $window, $sniffer, $rootElement, $rootScope) {
$rootElement.data(NG_ANIMATE_CONTROLLER, rootAnimateController);
/**
* @ngdoc function
* @name ng.$animator
* @function
*
* @description
* The $animator.create service provides the DOM manipulation API which is decorated with animations.
*
* @param {Scope} scope the scope for the ng-animate.
* @param {Attributes} attr the attributes object which contains the ngAnimate key / value pair. (The attributes are
* passed into the linking function of the directive using the `$animator`.)
* @return {object} the animator object which contains the enter, leave, move, show, hide and animate methods.
*/
var AnimatorService = function(scope, attrs) {
var animator = {};
/**
* @ngdoc function
* @name ng.animator#enter
* @methodOf ng.$animator
* @function
*
* @description
* Injects the element object into the DOM (inside of the parent element) and then runs the enter animation.
*
* @param {jQuery/jqLite element} element the element that will be the focus of the enter animation
* @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the enter animation
* @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation
*/
animator.enter = animateActionFactory('enter', insert, noop);
/**
* @ngdoc function
* @name ng.animator#leave
* @methodOf ng.$animator
* @function
*
* @description
* Runs the leave animation operation and, upon completion, removes the element from the DOM.
*
* @param {jQuery/jqLite element} element the element that will be the focus of the leave animation
* @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the leave animation
*/
animator.leave = animateActionFactory('leave', noop, remove);
/**
* @ngdoc function
* @name ng.animator#move
* @methodOf ng.$animator
* @function
*
* @description
* Fires the move DOM operation. Just before the animation starts, the animator will either append it into the parent container or
* add the element directly after the after element if present. Then the move animation will be run.
*
* @param {jQuery/jqLite element} element the element that will be the focus of the move animation
* @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the move animation
* @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation
*/
animator.move = animateActionFactory('move', move, noop);
/**
* @ngdoc function
* @name ng.animator#show
* @methodOf ng.$animator
* @function
*
* @description
* Reveals the element by setting the CSS property `display` to `block` and then starts the show animation directly after.
*
* @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
*/
animator.show = animateActionFactory('show', show, noop);
/**
* @ngdoc function
* @name ng.animator#hide
* @methodOf ng.$animator
*
* @description
* Starts the hide animation first and sets the CSS `display` property to `none` upon completion.
*
* @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
*/
animator.hide = animateActionFactory('hide', noop, hide);
/**
* @ngdoc function
* @name ng.animator#animate
* @methodOf ng.$animator
*
* @description
* Triggers a custom animation event to be executed on the given element
*
* @param {string} event the name of the custom event
* @param {jQuery/jqLite element} element the element that will be animated
*/
animator.animate = function(event, element) {
animateActionFactory(event, noop, noop)(element);
}
return animator;
function animateActionFactory(type, beforeFn, afterFn) {
return function(element, parent, after) {
var ngAnimateValue = scope.$eval(attrs.ngAnimate);
var className = ngAnimateValue
? isObject(ngAnimateValue) ? ngAnimateValue[type] : ngAnimateValue + '-' + type
: '';
var animationPolyfill = $animation(className);
var polyfillSetup = animationPolyfill && animationPolyfill.setup;
var polyfillStart = animationPolyfill && animationPolyfill.start;
var polyfillCancel = animationPolyfill && animationPolyfill.cancel;
if (!className) {
beforeFn(element, parent, after);
afterFn(element, parent, after);
} else {
var activeClassName = className + '-active';
if (!parent) {
parent = after ? after.parent() : element.parent();
}
if ((!$sniffer.transitions && !polyfillSetup && !polyfillStart) ||
(parent.inheritedData(NG_ANIMATE_CONTROLLER) || noop).running) {
beforeFn(element, parent, after);
afterFn(element, parent, after);
return;
}
var animationData = element.data(NG_ANIMATE_CONTROLLER) || {};
if(animationData.running) {
(polyfillCancel || noop)(element);
animationData.done();
}
element.data(NG_ANIMATE_CONTROLLER, {running:true, done:done});
element.addClass(className);
beforeFn(element, parent, after);
if (element.length == 0) return done();
var memento = (polyfillSetup || noop)(element);
// $window.setTimeout(beginAnimation, 0); this was causing the element not to animate
// keep at 1 for animation dom rerender
$window.setTimeout(beginAnimation, 1);
}
function parseMaxTime(str) {
var total = 0, values = isString(str) ? str.split(/\s*,\s*/) : [];
forEach(values, function(value) {
total = Math.max(parseFloat(value) || 0, total);
});
return total;
}
function beginAnimation() {
element.addClass(activeClassName);
if (polyfillStart) {
polyfillStart(element, done, memento);
} else if (isFunction($window.getComputedStyle)) {
//one day all browsers will have these properties
var w3cAnimationProp = 'animation';
var w3cTransitionProp = 'transition';
//but some still use vendor-prefixed styles
var vendorAnimationProp = $sniffer.vendorPrefix + 'Animation';
var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition';
var durationKey = 'Duration',
delayKey = 'Delay',
animationIterationCountKey = 'IterationCount',
duration = 0;
//we want all the styles defined before and after
var ELEMENT_NODE = 1;
forEach(element, function(element) {
if (element.nodeType == ELEMENT_NODE) {
var elementStyles = $window.getComputedStyle(element) || {};
var transitionDelay = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + delayKey]),
parseMaxTime(elementStyles[vendorTransitionProp + delayKey]));
var animationDelay = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + delayKey]),
parseMaxTime(elementStyles[vendorAnimationProp + delayKey]));
var transitionDuration = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + durationKey]),
parseMaxTime(elementStyles[vendorTransitionProp + durationKey]));
var animationDuration = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + durationKey]),
parseMaxTime(elementStyles[vendorAnimationProp + durationKey]));
if(animationDuration > 0) {
animationDuration *= Math.max(parseInt(elementStyles[w3cAnimationProp + animationIterationCountKey]) || 0,
parseInt(elementStyles[vendorAnimationProp + animationIterationCountKey]) || 0,
1);
}
duration = Math.max(animationDelay + animationDuration,
transitionDelay + transitionDuration,
duration);
}
});
$window.setTimeout(done, duration * 1000);
} else {
done();
}
}
function done() {
if(!done.run) {
done.run = true;
afterFn(element, parent, after);
element.removeClass(className);
element.removeClass(activeClassName);
element.removeData(NG_ANIMATE_CONTROLLER);
}
}
};
}
function show(element) {
element.css('display', '');
}
function hide(element) {
element.css('display', 'none');
}
function insert(element, parent, after) {
var afterNode = after && after[after.length - 1];
var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
var afterNextSibling = afterNode && afterNode.nextSibling;
forEach(element, function(node) {
if (afterNextSibling) {
parentNode.insertBefore(node, afterNextSibling);
} else {
parentNode.appendChild(node);
}
});
}
function remove(element) {
element.remove();
}
function move(element, parent, after) {
// Do not remove element before insert. Removing will cause data associated with the
// element to be dropped. Insert will implicitly do the remove.
insert(element, parent, after);
}
};
/**
* @ngdoc function
* @name ng.animator#enabled
* @methodOf ng.$animator
* @function
*
* @param {Boolean=} If provided then set the animation on or off.
* @return {Boolean} Current animation state.
*
* @description
* Globally enables/disables animations.
*
*/
AnimatorService.enabled = function(value) {
if (arguments.length) {
rootAnimateController.running = !value;
}
return !rootAnimateController.running;
};
return AnimatorService;
}];
};