'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 ngRoute.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: * *
 * 
 * 
 *
 * 
 * //!annotate="animation" ngAnimate|This *expands* to `{ enter: 'animation-enter', leave: 'animation-leave', ...}`
 * 
 *
 * 
 * //!annotate="computeCurrentAnimation\(\)" Scope Function|This will be called each time the scope changes...
 * 
 * 
* * 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. * *

CSS-defined Animations

* By default, ngAnimate attaches two CSS classes per animation event to the DOM element to achieve the animation. * It is up to you, the developer, to ensure that the animations take place using cross-browser CSS3 transitions as * well as CSS animations. * * The following code below demonstrates how to perform animations using **CSS transitions** with ngAnimate: * *
 * 
 *
 * 
*
* * 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. * *

JavaScript-defined Animations

* In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations to browsers that do not * yet support them, then you can make use of JavaScript animations defined inside of your AngularJS module. * *
 * 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(); } var disabledAnimation = { running : true }; if ((!$sniffer.transitions && !polyfillSetup && !polyfillStart) || (parent.inheritedData(NG_ANIMATE_CONTROLLER) || disabledAnimation).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; }]; };