From 5d0713f06e1130aca67020b92c7077025a010a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Wed, 26 Feb 2014 22:37:03 -0500 Subject: [PATCH] wip --- src/ngAnimate/animate.js | 187 ++++++++++++++++++++++++++------------- 1 file changed, 127 insertions(+), 60 deletions(-) diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js index 959dd55bc50b..6ad05ae855ff 100644 --- a/src/ngAnimate/animate.js +++ b/src/ngAnimate/animate.js @@ -347,6 +347,120 @@ angular.module('ngAnimate', ['ng']) } } + function animationRunner(element, animationEvent, className) { + var node = element[0]; + if(!node) { + throw new Error; + } + + var setClassOperation = animationEvent == 'setClass'; + var isClassBased = setClassOperation || + animationEvent == 'addClass' || + animationEvent == 'removeClass'; + + var classNameAdd, classNameRemove; + if(angular.isArray(className)) { + classNameAdd = className[0]; + classNameRemove = className[1]; + className = classNameAdd + ' ' + classNameRemove; + } + + var currentClassName = element.attr('class'); + var classes = currentClassName + ' ' + className; + if(isAnimatableClassName(classes)) { + throw new Error; + } + + var detectedAnimations = lookup(classes); + + var cancellations, + before = [], + after = []; + + var animationLookup = (' ' + classes).replace(/\s+/g,'.'); + var matches = lookup(animationLookup); + for(var i = 0; i < matches.length; i++) { + var animationFactory = matches[i]; + var created = registerAnimation(animationFactory, animationEvent); + if(!created && setClassOperation) { + registerAnimation(animationFactory, 'addClass'); + registerAnimation(animationFactory, 'removeClass'); + } + }); + + function registerAnimation(animationFactory, event) { + var afterFn = animationFactory[event]; + var beforeFn = animationFactory[ + 'before' + animationEvent.charAt(0).toUpperCase() + animationEvent.substr(1)]; + if(afterFn || beforeFn) { + if(event == 'leave') { + beforeFn = afterFn; + afterFn = angular.noop; + } + after.push({ + event : event, + fn : afterFn || angular.noop + }); + before.push({ + event : event, + fn : beforeFn || angular.noop + }); + return true; + } + }; + + function run(animations, onAllComplete) { + cancellations = []; + var count = 0, total = animations.length; + + function onComplete() { + if(!cancellations) return; + + cancellations[i](); + if(++count < total) return; + + cancellations = null; + onAllComplete(); + }; + + angular.forEach(animations, function(animation) { + var cancelFn; + switch(animation.event) { + case 'setClass': + cancelFn = animation.fn(element, classNameAdd, classNameRemove, done); + break; + case 'addClass': + case 'removeClass': + cancelFn = animation.fn(element, className, done); + break; + default: + cancelFn = animation.fn(element, done); + break; + } + cancellations.push(cancelFn || angular.noop); + }); + } + + return { + isClassBased : + allowAnimations : function() { + return true; + }, + before : function(allCompleteFn) { + run(before, allCompleteFn); + }, + after : function(allCompleteFn) { + run(before, allCompleteFn); + }, + cancelAnmations : function() { + angular.forEach(cancellation, function(cancelFn) { + cancelFn(true); + }); + cancellations = null; + } + }; + } + /** * @ngdoc service * @name $animate @@ -622,22 +736,12 @@ angular.module('ngAnimate', ['ng']) */ function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) { - var classNameAdd, classNameRemove, setClassOperation = animationEvent == 'setClass'; - if(setClassOperation) { - classNameAdd = className[0]; - classNameRemove = className[1]; - className = classNameAdd + ' ' + classNameRemove; - } - - var currentClassName, classes, node = element[0]; - if(node) { - currentClassName = node.className; - classes = currentClassName + ' ' + className; - } - //transcluded directives may sometimes fire an animation using only comment nodes //best to catch this early on to prevent any animation operations from occurring - if(!node || !isAnimatableClassName(classes)) { + var runner; + try { + runner = animationRunner(element, animationEvent, className); + } catch(e) { fireDOMOperation(); fireBeforeCallbackAsync(); fireAfterCallbackAsync(); @@ -648,26 +752,29 @@ angular.module('ngAnimate', ['ng']) var elementEvents = angular.element._data(node); elementEvents = elementEvents && elementEvents.events; - var animationLookup = (' ' + classes).replace(/\s+/g,'.'); if (!parentElement) { parentElement = afterElement ? afterElement.parent() : element.parent(); } - var matches = lookup(animationLookup); - var isClassBased = animationEvent == 'addClass' || - animationEvent == 'removeClass' || - setClassOperation; var ngAnimateState = element.data(NG_ANIMATE_STATE) || {}; var runningAnimations = ngAnimateState.active || {}; var totalActiveAnimations = ngAnimateState.totalActive || 0; var lastAnimation = ngAnimateState.last; + //only allow animations if the currently running animation is not structural + //or if there is no animation running at all + var skipAnimations = runner.isClassBased ? + !ngAnimateState.disabled && (!lastAnimation || lastAnimation.classBased) : + true; + //skip the animation if animations are disabled, a parent is already being animated, //the element is not currently attached to the document body or then completely close //the animation if any matching animations are not found at all. //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found. - if (animationsDisabled(element, parentElement) || matches.length === 0) { + if (skipAnimations || + !runner.allowAnimations() || + animationsDisabled(element, parentElement)) { fireDOMOperation(); fireBeforeCallbackAsync(); fireAfterCallbackAsync(); @@ -675,46 +782,6 @@ angular.module('ngAnimate', ['ng']) return; } - var animations = []; - - //only add animations if the currently running animation is not structural - //or if there is no animation running at all - var allowAnimations = isClassBased ? - !ngAnimateState.disabled && (!lastAnimation || lastAnimation.classBased) : - true; - - if(allowAnimations) { - forEach(matches, function(animation) { - //add the animation to the queue to if it is allowed to be cancelled - if(!animation.allowCancel || animation.allowCancel(element, animationEvent, className)) { - var beforeFn, afterFn = animation[animationEvent]; - - //Special case for a leave animation since there is no point in performing an - //animation on a element node that has already been removed from the DOM - if(animationEvent == 'leave') { - beforeFn = afterFn; - afterFn = null; //this must be falsy so that the animation is skipped for leave - } else { - beforeFn = animation['before' + animationEvent.charAt(0).toUpperCase() + animationEvent.substr(1)]; - } - animations.push({ - before : beforeFn, - after : afterFn - }); - } - }); - } - - //this would mean that an animation was not allowed so let the existing - //animation do it's thing and close this one early - if(animations.length === 0) { - fireDOMOperation(); - fireBeforeCallbackAsync(); - fireAfterCallbackAsync(); - fireDoneCallbackAsync(); - return; - } - var skipAnimation = false; if(totalActiveAnimations > 0) { var animationsToCancel = [];