aboutsummaryrefslogtreecommitdiffstats
path: root/docs/content/cookbook/advancedform.ngdoc
diff options
context:
space:
mode:
authorMisko Hevery2011-07-26 12:06:14 -0700
committerIgor Minar2011-10-11 10:53:05 -0700
commitbda2bba2be7a52bf39fb1b257b6363edc7b71173 (patch)
tree70de396d785f7632377391d3463032826aa2d4c9 /docs/content/cookbook/advancedform.ngdoc
parentca08c004c893310ed9b3b4a5d2a4d16314eaa677 (diff)
downloadangular.js-bda2bba2be7a52bf39fb1b257b6363edc7b71173.tar.bz2
feat(jqlite): added .inheritedData method and $destroy event.
- refactored .scope() to use .inheritedData() instead. - .bind('$destroy', callback) will call when the DOM element is removed
Diffstat (limited to 'docs/content/cookbook/advancedform.ngdoc')
0 files changed, 0 insertions, 0 deletions
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
'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:
 *
 * <pre>
 * <!-- you can also use data-ng-animate, ng:animate or x-ng-animate as well -->
 * <ANY ng-directive ng-animate="{event1: 'animation-name', event2: 'animation-name-2'}"></ANY>
 *
 * <!-- you can also use a short hand -->
 * <ANY ng-directive ng-animate=" 'animation' "></ANY>
 * <!-- which expands to -->
 * <ANY ng-directive ng-animate="{ enter: 'animation-enter', leave: 'animation-leave', ...}"></ANY>
 *
 * <!-- keep in mind that ng-animate can take expressions -->
 * <ANY ng-directive ng-animate=" computeCurrentAnimation() "></ANY>
 * </pre>
 *
 * 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.
 *
 * <h2>CSS-defined Animations</h2>
 * 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:
 *
 * <pre>
 * <style type="text/css">
 * /&#42;
 *  The animate-enter CSS class is the event name that you
 *  have provided within the ngAnimate attribute.
 * &#42;/
 * .animate-enter {
 *  -webkit-transition: 1s linear all; /&#42; Safari/Chrome &#42;/
 *  -moz-transition: 1s linear all; /&#42; Firefox &#42;/
 *  -o-transition: 1s linear all; /&#42; Opera &#42;/
 *  transition: 1s linear all; /&#42; IE10+ and Future Browsers &#42;/
 *
 *  /&#42; The animation preparation code &#42;/
 *  opacity: 0;
 * }
 *
 * /&#42;
 *  Keep in mind that you want to combine both CSS
 *  classes together to avoid any CSS-specificity
 *  conflicts
 * &#42;/
 * .animate-enter.animate-enter-active {
 *  /&#42; The animation code itself &#42;/
 *  opacity: 1;
 * }
 * </style>
 *
 * <div ng-directive ng-animate="{enter: 'animate-enter'}"></div>
 * </pre>
 *
 * The following code below demonstrates how to perform animations using **CSS animations** with ngAnimate:
 *
 * <pre>
 * <style type="text/css">
 * .animate-enter {
 *   -webkit-animation: enter_sequence 1s linear; /&#42; Safari/Chrome &#42;/
 *   -moz-animation: enter_sequence 1s linear; /&#42; Firefox &#42;/
 *   -o-animation: enter_sequence 1s linear; /&#42; Opera &#42;/
 *   animation: enter_sequence 1s linear; /&#42; IE10+ and Future Browsers &#42;/
 * }
 * &#64-webkit-keyframes enter_sequence {
 *   from { opacity:0; }
 *   to { opacity:1; }
 * }
 * &#64-moz-keyframes enter_sequence {
 *   from { opacity:0; }
 *   to { opacity:1; }
 * }
 * &#64-o-keyframes enter_sequence {
 *   from { opacity:0; }
 *   to { opacity:1; }
 * }
 * &#64keyframes enter_sequence {
 *   from { opacity:0; }
 *   to { opacity:1; }
 * }
 * </style>
 *
 * <div ng-directive ng-animate="{enter: 'animate-enter'}"></div>
 * </pre>
 *
 * 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.
 *
 * <h2>JavaScript-defined Animations</h2>
 * 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.
 *
 * <pre>
 * 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()
 *       });
 *     }
 *   }
 * });
 * </pre>
 *
 * 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;
  }];
};