diff --git a/awx/ui/client/lib/angular-animate/.bower.json b/awx/ui/client/lib/angular-animate/.bower.json
index 50b2bd1f15..27ddb8cc93 100644
--- a/awx/ui/client/lib/angular-animate/.bower.json
+++ b/awx/ui/client/lib/angular-animate/.bower.json
@@ -1,19 +1,19 @@
{
"name": "angular-animate",
- "version": "1.2.23",
+ "version": "1.4.3",
"main": "./angular-animate.js",
+ "ignore": [],
"dependencies": {
- "angular": "1.2.23"
+ "angular": "1.4.3"
},
"homepage": "https://github.com/angular/bower-angular-animate",
- "_release": "1.2.23",
+ "_release": "1.4.3",
"_resolution": {
"type": "version",
- "tag": "v1.2.23",
- "commit": "a5b1e7109e5b9a0276540cdafc92d6fedfec8da5"
+ "tag": "v1.4.3",
+ "commit": "4ce2a76359401102d2e0146ccf69e6c060799ff8"
},
"_source": "git://github.com/angular/bower-angular-animate.git",
- "_target": "~1.2.23",
- "_originalSource": "angular-animate",
- "_direct": true
+ "_target": "~1.4.3",
+ "_originalSource": "angular-animate"
}
\ No newline at end of file
diff --git a/awx/ui/client/lib/angular-animate/README.md b/awx/ui/client/lib/angular-animate/README.md
index de4c61b895..8313da67c3 100644
--- a/awx/ui/client/lib/angular-animate/README.md
+++ b/awx/ui/client/lib/angular-animate/README.md
@@ -1,24 +1,38 @@
-# bower-angular-animate
+# packaged angular-animate
-This repo is for distribution on `bower`. The source for this module is in the
+This repo is for distribution on `npm` and `bower`. The source for this module is in the
[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngAnimate).
Please file issues and pull requests against that repo.
## Install
-Install with `bower`:
+You can install this package either with `npm` or with `bower`.
+
+### npm
+
+```shell
+npm install angular-animate
+```
+
+Then add `ngAnimate` as a dependency for your app:
+
+```javascript
+angular.module('myApp', [require('angular-animate')]);
+```
+
+### bower
```shell
bower install angular-animate
```
-Add a `
```
-And add `ngAnimate` as a dependency for your app:
+Then add `ngAnimate` as a dependency for your app:
```javascript
angular.module('myApp', ['ngAnimate']);
@@ -33,7 +47,7 @@ Documentation is available on the
The MIT License
-Copyright (c) 2010-2012 Google, Inc. http://angularjs.org
+Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/awx/ui/client/lib/angular-animate/angular-animate.js b/awx/ui/client/lib/angular-animate/angular-animate.js
index 3768a03fb9..fc0e217f7e 100644
--- a/awx/ui/client/lib/angular-animate/angular-animate.js
+++ b/awx/ui/client/lib/angular-animate/angular-animate.js
@@ -1,1480 +1,1267 @@
/**
- * @license AngularJS v1.2.23
- * (c) 2010-2014 Google, Inc. http://angularjs.org
+ * @license AngularJS v1.4.3
+ * (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
-/* jshint maxlen: false */
+/* jshint ignore:start */
+var noop = angular.noop;
+var extend = angular.extend;
+var jqLite = angular.element;
+var forEach = angular.forEach;
+var isArray = angular.isArray;
+var isString = angular.isString;
+var isObject = angular.isObject;
+var isUndefined = angular.isUndefined;
+var isDefined = angular.isDefined;
+var isFunction = angular.isFunction;
+var isElement = angular.isElement;
+
+var ELEMENT_NODE = 1;
+var COMMENT_NODE = 8;
+
+var NG_ANIMATE_CLASSNAME = 'ng-animate';
+var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
+
+var isPromiseLike = function(p) {
+ return p && p.then ? true : false;
+}
+
+function assertArg(arg, name, reason) {
+ if (!arg) {
+ throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
+ }
+ return arg;
+}
+
+function mergeClasses(a,b) {
+ if (!a && !b) return '';
+ if (!a) return b;
+ if (!b) return a;
+ if (isArray(a)) a = a.join(' ');
+ if (isArray(b)) b = b.join(' ');
+ return a + ' ' + b;
+}
+
+function packageStyles(options) {
+ var styles = {};
+ if (options && (options.to || options.from)) {
+ styles.to = options.to;
+ styles.from = options.from;
+ }
+ return styles;
+}
+
+function pendClasses(classes, fix, isPrefix) {
+ var className = '';
+ classes = isArray(classes)
+ ? classes
+ : classes && isString(classes) && classes.length
+ ? classes.split(/\s+/)
+ : [];
+ forEach(classes, function(klass, i) {
+ if (klass && klass.length > 0) {
+ className += (i > 0) ? ' ' : '';
+ className += isPrefix ? fix + klass
+ : klass + fix;
+ }
+ });
+ return className;
+}
+
+function removeFromArray(arr, val) {
+ var index = arr.indexOf(val);
+ if (val >= 0) {
+ arr.splice(index, 1);
+ }
+}
+
+function stripCommentsFromElement(element) {
+ if (element instanceof jqLite) {
+ switch (element.length) {
+ case 0:
+ return [];
+ break;
+
+ case 1:
+ // there is no point of stripping anything if the element
+ // is the only element within the jqLite wrapper.
+ // (it's important that we retain the element instance.)
+ if (element[0].nodeType === ELEMENT_NODE) {
+ return element;
+ }
+ break;
+
+ default:
+ return jqLite(extractElementNode(element));
+ break;
+ }
+ }
+
+ if (element.nodeType === ELEMENT_NODE) {
+ return jqLite(element);
+ }
+}
+
+function extractElementNode(element) {
+ if (!element[0]) return element;
+ for (var i = 0; i < element.length; i++) {
+ var elm = element[i];
+ if (elm.nodeType == ELEMENT_NODE) {
+ return elm;
+ }
+ }
+}
+
+function $$addClass($$jqLite, element, className) {
+ forEach(element, function(elm) {
+ $$jqLite.addClass(elm, className);
+ });
+}
+
+function $$removeClass($$jqLite, element, className) {
+ forEach(element, function(elm) {
+ $$jqLite.removeClass(elm, className);
+ });
+}
+
+function applyAnimationClassesFactory($$jqLite) {
+ return function(element, options) {
+ if (options.addClass) {
+ $$addClass($$jqLite, element, options.addClass);
+ options.addClass = null;
+ }
+ if (options.removeClass) {
+ $$removeClass($$jqLite, element, options.removeClass);
+ options.removeClass = null;
+ }
+ }
+}
+
+function prepareAnimationOptions(options) {
+ options = options || {};
+ if (!options.$$prepared) {
+ var domOperation = options.domOperation || noop;
+ options.domOperation = function() {
+ options.$$domOperationFired = true;
+ domOperation();
+ domOperation = noop;
+ };
+ options.$$prepared = true;
+ }
+ return options;
+}
+
+function applyAnimationStyles(element, options) {
+ applyAnimationFromStyles(element, options);
+ applyAnimationToStyles(element, options);
+}
+
+function applyAnimationFromStyles(element, options) {
+ if (options.from) {
+ element.css(options.from);
+ options.from = null;
+ }
+}
+
+function applyAnimationToStyles(element, options) {
+ if (options.to) {
+ element.css(options.to);
+ options.to = null;
+ }
+}
+
+function mergeAnimationOptions(element, target, newOptions) {
+ var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
+ var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');
+ var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);
+
+ extend(target, newOptions);
+
+ if (classes.addClass) {
+ target.addClass = classes.addClass;
+ } else {
+ target.addClass = null;
+ }
+
+ if (classes.removeClass) {
+ target.removeClass = classes.removeClass;
+ } else {
+ target.removeClass = null;
+ }
+
+ return target;
+}
+
+function resolveElementClasses(existing, toAdd, toRemove) {
+ var ADD_CLASS = 1;
+ var REMOVE_CLASS = -1;
+
+ var flags = {};
+ existing = splitClassesToLookup(existing);
+
+ toAdd = splitClassesToLookup(toAdd);
+ forEach(toAdd, function(value, key) {
+ flags[key] = ADD_CLASS;
+ });
+
+ toRemove = splitClassesToLookup(toRemove);
+ forEach(toRemove, function(value, key) {
+ flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS;
+ });
+
+ var classes = {
+ addClass: '',
+ removeClass: ''
+ };
+
+ forEach(flags, function(val, klass) {
+ var prop, allow;
+ if (val === ADD_CLASS) {
+ prop = 'addClass';
+ allow = !existing[klass];
+ } else if (val === REMOVE_CLASS) {
+ prop = 'removeClass';
+ allow = existing[klass];
+ }
+ if (allow) {
+ if (classes[prop].length) {
+ classes[prop] += ' ';
+ }
+ classes[prop] += klass;
+ }
+ });
+
+ function splitClassesToLookup(classes) {
+ if (isString(classes)) {
+ classes = classes.split(' ');
+ }
+
+ var obj = {};
+ forEach(classes, function(klass) {
+ // sometimes the split leaves empty string values
+ // incase extra spaces were applied to the options
+ if (klass.length) {
+ obj[klass] = true;
+ }
+ });
+ return obj;
+ }
+
+ return classes;
+}
+
+function getDomNode(element) {
+ return (element instanceof angular.element) ? element[0] : element;
+}
+
+var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
+ var tickQueue = [];
+ var cancelFn;
+
+ function scheduler(tasks) {
+ // we make a copy since RAFScheduler mutates the state
+ // of the passed in array variable and this would be difficult
+ // to track down on the outside code
+ tickQueue.push([].concat(tasks));
+ nextTick();
+ }
+
+ /* waitUntilQuiet does two things:
+ * 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through
+ * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
+ *
+ * The motivation here is that animation code can request more time from the scheduler
+ * before the next wave runs. This allows for certain DOM properties such as classes to
+ * be resolved in time for the next animation to run.
+ */
+ scheduler.waitUntilQuiet = function(fn) {
+ if (cancelFn) cancelFn();
+
+ cancelFn = $$rAF(function() {
+ cancelFn = null;
+ fn();
+ nextTick();
+ });
+ };
+
+ return scheduler;
+
+ function nextTick() {
+ if (!tickQueue.length) return;
+
+ var updatedQueue = [];
+ for (var i = 0; i < tickQueue.length; i++) {
+ var innerQueue = tickQueue[i];
+ runNextTask(innerQueue);
+ if (innerQueue.length) {
+ updatedQueue.push(innerQueue);
+ }
+ }
+ tickQueue = updatedQueue;
+
+ if (!cancelFn) {
+ $$rAF(function() {
+ if (!cancelFn) nextTick();
+ });
+ }
+ }
+
+ function runNextTask(tasks) {
+ var nextTask = tasks.shift();
+ nextTask();
+ }
+}];
+
+var $$AnimateChildrenDirective = [function() {
+ return function(scope, element, attrs) {
+ var val = attrs.ngAnimateChildren;
+ if (angular.isString(val) && val.length === 0) { //empty attribute
+ element.data(NG_ANIMATE_CHILDREN_DATA, true);
+ } else {
+ attrs.$observe('ngAnimateChildren', function(value) {
+ value = value === 'on' || value === 'true';
+ element.data(NG_ANIMATE_CHILDREN_DATA, value);
+ });
+ }
+ };
+}];
/**
- * @ngdoc module
- * @name ngAnimate
+ * @ngdoc service
+ * @name $animateCss
+ * @kind object
+ *
* @description
+ * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes
+ * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT
+ * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or
+ * directives to create more complex animations that can be purely driven using CSS code.
*
- * # ngAnimate
+ * Note that only browsers that support CSS transitions and/or keyframe animations are capable of
+ * rendering animations triggered via `$animateCss` (bad news for IE9 and lower).
*
- * The `ngAnimate` module provides support for JavaScript, CSS3 transition and CSS3 keyframe animation hooks within existing core and custom directives.
+ * ## Usage
+ * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that
+ * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however,
+ * any automatic control over cancelling animations and/or preventing animations from being run on
+ * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to
+ * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger
+ * the CSS animation.
*
- *
- *
- *
- * # Usage
- *
- * To see animations in action, all that is required is to define the appropriate CSS classes
- * or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are:
- * `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation
- * by using the `$animate` service.
- *
- * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives:
- *
- * | Directive | Supported Animations |
- * |---------------------------------------------------------- |----------------------------------------------------|
- * | {@link ng.directive:ngRepeat#usage_animations ngRepeat} | enter, leave and move |
- * | {@link ngRoute.directive:ngView#usage_animations ngView} | enter and leave |
- * | {@link ng.directive:ngInclude#usage_animations ngInclude} | enter and leave |
- * | {@link ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave |
- * | {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave |
- * | {@link ng.directive:ngClass#usage_animations ngClass} | add and remove |
- * | {@link ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) |
- * | {@link ng.directive:form#usage_animations form} | add and remove (dirty, pristine, valid, invalid & all other validations) |
- * | {@link ng.directive:ngModel#usage_animations ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
- *
- * You can find out more information about animations upon visiting each directive page.
- *
- * Below is an example of how to apply animations to a directive that supports animation hooks:
+ * The example below shows how we can create a folding animation on an element using `ng-if`:
*
* ```html
- *
- *
- *
- *
- * ```
- *
- * Keep in mind that, by default, if an animation is running, any child elements cannot be animated
- * until the parent element's animation has completed. This blocking feature can be overridden by
- * placing the `ng-animate-children` attribute on a parent container tag.
- *
- * ```html
- *
- *
- *
- * ...
- *
- *
+ *
+ *
+ * This element will go BOOM
*
+ *
* ```
*
- * When the `on` expression value changes and an animation is triggered then each of the elements within
- * will all animate without the block being applied to child elements.
+ * Now we create the **JavaScript animation** that will trigger the CSS transition:
*
- *
CSS-defined Animations
- * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
- * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported
- * and can be used to play along with this naming structure.
- *
- * The following code below demonstrates how to perform animations using **CSS transitions** with Angular:
- *
- * ```html
- *
- *
- *
- *
- *
+ * ```js
+ * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
+ * return {
+ * enter: function(element, doneFn) {
+ * var height = element[0].offsetHeight;
+ * return $animateCss(element, {
+ * from: { height:'0px' },
+ * to: { height:height + 'px' },
+ * duration: 1 // one second
+ * });
+ * }
+ * }
+ * }]);
* ```
*
- * The following code below demonstrates how to perform animations using **CSS animations** with Angular:
+ * ## More Advanced Uses
*
- * ```html
- *
+ * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks
+ * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code.
*
- *
- *
- *
+ * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation,
+ * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with
+ * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order
+ * to provide a working animation that will run in CSS.
+ *
+ * The example below showcases a more advanced version of the `.fold-animation` from the example above:
+ *
+ * ```js
+ * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
+ * return {
+ * enter: function(element, doneFn) {
+ * var height = element[0].offsetHeight;
+ * return $animateCss(element, {
+ * addClass: 'red large-text pulse-twice',
+ * easing: 'ease-out',
+ * from: { height:'0px' },
+ * to: { height:height + 'px' },
+ * duration: 1 // one second
+ * });
+ * }
+ * }
+ * }]);
* ```
*
- * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing.
- *
- * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add
- * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically
- * detect the CSS code 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 its final state. This final state is when the DOM element
- * has no CSS transition/animation classes applied to it.
- *
- *
CSS Staggering Animations
- * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
- * curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be
- * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
- * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
- * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
+ * Since we're adding/removing CSS classes then the CSS transition will also pick those up:
*
* ```css
- * .my-animation.ng-enter {
- * /* standard transition code */
- * -webkit-transition: 1s linear all;
- * transition: 1s linear all;
- * opacity:0;
- * }
- * .my-animation.ng-enter-stagger {
- * /* this will have a 100ms delay between each successive leave animation */
- * -webkit-transition-delay: 0.1s;
- * transition-delay: 0.1s;
+ * /* since a hardcoded duration value of 1 was provided in the JavaScript animation code,
+ * the CSS classes below will be transitioned despite them being defined as regular CSS classes */
+ * .red { background:red; }
+ * .large-text { font-size:20px; }
*
- * /* in case the stagger doesn't work then these two values
- * must be set to 0 to avoid an accidental CSS inheritance */
- * -webkit-transition-duration: 0s;
- * transition-duration: 0s;
+ * /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */
+ * .pulse-twice {
+ * animation: 0.5s pulse linear 2;
+ * -webkit-animation: 0.5s pulse linear 2;
* }
- * .my-animation.ng-enter.ng-enter-active {
- * /* standard transition styles */
- * opacity:1;
+ *
+ * @keyframes pulse {
+ * from { transform: scale(0.5); }
+ * to { transform: scale(1.5); }
+ * }
+ *
+ * @-webkit-keyframes pulse {
+ * from { -webkit-transform: scale(0.5); }
+ * to { -webkit-transform: scale(1.5); }
* }
* ```
*
- * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
- * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
- * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
- * will also be reset if more than 10ms has passed after the last animation has been fired.
+ * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen.
*
- * The following code will issue the **ng-leave-stagger** event on the element provided:
+ * ## How the Options are handled
+ *
+ * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation
+ * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline
+ * styles using the `from` and `to` properties.
*
* ```js
- * var kids = parent.children();
- *
- * $animate.leave(kids[0]); //stagger index=0
- * $animate.leave(kids[1]); //stagger index=1
- * $animate.leave(kids[2]); //stagger index=2
- * $animate.leave(kids[3]); //stagger index=3
- * $animate.leave(kids[4]); //stagger index=4
- *
- * $timeout(function() {
- * //stagger has reset itself
- * $animate.leave(kids[5]); //stagger index=0
- * $animate.leave(kids[6]); //stagger index=1
- * }, 100, false);
- * ```
- *
- * Stagger animations are currently only supported within CSS-defined animations.
- *
- *
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 on browsers that do not
- * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module.
- *
- * ```js
- * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
- * var ngModule = angular.module('YourApp', ['ngAnimate']);
- * ngModule.animation('.my-crazy-animation', function() {
- * return {
- * enter: function(element, done) {
- * //run the animation here and call done when the animation is complete
- * return function(cancelled) {
- * //this (optional) function will be called when the animation
- * //completes or when the animation is cancelled (the cancelled
- * //flag will be set to true if cancelled).
- * };
- * },
- * leave: function(element, done) { },
- * move: function(element, done) { },
- *
- * //animation that can be triggered before the class is added
- * beforeAddClass: function(element, className, done) { },
- *
- * //animation that can be triggered after the class is added
- * addClass: function(element, className, done) { },
- *
- * //animation that can be triggered before the class is removed
- * beforeRemoveClass: function(element, className, done) { },
- *
- * //animation that can be triggered after the class is removed
- * removeClass: function(element, className, done) { }
- * };
+ * var animator = $animateCss(element, {
+ * from: { background:'red' },
+ * to: { background:'blue' }
* });
+ * animator.start();
* ```
*
- * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run
- * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits
- * the element's CSS class attribute value and then run the matching animation event function (if found).
- * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function will
- * be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported).
+ * ```css
+ * .rotating-animation {
+ * animation:0.5s rotate linear;
+ * -webkit-animation:0.5s rotate linear;
+ * }
*
- * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned.
- * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run,
- * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation
- * or transition code that is defined via a stylesheet).
+ * @keyframes rotate {
+ * from { transform: rotate(0deg); }
+ * to { transform: rotate(360deg); }
+ * }
*
+ * @-webkit-keyframes rotate {
+ * from { -webkit-transform: rotate(0deg); }
+ * to { -webkit-transform: rotate(360deg); }
+ * }
+ * ```
+ *
+ * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is
+ * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition
+ * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition
+ * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied
+ * and spread across the transition and keyframe animation.
+ *
+ * ## What is returned
+ *
+ * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually
+ * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are
+ * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties:
+ *
+ * ```js
+ * var animator = $animateCss(element, { ... });
+ * ```
+ *
+ * Now what do the contents of our `animator` variable look like:
+ *
+ * ```js
+ * {
+ * // starts the animation
+ * start: Function,
+ *
+ * // ends (aborts) the animation
+ * end: Function
+ * }
+ * ```
+ *
+ * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends.
+ * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and stlyes may have been
+ * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
+ * and that changing them will not reconfigure the parameters of the animation.
+ *
+ * ### runner.done() vs runner.then()
+ * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the
+ * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**.
+ * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()`
+ * unless you really need a digest to kick off afterwards.
+ *
+ * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss
+ * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code).
+ * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works.
+ *
+ * @param {DOMElement} element the element that will be animated
+ * @param {object} options the animation-related options that will be applied during the animation
+ *
+ * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied
+ * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.)
+ * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).
+ * * `transition` - The raw CSS transition style that will be used (e.g. `1s linear all`).
+ * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
+ * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.
+ * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.
+ * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation.
+ * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation.
+ * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0`
+ * is provided then the animation will be skipped entirely.
+ * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is
+ * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value
+ * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same
+ * CSS delay value.
+ * * `stagger` - A numeric time value representing the delay between successively animated elements
+ * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
+ * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
+ * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
+ * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
+ *
+ * @return {object} an object with start and end methods and details about the animation.
+ *
+ * * `start` - The method to start the animation. This will return a `Promise` when called.
+ * * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
*/
-angular.module('ngAnimate', ['ng'])
+// Detect proper transitionend/animationend event names.
+var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
- /**
- * @ngdoc provider
- * @name $animateProvider
- * @description
- *
- * The `$animateProvider` allows developers to register JavaScript animation event handlers directly inside of a module.
- * When an animation is triggered, the $animate service will query the $animate service to find any animations that match
- * the provided name value.
- *
- * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
- *
- * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
- *
- */
- .directive('ngAnimateChildren', function() {
- var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren';
- return function(scope, element, attrs) {
- var val = attrs.ngAnimateChildren;
- if(angular.isString(val) && val.length === 0) { //empty attribute
- element.data(NG_ANIMATE_CHILDREN, true);
+// If unprefixed events are not supported but webkit-prefixed are, use the latter.
+// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
+// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
+// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
+// Register both events in case `window.onanimationend` is not supported because of that,
+// do the same for `transitionend` as Safari is likely to exhibit similar behavior.
+// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
+// therefore there is no reason to test anymore for other vendor prefixes:
+// http://caniuse.com/#search=transition
+if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
+ CSS_PREFIX = '-webkit-';
+ TRANSITION_PROP = 'WebkitTransition';
+ TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
+} else {
+ TRANSITION_PROP = 'transition';
+ TRANSITIONEND_EVENT = 'transitionend';
+}
+
+if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
+ CSS_PREFIX = '-webkit-';
+ ANIMATION_PROP = 'WebkitAnimation';
+ ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
+} else {
+ ANIMATION_PROP = 'animation';
+ ANIMATIONEND_EVENT = 'animationend';
+}
+
+var DURATION_KEY = 'Duration';
+var PROPERTY_KEY = 'Property';
+var DELAY_KEY = 'Delay';
+var TIMING_KEY = 'TimingFunction';
+var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
+var ANIMATION_PLAYSTATE_KEY = 'PlayState';
+var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
+var CLOSING_TIME_BUFFER = 1.5;
+var ONE_SECOND = 1000;
+var BASE_TEN = 10;
+
+var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;
+
+var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
+var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
+
+var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
+var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
+
+var DETECT_CSS_PROPERTIES = {
+ transitionDuration: TRANSITION_DURATION_PROP,
+ transitionDelay: TRANSITION_DELAY_PROP,
+ transitionProperty: TRANSITION_PROP + PROPERTY_KEY,
+ animationDuration: ANIMATION_DURATION_PROP,
+ animationDelay: ANIMATION_DELAY_PROP,
+ animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY
+};
+
+var DETECT_STAGGER_CSS_PROPERTIES = {
+ transitionDuration: TRANSITION_DURATION_PROP,
+ transitionDelay: TRANSITION_DELAY_PROP,
+ animationDuration: ANIMATION_DURATION_PROP,
+ animationDelay: ANIMATION_DELAY_PROP
+};
+
+function computeCssStyles($window, element, properties) {
+ var styles = Object.create(null);
+ var detectedStyles = $window.getComputedStyle(element) || {};
+ forEach(properties, function(formalStyleName, actualStyleName) {
+ var val = detectedStyles[formalStyleName];
+ if (val) {
+ var c = val.charAt(0);
+
+ // only numerical-based values have a negative sign or digit as the first value
+ if (c === '-' || c === '+' || c >= 0) {
+ val = parseMaxTime(val);
+ }
+
+ // by setting this to null in the event that the delay is not set or is set directly as 0
+ // then we can still allow for zegative values to be used later on and not mistake this
+ // value for being greater than any other negative value.
+ if (val === 0) {
+ val = null;
+ }
+ styles[actualStyleName] = val;
+ }
+ });
+
+ return styles;
+}
+
+function parseMaxTime(str) {
+ var maxValue = 0;
+ var values = str.split(/\s*,\s*/);
+ forEach(values, function(value) {
+ // it's always safe to consider only second values and omit `ms` values since
+ // getComputedStyle will always handle the conversion for us
+ if (value.charAt(value.length - 1) == 's') {
+ value = value.substring(0, value.length - 1);
+ }
+ value = parseFloat(value) || 0;
+ maxValue = maxValue ? Math.max(value, maxValue) : value;
+ });
+ return maxValue;
+}
+
+function truthyTimingValue(val) {
+ return val === 0 || val != null;
+}
+
+function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
+ var style = TRANSITION_PROP;
+ var value = duration + 's';
+ if (applyOnlyDuration) {
+ style += DURATION_KEY;
+ } else {
+ value += ' linear all';
+ }
+ return [style, value];
+}
+
+function getCssKeyframeDurationStyle(duration) {
+ return [ANIMATION_DURATION_PROP, duration + 's'];
+}
+
+function getCssDelayStyle(delay, isKeyframeAnimation) {
+ var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
+ return [prop, delay + 's'];
+}
+
+function blockTransitions(node, duration) {
+ // we use a negative delay value since it performs blocking
+ // yet it doesn't kill any existing transitions running on the
+ // same element which makes this safe for class-based animations
+ var value = duration ? '-' + duration + 's' : '';
+ applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
+ return [TRANSITION_DELAY_PROP, value];
+}
+
+function blockKeyframeAnimations(node, applyBlock) {
+ var value = applyBlock ? 'paused' : '';
+ var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
+ applyInlineStyle(node, [key, value]);
+ return [key, value];
+}
+
+function applyInlineStyle(node, styleTuple) {
+ var prop = styleTuple[0];
+ var value = styleTuple[1];
+ node.style[prop] = value;
+}
+
+function createLocalCacheLookup() {
+ var cache = Object.create(null);
+ return {
+ flush: function() {
+ cache = Object.create(null);
+ },
+
+ count: function(key) {
+ var entry = cache[key];
+ return entry ? entry.total : 0;
+ },
+
+ get: function(key) {
+ var entry = cache[key];
+ return entry && entry.value;
+ },
+
+ put: function(key, value) {
+ if (!cache[key]) {
+ cache[key] = { total: 1, value: value };
} else {
- scope.$watch(val, function(value) {
- element.data(NG_ANIMATE_CHILDREN, !!value);
- });
+ cache[key].total++;
}
- };
- })
+ }
+ };
+}
- //this private service is only used within CSS-enabled animations
- //IE8 + IE9 do not support rAF natively, but that is fine since they
- //also don't support transitions and keyframes which means that the code
- //below will never be used by the two browsers.
- .factory('$$animateReflow', ['$$rAF', '$document', function($$rAF, $document) {
- var bod = $document[0].body;
- return function(fn) {
- //the returned function acts as the cancellation function
- return $$rAF(function() {
- //the line below will force the browser to perform a repaint
- //so that all the animated elements within the animation frame
- //will be properly updated and drawn on screen. This is
- //required to perform multi-class CSS based animations with
- //Firefox. DO NOT REMOVE THIS LINE.
- var a = bod.offsetWidth + 1;
- fn();
+var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
+ var gcsLookup = createLocalCacheLookup();
+ var gcsStaggerLookup = createLocalCacheLookup();
+
+ this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
+ '$document', '$sniffer', '$$rAFScheduler',
+ function($window, $$jqLite, $$AnimateRunner, $timeout,
+ $document, $sniffer, $$rAFScheduler) {
+
+ var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+
+ var parentCounter = 0;
+ function gcsHashFn(node, extraClasses) {
+ var KEY = "$$ngAnimateParentKey";
+ var parentNode = node.parentNode;
+ var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
+ return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
+ }
+
+ function computeCachedCssStyles(node, className, cacheKey, properties) {
+ var timings = gcsLookup.get(cacheKey);
+
+ if (!timings) {
+ timings = computeCssStyles($window, node, properties);
+ if (timings.animationIterationCount === 'infinite') {
+ timings.animationIterationCount = 1;
+ }
+ }
+
+ // we keep putting this in multiple times even though the value and the cacheKey are the same
+ // because we're keeping an interal tally of how many duplicate animations are detected.
+ gcsLookup.put(cacheKey, timings);
+ return timings;
+ }
+
+ function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
+ var stagger;
+
+ // if we have one or more existing matches of matching elements
+ // containing the same parent + CSS styles (which is how cacheKey works)
+ // then staggering is possible
+ if (gcsLookup.count(cacheKey) > 0) {
+ stagger = gcsStaggerLookup.get(cacheKey);
+
+ if (!stagger) {
+ var staggerClassName = pendClasses(className, '-stagger');
+
+ $$jqLite.addClass(node, staggerClassName);
+
+ stagger = computeCssStyles($window, node, properties);
+
+ // force the conversion of a null value to zero incase not set
+ stagger.animationDuration = Math.max(stagger.animationDuration, 0);
+ stagger.transitionDuration = Math.max(stagger.transitionDuration, 0);
+
+ $$jqLite.removeClass(node, staggerClassName);
+
+ gcsStaggerLookup.put(cacheKey, stagger);
+ }
+ }
+
+ return stagger || {};
+ }
+
+ var bod = getDomNode($document).body;
+ var rafWaitQueue = [];
+ function waitUntilQuiet(callback) {
+ rafWaitQueue.push(callback);
+ $$rAFScheduler.waitUntilQuiet(function() {
+ gcsLookup.flush();
+ gcsStaggerLookup.flush();
+
+ //the line below will force the browser to perform a repaint so
+ //that all the animated elements within the animation frame will
+ //be properly updated and drawn on screen. This is required to
+ //ensure that the preparation animation is properly flushed so that
+ //the active state picks up from there. DO NOT REMOVE THIS LINE.
+ //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
+ //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
+ //WILL TAKE YEARS AWAY FROM YOUR LIFE.
+ var width = bod.offsetWidth + 1;
+
+ // we use a for loop to ensure that if the queue is changed
+ // during this looping then it will consider new requests
+ for (var i = 0; i < rafWaitQueue.length; i++) {
+ rafWaitQueue[i](width);
+ }
+ rafWaitQueue.length = 0;
});
- };
- }])
-
- .config(['$provide', '$animateProvider', function($provide, $animateProvider) {
- var noop = angular.noop;
- var forEach = angular.forEach;
- var selectors = $animateProvider.$$selectors;
-
- var ELEMENT_NODE = 1;
- var NG_ANIMATE_STATE = '$$ngAnimateState';
- var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren';
- var NG_ANIMATE_CLASS_NAME = 'ng-animate';
- var rootAnimateState = {running: true};
-
- function extractElementNode(element) {
- for(var i = 0; i < element.length; i++) {
- var elm = element[i];
- if(elm.nodeType == ELEMENT_NODE) {
- return elm;
- }
- }
}
- function prepareElement(element) {
- return element && angular.element(element);
+ return init;
+
+ function computeTimings(node, className, cacheKey) {
+ var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
+ var aD = timings.animationDelay;
+ var tD = timings.transitionDelay;
+ timings.maxDelay = aD && tD
+ ? Math.max(aD, tD)
+ : (aD || tD);
+ timings.maxDuration = Math.max(
+ timings.animationDuration * timings.animationIterationCount,
+ timings.transitionDuration);
+
+ return timings;
}
- function stripCommentsFromElement(element) {
- return angular.element(extractElementNode(element));
- }
-
- function isMatchingElement(elm1, elm2) {
- return extractElementNode(elm1) == extractElementNode(elm2);
- }
-
- $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document',
- function($delegate, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document) {
-
- var globalAnimationCounter = 0;
- $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
-
- // disable animations during bootstrap, but once we bootstrapped, wait again
- // for another digest until enabling animations. The reason why we digest twice
- // is because all structural animations (enter, leave and move) all perform a
- // post digest operation before animating. If we only wait for a single digest
- // to pass then the structural animation would render its animation on page load.
- // (which is what we're trying to avoid when the application first boots up.)
- $rootScope.$$postDigest(function() {
- $rootScope.$$postDigest(function() {
- rootAnimateState.running = false;
- });
- });
-
- var classNameFilter = $animateProvider.classNameFilter();
- var isAnimatableClassName = !classNameFilter
- ? function() { return true; }
- : function(className) {
- return classNameFilter.test(className);
- };
-
- function blockElementAnimations(element) {
- var data = element.data(NG_ANIMATE_STATE) || {};
- data.running = true;
- element.data(NG_ANIMATE_STATE, data);
+ function init(element, options) {
+ var node = getDomNode(element);
+ if (!node || !node.parentNode) {
+ return closeAndReturnNoopAnimator();
}
- function lookup(name) {
- if (name) {
- var matches = [],
- flagMap = {},
- classes = name.substr(1).split('.');
+ options = prepareAnimationOptions(options);
- //the empty string value is the default animation
- //operation which performs CSS transition and keyframe
- //animations sniffing. This is always included for each
- //element animation procedure if the browser supports
- //transitions and/or keyframe animations. The default
- //animation is added to the top of the list to prevent
- //any previous animations from affecting the element styling
- //prior to the element being animated.
- if ($sniffer.transitions || $sniffer.animations) {
- matches.push($injector.get(selectors['']));
- }
+ var temporaryStyles = [];
+ var classes = element.attr('class');
+ var styles = packageStyles(options);
+ var animationClosed;
+ var animationPaused;
+ var animationCompleted;
+ var runner;
+ var runnerHost;
+ var maxDelay;
+ var maxDelayTime;
+ var maxDuration;
+ var maxDurationTime;
- for(var i=0; i < classes.length; i++) {
- var klass = classes[i],
- selectorFactoryName = selectors[klass];
- if(selectorFactoryName && !flagMap[klass]) {
- matches.push($injector.get(selectorFactoryName));
- flagMap[klass] = true;
- }
- }
- return matches;
- }
+ if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {
+ return closeAndReturnNoopAnimator();
}
- function animationRunner(element, animationEvent, 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
- var node = element[0];
- if(!node) {
- return;
+ var method = options.event && isArray(options.event)
+ ? options.event.join(' ')
+ : options.event;
+
+ var isStructural = method && options.structural;
+ var structuralClassName = '';
+ var addRemoveClassName = '';
+
+ if (isStructural) {
+ structuralClassName = pendClasses(method, 'ng-', true);
+ } else if (method) {
+ structuralClassName = method;
+ }
+
+ if (options.addClass) {
+ addRemoveClassName += pendClasses(options.addClass, '-add');
+ }
+
+ if (options.removeClass) {
+ if (addRemoveClassName.length) {
+ addRemoveClassName += ' ';
}
+ addRemoveClassName += pendClasses(options.removeClass, '-remove');
+ }
- var isSetClassOperation = animationEvent == 'setClass';
- var isClassBased = isSetClassOperation ||
- animationEvent == 'addClass' ||
- animationEvent == 'removeClass';
+ // there may be a situation where a structural animation is combined together
+ // with CSS classes that need to resolve before the animation is computed.
+ // However this means that there is no explicit CSS code to block the animation
+ // from happening (by setting 0s none in the class name). If this is the case
+ // we need to apply the classes before the first rAF so we know to continue if
+ // there actually is a detected transition or keyframe animation
+ if (options.applyClassesEarly && addRemoveClassName.length) {
+ applyAnimationClasses(element, options);
+ addRemoveClassName = '';
+ }
- var classNameAdd, classNameRemove;
- if(angular.isArray(className)) {
- classNameAdd = className[0];
- classNameRemove = className[1];
- className = classNameAdd + ' ' + classNameRemove;
- }
+ var setupClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
+ var fullClassName = classes + ' ' + setupClasses;
+ var activeClasses = pendClasses(setupClasses, '-active');
+ var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
+ var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
- var currentClassName = element.attr('class');
- var classes = currentClassName + ' ' + className;
- if(!isAnimatableClassName(classes)) {
- return;
- }
+ // there is no way we can trigger an animation if no styles and
+ // no classes are being applied which would then trigger a transition,
+ // unless there a is raw keyframe value that is applied to the element.
+ if (!containsKeyframeAnimation
+ && !hasToStyles
+ && !setupClasses) {
+ return closeAndReturnNoopAnimator();
+ }
- var beforeComplete = noop,
- beforeCancel = [],
- before = [],
- afterComplete = noop,
- afterCancel = [],
- after = [];
-
- var animationLookup = (' ' + classes).replace(/\s+/g,'.');
- forEach(lookup(animationLookup), function(animationFactory) {
- var created = registerAnimation(animationFactory, animationEvent);
- if(!created && isSetClassOperation) {
- registerAnimation(animationFactory, 'addClass');
- registerAnimation(animationFactory, 'removeClass');
- }
- });
-
- function registerAnimation(animationFactory, event) {
- var afterFn = animationFactory[event];
- var beforeFn = animationFactory['before' + event.charAt(0).toUpperCase() + event.substr(1)];
- if(afterFn || beforeFn) {
- if(event == 'leave') {
- beforeFn = afterFn;
- //when set as null then animation knows to skip this phase
- afterFn = null;
- }
- after.push({
- event : event, fn : afterFn
- });
- before.push({
- event : event, fn : beforeFn
- });
- return true;
- }
- }
-
- function run(fns, cancellations, allCompleteFn) {
- var animations = [];
- forEach(fns, function(animation) {
- animation.fn && animations.push(animation);
- });
-
- var count = 0;
- function afterAnimationComplete(index) {
- if(cancellations) {
- (cancellations[index] || noop)();
- if(++count < animations.length) return;
- cancellations = null;
- }
- allCompleteFn();
- }
-
- //The code below adds directly to the array in order to work with
- //both sync and async animations. Sync animations are when the done()
- //operation is called right away. DO NOT REFACTOR!
- forEach(animations, function(animation, index) {
- var progress = function() {
- afterAnimationComplete(index);
- };
- switch(animation.event) {
- case 'setClass':
- cancellations.push(animation.fn(element, classNameAdd, classNameRemove, progress));
- break;
- case 'addClass':
- cancellations.push(animation.fn(element, classNameAdd || className, progress));
- break;
- case 'removeClass':
- cancellations.push(animation.fn(element, classNameRemove || className, progress));
- break;
- default:
- cancellations.push(animation.fn(element, progress));
- break;
- }
- });
-
- if(cancellations && cancellations.length === 0) {
- allCompleteFn();
- }
- }
-
- return {
- node : node,
- event : animationEvent,
- className : className,
- isClassBased : isClassBased,
- isSetClassOperation : isSetClassOperation,
- before : function(allCompleteFn) {
- beforeComplete = allCompleteFn;
- run(before, beforeCancel, function() {
- beforeComplete = noop;
- allCompleteFn();
- });
- },
- after : function(allCompleteFn) {
- afterComplete = allCompleteFn;
- run(after, afterCancel, function() {
- afterComplete = noop;
- allCompleteFn();
- });
- },
- cancel : function() {
- if(beforeCancel) {
- forEach(beforeCancel, function(cancelFn) {
- (cancelFn || noop)(true);
- });
- beforeComplete(true);
- }
- if(afterCancel) {
- forEach(afterCancel, function(cancelFn) {
- (cancelFn || noop)(true);
- });
- afterComplete(true);
- }
- }
+ var cacheKey, stagger;
+ if (options.stagger > 0) {
+ var staggerVal = parseFloat(options.stagger);
+ stagger = {
+ transitionDelay: staggerVal,
+ animationDelay: staggerVal,
+ transitionDuration: 0,
+ animationDuration: 0
};
+ } else {
+ cacheKey = gcsHashFn(node, fullClassName);
+ stagger = computeCachedCssStaggerStyles(node, setupClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
}
- /**
- * @ngdoc service
- * @name $animate
- * @kind function
- *
- * @description
- * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations.
- * When any of these operations are run, the $animate service
- * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object)
- * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run.
- *
- * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives
- * will work out of the box without any extra configuration.
- *
- * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
- *
- * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
- *
- */
+ $$jqLite.addClass(element, setupClasses);
+
+ var applyOnlyDuration;
+
+ if (options.transitionStyle) {
+ var transitionStyle = [TRANSITION_PROP, options.transitionStyle];
+ applyInlineStyle(node, transitionStyle);
+ temporaryStyles.push(transitionStyle);
+ }
+
+ if (options.duration >= 0) {
+ applyOnlyDuration = node.style[TRANSITION_PROP].length > 0;
+ var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration);
+
+ // we set the duration so that it will be picked up by getComputedStyle later
+ applyInlineStyle(node, durationStyle);
+ temporaryStyles.push(durationStyle);
+ }
+
+ if (options.keyframeStyle) {
+ var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle];
+ applyInlineStyle(node, keyframeStyle);
+ temporaryStyles.push(keyframeStyle);
+ }
+
+ var itemIndex = stagger
+ ? options.staggerIndex >= 0
+ ? options.staggerIndex
+ : gcsLookup.count(cacheKey)
+ : 0;
+
+ var isFirst = itemIndex === 0;
+
+ // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY
+ // without causing any combination of transitions to kick in. By adding a negative delay value
+ // it forces the setup class' transition to end immediately. We later then remove the negative
+ // transition delay to allow for the transition to naturally do it's thing. The beauty here is
+ // that if there is no transition defined then nothing will happen and this will also allow
+ // other transitions to be stacked on top of each other without any chopping them out.
+ if (isFirst) {
+ blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
+ }
+
+ var timings = computeTimings(node, fullClassName, cacheKey);
+ var relativeDelay = timings.maxDelay;
+ maxDelay = Math.max(relativeDelay, 0);
+ maxDuration = timings.maxDuration;
+
+ var flags = {};
+ flags.hasTransitions = timings.transitionDuration > 0;
+ flags.hasAnimations = timings.animationDuration > 0;
+ flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty == 'all';
+ flags.applyTransitionDuration = hasToStyles && (
+ (flags.hasTransitions && !flags.hasTransitionAll)
+ || (flags.hasAnimations && !flags.hasTransitions));
+ flags.applyAnimationDuration = options.duration && flags.hasAnimations;
+ flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions);
+ flags.applyAnimationDelay = truthyTimingValue(options.delay) && flags.hasAnimations;
+ flags.recalculateTimingStyles = addRemoveClassName.length > 0;
+
+ if (flags.applyTransitionDuration || flags.applyAnimationDuration) {
+ maxDuration = options.duration ? parseFloat(options.duration) : maxDuration;
+
+ if (flags.applyTransitionDuration) {
+ flags.hasTransitions = true;
+ timings.transitionDuration = maxDuration;
+ applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0;
+ temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration));
+ }
+
+ if (flags.applyAnimationDuration) {
+ flags.hasAnimations = true;
+ timings.animationDuration = maxDuration;
+ temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration));
+ }
+ }
+
+ if (maxDuration === 0 && !flags.recalculateTimingStyles) {
+ return closeAndReturnNoopAnimator();
+ }
+
+ // we need to recalculate the delay value since we used a pre-emptive negative
+ // delay value and the delay value is required for the final event checking. This
+ // property will ensure that this will happen after the RAF phase has passed.
+ if (options.duration == null && timings.transitionDuration > 0) {
+ flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst;
+ }
+
+ maxDelayTime = maxDelay * ONE_SECOND;
+ maxDurationTime = maxDuration * ONE_SECOND;
+ if (!options.skipBlocking) {
+ flags.blockTransition = timings.transitionDuration > 0;
+ flags.blockKeyframeAnimation = timings.animationDuration > 0 &&
+ stagger.animationDelay > 0 &&
+ stagger.animationDuration === 0;
+ }
+
+ applyAnimationFromStyles(element, options);
+ if (!flags.blockTransition) {
+ blockTransitions(node, false);
+ }
+
+ applyBlocking(maxDuration);
+
+ // TODO(matsko): for 1.5 change this code to have an animator object for better debugging
return {
- /**
- * @ngdoc method
- * @name $animate#enter
- * @kind function
- *
- * @description
- * Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once
- * the animation is started, the following CSS classes will be present on the element for the duration of the animation:
- *
- * Below is a breakdown of each step that occurs during enter animation:
- *
- * | Animation Step | What the element class attribute looks like |
- * |----------------------------------------------------------------------------------------------|---------------------------------------------|
- * | 1. $animate.enter(...) is called | class="my-animation" |
- * | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" |
- * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
- * | 4. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" |
- * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" |
- * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-enter" |
- * | 7. the .ng-enter-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
- * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
- * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
- * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" |
- *
- * @param {DOMElement} element the element that will be the focus of the enter animation
- * @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
- * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
- */
- enter : function(element, parentElement, afterElement, doneCallback) {
- element = angular.element(element);
- parentElement = prepareElement(parentElement);
- afterElement = prepareElement(afterElement);
+ $$willAnimate: true,
+ end: endFn,
+ start: function() {
+ if (animationClosed) return;
- blockElementAnimations(element);
- $delegate.enter(element, parentElement, afterElement);
- $rootScope.$$postDigest(function() {
- element = stripCommentsFromElement(element);
- performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
- });
- },
+ runnerHost = {
+ end: endFn,
+ cancel: cancelFn,
+ resume: null, //this will be set during the start() phase
+ pause: null
+ };
- /**
- * @ngdoc method
- * @name $animate#leave
- * @kind function
- *
- * @description
- * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once
- * the animation is started, the following CSS classes will be added for the duration of the animation:
- *
- * Below is a breakdown of each step that occurs during leave animation:
- *
- * | Animation Step | What the element class attribute looks like |
- * |----------------------------------------------------------------------------------------------|---------------------------------------------|
- * | 1. $animate.leave(...) is called | class="my-animation" |
- * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
- * | 3. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" |
- * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" |
- * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-leave" |
- * | 6. the .ng-leave-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
- * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
- * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
- * | 9. The element is removed from the DOM | ... |
- * | 10. The doneCallback() callback is fired (if provided) | ... |
- *
- * @param {DOMElement} element the element that will be the focus of the leave animation
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
- */
- leave : function(element, doneCallback) {
- element = angular.element(element);
- cancelChildAnimations(element);
- blockElementAnimations(element);
- $rootScope.$$postDigest(function() {
- performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
- $delegate.leave(element);
- }, doneCallback);
- });
- },
+ runner = new $$AnimateRunner(runnerHost);
- /**
- * @ngdoc method
- * @name $animate#move
- * @kind function
- *
- * @description
- * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or
- * add the element directly after the afterElement element if present. Then the move animation will be run. Once
- * the animation is started, the following CSS classes will be added for the duration of the animation:
- *
- * Below is a breakdown of each step that occurs during move animation:
- *
- * | Animation Step | What the element class attribute looks like |
- * |----------------------------------------------------------------------------------------------|---------------------------------------------|
- * | 1. $animate.move(...) is called | class="my-animation" |
- * | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" |
- * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
- * | 4. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" |
- * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" |
- * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-move" |
- * | 7. the .ng-move-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
- * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
- * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
- * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" |
- *
- * @param {DOMElement} element the element that will be the focus of the move animation
- * @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
- * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
- */
- move : function(element, parentElement, afterElement, doneCallback) {
- element = angular.element(element);
- parentElement = prepareElement(parentElement);
- afterElement = prepareElement(afterElement);
+ waitUntilQuiet(start);
- cancelChildAnimations(element);
- blockElementAnimations(element);
- $delegate.move(element, parentElement, afterElement);
- $rootScope.$$postDigest(function() {
- element = stripCommentsFromElement(element);
- performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
- });
- },
-
- /**
- * @ngdoc method
- * @name $animate#addClass
- *
- * @description
- * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class.
- * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide
- * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions
- * or keyframes are defined on the -add or base CSS class).
- *
- * Below is a breakdown of each step that occurs during addClass animation:
- *
- * | Animation Step | What the element class attribute looks like |
- * |------------------------------------------------------------------------------------------------|---------------------------------------------|
- * | 1. $animate.addClass(element, 'super') is called | class="my-animation" |
- * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
- * | 3. the .super-add class are added to the element | class="my-animation ng-animate super-add" |
- * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-add" |
- * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate super-add" |
- * | 6. the .super, .super-add-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super super-add super-add-active" |
- * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation super super-add super-add-active" |
- * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" |
- * | 9. The super class is kept on the element | class="my-animation super" |
- * | 10. The doneCallback() callback is fired (if provided) | class="my-animation super" |
- *
- * @param {DOMElement} element the element that will be animated
- * @param {string} className the CSS class that will be added to the element and then animated
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
- */
- addClass : function(element, className, doneCallback) {
- element = angular.element(element);
- element = stripCommentsFromElement(element);
- performAnimation('addClass', className, element, null, null, function() {
- $delegate.addClass(element, className);
- }, doneCallback);
- },
-
- /**
- * @ngdoc method
- * @name $animate#removeClass
- *
- * @description
- * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value
- * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in
- * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if
- * no CSS transitions or keyframes are defined on the -remove or base CSS classes).
- *
- * Below is a breakdown of each step that occurs during removeClass animation:
- *
- * | Animation Step | What the element class attribute looks like |
- * |-----------------------------------------------------------------------------------------------|---------------------------------------------|
- * | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" |
- * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation super ng-animate" |
- * | 3. the .super-remove class are added to the element | class="my-animation super ng-animate super-remove"|
- * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" |
- * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation super ng-animate super-remove" |
- * | 6. the .super-remove-active and .ng-animate-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" |
- * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" |
- * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
- * | 9. The doneCallback() callback is fired (if provided) | class="my-animation" |
- *
- *
- * @param {DOMElement} element the element that will be animated
- * @param {string} className the CSS class that will be animated and then removed from the element
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
- */
- removeClass : function(element, className, doneCallback) {
- element = angular.element(element);
- element = stripCommentsFromElement(element);
- performAnimation('removeClass', className, element, null, null, function() {
- $delegate.removeClass(element, className);
- }, doneCallback);
- },
-
- /**
- *
- * @ngdoc function
- * @name $animate#setClass
- * @function
- * @description Adds and/or removes the given CSS classes to and from the element.
- * Once complete, the done() callback will be fired (if provided).
- * @param {DOMElement} element the element which will its CSS classes changed
- * removed from it
- * @param {string} add the CSS classes which will be added to the element
- * @param {string} remove the CSS class which will be removed from the element
- * @param {Function=} done the callback function (if provided) that will be fired after the
- * CSS classes have been set on the element
- */
- setClass : function(element, add, remove, doneCallback) {
- element = angular.element(element);
- element = stripCommentsFromElement(element);
- performAnimation('setClass', [add, remove], element, null, null, function() {
- $delegate.setClass(element, add, remove);
- }, doneCallback);
- },
-
- /**
- * @ngdoc method
- * @name $animate#enabled
- * @kind function
- *
- * @param {boolean=} value If provided then set the animation on or off.
- * @param {DOMElement=} element If provided then the element will be used to represent the enable/disable operation
- * @return {boolean} Current animation state.
- *
- * @description
- * Globally enables/disables animations.
- *
- */
- enabled : function(value, element) {
- switch(arguments.length) {
- case 2:
- if(value) {
- cleanup(element);
- } else {
- var data = element.data(NG_ANIMATE_STATE) || {};
- data.disabled = true;
- element.data(NG_ANIMATE_STATE, data);
- }
- break;
-
- case 1:
- rootAnimateState.disabled = !value;
- break;
-
- default:
- value = !rootAnimateState.disabled;
- break;
- }
- return !!value;
- }
+ // we don't have access to pause/resume the animation
+ // since it hasn't run yet. AnimateRunner will therefore
+ // set noop functions for resume and pause and they will
+ // later be overridden once the animation is triggered
+ return runner;
+ }
};
- /*
- all animations call this shared animation triggering function internally.
- The animationEvent variable refers to the JavaScript animation event that will be triggered
- and the className value is the name of the animation that will be applied within the
- CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
- and the onComplete callback will be fired once the animation is fully complete.
- */
- function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
+ function endFn() {
+ close();
+ }
- var runner = animationRunner(element, animationEvent, className);
- if(!runner) {
- fireDOMOperation();
- fireBeforeCallbackAsync();
- fireAfterCallbackAsync();
- closeAnimation();
+ function cancelFn() {
+ close(true);
+ }
+
+ function close(rejected) { // jshint ignore:line
+ // if the promise has been called already then we shouldn't close
+ // the animation again
+ if (animationClosed || (animationCompleted && animationPaused)) return;
+ animationClosed = true;
+ animationPaused = false;
+
+ $$jqLite.removeClass(element, setupClasses);
+ $$jqLite.removeClass(element, activeClasses);
+
+ blockKeyframeAnimations(node, false);
+ blockTransitions(node, false);
+
+ forEach(temporaryStyles, function(entry) {
+ // There is only one way to remove inline style properties entirely from elements.
+ // By using `removeProperty` this works, but we need to convert camel-cased CSS
+ // styles down to hyphenated values.
+ node.style[entry[0]] = '';
+ });
+
+ applyAnimationClasses(element, options);
+ applyAnimationStyles(element, options);
+
+ // the reason why we have this option is to allow a synchronous closing callback
+ // that is fired as SOON as the animation ends (when the CSS is removed) or if
+ // the animation never takes off at all. A good example is a leave animation since
+ // the element must be removed just after the animation is over or else the element
+ // will appear on screen for one animation frame causing an overbearing flicker.
+ if (options.onDone) {
+ options.onDone();
+ }
+
+ // if the preparation function fails then the promise is not setup
+ if (runner) {
+ runner.complete(!rejected);
+ }
+ }
+
+ function applyBlocking(duration) {
+ if (flags.blockTransition) {
+ blockTransitions(node, duration);
+ }
+
+ if (flags.blockKeyframeAnimation) {
+ blockKeyframeAnimations(node, !!duration);
+ }
+ }
+
+ function closeAndReturnNoopAnimator() {
+ runner = new $$AnimateRunner({
+ end: endFn,
+ cancel: cancelFn
+ });
+
+ close();
+
+ return {
+ $$willAnimate: false,
+ start: function() {
+ return runner;
+ },
+ end: endFn
+ };
+ }
+
+ function start() {
+ if (animationClosed) return;
+ if (!node.parentNode) {
+ close();
return;
}
- className = runner.className;
- var elementEvents = angular.element._data(runner.node);
- elementEvents = elementEvents && elementEvents.events;
+ var startTime, events = [];
- if (!parentElement) {
- parentElement = afterElement ? afterElement.parent() : element.parent();
- }
-
- 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;
- if (runner.isClassBased) {
- skipAnimations = ngAnimateState.running ||
- ngAnimateState.disabled ||
- (lastAnimation && !lastAnimation.isClassBased);
- }
-
- //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 an animation was found.
- if (skipAnimations || animationsDisabled(element, parentElement)) {
- fireDOMOperation();
- fireBeforeCallbackAsync();
- fireAfterCallbackAsync();
- closeAnimation();
- return;
- }
-
- var skipAnimation = false;
- if(totalActiveAnimations > 0) {
- var animationsToCancel = [];
- if(!runner.isClassBased) {
- if(animationEvent == 'leave' && runningAnimations['ng-leave']) {
- skipAnimation = true;
- } else {
- //cancel all animations when a structural animation takes place
- for(var klass in runningAnimations) {
- animationsToCancel.push(runningAnimations[klass]);
- cleanup(element, klass);
- }
- runningAnimations = {};
- totalActiveAnimations = 0;
+ // even though we only pause keyframe animations here the pause flag
+ // will still happen when transitions are used. Only the transition will
+ // not be paused since that is not possible. If the animation ends when
+ // paused then it will not complete until unpaused or cancelled.
+ var playPause = function(playAnimation) {
+ if (!animationCompleted) {
+ animationPaused = !playAnimation;
+ if (timings.animationDuration) {
+ var value = blockKeyframeAnimations(node, animationPaused);
+ animationPaused
+ ? temporaryStyles.push(value)
+ : removeFromArray(temporaryStyles, value);
}
- } else if(lastAnimation.event == 'setClass') {
- animationsToCancel.push(lastAnimation);
- cleanup(element, className);
+ } else if (animationPaused && playAnimation) {
+ animationPaused = false;
+ close();
}
- else if(runningAnimations[className]) {
- var current = runningAnimations[className];
- if(current.event == animationEvent) {
- skipAnimation = true;
- } else {
- animationsToCancel.push(current);
- cleanup(element, className);
- }
- }
-
- if(animationsToCancel.length > 0) {
- forEach(animationsToCancel, function(operation) {
- operation.cancel();
- });
- }
- }
-
- if(runner.isClassBased && !runner.isSetClassOperation && !skipAnimation) {
- skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR
- }
-
- if(skipAnimation) {
- fireDOMOperation();
- fireBeforeCallbackAsync();
- fireAfterCallbackAsync();
- fireDoneCallbackAsync();
- return;
- }
-
- if(animationEvent == 'leave') {
- //there's no need to ever remove the listener since the element
- //will be removed (destroyed) after the leave animation ends or
- //is cancelled midway
- element.one('$destroy', function(e) {
- var element = angular.element(this);
- var state = element.data(NG_ANIMATE_STATE);
- if(state) {
- var activeLeaveAnimation = state.active['ng-leave'];
- if(activeLeaveAnimation) {
- activeLeaveAnimation.cancel();
- cleanup(element, 'ng-leave');
- }
- }
- });
- }
-
- //the ng-animate class does nothing, but it's here to allow for
- //parent animations to find and cancel child animations when needed
- element.addClass(NG_ANIMATE_CLASS_NAME);
-
- var localAnimationCount = globalAnimationCounter++;
- totalActiveAnimations++;
- runningAnimations[className] = runner;
-
- element.data(NG_ANIMATE_STATE, {
- last : runner,
- active : runningAnimations,
- index : localAnimationCount,
- totalActive : totalActiveAnimations
- });
-
- //first we run the before animations and when all of those are complete
- //then we perform the DOM operation and run the next set of animations
- fireBeforeCallbackAsync();
- runner.before(function(cancelled) {
- var data = element.data(NG_ANIMATE_STATE);
- cancelled = cancelled ||
- !data || !data.active[className] ||
- (runner.isClassBased && data.active[className].event != animationEvent);
-
- fireDOMOperation();
- if(cancelled === true) {
- closeAnimation();
- } else {
- fireAfterCallbackAsync();
- runner.after(closeAnimation);
- }
- });
-
- function fireDOMCallback(animationPhase) {
- var eventName = '$animate:' + animationPhase;
- if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
- $$asyncCallback(function() {
- element.triggerHandler(eventName, {
- event : animationEvent,
- className : className
- });
- });
- }
- }
-
- function fireBeforeCallbackAsync() {
- fireDOMCallback('before');
- }
-
- function fireAfterCallbackAsync() {
- fireDOMCallback('after');
- }
-
- function fireDoneCallbackAsync() {
- fireDOMCallback('close');
- if(doneCallback) {
- $$asyncCallback(function() {
- doneCallback();
- });
- }
- }
-
- //it is less complicated to use a flag than managing and canceling
- //timeouts containing multiple callbacks.
- function fireDOMOperation() {
- if(!fireDOMOperation.hasBeenRun) {
- fireDOMOperation.hasBeenRun = true;
- domOperation();
- }
- }
-
- function closeAnimation() {
- if(!closeAnimation.hasBeenRun) {
- closeAnimation.hasBeenRun = true;
- var data = element.data(NG_ANIMATE_STATE);
- if(data) {
- /* only structural animations wait for reflow before removing an
- animation, but class-based animations don't. An example of this
- failing would be when a parent HTML tag has a ng-class attribute
- causing ALL directives below to skip animations during the digest */
- if(runner && runner.isClassBased) {
- cleanup(element, className);
- } else {
- $$asyncCallback(function() {
- var data = element.data(NG_ANIMATE_STATE) || {};
- if(localAnimationCount == data.index) {
- cleanup(element, className, animationEvent);
- }
- });
- element.data(NG_ANIMATE_STATE, data);
- }
- }
- fireDoneCallbackAsync();
- }
- }
- }
-
- function cancelChildAnimations(element) {
- var node = extractElementNode(element);
- if (node) {
- var nodes = angular.isFunction(node.getElementsByClassName) ?
- node.getElementsByClassName(NG_ANIMATE_CLASS_NAME) :
- node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME);
- forEach(nodes, function(element) {
- element = angular.element(element);
- var data = element.data(NG_ANIMATE_STATE);
- if(data && data.active) {
- forEach(data.active, function(runner) {
- runner.cancel();
- });
- }
- });
- }
- }
-
- function cleanup(element, className) {
- if(isMatchingElement(element, $rootElement)) {
- if(!rootAnimateState.disabled) {
- rootAnimateState.running = false;
- rootAnimateState.structural = false;
- }
- } else if(className) {
- var data = element.data(NG_ANIMATE_STATE) || {};
-
- var removeAnimations = className === true;
- if(!removeAnimations && data.active && data.active[className]) {
- data.totalActive--;
- delete data.active[className];
- }
-
- if(removeAnimations || !data.totalActive) {
- element.removeClass(NG_ANIMATE_CLASS_NAME);
- element.removeData(NG_ANIMATE_STATE);
- }
- }
- }
-
- function animationsDisabled(element, parentElement) {
- if (rootAnimateState.disabled) {
- return true;
- }
-
- if (isMatchingElement(element, $rootElement)) {
- return rootAnimateState.running;
- }
-
- var allowChildAnimations, parentRunningAnimation, hasParent;
- do {
- //the element did not reach the root element which means that it
- //is not apart of the DOM. Therefore there is no reason to do
- //any animations on it
- if (parentElement.length === 0) break;
-
- var isRoot = isMatchingElement(parentElement, $rootElement);
- var state = isRoot ? rootAnimateState : (parentElement.data(NG_ANIMATE_STATE) || {});
- if (state.disabled) {
- return true;
- }
-
- //no matter what, for an animation to work it must reach the root element
- //this implies that the element is attached to the DOM when the animation is run
- if (isRoot) {
- hasParent = true;
- }
-
- //once a flag is found that is strictly false then everything before
- //it will be discarded and all child animations will be restricted
- if (allowChildAnimations !== false) {
- var animateChildrenFlag = parentElement.data(NG_ANIMATE_CHILDREN);
- if(angular.isDefined(animateChildrenFlag)) {
- allowChildAnimations = animateChildrenFlag;
- }
- }
-
- parentRunningAnimation = parentRunningAnimation ||
- state.running ||
- (state.last && !state.last.isClassBased);
- }
- while(parentElement = parentElement.parent());
-
- return !hasParent || (!allowChildAnimations && parentRunningAnimation);
- }
- }]);
-
- $animateProvider.register('', ['$window', '$sniffer', '$timeout', '$$animateReflow',
- function($window, $sniffer, $timeout, $$animateReflow) {
- // Detect proper transitionend/animationend event names.
- var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
-
- // If unprefixed events are not supported but webkit-prefixed are, use the latter.
- // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
- // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
- // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
- // Register both events in case `window.onanimationend` is not supported because of that,
- // do the same for `transitionend` as Safari is likely to exhibit similar behavior.
- // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
- // therefore there is no reason to test anymore for other vendor prefixes: http://caniuse.com/#search=transition
- if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
- CSS_PREFIX = '-webkit-';
- TRANSITION_PROP = 'WebkitTransition';
- TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
- } else {
- TRANSITION_PROP = 'transition';
- TRANSITIONEND_EVENT = 'transitionend';
- }
-
- if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
- CSS_PREFIX = '-webkit-';
- ANIMATION_PROP = 'WebkitAnimation';
- ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
- } else {
- ANIMATION_PROP = 'animation';
- ANIMATIONEND_EVENT = 'animationend';
- }
-
- var DURATION_KEY = 'Duration';
- var PROPERTY_KEY = 'Property';
- var DELAY_KEY = 'Delay';
- var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
- var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
- var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
- var NG_ANIMATE_BLOCK_CLASS_NAME = 'ng-animate-block-transitions';
- var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
- var CLOSING_TIME_BUFFER = 1.5;
- var ONE_SECOND = 1000;
-
- var lookupCache = {};
- var parentCounter = 0;
- var animationReflowQueue = [];
- var cancelAnimationReflow;
- function afterReflow(element, callback) {
- if(cancelAnimationReflow) {
- cancelAnimationReflow();
- }
- animationReflowQueue.push(callback);
- cancelAnimationReflow = $$animateReflow(function() {
- forEach(animationReflowQueue, function(fn) {
- fn();
- });
-
- animationReflowQueue = [];
- cancelAnimationReflow = null;
- lookupCache = {};
- });
- }
-
- var closingTimer = null;
- var closingTimestamp = 0;
- var animationElementQueue = [];
- function animationCloseHandler(element, totalTime) {
- var node = extractElementNode(element);
- element = angular.element(node);
-
- //this item will be garbage collected by the closing
- //animation timeout
- animationElementQueue.push(element);
-
- //but it may not need to cancel out the existing timeout
- //if the timestamp is less than the previous one
- var futureTimestamp = Date.now() + totalTime;
- if(futureTimestamp <= closingTimestamp) {
- return;
- }
-
- $timeout.cancel(closingTimer);
-
- closingTimestamp = futureTimestamp;
- closingTimer = $timeout(function() {
- closeAllAnimations(animationElementQueue);
- animationElementQueue = [];
- }, totalTime, false);
- }
-
- function closeAllAnimations(elements) {
- forEach(elements, function(element) {
- var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
- if(elementData) {
- (elementData.closeAnimationFn || noop)();
- }
- });
- }
-
- function getElementAnimationDetails(element, cacheKey) {
- var data = cacheKey ? lookupCache[cacheKey] : null;
- if(!data) {
- var transitionDuration = 0;
- var transitionDelay = 0;
- var animationDuration = 0;
- var animationDelay = 0;
- var transitionDelayStyle;
- var animationDelayStyle;
- var transitionDurationStyle;
- var transitionPropertyStyle;
-
- //we want all the styles defined before and after
- forEach(element, function(element) {
- if (element.nodeType == ELEMENT_NODE) {
- var elementStyles = $window.getComputedStyle(element) || {};
-
- transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
-
- transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration);
-
- transitionPropertyStyle = elementStyles[TRANSITION_PROP + PROPERTY_KEY];
-
- transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
-
- transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay);
-
- animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
-
- animationDelay = Math.max(parseMaxTime(animationDelayStyle), animationDelay);
-
- var aDuration = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]);
-
- if(aDuration > 0) {
- aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1;
- }
-
- animationDuration = Math.max(aDuration, animationDuration);
- }
- });
- data = {
- total : 0,
- transitionPropertyStyle: transitionPropertyStyle,
- transitionDurationStyle: transitionDurationStyle,
- transitionDelayStyle: transitionDelayStyle,
- transitionDelay: transitionDelay,
- transitionDuration: transitionDuration,
- animationDelayStyle: animationDelayStyle,
- animationDelay: animationDelay,
- animationDuration: animationDuration
- };
- if(cacheKey) {
- lookupCache[cacheKey] = data;
- }
- }
- return data;
- }
-
- function parseMaxTime(str) {
- var maxValue = 0;
- var values = angular.isString(str) ?
- str.split(/\s*,\s*/) :
- [];
- forEach(values, function(value) {
- maxValue = Math.max(parseFloat(value) || 0, maxValue);
- });
- return maxValue;
- }
-
- function getCacheKey(element) {
- var parentElement = element.parent();
- var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY);
- if(!parentID) {
- parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
- parentID = parentCounter;
- }
- return parentID + '-' + extractElementNode(element).getAttribute('class');
- }
-
- function animateSetup(animationEvent, element, className, calculationDecorator) {
- var cacheKey = getCacheKey(element);
- var eventCacheKey = cacheKey + ' ' + className;
- var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
-
- var stagger = {};
- if(itemIndex > 0) {
- var staggerClassName = className + '-stagger';
- var staggerCacheKey = cacheKey + ' ' + staggerClassName;
- var applyClasses = !lookupCache[staggerCacheKey];
-
- applyClasses && element.addClass(staggerClassName);
-
- stagger = getElementAnimationDetails(element, staggerCacheKey);
-
- applyClasses && element.removeClass(staggerClassName);
- }
-
- /* the animation itself may need to add/remove special CSS classes
- * before calculating the anmation styles */
- calculationDecorator = calculationDecorator ||
- function(fn) { return fn(); };
-
- element.addClass(className);
-
- var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
-
- var timings = calculationDecorator(function() {
- return getElementAnimationDetails(element, eventCacheKey);
- });
-
- var transitionDuration = timings.transitionDuration;
- var animationDuration = timings.animationDuration;
- if(transitionDuration === 0 && animationDuration === 0) {
- element.removeClass(className);
- return false;
- }
-
- element.data(NG_ANIMATE_CSS_DATA_KEY, {
- running : formerData.running || 0,
- itemIndex : itemIndex,
- stagger : stagger,
- timings : timings,
- closeAnimationFn : noop
- });
-
- //temporarily disable the transition so that the enter styles
- //don't animate twice (this is here to avoid a bug in Chrome/FF).
- var isCurrentlyAnimating = formerData.running > 0 || animationEvent == 'setClass';
- if(transitionDuration > 0) {
- blockTransitions(element, className, isCurrentlyAnimating);
- }
-
- //staggering keyframe animations work by adjusting the `animation-delay` CSS property
- //on the given element, however, the delay value can only calculated after the reflow
- //since by that time $animate knows how many elements are being animated. Therefore,
- //until the reflow occurs the element needs to be blocked (where the keyframe animation
- //is set to `none 0s`). This blocking mechanism should only be set for when a stagger
- //animation is detected and when the element item index is greater than 0.
- if(animationDuration > 0 && stagger.animationDelay > 0 && stagger.animationDuration === 0) {
- blockKeyframeAnimations(element);
- }
-
- return true;
- }
-
- function isStructuralAnimation(className) {
- return className == 'ng-enter' || className == 'ng-move' || className == 'ng-leave';
- }
-
- function blockTransitions(element, className, isAnimating) {
- if(isStructuralAnimation(className) || !isAnimating) {
- extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
- } else {
- element.addClass(NG_ANIMATE_BLOCK_CLASS_NAME);
- }
- }
-
- function blockKeyframeAnimations(element) {
- extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
- }
-
- function unblockTransitions(element, className) {
- var prop = TRANSITION_PROP + PROPERTY_KEY;
- var node = extractElementNode(element);
- if(node.style[prop] && node.style[prop].length > 0) {
- node.style[prop] = '';
- }
- element.removeClass(NG_ANIMATE_BLOCK_CLASS_NAME);
- }
-
- function unblockKeyframeAnimations(element) {
- var prop = ANIMATION_PROP;
- var node = extractElementNode(element);
- if(node.style[prop] && node.style[prop].length > 0) {
- node.style[prop] = '';
- }
- }
-
- function animateRun(animationEvent, element, className, activeAnimationComplete) {
- var node = extractElementNode(element);
- var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
- if(node.getAttribute('class').indexOf(className) == -1 || !elementData) {
- activeAnimationComplete();
- return;
- }
-
- var activeClassName = '';
- forEach(className.split(' '), function(klass, i) {
- activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
- });
-
- var stagger = elementData.stagger;
- var timings = elementData.timings;
- var itemIndex = elementData.itemIndex;
- var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
- var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
- var maxDelayTime = maxDelay * ONE_SECOND;
-
- var startTime = Date.now();
- var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
-
- var style = '', appliedStyles = [];
- if(timings.transitionDuration > 0) {
- var propertyStyle = timings.transitionPropertyStyle;
- if(propertyStyle.indexOf('all') == -1) {
- style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';';
- style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ';';
- appliedStyles.push(CSS_PREFIX + 'transition-property');
- appliedStyles.push(CSS_PREFIX + 'transition-duration');
- }
- }
-
- if(itemIndex > 0) {
- if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
- var delayStyle = timings.transitionDelayStyle;
- style += CSS_PREFIX + 'transition-delay: ' +
- prepareStaggerDelay(delayStyle, stagger.transitionDelay, itemIndex) + '; ';
- appliedStyles.push(CSS_PREFIX + 'transition-delay');
- }
-
- if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
- style += CSS_PREFIX + 'animation-delay: ' +
- prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, itemIndex) + '; ';
- appliedStyles.push(CSS_PREFIX + 'animation-delay');
- }
- }
-
- if(appliedStyles.length > 0) {
- //the element being animated may sometimes contain comment nodes in
- //the jqLite object, so we're safe to use a single variable to house
- //the styles since there is always only one element being animated
- var oldStyle = node.getAttribute('style') || '';
- node.setAttribute('style', oldStyle + '; ' + style);
- }
-
- element.on(css3AnimationEvents, onAnimationProgress);
- element.addClass(activeClassName);
- elementData.closeAnimationFn = function() {
- onEnd();
- activeAnimationComplete();
};
- var staggerTime = itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
- var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
- var totalTime = (staggerTime + animationTime) * ONE_SECOND;
+ // checking the stagger duration prevents an accidently cascade of the CSS delay style
+ // being inherited from the parent. If the transition duration is zero then we can safely
+ // rely that the delay value is an intential stagger delay style.
+ var maxStagger = itemIndex > 0
+ && ((timings.transitionDuration && stagger.transitionDuration === 0) ||
+ (timings.animationDuration && stagger.animationDuration === 0))
+ && Math.max(stagger.animationDelay, stagger.transitionDelay);
+ if (maxStagger) {
+ $timeout(triggerAnimationStart,
+ Math.floor(maxStagger * itemIndex * ONE_SECOND),
+ false);
+ } else {
+ triggerAnimationStart();
+ }
- elementData.running++;
- animationCloseHandler(element, totalTime);
- return onEnd;
+ // this will decorate the existing promise runner with pause/resume methods
+ runnerHost.resume = function() {
+ playPause(true);
+ };
- // This will automatically be called by $animate so
- // there is no need to attach this internally to the
- // timeout done method.
- function onEnd(cancelled) {
- element.off(css3AnimationEvents, onAnimationProgress);
- element.removeClass(activeClassName);
- animateClose(element, className);
- var node = extractElementNode(element);
- for (var i in appliedStyles) {
- node.style.removeProperty(appliedStyles[i]);
+ runnerHost.pause = function() {
+ playPause(false);
+ };
+
+ function triggerAnimationStart() {
+ // just incase a stagger animation kicks in when the animation
+ // itself was cancelled entirely
+ if (animationClosed) return;
+
+ applyBlocking(false);
+
+ forEach(temporaryStyles, function(entry) {
+ var key = entry[0];
+ var value = entry[1];
+ node.style[key] = value;
+ });
+
+ applyAnimationClasses(element, options);
+ $$jqLite.addClass(element, activeClasses);
+
+ if (flags.recalculateTimingStyles) {
+ fullClassName = node.className + ' ' + setupClasses;
+ cacheKey = gcsHashFn(node, fullClassName);
+
+ timings = computeTimings(node, fullClassName, cacheKey);
+ relativeDelay = timings.maxDelay;
+ maxDelay = Math.max(relativeDelay, 0);
+ maxDuration = timings.maxDuration;
+
+ if (maxDuration === 0) {
+ close();
+ return;
+ }
+
+ flags.hasTransitions = timings.transitionDuration > 0;
+ flags.hasAnimations = timings.animationDuration > 0;
}
+
+ if (flags.applyTransitionDelay || flags.applyAnimationDelay) {
+ relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
+ ? parseFloat(options.delay)
+ : relativeDelay;
+
+ maxDelay = Math.max(relativeDelay, 0);
+
+ var delayStyle;
+ if (flags.applyTransitionDelay) {
+ timings.transitionDelay = relativeDelay;
+ delayStyle = getCssDelayStyle(relativeDelay);
+ temporaryStyles.push(delayStyle);
+ node.style[delayStyle[0]] = delayStyle[1];
+ }
+
+ if (flags.applyAnimationDelay) {
+ timings.animationDelay = relativeDelay;
+ delayStyle = getCssDelayStyle(relativeDelay, true);
+ temporaryStyles.push(delayStyle);
+ node.style[delayStyle[0]] = delayStyle[1];
+ }
+ }
+
+ maxDelayTime = maxDelay * ONE_SECOND;
+ maxDurationTime = maxDuration * ONE_SECOND;
+
+ if (options.easing) {
+ var easeProp, easeVal = options.easing;
+ if (flags.hasTransitions) {
+ easeProp = TRANSITION_PROP + TIMING_KEY;
+ temporaryStyles.push([easeProp, easeVal]);
+ node.style[easeProp] = easeVal;
+ }
+ if (flags.hasAnimations) {
+ easeProp = ANIMATION_PROP + TIMING_KEY;
+ temporaryStyles.push([easeProp, easeVal]);
+ node.style[easeProp] = easeVal;
+ }
+ }
+
+ if (timings.transitionDuration) {
+ events.push(TRANSITIONEND_EVENT);
+ }
+
+ if (timings.animationDuration) {
+ events.push(ANIMATIONEND_EVENT);
+ }
+
+ startTime = Date.now();
+ element.on(events.join(' '), onAnimationProgress);
+ $timeout(onAnimationExpired, maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime);
+
+ applyAnimationToStyles(element, options);
+ }
+
+ function onAnimationExpired() {
+ // although an expired animation is a failed animation, getting to
+ // this outcome is very easy if the CSS code screws up. Therefore we
+ // should still continue normally as if the animation completed correctly.
+ close();
}
function onAnimationProgress(event) {
@@ -1493,197 +1280,2442 @@ angular.module('ngAnimate', ['ng'])
* We're checking to see if the timeStamp surpasses the expected delay,
* but we're using elapsedTime instead of the timeStamp on the 2nd
* pre-condition since animations sometimes close off early */
- if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
- activeAnimationComplete();
+ if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
+ // we set this flag to ensure that if the transition is paused then, when resumed,
+ // the animation will automatically close itself since transitions cannot be paused.
+ animationCompleted = true;
+ close();
}
}
}
+ }
+ }];
+}];
- function prepareStaggerDelay(delayStyle, staggerDelay, index) {
- var style = '';
- forEach(delayStyle.split(','), function(val, i) {
- style += (i > 0 ? ',' : '') +
- (index * staggerDelay + parseInt(val, 10)) + 's';
- });
- return style;
- }
+var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) {
+ $$animationProvider.drivers.push('$$animateCssDriver');
- function animateBefore(animationEvent, element, className, calculationDecorator) {
- if(animateSetup(animationEvent, element, className, calculationDecorator)) {
- return function(cancelled) {
- cancelled && animateClose(element, className);
- };
+ var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';
+ var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';
+
+ var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
+ var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
+
+ this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$document', '$sniffer',
+ function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $document, $sniffer) {
+
+ // only browsers that support these properties can render animations
+ if (!$sniffer.animations && !$sniffer.transitions) return noop;
+
+ var bodyNode = getDomNode($document).body;
+ var rootNode = getDomNode($rootElement);
+
+ var rootBodyElement = jqLite(bodyNode.parentNode === rootNode ? bodyNode : rootNode);
+
+ return function initDriverFn(animationDetails) {
+ return animationDetails.from && animationDetails.to
+ ? prepareFromToAnchorAnimation(animationDetails.from,
+ animationDetails.to,
+ animationDetails.classes,
+ animationDetails.anchors)
+ : prepareRegularAnimation(animationDetails);
+ };
+
+ function filterCssClasses(classes) {
+ //remove all the `ng-` stuff
+ return classes.replace(/\bng-\S+\b/g, '');
+ }
+
+ function getUniqueValues(a, b) {
+ if (isString(a)) a = a.split(' ');
+ if (isString(b)) b = b.split(' ');
+ return a.filter(function(val) {
+ return b.indexOf(val) === -1;
+ }).join(' ');
+ }
+
+ function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
+ var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
+ var startingClasses = filterCssClasses(getClassVal(clone));
+
+ outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
+ inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
+
+ clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME);
+
+ rootBodyElement.append(clone);
+
+ var animatorIn, animatorOut = prepareOutAnimation();
+
+ // the user may not end up using the `out` animation and
+ // only making use of the `in` animation or vice-versa.
+ // In either case we should allow this and not assume the
+ // animation is over unless both animations are not used.
+ if (!animatorOut) {
+ animatorIn = prepareInAnimation();
+ if (!animatorIn) {
+ return end();
}
}
- function animateAfter(animationEvent, element, className, afterAnimationComplete) {
- if(element.data(NG_ANIMATE_CSS_DATA_KEY)) {
- return animateRun(animationEvent, element, className, afterAnimationComplete);
- } else {
- animateClose(element, className);
- afterAnimationComplete();
- }
- }
-
- function animate(animationEvent, element, className, animationComplete) {
- //If the animateSetup function doesn't bother returning a
- //cancellation function then it means that there is no animation
- //to perform at all
- var preReflowCancellation = animateBefore(animationEvent, element, className);
- if(!preReflowCancellation) {
- animationComplete();
- return;
- }
-
- //There are two cancellation functions: one is before the first
- //reflow animation and the second is during the active state
- //animation. The first function will take care of removing the
- //data from the element which will not make the 2nd animation
- //happen in the first place
- var cancel = preReflowCancellation;
- afterReflow(element, function() {
- unblockTransitions(element, className);
- unblockKeyframeAnimations(element);
- //once the reflow is complete then we point cancel to
- //the new cancellation function which will remove all of the
- //animation properties from the active animation
- cancel = animateAfter(animationEvent, element, className, animationComplete);
- });
-
- return function(cancelled) {
- (cancel || noop)(cancelled);
- };
- }
-
- function animateClose(element, className) {
- element.removeClass(className);
- var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
- if(data) {
- if(data.running) {
- data.running--;
- }
- if(!data.running || data.running === 0) {
- element.removeData(NG_ANIMATE_CSS_DATA_KEY);
- }
- }
- }
+ var startingAnimator = animatorOut || animatorIn;
return {
- enter : function(element, animationCompleted) {
- return animate('enter', element, 'ng-enter', animationCompleted);
- },
+ start: function() {
+ var runner;
- leave : function(element, animationCompleted) {
- return animate('leave', element, 'ng-leave', animationCompleted);
- },
-
- move : function(element, animationCompleted) {
- return animate('move', element, 'ng-move', animationCompleted);
- },
-
- beforeSetClass : function(element, add, remove, animationCompleted) {
- var className = suffixClasses(remove, '-remove') + ' ' +
- suffixClasses(add, '-add');
- var cancellationMethod = animateBefore('setClass', element, className, function(fn) {
- /* when classes are removed from an element then the transition style
- * that is applied is the transition defined on the element without the
- * CSS class being there. This is how CSS3 functions outside of ngAnimate.
- * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
- var klass = element.attr('class');
- element.removeClass(remove);
- element.addClass(add);
- var timings = fn();
- element.attr('class', klass);
- return timings;
+ var currentAnimation = startingAnimator.start();
+ currentAnimation.done(function() {
+ currentAnimation = null;
+ if (!animatorIn) {
+ animatorIn = prepareInAnimation();
+ if (animatorIn) {
+ currentAnimation = animatorIn.start();
+ currentAnimation.done(function() {
+ currentAnimation = null;
+ end();
+ runner.complete();
+ });
+ return currentAnimation;
+ }
+ }
+ // in the event that there is no `in` animation
+ end();
+ runner.complete();
});
- if(cancellationMethod) {
- afterReflow(element, function() {
- unblockTransitions(element, className);
- unblockKeyframeAnimations(element);
- animationCompleted();
- });
- return cancellationMethod;
- }
- animationCompleted();
- },
-
- beforeAddClass : function(element, className, animationCompleted) {
- var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'), function(fn) {
-
- /* when a CSS class is added to an element then the transition style that
- * is applied is the transition defined on the element when the CSS class
- * is added at the time of the animation. This is how CSS3 functions
- * outside of ngAnimate. */
- element.addClass(className);
- var timings = fn();
- element.removeClass(className);
- return timings;
+ runner = new $$AnimateRunner({
+ end: endFn,
+ cancel: endFn
});
- if(cancellationMethod) {
- afterReflow(element, function() {
- unblockTransitions(element, className);
- unblockKeyframeAnimations(element);
- animationCompleted();
- });
- return cancellationMethod;
+ return runner;
+
+ function endFn() {
+ if (currentAnimation) {
+ currentAnimation.end();
+ }
}
- animationCompleted();
- },
-
- setClass : function(element, add, remove, animationCompleted) {
- remove = suffixClasses(remove, '-remove');
- add = suffixClasses(add, '-add');
- var className = remove + ' ' + add;
- return animateAfter('setClass', element, className, animationCompleted);
- },
-
- addClass : function(element, className, animationCompleted) {
- return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted);
- },
-
- beforeRemoveClass : function(element, className, animationCompleted) {
- var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'), function(fn) {
- /* when classes are removed from an element then the transition style
- * that is applied is the transition defined on the element without the
- * CSS class being there. This is how CSS3 functions outside of ngAnimate.
- * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
- var klass = element.attr('class');
- element.removeClass(className);
- var timings = fn();
- element.attr('class', klass);
- return timings;
- });
-
- if(cancellationMethod) {
- afterReflow(element, function() {
- unblockTransitions(element, className);
- unblockKeyframeAnimations(element);
- animationCompleted();
- });
- return cancellationMethod;
- }
- animationCompleted();
- },
-
- removeClass : function(element, className, animationCompleted) {
- return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted);
}
};
- function suffixClasses(classes, suffix) {
- var className = '';
- classes = angular.isArray(classes) ? classes : classes.split(/\s+/);
- forEach(classes, function(klass, i) {
- if(klass && klass.length > 0) {
- className += (i > 0 ? ' ' : '') + klass + suffix;
+ function calculateAnchorStyles(anchor) {
+ var styles = {};
+
+ var coords = getDomNode(anchor).getBoundingClientRect();
+
+ // we iterate directly since safari messes up and doesn't return
+ // all the keys for the coods object when iterated
+ forEach(['width','height','top','left'], function(key) {
+ var value = coords[key];
+ switch (key) {
+ case 'top':
+ value += bodyNode.scrollTop;
+ break;
+ case 'left':
+ value += bodyNode.scrollLeft;
+ break;
+ }
+ styles[key] = Math.floor(value) + 'px';
+ });
+ return styles;
+ }
+
+ function prepareOutAnimation() {
+ var animator = $animateCss(clone, {
+ addClass: NG_OUT_ANCHOR_CLASS_NAME,
+ delay: true,
+ from: calculateAnchorStyles(outAnchor)
+ });
+
+ // read the comment within `prepareRegularAnimation` to understand
+ // why this check is necessary
+ return animator.$$willAnimate ? animator : null;
+ }
+
+ function getClassVal(element) {
+ return element.attr('class') || '';
+ }
+
+ function prepareInAnimation() {
+ var endingClasses = filterCssClasses(getClassVal(inAnchor));
+ var toAdd = getUniqueValues(endingClasses, startingClasses);
+ var toRemove = getUniqueValues(startingClasses, endingClasses);
+
+ var animator = $animateCss(clone, {
+ to: calculateAnchorStyles(inAnchor),
+ addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd,
+ removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove,
+ delay: true
+ });
+
+ // read the comment within `prepareRegularAnimation` to understand
+ // why this check is necessary
+ return animator.$$willAnimate ? animator : null;
+ }
+
+ function end() {
+ clone.remove();
+ outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
+ inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
+ }
+ }
+
+ function prepareFromToAnchorAnimation(from, to, classes, anchors) {
+ var fromAnimation = prepareRegularAnimation(from);
+ var toAnimation = prepareRegularAnimation(to);
+
+ var anchorAnimations = [];
+ forEach(anchors, function(anchor) {
+ var outElement = anchor['out'];
+ var inElement = anchor['in'];
+ var animator = prepareAnchoredAnimation(classes, outElement, inElement);
+ if (animator) {
+ anchorAnimations.push(animator);
+ }
+ });
+
+ // no point in doing anything when there are no elements to animate
+ if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return;
+
+ return {
+ start: function() {
+ var animationRunners = [];
+
+ if (fromAnimation) {
+ animationRunners.push(fromAnimation.start());
+ }
+
+ if (toAnimation) {
+ animationRunners.push(toAnimation.start());
+ }
+
+ forEach(anchorAnimations, function(animation) {
+ animationRunners.push(animation.start());
+ });
+
+ var runner = new $$AnimateRunner({
+ end: endFn,
+ cancel: endFn // CSS-driven animations cannot be cancelled, only ended
+ });
+
+ $$AnimateRunner.all(animationRunners, function(status) {
+ runner.complete(status);
+ });
+
+ return runner;
+
+ function endFn() {
+ forEach(animationRunners, function(runner) {
+ runner.end();
+ });
+ }
+ }
+ };
+ }
+
+ function prepareRegularAnimation(animationDetails) {
+ var element = animationDetails.element;
+ var options = animationDetails.options || {};
+
+ if (animationDetails.structural) {
+ // structural animations ensure that the CSS classes are always applied
+ // before the detection starts.
+ options.structural = options.applyClassesEarly = true;
+
+ // we special case the leave animation since we want to ensure that
+ // the element is removed as soon as the animation is over. Otherwise
+ // a flicker might appear or the element may not be removed at all
+ options.event = animationDetails.event;
+ if (options.event === 'leave') {
+ options.onDone = options.domOperation;
+ }
+ } else {
+ options.event = null;
+ }
+
+ var animator = $animateCss(element, options);
+
+ // the driver lookup code inside of $$animation attempts to spawn a
+ // driver one by one until a driver returns a.$$willAnimate animator object.
+ // $animateCss will always return an object, however, it will pass in
+ // a flag as a hint as to whether an animation was detected or not
+ return animator.$$willAnimate ? animator : null;
+ }
+ }];
+}];
+
+// TODO(matsko): use caching here to speed things up for detection
+// TODO(matsko): add documentation
+// by the time...
+
+var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
+ this.$get = ['$injector', '$$AnimateRunner', '$$rAFMutex', '$$jqLite',
+ function($injector, $$AnimateRunner, $$rAFMutex, $$jqLite) {
+
+ var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+ // $animateJs(element, 'enter');
+ return function(element, event, classes, options) {
+ // the `classes` argument is optional and if it is not used
+ // then the classes will be resolved from the element's className
+ // property as well as options.addClass/options.removeClass.
+ if (arguments.length === 3 && isObject(classes)) {
+ options = classes;
+ classes = null;
+ }
+
+ options = prepareAnimationOptions(options);
+ if (!classes) {
+ classes = element.attr('class') || '';
+ if (options.addClass) {
+ classes += ' ' + options.addClass;
+ }
+ if (options.removeClass) {
+ classes += ' ' + options.removeClass;
+ }
+ }
+
+ var classesToAdd = options.addClass;
+ var classesToRemove = options.removeClass;
+
+ // the lookupAnimations function returns a series of animation objects that are
+ // matched up with one or more of the CSS classes. These animation objects are
+ // defined via the module.animation factory function. If nothing is detected then
+ // we don't return anything which then makes $animation query the next driver.
+ var animations = lookupAnimations(classes);
+ var before, after;
+ if (animations.length) {
+ var afterFn, beforeFn;
+ if (event == 'leave') {
+ beforeFn = 'leave';
+ afterFn = 'afterLeave'; // TODO(matsko): get rid of this
+ } else {
+ beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
+ afterFn = event;
+ }
+
+ if (event !== 'enter' && event !== 'move') {
+ before = packageAnimations(element, event, options, animations, beforeFn);
+ }
+ after = packageAnimations(element, event, options, animations, afterFn);
+ }
+
+ // no matching animations
+ if (!before && !after) return;
+
+ function applyOptions() {
+ options.domOperation();
+ applyAnimationClasses(element, options);
+ }
+
+ return {
+ start: function() {
+ var closeActiveAnimations;
+ var chain = [];
+
+ if (before) {
+ chain.push(function(fn) {
+ closeActiveAnimations = before(fn);
+ });
+ }
+
+ if (chain.length) {
+ chain.push(function(fn) {
+ applyOptions();
+ fn(true);
+ });
+ } else {
+ applyOptions();
+ }
+
+ if (after) {
+ chain.push(function(fn) {
+ closeActiveAnimations = after(fn);
+ });
+ }
+
+ var animationClosed = false;
+ var runner = new $$AnimateRunner({
+ end: function() {
+ endAnimations();
+ },
+ cancel: function() {
+ endAnimations(true);
+ }
+ });
+
+ $$AnimateRunner.chain(chain, onComplete);
+ return runner;
+
+ function onComplete(success) {
+ animationClosed = true;
+ applyOptions();
+ applyAnimationStyles(element, options);
+ runner.complete(success);
+ }
+
+ function endAnimations(cancelled) {
+ if (!animationClosed) {
+ (closeActiveAnimations || noop)(cancelled);
+ onComplete(cancelled);
+ }
+ }
+ }
+ };
+
+ function executeAnimationFn(fn, element, event, options, onDone) {
+ var args;
+ switch (event) {
+ case 'animate':
+ args = [element, options.from, options.to, onDone];
+ break;
+
+ case 'setClass':
+ args = [element, classesToAdd, classesToRemove, onDone];
+ break;
+
+ case 'addClass':
+ args = [element, classesToAdd, onDone];
+ break;
+
+ case 'removeClass':
+ args = [element, classesToRemove, onDone];
+ break;
+
+ default:
+ args = [element, onDone];
+ break;
+ }
+
+ args.push(options);
+
+ var value = fn.apply(fn, args);
+ if (value) {
+ if (isFunction(value.start)) {
+ value = value.start();
+ }
+
+ if (value instanceof $$AnimateRunner) {
+ value.done(onDone);
+ } else if (isFunction(value)) {
+ // optional onEnd / onCancel callback
+ return value;
+ }
+ }
+
+ return noop;
+ }
+
+ function groupEventedAnimations(element, event, options, animations, fnName) {
+ var operations = [];
+ forEach(animations, function(ani) {
+ var animation = ani[fnName];
+ if (!animation) return;
+
+ // note that all of these animations will run in parallel
+ operations.push(function() {
+ var runner;
+ var endProgressCb;
+
+ var resolved = false;
+ var onAnimationComplete = function(rejected) {
+ if (!resolved) {
+ resolved = true;
+ (endProgressCb || noop)(rejected);
+ runner.complete(!rejected);
+ }
+ };
+
+ runner = new $$AnimateRunner({
+ end: function() {
+ onAnimationComplete();
+ },
+ cancel: function() {
+ onAnimationComplete(true);
+ }
+ });
+
+ endProgressCb = executeAnimationFn(animation, element, event, options, function(result) {
+ var cancelled = result === false;
+ onAnimationComplete(cancelled);
+ });
+
+ return runner;
+ });
+ });
+
+ return operations;
+ }
+
+ function packageAnimations(element, event, options, animations, fnName) {
+ var operations = groupEventedAnimations(element, event, options, animations, fnName);
+ if (operations.length === 0) {
+ var a,b;
+ if (fnName === 'beforeSetClass') {
+ a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass');
+ b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass');
+ } else if (fnName === 'setClass') {
+ a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass');
+ b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass');
+ }
+
+ if (a) {
+ operations = operations.concat(a);
+ }
+ if (b) {
+ operations = operations.concat(b);
+ }
+ }
+
+ if (operations.length === 0) return;
+
+ // TODO(matsko): add documentation
+ return function startAnimation(callback) {
+ var runners = [];
+ if (operations.length) {
+ forEach(operations, function(animateFn) {
+ runners.push(animateFn());
+ });
+ }
+
+ runners.length ? $$AnimateRunner.all(runners, callback) : callback();
+
+ return function endFn(reject) {
+ forEach(runners, function(runner) {
+ reject ? runner.cancel() : runner.end();
+ });
+ };
+ };
+ }
+ };
+
+ function lookupAnimations(classes) {
+ classes = isArray(classes) ? classes : classes.split(' ');
+ var matches = [], flagMap = {};
+ for (var i=0; i < classes.length; i++) {
+ var klass = classes[i],
+ animationFactory = $animateProvider.$$registeredAnimations[klass];
+ if (animationFactory && !flagMap[klass]) {
+ matches.push($injector.get(animationFactory));
+ flagMap[klass] = true;
+ }
+ }
+ return matches;
+ }
+ }];
+}];
+
+var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) {
+ $$animationProvider.drivers.push('$$animateJsDriver');
+ this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) {
+ return function initDriverFn(animationDetails) {
+ if (animationDetails.from && animationDetails.to) {
+ var fromAnimation = prepareAnimation(animationDetails.from);
+ var toAnimation = prepareAnimation(animationDetails.to);
+ if (!fromAnimation && !toAnimation) return;
+
+ return {
+ start: function() {
+ var animationRunners = [];
+
+ if (fromAnimation) {
+ animationRunners.push(fromAnimation.start());
+ }
+
+ if (toAnimation) {
+ animationRunners.push(toAnimation.start());
+ }
+
+ $$AnimateRunner.all(animationRunners, done);
+
+ var runner = new $$AnimateRunner({
+ end: endFnFactory(),
+ cancel: endFnFactory()
+ });
+
+ return runner;
+
+ function endFnFactory() {
+ return function() {
+ forEach(animationRunners, function(runner) {
+ // at this point we cannot cancel animations for groups just yet. 1.5+
+ runner.end();
+ });
+ };
+ }
+
+ function done(status) {
+ runner.complete(status);
+ }
+ }
+ };
+ } else {
+ return prepareAnimation(animationDetails);
+ }
+ };
+
+ function prepareAnimation(animationDetails) {
+ // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations
+ var element = animationDetails.element;
+ var event = animationDetails.event;
+ var options = animationDetails.options;
+ var classes = animationDetails.classes;
+ return $$animateJs(element, event, classes, options);
+ }
+ }];
+}];
+
+var NG_ANIMATE_ATTR_NAME = 'data-ng-animate';
+var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
+var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
+ var PRE_DIGEST_STATE = 1;
+ var RUNNING_STATE = 2;
+
+ var rules = this.rules = {
+ skip: [],
+ cancel: [],
+ join: []
+ };
+
+ function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
+ return rules[ruleType].some(function(fn) {
+ return fn(element, currentAnimation, previousAnimation);
+ });
+ }
+
+ function hasAnimationClasses(options, and) {
+ options = options || {};
+ var a = (options.addClass || '').length > 0;
+ var b = (options.removeClass || '').length > 0;
+ return and ? a && b : a || b;
+ }
+
+ rules.join.push(function(element, newAnimation, currentAnimation) {
+ // if the new animation is class-based then we can just tack that on
+ return !newAnimation.structural && hasAnimationClasses(newAnimation.options);
+ });
+
+ rules.skip.push(function(element, newAnimation, currentAnimation) {
+ // there is no need to animate anything if no classes are being added and
+ // there is no structural animation that will be triggered
+ return !newAnimation.structural && !hasAnimationClasses(newAnimation.options);
+ });
+
+ rules.skip.push(function(element, newAnimation, currentAnimation) {
+ // why should we trigger a new structural animation if the element will
+ // be removed from the DOM anyway?
+ return currentAnimation.event == 'leave' && newAnimation.structural;
+ });
+
+ rules.skip.push(function(element, newAnimation, currentAnimation) {
+ // if there is a current animation then skip the class-based animation
+ return currentAnimation.structural && !newAnimation.structural;
+ });
+
+ rules.cancel.push(function(element, newAnimation, currentAnimation) {
+ // there can never be two structural animations running at the same time
+ return currentAnimation.structural && newAnimation.structural;
+ });
+
+ rules.cancel.push(function(element, newAnimation, currentAnimation) {
+ // if the previous animation is already running, but the new animation will
+ // be triggered, but the new animation is structural
+ return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
+ });
+
+ rules.cancel.push(function(element, newAnimation, currentAnimation) {
+ var nO = newAnimation.options;
+ var cO = currentAnimation.options;
+
+ // if the exact same CSS class is added/removed then it's safe to cancel it
+ return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass);
+ });
+
+ this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
+ '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite',
+ function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
+ $$animation, $$AnimateRunner, $templateRequest, $$jqLite) {
+
+ var activeAnimationsLookup = new $$HashMap();
+ var disabledElementsLookup = new $$HashMap();
+
+ var animationsEnabled = null;
+
+ // Wait until all directive and route-related templates are downloaded and
+ // compiled. The $templateRequest.totalPendingRequests variable keeps track of
+ // all of the remote templates being currently downloaded. If there are no
+ // templates currently downloading then the watcher will still fire anyway.
+ var deregisterWatch = $rootScope.$watch(
+ function() { return $templateRequest.totalPendingRequests === 0; },
+ function(isEmpty) {
+ if (!isEmpty) return;
+ deregisterWatch();
+
+ // Now that all templates have been downloaded, $animate will wait until
+ // the post digest queue is empty before enabling animations. By having two
+ // calls to $postDigest calls we can ensure that the flag is enabled at the
+ // very end of the post digest queue. Since all of the animations in $animate
+ // use $postDigest, it's important that the code below executes at the end.
+ // This basically means that the page is fully downloaded and compiled before
+ // any animations are triggered.
+ $rootScope.$$postDigest(function() {
+ $rootScope.$$postDigest(function() {
+ // we check for null directly in the event that the application already called
+ // .enabled() with whatever arguments that it provided it with
+ if (animationsEnabled === null) {
+ animationsEnabled = true;
+ }
+ });
+ });
+ }
+ );
+
+ var bodyElement = jqLite($document[0].body);
+
+ var callbackRegistry = {};
+
+ // remember that the classNameFilter is set during the provider/config
+ // stage therefore we can optimize here and setup a helper function
+ var classNameFilter = $animateProvider.classNameFilter();
+ var isAnimatableClassName = !classNameFilter
+ ? function() { return true; }
+ : function(className) {
+ return classNameFilter.test(className);
+ };
+
+ var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+
+ function normalizeAnimationOptions(element, options) {
+ return mergeAnimationOptions(element, options, {});
+ }
+
+ function findCallbacks(element, event) {
+ var targetNode = getDomNode(element);
+
+ var matches = [];
+ var entries = callbackRegistry[event];
+ if (entries) {
+ forEach(entries, function(entry) {
+ if (entry.node.contains(targetNode)) {
+ matches.push(entry.callback);
}
});
- return className;
}
- }]);
- }]);
+
+ return matches;
+ }
+
+ function triggerCallback(event, element, phase, data) {
+ $$rAF(function() {
+ forEach(findCallbacks(element, event), function(callback) {
+ callback(element, phase, data);
+ });
+ });
+ }
+
+ return {
+ on: function(event, container, callback) {
+ var node = extractElementNode(container);
+ callbackRegistry[event] = callbackRegistry[event] || [];
+ callbackRegistry[event].push({
+ node: node,
+ callback: callback
+ });
+ },
+
+ off: function(event, container, callback) {
+ var entries = callbackRegistry[event];
+ if (!entries) return;
+
+ callbackRegistry[event] = arguments.length === 1
+ ? null
+ : filterFromRegistry(entries, container, callback);
+
+ function filterFromRegistry(list, matchContainer, matchCallback) {
+ var containerNode = extractElementNode(matchContainer);
+ return list.filter(function(entry) {
+ var isMatch = entry.node === containerNode &&
+ (!matchCallback || entry.callback === matchCallback);
+ return !isMatch;
+ });
+ }
+ },
+
+ pin: function(element, parentElement) {
+ assertArg(isElement(element), 'element', 'not an element');
+ assertArg(isElement(parentElement), 'parentElement', 'not an element');
+ element.data(NG_ANIMATE_PIN_DATA, parentElement);
+ },
+
+ push: function(element, event, options, domOperation) {
+ options = options || {};
+ options.domOperation = domOperation;
+ return queueAnimation(element, event, options);
+ },
+
+ // this method has four signatures:
+ // () - global getter
+ // (bool) - global setter
+ // (element) - element getter
+ // (element, bool) - element setter
+ enabled: function(element, bool) {
+ var argCount = arguments.length;
+
+ if (argCount === 0) {
+ // () - Global getter
+ bool = !!animationsEnabled;
+ } else {
+ var hasElement = isElement(element);
+
+ if (!hasElement) {
+ // (bool) - Global setter
+ bool = animationsEnabled = !!element;
+ } else {
+ var node = getDomNode(element);
+ var recordExists = disabledElementsLookup.get(node);
+
+ if (argCount === 1) {
+ // (element) - Element getter
+ bool = !recordExists;
+ } else {
+ // (element, bool) - Element setter
+ bool = !!bool;
+ if (!bool) {
+ disabledElementsLookup.put(node, true);
+ } else if (recordExists) {
+ disabledElementsLookup.remove(node);
+ }
+ }
+ }
+ }
+
+ return bool;
+ }
+ };
+
+ function queueAnimation(element, event, options) {
+ var node, parent;
+ element = stripCommentsFromElement(element);
+ if (element) {
+ node = getDomNode(element);
+ parent = element.parent();
+ }
+
+ options = prepareAnimationOptions(options);
+
+ // we create a fake runner with a working promise.
+ // These methods will become available after the digest has passed
+ var runner = new $$AnimateRunner();
+
+ // there are situations where a directive issues an animation for
+ // a jqLite wrapper that contains only comment nodes... If this
+ // happens then there is no way we can perform an animation
+ if (!node) {
+ close();
+ return runner;
+ }
+
+ if (isArray(options.addClass)) {
+ options.addClass = options.addClass.join(' ');
+ }
+
+ if (isArray(options.removeClass)) {
+ options.removeClass = options.removeClass.join(' ');
+ }
+
+ if (options.from && !isObject(options.from)) {
+ options.from = null;
+ }
+
+ if (options.to && !isObject(options.to)) {
+ options.to = null;
+ }
+
+ var className = [node.className, options.addClass, options.removeClass].join(' ');
+ if (!isAnimatableClassName(className)) {
+ close();
+ return runner;
+ }
+
+ var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
+
+ // this is a hard disable of all animations for the application or on
+ // the element itself, therefore there is no need to continue further
+ // past this point if not enabled
+ var skipAnimations = !animationsEnabled || disabledElementsLookup.get(node);
+ var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
+ var hasExistingAnimation = !!existingAnimation.state;
+
+ // there is no point in traversing the same collection of parent ancestors if a followup
+ // animation will be run on the same element that already did all that checking work
+ if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) {
+ skipAnimations = !areAnimationsAllowed(element, parent, event);
+ }
+
+ if (skipAnimations) {
+ close();
+ return runner;
+ }
+
+ if (isStructural) {
+ closeChildAnimations(element);
+ }
+
+ var newAnimation = {
+ structural: isStructural,
+ element: element,
+ event: event,
+ close: close,
+ options: options,
+ runner: runner
+ };
+
+ if (hasExistingAnimation) {
+ var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
+ if (skipAnimationFlag) {
+ if (existingAnimation.state === RUNNING_STATE) {
+ close();
+ return runner;
+ } else {
+ mergeAnimationOptions(element, existingAnimation.options, options);
+ return existingAnimation.runner;
+ }
+ }
+
+ var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
+ if (cancelAnimationFlag) {
+ if (existingAnimation.state === RUNNING_STATE) {
+ // this will end the animation right away and it is safe
+ // to do so since the animation is already running and the
+ // runner callback code will run in async
+ existingAnimation.runner.end();
+ } else if (existingAnimation.structural) {
+ // this means that the animation is queued into a digest, but
+ // hasn't started yet. Therefore it is safe to run the close
+ // method which will call the runner methods in async.
+ existingAnimation.close();
+ } else {
+ // this will merge the existing animation options into this new follow-up animation
+ mergeAnimationOptions(element, newAnimation.options, existingAnimation.options);
+ }
+ } else {
+ // a joined animation means that this animation will take over the existing one
+ // so an example would involve a leave animation taking over an enter. Then when
+ // the postDigest kicks in the enter will be ignored.
+ var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
+ if (joinAnimationFlag) {
+ if (existingAnimation.state === RUNNING_STATE) {
+ normalizeAnimationOptions(element, options);
+ } else {
+ event = newAnimation.event = existingAnimation.event;
+ options = mergeAnimationOptions(element, existingAnimation.options, newAnimation.options);
+ return runner;
+ }
+ }
+ }
+ } else {
+ // normalization in this case means that it removes redundant CSS classes that
+ // already exist (addClass) or do not exist (removeClass) on the element
+ normalizeAnimationOptions(element, options);
+ }
+
+ // when the options are merged and cleaned up we may end up not having to do
+ // an animation at all, therefore we should check this before issuing a post
+ // digest callback. Structural animations will always run no matter what.
+ var isValidAnimation = newAnimation.structural;
+ if (!isValidAnimation) {
+ // animate (from/to) can be quickly checked first, otherwise we check if any classes are present
+ isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0)
+ || hasAnimationClasses(newAnimation.options);
+ }
+
+ if (!isValidAnimation) {
+ close();
+ clearElementAnimationState(element);
+ return runner;
+ }
+
+ if (isStructural) {
+ closeParentClassBasedAnimations(parent);
+ }
+
+ // the counter keeps track of cancelled animations
+ var counter = (existingAnimation.counter || 0) + 1;
+ newAnimation.counter = counter;
+
+ markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);
+
+ $rootScope.$$postDigest(function() {
+ var animationDetails = activeAnimationsLookup.get(node);
+ var animationCancelled = !animationDetails;
+ animationDetails = animationDetails || {};
+
+ // if addClass/removeClass is called before something like enter then the
+ // registered parent element may not be present. The code below will ensure
+ // that a final value for parent element is obtained
+ var parentElement = element.parent() || [];
+
+ // animate/structural/class-based animations all have requirements. Otherwise there
+ // is no point in performing an animation. The parent node must also be set.
+ var isValidAnimation = parentElement.length > 0
+ && (animationDetails.event === 'animate'
+ || animationDetails.structural
+ || hasAnimationClasses(animationDetails.options));
+
+ // this means that the previous animation was cancelled
+ // even if the follow-up animation is the same event
+ if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) {
+ // if another animation did not take over then we need
+ // to make sure that the domOperation and options are
+ // handled accordingly
+ if (animationCancelled) {
+ applyAnimationClasses(element, options);
+ applyAnimationStyles(element, options);
+ }
+
+ // if the event changed from something like enter to leave then we do
+ // it, otherwise if it's the same then the end result will be the same too
+ if (animationCancelled || (isStructural && animationDetails.event !== event)) {
+ options.domOperation();
+ runner.end();
+ }
+
+ // in the event that the element animation was not cancelled or a follow-up animation
+ // isn't allowed to animate from here then we need to clear the state of the element
+ // so that any future animations won't read the expired animation data.
+ if (!isValidAnimation) {
+ clearElementAnimationState(element);
+ }
+
+ return;
+ }
+
+ // this combined multiple class to addClass / removeClass into a setClass event
+ // so long as a structural event did not take over the animation
+ event = !animationDetails.structural && hasAnimationClasses(animationDetails.options, true)
+ ? 'setClass'
+ : animationDetails.event;
+
+ if (animationDetails.structural) {
+ closeParentClassBasedAnimations(parentElement);
+ }
+
+ markElementAnimationState(element, RUNNING_STATE);
+ var realRunner = $$animation(element, event, animationDetails.options);
+ realRunner.done(function(status) {
+ close(!status);
+ var animationDetails = activeAnimationsLookup.get(node);
+ if (animationDetails && animationDetails.counter === counter) {
+ clearElementAnimationState(getDomNode(element));
+ }
+ notifyProgress(runner, event, 'close', {});
+ });
+
+ // this will update the runner's flow-control events based on
+ // the `realRunner` object.
+ runner.setHost(realRunner);
+ notifyProgress(runner, event, 'start', {});
+ });
+
+ return runner;
+
+ function notifyProgress(runner, event, phase, data) {
+ triggerCallback(event, element, phase, data);
+ runner.progress(event, phase, data);
+ }
+
+ function close(reject) { // jshint ignore:line
+ applyAnimationClasses(element, options);
+ applyAnimationStyles(element, options);
+ options.domOperation();
+ runner.complete(!reject);
+ }
+ }
+
+ function closeChildAnimations(element) {
+ var node = getDomNode(element);
+ var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
+ forEach(children, function(child) {
+ var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
+ var animationDetails = activeAnimationsLookup.get(child);
+ switch (state) {
+ case RUNNING_STATE:
+ animationDetails.runner.end();
+ /* falls through */
+ case PRE_DIGEST_STATE:
+ if (animationDetails) {
+ activeAnimationsLookup.remove(child);
+ }
+ break;
+ }
+ });
+ }
+
+ function clearElementAnimationState(element) {
+ var node = getDomNode(element);
+ node.removeAttribute(NG_ANIMATE_ATTR_NAME);
+ activeAnimationsLookup.remove(node);
+ }
+
+ function isMatchingElement(nodeOrElmA, nodeOrElmB) {
+ return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
+ }
+
+ function closeParentClassBasedAnimations(startingElement) {
+ var parentNode = getDomNode(startingElement);
+ do {
+ if (!parentNode || parentNode.nodeType !== ELEMENT_NODE) break;
+
+ var animationDetails = activeAnimationsLookup.get(parentNode);
+ if (animationDetails) {
+ examineParentAnimation(parentNode, animationDetails);
+ }
+
+ parentNode = parentNode.parentNode;
+ } while (true);
+
+ // since animations are detected from CSS classes, we need to flush all parent
+ // class-based animations so that the parent classes are all present for child
+ // animations to properly function (otherwise any CSS selectors may not work)
+ function examineParentAnimation(node, animationDetails) {
+ // enter/leave/move always have priority
+ if (animationDetails.structural || !hasAnimationClasses(animationDetails.options)) return;
+
+ if (animationDetails.state === RUNNING_STATE) {
+ animationDetails.runner.end();
+ }
+ clearElementAnimationState(node);
+ }
+ }
+
+ function areAnimationsAllowed(element, parentElement, event) {
+ var bodyElementDetected = false;
+ var rootElementDetected = false;
+ var parentAnimationDetected = false;
+ var animateChildren;
+
+ var parentHost = element.data(NG_ANIMATE_PIN_DATA);
+ if (parentHost) {
+ parentElement = parentHost;
+ }
+
+ while (parentElement && parentElement.length) {
+ if (!rootElementDetected) {
+ // angular doesn't want to attempt to animate elements outside of the application
+ // therefore we need to ensure that the rootElement is an ancestor of the current element
+ rootElementDetected = isMatchingElement(parentElement, $rootElement);
+ }
+
+ var parentNode = parentElement[0];
+ if (parentNode.nodeType !== ELEMENT_NODE) {
+ // no point in inspecting the #document element
+ break;
+ }
+
+ var details = activeAnimationsLookup.get(parentNode) || {};
+ // either an enter, leave or move animation will commence
+ // therefore we can't allow any animations to take place
+ // but if a parent animation is class-based then that's ok
+ if (!parentAnimationDetected) {
+ parentAnimationDetected = details.structural || disabledElementsLookup.get(parentNode);
+ }
+
+ if (isUndefined(animateChildren) || animateChildren === true) {
+ var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);
+ if (isDefined(value)) {
+ animateChildren = value;
+ }
+ }
+
+ // there is no need to continue traversing at this point
+ if (parentAnimationDetected && animateChildren === false) break;
+
+ if (!rootElementDetected) {
+ // angular doesn't want to attempt to animate elements outside of the application
+ // therefore we need to ensure that the rootElement is an ancestor of the current element
+ rootElementDetected = isMatchingElement(parentElement, $rootElement);
+ if (!rootElementDetected) {
+ parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
+ if (parentHost) {
+ parentElement = parentHost;
+ }
+ }
+ }
+
+ if (!bodyElementDetected) {
+ // we also need to ensure that the element is or will be apart of the body element
+ // otherwise it is pointless to even issue an animation to be rendered
+ bodyElementDetected = isMatchingElement(parentElement, bodyElement);
+ }
+
+ parentElement = parentElement.parent();
+ }
+
+ var allowAnimation = !parentAnimationDetected || animateChildren;
+ return allowAnimation && rootElementDetected && bodyElementDetected;
+ }
+
+ function markElementAnimationState(element, state, details) {
+ details = details || {};
+ details.state = state;
+
+ var node = getDomNode(element);
+ node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
+
+ var oldValue = activeAnimationsLookup.get(node);
+ var newValue = oldValue
+ ? extend(oldValue, details)
+ : details;
+ activeAnimationsLookup.put(node, newValue);
+ }
+ }];
+}];
+
+var $$rAFMutexFactory = ['$$rAF', function($$rAF) {
+ return function() {
+ var passed = false;
+ $$rAF(function() {
+ passed = true;
+ });
+ return function(fn) {
+ passed ? fn() : $$rAF(fn);
+ };
+ };
+}];
+
+var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {
+ var INITIAL_STATE = 0;
+ var DONE_PENDING_STATE = 1;
+ var DONE_COMPLETE_STATE = 2;
+
+ AnimateRunner.chain = function(chain, callback) {
+ var index = 0;
+
+ next();
+ function next() {
+ if (index === chain.length) {
+ callback(true);
+ return;
+ }
+
+ chain[index](function(response) {
+ if (response === false) {
+ callback(false);
+ return;
+ }
+ index++;
+ next();
+ });
+ }
+ };
+
+ AnimateRunner.all = function(runners, callback) {
+ var count = 0;
+ var status = true;
+ forEach(runners, function(runner) {
+ runner.done(onProgress);
+ });
+
+ function onProgress(response) {
+ status = status && response;
+ if (++count === runners.length) {
+ callback(status);
+ }
+ }
+ };
+
+ function AnimateRunner(host) {
+ this.setHost(host);
+
+ this._doneCallbacks = [];
+ this._runInAnimationFrame = $$rAFMutex();
+ this._state = 0;
+ }
+
+ AnimateRunner.prototype = {
+ setHost: function(host) {
+ this.host = host || {};
+ },
+
+ done: function(fn) {
+ if (this._state === DONE_COMPLETE_STATE) {
+ fn();
+ } else {
+ this._doneCallbacks.push(fn);
+ }
+ },
+
+ progress: noop,
+
+ getPromise: function() {
+ if (!this.promise) {
+ var self = this;
+ this.promise = $q(function(resolve, reject) {
+ self.done(function(status) {
+ status === false ? reject() : resolve();
+ });
+ });
+ }
+ return this.promise;
+ },
+
+ then: function(resolveHandler, rejectHandler) {
+ return this.getPromise().then(resolveHandler, rejectHandler);
+ },
+
+ 'catch': function(handler) {
+ return this.getPromise()['catch'](handler);
+ },
+
+ 'finally': function(handler) {
+ return this.getPromise()['finally'](handler);
+ },
+
+ pause: function() {
+ if (this.host.pause) {
+ this.host.pause();
+ }
+ },
+
+ resume: function() {
+ if (this.host.resume) {
+ this.host.resume();
+ }
+ },
+
+ end: function() {
+ if (this.host.end) {
+ this.host.end();
+ }
+ this._resolve(true);
+ },
+
+ cancel: function() {
+ if (this.host.cancel) {
+ this.host.cancel();
+ }
+ this._resolve(false);
+ },
+
+ complete: function(response) {
+ var self = this;
+ if (self._state === INITIAL_STATE) {
+ self._state = DONE_PENDING_STATE;
+ self._runInAnimationFrame(function() {
+ self._resolve(response);
+ });
+ }
+ },
+
+ _resolve: function(response) {
+ if (this._state !== DONE_COMPLETE_STATE) {
+ forEach(this._doneCallbacks, function(fn) {
+ fn(response);
+ });
+ this._doneCallbacks.length = 0;
+ this._state = DONE_COMPLETE_STATE;
+ }
+ }
+ };
+
+ return AnimateRunner;
+}];
+
+var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
+ var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
+
+ var drivers = this.drivers = [];
+
+ var RUNNER_STORAGE_KEY = '$$animationRunner';
+
+ function setRunner(element, runner) {
+ element.data(RUNNER_STORAGE_KEY, runner);
+ }
+
+ function removeRunner(element) {
+ element.removeData(RUNNER_STORAGE_KEY);
+ }
+
+ function getRunner(element) {
+ return element.data(RUNNER_STORAGE_KEY);
+ }
+
+ this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$rAFScheduler',
+ function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$rAFScheduler) {
+
+ var animationQueue = [];
+ var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+
+ var totalPendingClassBasedAnimations = 0;
+ var totalActiveClassBasedAnimations = 0;
+ var classBasedAnimationsQueue = [];
+
+ // TODO(matsko): document the signature in a better way
+ return function(element, event, options) {
+ options = prepareAnimationOptions(options);
+ var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
+
+ // there is no animation at the current moment, however
+ // these runner methods will get later updated with the
+ // methods leading into the driver's end/cancel methods
+ // for now they just stop the animation from starting
+ var runner = new $$AnimateRunner({
+ end: function() { close(); },
+ cancel: function() { close(true); }
+ });
+
+ if (!drivers.length) {
+ close();
+ return runner;
+ }
+
+ setRunner(element, runner);
+
+ var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));
+ var tempClasses = options.tempClasses;
+ if (tempClasses) {
+ classes += ' ' + tempClasses;
+ options.tempClasses = null;
+ }
+
+ var classBasedIndex;
+ if (!isStructural) {
+ classBasedIndex = totalPendingClassBasedAnimations;
+ totalPendingClassBasedAnimations += 1;
+ }
+
+ animationQueue.push({
+ // this data is used by the postDigest code and passed into
+ // the driver step function
+ element: element,
+ classes: classes,
+ event: event,
+ classBasedIndex: classBasedIndex,
+ structural: isStructural,
+ options: options,
+ beforeStart: beforeStart,
+ close: close
+ });
+
+ element.on('$destroy', handleDestroyedElement);
+
+ // we only want there to be one function called within the post digest
+ // block. This way we can group animations for all the animations that
+ // were apart of the same postDigest flush call.
+ if (animationQueue.length > 1) return runner;
+
+ $rootScope.$$postDigest(function() {
+ totalActiveClassBasedAnimations = totalPendingClassBasedAnimations;
+ totalPendingClassBasedAnimations = 0;
+ classBasedAnimationsQueue.length = 0;
+
+ var animations = [];
+ forEach(animationQueue, function(entry) {
+ // the element was destroyed early on which removed the runner
+ // form its storage. This means we can't animate this element
+ // at all and it already has been closed due to destruction.
+ if (getRunner(entry.element)) {
+ animations.push(entry);
+ }
+ });
+
+ // now any future animations will be in another postDigest
+ animationQueue.length = 0;
+
+ forEach(groupAnimations(animations), function(animationEntry) {
+ if (animationEntry.structural) {
+ triggerAnimationStart();
+ } else {
+ classBasedAnimationsQueue.push({
+ node: getDomNode(animationEntry.element),
+ fn: triggerAnimationStart
+ });
+
+ if (animationEntry.classBasedIndex === totalActiveClassBasedAnimations - 1) {
+ // we need to sort each of the animations in order of parent to child
+ // relationships. This ensures that the child classes are applied at the
+ // right time.
+ classBasedAnimationsQueue = classBasedAnimationsQueue.sort(function(a,b) {
+ return b.node.contains(a.node);
+ }).map(function(entry) {
+ return entry.fn;
+ });
+
+ $$rAFScheduler(classBasedAnimationsQueue);
+ }
+ }
+
+ function triggerAnimationStart() {
+ // it's important that we apply the `ng-animate` CSS class and the
+ // temporary classes before we do any driver invoking since these
+ // CSS classes may be required for proper CSS detection.
+ animationEntry.beforeStart();
+
+ var startAnimationFn, closeFn = animationEntry.close;
+
+ // in the event that the element was removed before the digest runs or
+ // during the RAF sequencing then we should not trigger the animation.
+ var targetElement = animationEntry.anchors
+ ? (animationEntry.from.element || animationEntry.to.element)
+ : animationEntry.element;
+
+ if (getRunner(targetElement) && getDomNode(targetElement).parentNode) {
+ var operation = invokeFirstDriver(animationEntry);
+ if (operation) {
+ startAnimationFn = operation.start;
+ }
+ }
+
+ if (!startAnimationFn) {
+ closeFn();
+ } else {
+ var animationRunner = startAnimationFn();
+ animationRunner.done(function(status) {
+ closeFn(!status);
+ });
+ updateAnimationRunners(animationEntry, animationRunner);
+ }
+ }
+ });
+ });
+
+ return runner;
+
+ // TODO(matsko): change to reference nodes
+ function getAnchorNodes(node) {
+ var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']';
+ var items = node.hasAttribute(NG_ANIMATE_REF_ATTR)
+ ? [node]
+ : node.querySelectorAll(SELECTOR);
+ var anchors = [];
+ forEach(items, function(node) {
+ var attr = node.getAttribute(NG_ANIMATE_REF_ATTR);
+ if (attr && attr.length) {
+ anchors.push(node);
+ }
+ });
+ return anchors;
+ }
+
+ function groupAnimations(animations) {
+ var preparedAnimations = [];
+ var refLookup = {};
+ forEach(animations, function(animation, index) {
+ var element = animation.element;
+ var node = getDomNode(element);
+ var event = animation.event;
+ var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;
+ var anchorNodes = animation.structural ? getAnchorNodes(node) : [];
+
+ if (anchorNodes.length) {
+ var direction = enterOrMove ? 'to' : 'from';
+
+ forEach(anchorNodes, function(anchor) {
+ var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR);
+ refLookup[key] = refLookup[key] || {};
+ refLookup[key][direction] = {
+ animationID: index,
+ element: jqLite(anchor)
+ };
+ });
+ } else {
+ preparedAnimations.push(animation);
+ }
+ });
+
+ var usedIndicesLookup = {};
+ var anchorGroups = {};
+ forEach(refLookup, function(operations, key) {
+ var from = operations.from;
+ var to = operations.to;
+
+ if (!from || !to) {
+ // only one of these is set therefore we can't have an
+ // anchor animation since all three pieces are required
+ var index = from ? from.animationID : to.animationID;
+ var indexKey = index.toString();
+ if (!usedIndicesLookup[indexKey]) {
+ usedIndicesLookup[indexKey] = true;
+ preparedAnimations.push(animations[index]);
+ }
+ return;
+ }
+
+ var fromAnimation = animations[from.animationID];
+ var toAnimation = animations[to.animationID];
+ var lookupKey = from.animationID.toString();
+ if (!anchorGroups[lookupKey]) {
+ var group = anchorGroups[lookupKey] = {
+ structural: true,
+ beforeStart: function() {
+ fromAnimation.beforeStart();
+ toAnimation.beforeStart();
+ },
+ close: function() {
+ fromAnimation.close();
+ toAnimation.close();
+ },
+ classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes),
+ from: fromAnimation,
+ to: toAnimation,
+ anchors: [] // TODO(matsko): change to reference nodes
+ };
+
+ // the anchor animations require that the from and to elements both have at least
+ // one shared CSS class which effictively marries the two elements together to use
+ // the same animation driver and to properly sequence the anchor animation.
+ if (group.classes.length) {
+ preparedAnimations.push(group);
+ } else {
+ preparedAnimations.push(fromAnimation);
+ preparedAnimations.push(toAnimation);
+ }
+ }
+
+ anchorGroups[lookupKey].anchors.push({
+ 'out': from.element, 'in': to.element
+ });
+ });
+
+ return preparedAnimations;
+ }
+
+ function cssClassesIntersection(a,b) {
+ a = a.split(' ');
+ b = b.split(' ');
+ var matches = [];
+
+ for (var i = 0; i < a.length; i++) {
+ var aa = a[i];
+ if (aa.substring(0,3) === 'ng-') continue;
+
+ for (var j = 0; j < b.length; j++) {
+ if (aa === b[j]) {
+ matches.push(aa);
+ break;
+ }
+ }
+ }
+
+ return matches.join(' ');
+ }
+
+ function invokeFirstDriver(animationDetails) {
+ // we loop in reverse order since the more general drivers (like CSS and JS)
+ // may attempt more elements, but custom drivers are more particular
+ for (var i = drivers.length - 1; i >= 0; i--) {
+ var driverName = drivers[i];
+ if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
+
+ var factory = $injector.get(driverName);
+ var driver = factory(animationDetails);
+ if (driver) {
+ return driver;
+ }
+ }
+ }
+
+ function beforeStart() {
+ element.addClass(NG_ANIMATE_CLASSNAME);
+ if (tempClasses) {
+ $$jqLite.addClass(element, tempClasses);
+ }
+ }
+
+ function updateAnimationRunners(animation, newRunner) {
+ if (animation.from && animation.to) {
+ update(animation.from.element);
+ update(animation.to.element);
+ } else {
+ update(animation.element);
+ }
+
+ function update(element) {
+ getRunner(element).setHost(newRunner);
+ }
+ }
+
+ function handleDestroyedElement() {
+ var runner = getRunner(element);
+ if (runner && (event !== 'leave' || !options.$$domOperationFired)) {
+ runner.end();
+ }
+ }
+
+ function close(rejected) { // jshint ignore:line
+ element.off('$destroy', handleDestroyedElement);
+ removeRunner(element);
+
+ applyAnimationClasses(element, options);
+ applyAnimationStyles(element, options);
+ options.domOperation();
+
+ if (tempClasses) {
+ $$jqLite.removeClass(element, tempClasses);
+ }
+
+ element.removeClass(NG_ANIMATE_CLASSNAME);
+ runner.complete(!rejected);
+ }
+ };
+ }];
+}];
+
+/* global angularAnimateModule: true,
+
+ $$rAFMutexFactory,
+ $$rAFSchedulerFactory,
+ $$AnimateChildrenDirective,
+ $$AnimateRunnerFactory,
+ $$AnimateQueueProvider,
+ $$AnimationProvider,
+ $AnimateCssProvider,
+ $$AnimateCssDriverProvider,
+ $$AnimateJsProvider,
+ $$AnimateJsDriverProvider,
+*/
+
+/**
+ * @ngdoc module
+ * @name ngAnimate
+ * @description
+ *
+ * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
+ * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` then the animation hooks are enabled for an Angular app.
+ *
+ *
+ *
+ * # Usage
+ * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based
+ * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For
+ * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within
+ * the HTML element that the animation will be triggered on.
+ *
+ * ## Directive Support
+ * The following directives are "animation aware":
+ *
+ * | 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:ngClass#animations ngClass} | add and remove (the CSS class(es) present) |
+ * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) |
+ * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
+ * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) |
+ * | {@link module:ngMessages#animations ngMessage} | enter and leave |
+ *
+ * (More information can be found by visiting each the documentation associated with each directive.)
+ *
+ * ## CSS-based Animations
+ *
+ * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML
+ * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
+ *
+ * The example below shows how an `enter` animation can be made possible on a element using `ng-if`:
+ *
+ * ```html
+ *
+ * Fade me in out
+ *
+ *
+ *
+ * ```
+ *
+ * Notice the CSS class **fade**? We can now create the CSS transition code that references this class:
+ *
+ * ```css
+ * /* The starting CSS styles for the enter animation */
+ * .fade.ng-enter {
+ * transition:0.5s linear all;
+ * opacity:0;
+ * }
+ *
+ * /* The finishing CSS styles for the enter animation */
+ * .fade.ng-enter.ng-enter-active {
+ * opacity:1;
+ * }
+ * ```
+ *
+ * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two
+ * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition
+ * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards.
+ *
+ * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions:
+ *
+ * ```css
+ * /* now the element will fade out before it is removed from the DOM */
+ * .fade.ng-leave {
+ * transition:0.5s linear all;
+ * opacity:1;
+ * }
+ * .fade.ng-leave.ng-leave-active {
+ * opacity:0;
+ * }
+ * ```
+ *
+ * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class:
+ *
+ * ```css
+ * /* there is no need to define anything inside of the destination
+ * CSS class since the keyframe will take charge of the animation */
+ * .fade.ng-leave {
+ * animation: my_fade_animation 0.5s linear;
+ * -webkit-animation: my_fade_animation 0.5s linear;
+ * }
+ *
+ * @keyframes my_fade_animation {
+ * from { opacity:1; }
+ * to { opacity:0; }
+ * }
+ *
+ * @-webkit-keyframes my_fade_animation {
+ * from { opacity:1; }
+ * to { opacity:0; }
+ * }
+ * ```
+ *
+ * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element.
+ *
+ * ### CSS Class-based Animations
+ *
+ * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different
+ * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added
+ * and removed.
+ *
+ * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class:
+ *
+ * ```html
+ *
+ * Show and hide me
+ *
+ *
+ *
+ *
+ * ```
+ *
+ * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since
+ * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest.
+ *
+ * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation
+ * with CSS styles.
+ *
+ * ```html
+ *
+ * Highlight this box
+ *
+ *
+ *
+ *
+ * ```
+ *
+ * We can also make use of CSS keyframes by placing them within the CSS classes.
+ *
+ *
+ * ### CSS Staggering Animations
+ * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
+ * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
+ * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
+ * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
+ * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
+ *
+ * ```css
+ * .my-animation.ng-enter {
+ * /* standard transition code */
+ * transition: 1s linear all;
+ * opacity:0;
+ * }
+ * .my-animation.ng-enter-stagger {
+ * /* this will have a 100ms delay between each successive leave animation */
+ * transition-delay: 0.1s;
+ *
+ * /* in case the stagger doesn't work then the duration value
+ * must be set to 0 to avoid an accidental CSS inheritance */
+ * transition-duration: 0s;
+ * }
+ * .my-animation.ng-enter.ng-enter-active {
+ * /* standard transition styles */
+ * opacity:1;
+ * }
+ * ```
+ *
+ * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
+ * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
+ * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
+ * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired.
+ *
+ * The following code will issue the **ng-leave-stagger** event on the element provided:
+ *
+ * ```js
+ * var kids = parent.children();
+ *
+ * $animate.leave(kids[0]); //stagger index=0
+ * $animate.leave(kids[1]); //stagger index=1
+ * $animate.leave(kids[2]); //stagger index=2
+ * $animate.leave(kids[3]); //stagger index=3
+ * $animate.leave(kids[4]); //stagger index=4
+ *
+ * window.requestAnimationFrame(function() {
+ * //stagger has reset itself
+ * $animate.leave(kids[5]); //stagger index=0
+ * $animate.leave(kids[6]); //stagger index=1
+ *
+ * $scope.$digest();
+ * });
+ * ```
+ *
+ * Stagger animations are currently only supported within CSS-defined animations.
+ *
+ * ### The `ng-animate` CSS class
+ *
+ * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation.
+ * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations).
+ *
+ * Therefore, animations can be applied to an element using this temporary class directly via CSS.
+ *
+ * ```css
+ * .zipper.ng-animate {
+ * transition:0.5s linear all;
+ * }
+ * .zipper.ng-enter {
+ * opacity:0;
+ * }
+ * .zipper.ng-enter.ng-enter-active {
+ * opacity:1;
+ * }
+ * .zipper.ng-leave {
+ * opacity:1;
+ * }
+ * .zipper.ng-leave.ng-leave-active {
+ * opacity:0;
+ * }
+ * ```
+ *
+ * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove
+ * the CSS class once an animation has completed.)
+ *
+ *
+ * ## JavaScript-based Animations
+ *
+ * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
+ * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the
+ * `module.animation()` module function we can register the ainmation.
+ *
+ * Let's see an example of a enter/leave animation using `ngRepeat`:
+ *
+ * ```html
+ *
+ * {{ item }}
+ *
+ * ```
+ *
+ * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`:
+ *
+ * ```js
+ * myModule.animation('.slide', [function() {
+ * return {
+ * // make note that other events (like addClass/removeClass)
+ * // have different function input parameters
+ * enter: function(element, doneFn) {
+ * jQuery(element).fadeIn(1000, doneFn);
+ *
+ * // remember to call doneFn so that angular
+ * // knows that the animation has concluded
+ * },
+ *
+ * move: function(element, doneFn) {
+ * jQuery(element).fadeIn(1000, doneFn);
+ * },
+ *
+ * leave: function(element, doneFn) {
+ * jQuery(element).fadeOut(1000, doneFn);
+ * }
+ * }
+ * }]
+ * ```
+ *
+ * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as
+ * greensock.js and velocity.js.
+ *
+ * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define
+ * our animations inside of the same registered animation, however, the function input arguments are a bit different:
+ *
+ * ```html
+ *
+ * this box is moody
+ *
+ *
+ *
+ *
+ * ```
+ *
+ * ```js
+ * myModule.animation('.colorful', [function() {
+ * return {
+ * addClass: function(element, className, doneFn) {
+ * // do some cool animation and call the doneFn
+ * },
+ * removeClass: function(element, className, doneFn) {
+ * // do some cool animation and call the doneFn
+ * },
+ * setClass: function(element, addedClass, removedClass, doneFn) {
+ * // do some cool animation and call the doneFn
+ * }
+ * }
+ * }]
+ * ```
+ *
+ * ## CSS + JS Animations Together
+ *
+ * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular,
+ * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking
+ * charge of the animation**:
+ *
+ * ```html
+ *
+ * Slide in and out
+ *
+ * ```
+ *
+ * ```js
+ * myModule.animation('.slide', [function() {
+ * return {
+ * enter: function(element, doneFn) {
+ * jQuery(element).slideIn(1000, doneFn);
+ * }
+ * }
+ * }]
+ * ```
+ *
+ * ```css
+ * .slide.ng-enter {
+ * transition:0.5s linear all;
+ * transform:translateY(-100px);
+ * }
+ * .slide.ng-enter.ng-enter-active {
+ * transform:translateY(0);
+ * }
+ * ```
+ *
+ * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the
+ * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from
+ * our own JS-based animation code:
+ *
+ * ```js
+ * myModule.animation('.slide', ['$animateCss', function($animateCss) {
+ * return {
+ * enter: function(element, doneFn) {
+* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
+ * var runner = $animateCss(element, {
+ * event: 'enter',
+ * structural: true
+ * }).start();
+* runner.done(doneFn);
+ * }
+ * }
+ * }]
+ * ```
+ *
+ * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework.
+ *
+ * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or
+ * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that
+ * data into `$animateCss` directly:
+ *
+ * ```js
+ * myModule.animation('.slide', ['$animateCss', function($animateCss) {
+ * return {
+ * enter: function(element, doneFn) {
+ * var runner = $animateCss(element, {
+ * event: 'enter',
+ * addClass: 'maroon-setting',
+ * from: { height:0 },
+ * to: { height: 200 }
+ * }).start();
+ *
+ * runner.done(doneFn);
+ * }
+ * }
+ * }]
+ * ```
+ *
+ * Now we can fill in the rest via our transition CSS code:
+ *
+ * ```css
+ * /* the transition tells ngAnimate to make the animation happen */
+ * .slide.ng-enter { transition:0.5s linear all; }
+ *
+ * /* this extra CSS class will be absorbed into the transition
+ * since the $animateCss code is adding the class */
+ * .maroon-setting { background:red; }
+ * ```
+ *
+ * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over.
+ *
+ * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}.
+ *
+ * ## Animation Anchoring (via `ng-animate-ref`)
+ *
+ * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between
+ * structural areas of an application (like views) by pairing up elements using an attribute
+ * called `ng-animate-ref`.
+ *
+ * Let's say for example we have two views that are managed by `ng-view` and we want to show
+ * that there is a relationship between two components situated in within these views. By using the
+ * `ng-animate-ref` attribute we can identify that the two components are paired together and we
+ * can then attach an animation, which is triggered when the view changes.
+ *
+ * Say for example we have the following template code:
+ *
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ *
+ * Now, when the view changes (once the link is clicked), ngAnimate will examine the
+ * HTML contents to see if there is a match reference between any components in the view
+ * that is leaving and the view that is entering. It will scan both the view which is being
+ * removed (leave) and inserted (enter) to see if there are any paired DOM elements that
+ * contain a matching ref value.
+ *
+ * The two images match since they share the same ref value. ngAnimate will now create a
+ * transport element (which is a clone of the first image element) and it will then attempt
+ * to animate to the position of the second image element in the next view. For the animation to
+ * work a special CSS class called `ng-anchor` will be added to the transported element.
+ *
+ * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then
+ * ngAnimate will handle the entire transition for us as well as the addition and removal of
+ * any changes of CSS classes between the elements:
+ *
+ * ```css
+ * .banner.ng-anchor {
+ * /* this animation will last for 1 second since there are
+ * two phases to the animation (an `in` and an `out` phase) */
+ * transition:0.5s linear all;
+ * }
+ * ```
+ *
+ * We also **must** include animations for the views that are being entered and removed
+ * (otherwise anchoring wouldn't be possible since the new view would be inserted right away).
+ *
+ * ```css
+ * .view-animation.ng-enter, .view-animation.ng-leave {
+ * transition:0.5s linear all;
+ * position:fixed;
+ * left:0;
+ * top:0;
+ * width:100%;
+ * }
+ * .view-animation.ng-enter {
+ * transform:translateX(100%);
+ * }
+ * .view-animation.ng-leave,
+ * .view-animation.ng-enter.ng-enter-active {
+ * transform:translateX(0%);
+ * }
+ * .view-animation.ng-leave.ng-leave-active {
+ * transform:translateX(-100%);
+ * }
+ * ```
+ *
+ * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur:
+ * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away
+ * from its origin. Once that animation is over then the `in` stage occurs which animates the
+ * element to its destination. The reason why there are two animations is to give enough time
+ * for the enter animation on the new element to be ready.
+ *
+ * The example above sets up a transition for both the in and out phases, but we can also target the out or
+ * in phases directly via `ng-anchor-out` and `ng-anchor-in`.
+ *
+ * ```css
+ * .banner.ng-anchor-out {
+ * transition: 0.5s linear all;
+ *
+ * /* the scale will be applied during the out animation,
+ * but will be animated away when the in animation runs */
+ * transform: scale(1.2);
+ * }
+ *
+ * .banner.ng-anchor-in {
+ * transition: 1s linear all;
+ * }
+ * ```
+ *
+ *
+ *
+ *
+ * ### Anchoring Demo
+ *
+
+
+ Home
+
+
+
+
+ .record {
+ display:block;
+ font-size:20px;
+ }
+ .profile {
+ background:black;
+ color:white;
+ font-size:100px;
+ }
+ .view-container {
+ position:relative;
+ }
+ .view-container > .view.ng-animate {
+ position:absolute;
+ top:0;
+ left:0;
+ width:100%;
+ min-height:500px;
+ }
+ .view.ng-enter, .view.ng-leave,
+ .record.ng-anchor {
+ transition:0.5s linear all;
+ }
+ .view.ng-enter {
+ transform:translateX(100%);
+ }
+ .view.ng-enter.ng-enter-active, .view.ng-leave {
+ transform:translateX(0%);
+ }
+ .view.ng-leave.ng-leave-active {
+ transform:translateX(-100%);
+ }
+ .record.ng-anchor-out {
+ background:red;
+ }
+
+
+ *
+ * ### How is the element transported?
+ *
+ * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting
+ * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element
+ * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The
+ * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match
+ * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied
+ * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class
+ * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element
+ * will become visible since the shim class will be removed.
+ *
+ * ### How is the morphing handled?
+ *
+ * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out
+ * what CSS classes differ between the starting element and the destination element. These different CSS classes
+ * will be added/removed on the anchor element and a transition will be applied (the transition that is provided
+ * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will
+ * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that
+ * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since
+ * the cloned element is placed inside of root element which is likely close to the body element).
+ *
+ * Note that if the root element is on the `` element then the cloned node will be placed inside of body.
+ *
+ *
+ * ## Using $animate in your directive code
+ *
+ * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application?
+ * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's
+ * imagine we have a greeting box that shows and hides itself when the data changes
+ *
+ * ```html
+ * Hi there
+ * ```
+ *
+ * ```js
+ * ngModule.directive('greetingBox', ['$animate', function($animate) {
+ * return function(scope, element, attrs) {
+ * attrs.$observe('active', function(value) {
+ * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on');
+ * });
+ * });
+ * }]);
+ * ```
+ *
+ * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element
+ * in our HTML code then we can trigger a CSS or JS animation to happen.
+ *
+ * ```css
+ * /* normally we would create a CSS class to reference on the element */
+ * greeting-box.on { transition:0.5s linear all; background:green; color:white; }
+ * ```
+ *
+ * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's
+ * possible be sure to visit the {@link ng.$animate $animate service API page}.
+ *
+ *
+ * ### Preventing Collisions With Third Party Libraries
+ *
+ * Some third-party frameworks place animation duration defaults across many element or className
+ * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which
+ * is expecting actual animations on these elements and has to wait for their completion.
+ *
+ * You can prevent this unwanted behavior by using a prefix on all your animation classes:
+ *
+ * ```css
+ * /* prefixed with animate- */
+ * .animate-fade-add.animate-fade-add-active {
+ * transition:1s linear all;
+ * opacity:0;
+ * }
+ * ```
+ *
+ * You then configure `$animate` to enforce this prefix:
+ *
+ * ```js
+ * $animateProvider.classNameFilter(/animate-/);
+ * ```
+ *
+ * This also may provide your application with a speed boost since only specific elements containing CSS class prefix
+ * will be evaluated for animation when any DOM changes occur in the application.
+ *
+ * ## Callbacks and Promises
+ *
+ * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger
+ * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has
+ * ended by chaining onto the returned promise that animation method returns.
+ *
+ * ```js
+ * // somewhere within the depths of the directive
+ * $animate.enter(element, parent).then(function() {
+ * //the animation has completed
+ * });
+ * ```
+ *
+ * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case
+ * anymore.)
+ *
+ * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering
+ * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view
+ * routing controller to hook into that:
+ *
+ * ```js
+ * ngModule.controller('HomePageController', ['$animate', function($animate) {
+ * $animate.on('enter', ngViewElement, function(element) {
+ * // the animation for this route has completed
+ * }]);
+ * }])
+ * ```
+ *
+ * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.)
+ */
+
+/**
+ * @ngdoc service
+ * @name $animate
+ * @kind object
+ *
+ * @description
+ * The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
+ *
+ * Click here {@link ng.$animate $animate to learn more about animations with `$animate`}.
+ */
+angular.module('ngAnimate', [])
+ .directive('ngAnimateChildren', $$AnimateChildrenDirective)
+
+ .factory('$$rAFMutex', $$rAFMutexFactory)
+ .factory('$$rAFScheduler', $$rAFSchedulerFactory)
+
+ .factory('$$AnimateRunner', $$AnimateRunnerFactory)
+
+ .provider('$$animateQueue', $$AnimateQueueProvider)
+ .provider('$$animation', $$AnimationProvider)
+
+ .provider('$animateCss', $AnimateCssProvider)
+ .provider('$$animateCssDriver', $$AnimateCssDriverProvider)
+
+ .provider('$$animateJs', $$AnimateJsProvider)
+ .provider('$$animateJsDriver', $$AnimateJsDriverProvider);
})(window, window.angular);
diff --git a/awx/ui/client/lib/angular-animate/angular-animate.min.js b/awx/ui/client/lib/angular-animate/angular-animate.min.js
index 591544a51a..a99eac13cc 100644
--- a/awx/ui/client/lib/angular-animate/angular-animate.min.js
+++ b/awx/ui/client/lib/angular-animate/angular-animate.min.js
@@ -1,28 +1,52 @@
/*
- AngularJS v1.2.23
- (c) 2010-2014 Google, Inc. http://angularjs.org
+ AngularJS v1.4.3
+ (c) 2010-2015 Google, Inc. http://angularjs.org
License: MIT
*/
-(function(F,e,O){'use strict';e.module("ngAnimate",["ng"]).directive("ngAnimateChildren",function(){return function(G,s,g){g=g.ngAnimateChildren;e.isString(g)&&0===g.length?s.data("$$ngAnimateChildren",!0):G.$watch(g,function(e){s.data("$$ngAnimateChildren",!!e)})}}).factory("$$animateReflow",["$$rAF","$document",function(e,s){return function(g){return e(function(){g()})}}]).config(["$provide","$animateProvider",function(G,s){function g(e){for(var g=0;g=y&&b>=v&&e()}var h=g(b);a=b.data(q);if(-1!=h.getAttribute("class").indexOf(d)&&a){var m="";u(d.split(" "),function(a,b){m+=(0=F&&b>=J&&(C=!0,m())}if(!K)if(g.parentNode){var x,p=[],k=function(a){if(C)D&&a&&(D=!1,m());else if(D=!a,y.animationDuration)if(a=
+ma(g,D),D)l.push(a);else{var b=l,c=b.indexOf(a);0<=a&&b.splice(c,1)}},r=0",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/angular/angular.js/issues"
+ },
+ "homepage": "http://angularjs.org"
+}
diff --git a/awx/ui/client/lib/angular-cookies/.bower.json b/awx/ui/client/lib/angular-cookies/.bower.json
index 60abf2f915..30f123e4cd 100644
--- a/awx/ui/client/lib/angular-cookies/.bower.json
+++ b/awx/ui/client/lib/angular-cookies/.bower.json
@@ -1,18 +1,19 @@
{
"name": "angular-cookies",
- "version": "1.2.15-build.2398+sha.4bab3d8",
+ "version": "1.4.3",
"main": "./angular-cookies.js",
+ "ignore": [],
"dependencies": {
- "angular": "1.2.15-build.2398+sha.4bab3d8"
+ "angular": "1.4.3"
},
"homepage": "https://github.com/angular/bower-angular-cookies",
- "_release": "1.2.15-build.2398+sha.4bab3d8",
+ "_release": "1.4.3",
"_resolution": {
"type": "version",
- "tag": "v1.2.15-build.2398+sha.4bab3d8",
- "commit": "8ca6ddfbd86f4bda921fa3d22e8d2e7d7e9b04d4"
+ "tag": "v1.4.3",
+ "commit": "1ef7a87fc52ed419322aefe14890bdb24e717fc9"
},
"_source": "git://github.com/angular/bower-angular-cookies.git",
- "_target": "~1.2.12",
+ "_target": "~1.4.3",
"_originalSource": "angular-cookies"
}
\ No newline at end of file
diff --git a/awx/ui/client/lib/angular-cookies/README.md b/awx/ui/client/lib/angular-cookies/README.md
index 77c71f4d8b..7b190d3461 100644
--- a/awx/ui/client/lib/angular-cookies/README.md
+++ b/awx/ui/client/lib/angular-cookies/README.md
@@ -1,12 +1,26 @@
-# bower-angular-cookies
+# packaged angular-cookies
-This repo is for distribution on `bower`. The source for this module is in the
+This repo is for distribution on `npm` and `bower`. The source for this module is in the
[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngCookies).
Please file issues and pull requests against that repo.
## Install
-Install with `bower`:
+You can install this package either with `npm` or with `bower`.
+
+### npm
+
+```shell
+npm install angular-cookies
+```
+
+Then add `ngCookies` as a dependency for your app:
+
+```javascript
+angular.module('myApp', [require('angular-cookies')]);
+```
+
+### bower
```shell
bower install angular-cookies
@@ -18,7 +32,7 @@ Add a `
```
-And add `ngCookies` as a dependency for your app:
+Then add `ngCookies` as a dependency for your app:
```javascript
angular.module('myApp', ['ngCookies']);
@@ -33,7 +47,7 @@ Documentation is available on the
The MIT License
-Copyright (c) 2010-2012 Google, Inc. http://angularjs.org
+Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/awx/ui/client/lib/angular-cookies/angular-cookies.js b/awx/ui/client/lib/angular-cookies/angular-cookies.js
index 9d3d5a7542..ddfcd1469d 100644
--- a/awx/ui/client/lib/angular-cookies/angular-cookies.js
+++ b/awx/ui/client/lib/angular-cookies/angular-cookies.js
@@ -1,6 +1,6 @@
/**
- * @license AngularJS v1.2.15-build.2398+sha.4bab3d8
- * (c) 2010-2014 Google, Inc. http://angularjs.org
+ * @license AngularJS v1.4.3
+ * (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
@@ -17,181 +17,305 @@
*
*
*
- * See {@link ngCookies.$cookies `$cookies`} and
- * {@link ngCookies.$cookieStore `$cookieStore`} for usage.
+ * See {@link ngCookies.$cookies `$cookies`} for usage.
*/
angular.module('ngCookies', ['ng']).
/**
- * @ngdoc service
- * @name $cookies
- *
+ * @ngdoc provider
+ * @name $cookiesProvider
* @description
- * Provides read/write access to browser's cookies.
- *
- * Only a simple Object is exposed and by adding or removing properties to/from
- * this object, new cookies are created/deleted at the end of current $eval.
- *
- * Requires the {@link ngCookies `ngCookies`} module to be installed.
- *
- * @example
-
-
-
-
-
- */
- factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
- var cookies = {},
- lastCookies = {},
- lastBrowserCookies,
- runEval = false,
- copy = angular.copy,
- isUndefined = angular.isUndefined;
+ * Use `$cookiesProvider` to change the default behavior of the {@link ngCookies.$cookies $cookies} service.
+ * */
+ provider('$cookies', [function $CookiesProvider() {
+ /**
+ * @ngdoc property
+ * @name $cookiesProvider#defaults
+ * @description
+ *
+ * Object containing default options to pass when setting cookies.
+ *
+ * The object may have following properties:
+ *
+ * - **path** - `{string}` - The cookie will be available only for this path and its
+ * sub-paths. By default, this would be the URL that appears in your base tag.
+ * - **domain** - `{string}` - The cookie will be available only for this domain and
+ * its sub-domains. For obvious security reasons the user agent will not accept the
+ * cookie if the current domain is not a sub domain or equals to the requested domain.
+ * - **expires** - `{string|Date}` - String of the form "Wdy, DD Mon YYYY HH:MM:SS GMT"
+ * or a Date object indicating the exact date/time this cookie will expire.
+ * - **secure** - `{boolean}` - The cookie will be available only in secured connection.
+ *
+ * Note: by default the address that appears in your `` tag will be used as path.
+ * This is import so that cookies will be visible for all routes in case html5mode is enabled
+ *
+ **/
+ var defaults = this.defaults = {};
- //creates a poller fn that copies all cookies from the $browser to service & inits the service
- $browser.addPollFn(function() {
- var currentCookies = $browser.cookies();
- if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
- lastBrowserCookies = currentCookies;
- copy(currentCookies, lastCookies);
- copy(currentCookies, cookies);
- if (runEval) $rootScope.$apply();
- }
- })();
-
- runEval = true;
-
- //at the end of each eval, push cookies
- //TODO: this should happen before the "delayed" watches fire, because if some cookies are not
- // strings or browser refuses to store some cookies, we update the model in the push fn.
- $rootScope.$watch(push);
-
- return cookies;
-
-
- /**
- * Pushes all the cookies from the service to the browser and verifies if all cookies were
- * stored.
- */
- function push() {
- var name,
- value,
- browserCookies,
- updated;
-
- //delete any cookies deleted in $cookies
- for (name in lastCookies) {
- if (isUndefined(cookies[name])) {
- $browser.cookies(name, undefined);
- }
- }
-
- //update all cookies updated in $cookies
- for(name in cookies) {
- value = cookies[name];
- if (!angular.isString(value)) {
- if (angular.isDefined(lastCookies[name])) {
- cookies[name] = lastCookies[name];
- } else {
- delete cookies[name];
- }
- } else if (value !== lastCookies[name]) {
- $browser.cookies(name, value);
- updated = true;
- }
- }
-
- //verify what was actually stored
- if (updated){
- updated = false;
- browserCookies = $browser.cookies();
-
- for (name in cookies) {
- if (cookies[name] !== browserCookies[name]) {
- //delete or reset all cookies that the browser dropped from $cookies
- if (isUndefined(browserCookies[name])) {
- delete cookies[name];
- } else {
- cookies[name] = browserCookies[name];
- }
- updated = true;
- }
- }
- }
- }
- }]).
-
-
- /**
- * @ngdoc service
- * @name $cookieStore
- * @requires $cookies
- *
- * @description
- * Provides a key-value (string-object) storage, that is backed by session cookies.
- * Objects put or retrieved from this storage are automatically serialized or
- * deserialized by angular's toJson/fromJson.
- *
- * Requires the {@link ngCookies `ngCookies`} module to be installed.
- *
- * @example
- */
- factory('$cookieStore', ['$cookies', function($cookies) {
+ function calcOptions(options) {
+ return options ? angular.extend({}, defaults, options) : defaults;
+ }
+ /**
+ * @ngdoc service
+ * @name $cookies
+ *
+ * @description
+ * Provides read/write access to browser's cookies.
+ *
+ *
+ * Up until Angular 1.3, `$cookies` exposed properties that represented the
+ * current browser cookie values. In version 1.4, this behavior has changed, and
+ * `$cookies` now provides a standard api of getters, setters etc.
+ *
+ *
+ * Requires the {@link ngCookies `ngCookies`} module to be installed.
+ *
+ * @example
+ *
+ * ```js
+ * angular.module('cookiesExample', ['ngCookies'])
+ * .controller('ExampleController', ['$cookies', function($cookies) {
+ * // Retrieving a cookie
+ * var favoriteCookie = $cookies.get('myFavorite');
+ * // Setting a cookie
+ * $cookies.put('myFavorite', 'oatmeal');
+ * }]);
+ * ```
+ */
+ this.$get = ['$$cookieReader', '$$cookieWriter', function($$cookieReader, $$cookieWriter) {
return {
/**
* @ngdoc method
- * @name $cookieStore#get
+ * @name $cookies#get
*
* @description
* Returns the value of given cookie key
*
* @param {string} key Id to use for lookup.
- * @returns {Object} Deserialized cookie value.
+ * @returns {string} Raw cookie value.
*/
get: function(key) {
- var value = $cookies[key];
+ return $$cookieReader()[key];
+ },
+
+ /**
+ * @ngdoc method
+ * @name $cookies#getObject
+ *
+ * @description
+ * Returns the deserialized value of given cookie key
+ *
+ * @param {string} key Id to use for lookup.
+ * @returns {Object} Deserialized cookie value.
+ */
+ getObject: function(key) {
+ var value = this.get(key);
return value ? angular.fromJson(value) : value;
},
/**
* @ngdoc method
- * @name $cookieStore#put
+ * @name $cookies#getAll
+ *
+ * @description
+ * Returns a key value object with all the cookies
+ *
+ * @returns {Object} All cookies
+ */
+ getAll: function() {
+ return $$cookieReader();
+ },
+
+ /**
+ * @ngdoc method
+ * @name $cookies#put
*
* @description
* Sets a value for given cookie key
*
* @param {string} key Id for the `value`.
- * @param {Object} value Value to be stored.
+ * @param {string} value Raw value to be stored.
+ * @param {Object=} options Options object.
+ * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults}
*/
- put: function(key, value) {
- $cookies[key] = angular.toJson(value);
+ put: function(key, value, options) {
+ $$cookieWriter(key, value, calcOptions(options));
},
/**
* @ngdoc method
- * @name $cookieStore#remove
+ * @name $cookies#putObject
+ *
+ * @description
+ * Serializes and sets a value for given cookie key
+ *
+ * @param {string} key Id for the `value`.
+ * @param {Object} value Value to be stored.
+ * @param {Object=} options Options object.
+ * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults}
+ */
+ putObject: function(key, value, options) {
+ this.put(key, angular.toJson(value), options);
+ },
+
+ /**
+ * @ngdoc method
+ * @name $cookies#remove
*
* @description
* Remove given cookie
*
* @param {string} key Id of the key-value pair to delete.
+ * @param {Object=} options Options object.
+ * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults}
*/
- remove: function(key) {
- delete $cookies[key];
+ remove: function(key, options) {
+ $$cookieWriter(key, undefined, calcOptions(options));
}
};
+ }];
+ }]);
- }]);
+angular.module('ngCookies').
+/**
+ * @ngdoc service
+ * @name $cookieStore
+ * @deprecated
+ * @requires $cookies
+ *
+ * @description
+ * Provides a key-value (string-object) storage, that is backed by session cookies.
+ * Objects put or retrieved from this storage are automatically serialized or
+ * deserialized by angular's toJson/fromJson.
+ *
+ * Requires the {@link ngCookies `ngCookies`} module to be installed.
+ *
+ *
+ * **Note:** The $cookieStore service is **deprecated**.
+ * Please use the {@link ngCookies.$cookies `$cookies`} service instead.
+ *
+ *
+ * @example
+ *
+ * ```js
+ * angular.module('cookieStoreExample', ['ngCookies'])
+ * .controller('ExampleController', ['$cookieStore', function($cookieStore) {
+ * // Put cookie
+ * $cookieStore.put('myFavorite','oatmeal');
+ * // Get cookie
+ * var favoriteCookie = $cookieStore.get('myFavorite');
+ * // Removing a cookie
+ * $cookieStore.remove('myFavorite');
+ * }]);
+ * ```
+ */
+ factory('$cookieStore', ['$cookies', function($cookies) {
+
+ return {
+ /**
+ * @ngdoc method
+ * @name $cookieStore#get
+ *
+ * @description
+ * Returns the value of given cookie key
+ *
+ * @param {string} key Id to use for lookup.
+ * @returns {Object} Deserialized cookie value, undefined if the cookie does not exist.
+ */
+ get: function(key) {
+ return $cookies.getObject(key);
+ },
+
+ /**
+ * @ngdoc method
+ * @name $cookieStore#put
+ *
+ * @description
+ * Sets a value for given cookie key
+ *
+ * @param {string} key Id for the `value`.
+ * @param {Object} value Value to be stored.
+ */
+ put: function(key, value) {
+ $cookies.putObject(key, value);
+ },
+
+ /**
+ * @ngdoc method
+ * @name $cookieStore#remove
+ *
+ * @description
+ * Remove given cookie
+ *
+ * @param {string} key Id of the key-value pair to delete.
+ */
+ remove: function(key) {
+ $cookies.remove(key);
+ }
+ };
+
+ }]);
+
+/**
+ * @name $$cookieWriter
+ * @requires $document
+ *
+ * @description
+ * This is a private service for writing cookies
+ *
+ * @param {string} name Cookie name
+ * @param {string=} value Cookie value (if undefined, cookie will be deleted)
+ * @param {Object=} options Object with options that need to be stored for the cookie.
+ */
+function $$CookieWriter($document, $log, $browser) {
+ var cookiePath = $browser.baseHref();
+ var rawDocument = $document[0];
+
+ function buildCookieString(name, value, options) {
+ var path, expires;
+ options = options || {};
+ expires = options.expires;
+ path = angular.isDefined(options.path) ? options.path : cookiePath;
+ if (value === undefined) {
+ expires = 'Thu, 01 Jan 1970 00:00:00 GMT';
+ value = '';
+ }
+ if (angular.isString(expires)) {
+ expires = new Date(expires);
+ }
+
+ var str = encodeURIComponent(name) + '=' + encodeURIComponent(value);
+ str += path ? ';path=' + path : '';
+ str += options.domain ? ';domain=' + options.domain : '';
+ str += expires ? ';expires=' + expires.toUTCString() : '';
+ str += options.secure ? ';secure' : '';
+
+ // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
+ // - 300 cookies
+ // - 20 cookies per unique domain
+ // - 4096 bytes per cookie
+ var cookieLength = str.length + 1;
+ if (cookieLength > 4096) {
+ $log.warn("Cookie '" + name +
+ "' possibly not set or overflowed because it was too large (" +
+ cookieLength + " > 4096 bytes)!");
+ }
+
+ return str;
+ }
+
+ return function(name, value, options) {
+ rawDocument.cookie = buildCookieString(name, value, options);
+ };
+}
+
+$$CookieWriter.$inject = ['$document', '$log', '$browser'];
+
+angular.module('ngCookies').provider('$$cookieWriter', function $$CookieWriterProvider() {
+ this.$get = $$CookieWriter;
+});
})(window, window.angular);
diff --git a/awx/ui/client/lib/angular-cookies/angular-cookies.min.js b/awx/ui/client/lib/angular-cookies/angular-cookies.min.js
index 824ae4fbf2..36b74e96fd 100644
--- a/awx/ui/client/lib/angular-cookies/angular-cookies.min.js
+++ b/awx/ui/client/lib/angular-cookies/angular-cookies.min.js
@@ -1,8 +1,9 @@
/*
- AngularJS v1.2.15-build.2398+sha.4bab3d8
- (c) 2010-2014 Google, Inc. http://angularjs.org
+ AngularJS v1.4.3
+ (c) 2010-2015 Google, Inc. http://angularjs.org
License: MIT
*/
-(function(p,f,n){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,b){var c={},g={},h,k=!1,l=f.copy,m=f.isUndefined;b.addPollFn(function(){var a=b.cookies();h!=a&&(h=a,l(a,g),l(a,c),k&&d.$apply())})();k=!0;d.$watch(function(){var a,e,d;for(a in g)m(c[a])&&b.cookies(a,n);for(a in c)(e=c[a],f.isString(e))?e!==g[a]&&(b.cookies(a,e),d=!0):f.isDefined(g[a])?c[a]=g[a]:delete c[a];if(d)for(a in e=b.cookies(),c)c[a]!==e[a]&&(m(e[a])?delete c[a]:c[a]=e[a])});
-return c}]).factory("$cookieStore",["$cookies",function(d){return{get:function(b){return(b=d[b])?f.fromJson(b):b},put:function(b,c){d[b]=f.toJson(c)},remove:function(b){delete d[b]}}}])})(window,window.angular);
+(function(p,g,l){'use strict';function m(b,a,f){var c=f.baseHref(),k=b[0];return function(b,d,e){var f,h;e=e||{};h=e.expires;f=g.isDefined(e.path)?e.path:c;d===l&&(h="Thu, 01 Jan 1970 00:00:00 GMT",d="");g.isString(h)&&(h=new Date(h));d=encodeURIComponent(b)+"="+encodeURIComponent(d);d=d+(f?";path="+f:"")+(e.domain?";domain="+e.domain:"");d+=h?";expires="+h.toUTCString():"";d+=e.secure?";secure":"";e=d.length+1;4096 4096 bytes)!");k.cookie=d}}g.module("ngCookies",["ng"]).provider("$cookies",[function(){var b=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(a,f){return{get:function(c){return a()[c]},getObject:function(c){return(c=this.get(c))?g.fromJson(c):c},getAll:function(){return a()},put:function(c,a,n){f(c,a,n?g.extend({},b,n):b)},putObject:function(c,b,a){this.put(c,g.toJson(b),a)},remove:function(a,k){f(a,l,k?g.extend({},b,k):b)}}}]}]);g.module("ngCookies").factory("$cookieStore",
+["$cookies",function(b){return{get:function(a){return b.getObject(a)},put:function(a,f){b.putObject(a,f)},remove:function(a){b.remove(a)}}}]);m.$inject=["$document","$log","$browser"];g.module("ngCookies").provider("$$cookieWriter",function(){this.$get=m})})(window,window.angular);
//# sourceMappingURL=angular-cookies.min.js.map
diff --git a/awx/ui/client/lib/angular-cookies/angular-cookies.min.js.map b/awx/ui/client/lib/angular-cookies/angular-cookies.min.js.map
index 6a04717c18..84e6a5701f 100644
--- a/awx/ui/client/lib/angular-cookies/angular-cookies.min.js.map
+++ b/awx/ui/client/lib/angular-cookies/angular-cookies.min.js.map
@@ -1,8 +1,8 @@
{
"version":3,
"file":"angular-cookies.min.js",
-"lineCount":7,
-"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkBC,CAAlB,CAA6B,CAmBtCD,CAAAE,OAAA,CAAe,WAAf,CAA4B,CAAC,IAAD,CAA5B,CAAAC,QAAA,CA2BW,UA3BX,CA2BuB,CAAC,YAAD,CAAe,UAAf,CAA2B,QAAS,CAACC,CAAD,CAAaC,CAAb,CAAuB,CAAA,IACxEC,EAAU,EAD8D,CAExEC,EAAc,EAF0D,CAGxEC,CAHwE,CAIxEC,EAAU,CAAA,CAJ8D,CAKxEC,EAAOV,CAAAU,KALiE,CAMxEC,EAAcX,CAAAW,YAGlBN,EAAAO,UAAA,CAAmB,QAAQ,EAAG,CAC5B,IAAIC,EAAiBR,CAAAC,QAAA,EACjBE,EAAJ,EAA0BK,CAA1B,GACEL,CAGA,CAHqBK,CAGrB,CAFAH,CAAA,CAAKG,CAAL,CAAqBN,CAArB,CAEA,CADAG,CAAA,CAAKG,CAAL,CAAqBP,CAArB,CACA,CAAIG,CAAJ,EAAaL,CAAAU,OAAA,EAJf,CAF4B,CAA9B,CAAA,EAUAL,EAAA,CAAU,CAAA,CAKVL,EAAAW,OAAA,CASAC,QAAa,EAAG,CAAA,IACVC,CADU,CAEVC,CAFU,CAIVC,CAGJ,KAAKF,CAAL,GAAaV,EAAb,CACMI,CAAA,CAAYL,CAAA,CAAQW,CAAR,CAAZ,CAAJ,EACEZ,CAAAC,QAAA,CAAiBW,CAAjB,CAAuBhB,CAAvB,CAKJ,KAAIgB,CAAJ,GAAYX,EAAZ,CAEE,CADAY,CACK,CADGZ,CAAA,CAAQW,CAAR,CACH,CAAAjB,CAAAoB,SAAA,CAAiBF,CAAjB,CAAL,EAMWA,CANX,GAMqBX,CAAA,CAAYU,CAAZ,CANrB,GAOEZ,CAAAC,QAAA,CAAiBW,CAAjB,CAAuBC,CAAvB,CACA,CAAAC,CAAA,CAAU,CAAA,CARZ,EACMnB,CAAAqB,UAAA,CAAkBd,CAAA,CAAYU,CAAZ,CAAlB,CAAJ,CACEX,CAAA,CAAQW,CAAR,CADF,CACkBV,CAAA,CAAYU,CAAZ,CADlB,CAGE,OAAOX,CAAA,CAAQW,CAAR,CASb,IAAIE,CAAJ,CAIE,IAAKF,CAAL,GAFAK,EAEahB,CAFID,CAAAC,QAAA,EAEJA,CAAAA,CAAb,CACMA,CAAA,CAAQW,CAAR,CAAJ,GAAsBK,CAAA,CAAeL,CAAf,CAAtB,GAEMN,CAAA,CAAYW,CAAA,CAAeL,CAAf,CAAZ,CAAJ,CACE,OAAOX,CAAA,CAAQW,CAAR,CADT,CAGEX,CAAA,CAAQW,CAAR,CAHF,CAGkBK,CAAA,CAAeL,CAAf,CALpB,CAlCU,CAThB,CAEA;MAAOX,EA1BqE,CAA3D,CA3BvB,CAAAH,QAAA,CA2HW,cA3HX,CA2H2B,CAAC,UAAD,CAAa,QAAQ,CAACoB,CAAD,CAAW,CAErD,MAAO,KAWAC,QAAQ,CAACC,CAAD,CAAM,CAEjB,MAAO,CADHP,CACG,CADKK,CAAA,CAASE,CAAT,CACL,EAAQzB,CAAA0B,SAAA,CAAiBR,CAAjB,CAAR,CAAkCA,CAFxB,CAXd,KA0BAS,QAAQ,CAACF,CAAD,CAAMP,CAAN,CAAa,CACxBK,CAAA,CAASE,CAAT,CAAA,CAAgBzB,CAAA4B,OAAA,CAAeV,CAAf,CADQ,CA1BrB,QAuCGW,QAAQ,CAACJ,CAAD,CAAM,CACpB,OAAOF,CAAA,CAASE,CAAT,CADa,CAvCjB,CAF8C,CAAhC,CA3H3B,CAnBsC,CAArC,CAAA,CA+LE1B,MA/LF,CA+LUA,MAAAC,QA/LV;",
+"lineCount":8,
+"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkBC,CAAlB,CAA6B,CA0QtCC,QAASA,EAAc,CAACC,CAAD,CAAYC,CAAZ,CAAkBC,CAAlB,CAA4B,CACjD,IAAIC,EAAaD,CAAAE,SAAA,EAAjB,CACIC,EAAcL,CAAA,CAAU,CAAV,CAmClB,OAAO,SAAQ,CAACM,CAAD,CAAOC,CAAP,CAAcC,CAAd,CAAuB,CAjCW,IAC3CC,CAD2C,CACrCC,CACVF,EAAA,CAgCoDA,CAhCpD,EAAqB,EACrBE,EAAA,CAAUF,CAAAE,QACVD,EAAA,CAAOZ,CAAAc,UAAA,CAAkBH,CAAAC,KAAlB,CAAA,CAAkCD,CAAAC,KAAlC,CAAiDN,CACpDI,EAAJ,GAAcT,CAAd,GACEY,CACA,CADU,+BACV,CAAAH,CAAA,CAAQ,EAFV,CAIIV,EAAAe,SAAA,CAAiBF,CAAjB,CAAJ,GACEA,CADF,CACY,IAAIG,IAAJ,CAASH,CAAT,CADZ,CAIII,EAAAA,CAAMC,kBAAA,CAqB6BT,CArB7B,CAANQ,CAAiC,GAAjCA,CAAuCC,kBAAA,CAAmBR,CAAnB,CAE3CO,EAAA,CADAA,CACA,EADOL,CAAA,CAAO,QAAP,CAAkBA,CAAlB,CAAyB,EAChC,GAAOD,CAAAQ,OAAA,CAAiB,UAAjB,CAA8BR,CAAAQ,OAA9B,CAA+C,EAAtD,CACAF,EAAA,EAAOJ,CAAA,CAAU,WAAV,CAAwBA,CAAAO,YAAA,EAAxB,CAAgD,EACvDH,EAAA,EAAON,CAAAU,OAAA,CAAiB,SAAjB,CAA6B,EAMhCC,EAAAA,CAAeL,CAAAM,OAAfD,CAA4B,CACb,KAAnB,CAAIA,CAAJ,EACElB,CAAAoB,KAAA,CAAU,UAAV,CASqCf,CATrC,CACE,6DADF;AAEEa,CAFF,CAEiB,iBAFjB,CASFd,EAAAiB,OAAA,CAJOR,CAG6B,CArCW,CAxPnDjB,CAAA0B,OAAA,CAAe,WAAf,CAA4B,CAAC,IAAD,CAA5B,CAAAC,SAAA,CAOY,UAPZ,CAOwB,CAACC,QAAyB,EAAG,CAuBjD,IAAIC,EAAW,IAAAA,SAAXA,CAA2B,EAiC/B,KAAAC,KAAA,CAAY,CAAC,gBAAD,CAAmB,gBAAnB,CAAqC,QAAQ,CAACC,CAAD,CAAiBC,CAAjB,CAAiC,CACxF,MAAO,CAWLC,IAAKA,QAAQ,CAACC,CAAD,CAAM,CACjB,MAAOH,EAAA,EAAA,CAAiBG,CAAjB,CADU,CAXd,CAyBLC,UAAWA,QAAQ,CAACD,CAAD,CAAM,CAEvB,MAAO,CADHxB,CACG,CADK,IAAAuB,IAAA,CAASC,CAAT,CACL,EAAQlC,CAAAoC,SAAA,CAAiB1B,CAAjB,CAAR,CAAkCA,CAFlB,CAzBpB,CAuCL2B,OAAQA,QAAQ,EAAG,CACjB,MAAON,EAAA,EADU,CAvCd,CAuDLO,IAAKA,QAAQ,CAACJ,CAAD,CAAMxB,CAAN,CAAaC,CAAb,CAAsB,CACjCqB,CAAA,CAAeE,CAAf,CAAoBxB,CAApB,CAAuCC,CAvFpC,CAAUX,CAAAuC,OAAA,CAAe,EAAf,CAAmBV,CAAnB,CAuF0BlB,CAvF1B,CAAV,CAAkDkB,CAuFrD,CADiC,CAvD9B,CAuELW,UAAWA,QAAQ,CAACN,CAAD,CAAMxB,CAAN,CAAaC,CAAb,CAAsB,CACvC,IAAA2B,IAAA,CAASJ,CAAT,CAAclC,CAAAyC,OAAA,CAAe/B,CAAf,CAAd,CAAqCC,CAArC,CADuC,CAvEpC,CAsFL+B,OAAQA,QAAQ,CAACR,CAAD,CAAMvB,CAAN,CAAe,CAC7BqB,CAAA,CAAeE,CAAf,CAAoBjC,CAApB,CAA2CU,CAtHxC,CAAUX,CAAAuC,OAAA,CAAe,EAAf,CAAmBV,CAAnB,CAsH8BlB,CAtH9B,CAAV,CAAkDkB,CAsHrD,CAD6B,CAtF1B,CADiF,CAA9E,CAxDqC,CAA7B,CAPxB,CA6JA7B,EAAA0B,OAAA,CAAe,WAAf,CAAAiB,QAAA,CAiCS,cAjCT;AAiCyB,CAAC,UAAD,CAAa,QAAQ,CAACC,CAAD,CAAW,CAErD,MAAO,CAWLX,IAAKA,QAAQ,CAACC,CAAD,CAAM,CACjB,MAAOU,EAAAT,UAAA,CAAmBD,CAAnB,CADU,CAXd,CAyBLI,IAAKA,QAAQ,CAACJ,CAAD,CAAMxB,CAAN,CAAa,CACxBkC,CAAAJ,UAAA,CAAmBN,CAAnB,CAAwBxB,CAAxB,CADwB,CAzBrB,CAsCLgC,OAAQA,QAAQ,CAACR,CAAD,CAAM,CACpBU,CAAAF,OAAA,CAAgBR,CAAhB,CADoB,CAtCjB,CAF8C,CAAhC,CAjCzB,CAqIAhC,EAAA2C,QAAA,CAAyB,CAAC,WAAD,CAAc,MAAd,CAAsB,UAAtB,CAEzB7C,EAAA0B,OAAA,CAAe,WAAf,CAAAC,SAAA,CAAqC,gBAArC,CAAuDmB,QAA+B,EAAG,CACvF,IAAAhB,KAAA,CAAY5B,CAD2E,CAAzF,CAtTsC,CAArC,CAAD,CA2TGH,MA3TH,CA2TWA,MAAAC,QA3TX;",
"sources":["angular-cookies.js"],
-"names":["window","angular","undefined","module","factory","$rootScope","$browser","cookies","lastCookies","lastBrowserCookies","runEval","copy","isUndefined","addPollFn","currentCookies","$apply","$watch","push","name","value","updated","isString","isDefined","browserCookies","$cookies","get","key","fromJson","put","toJson","remove"]
+"names":["window","angular","undefined","$$CookieWriter","$document","$log","$browser","cookiePath","baseHref","rawDocument","name","value","options","path","expires","isDefined","isString","Date","str","encodeURIComponent","domain","toUTCString","secure","cookieLength","length","warn","cookie","module","provider","$CookiesProvider","defaults","$get","$$cookieReader","$$cookieWriter","get","key","getObject","fromJson","getAll","put","extend","putObject","toJson","remove","factory","$cookies","$inject","$$CookieWriterProvider"]
}
diff --git a/awx/ui/client/lib/angular-cookies/bower.json b/awx/ui/client/lib/angular-cookies/bower.json
index 6356d244ff..9ab6d5f01b 100644
--- a/awx/ui/client/lib/angular-cookies/bower.json
+++ b/awx/ui/client/lib/angular-cookies/bower.json
@@ -1,8 +1,9 @@
{
"name": "angular-cookies",
- "version": "1.2.15-build.2398+sha.4bab3d8",
+ "version": "1.4.3",
"main": "./angular-cookies.js",
+ "ignore": [],
"dependencies": {
- "angular": "1.2.15-build.2398+sha.4bab3d8"
+ "angular": "1.4.3"
}
}
diff --git a/awx/ui/client/lib/angular-cookies/index.js b/awx/ui/client/lib/angular-cookies/index.js
new file mode 100644
index 0000000000..657667549a
--- /dev/null
+++ b/awx/ui/client/lib/angular-cookies/index.js
@@ -0,0 +1,2 @@
+require('./angular-cookies');
+module.exports = 'ngCookies';
diff --git a/awx/ui/client/lib/angular-cookies/package.json b/awx/ui/client/lib/angular-cookies/package.json
new file mode 100644
index 0000000000..a9862f9bcc
--- /dev/null
+++ b/awx/ui/client/lib/angular-cookies/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "angular-cookies",
+ "version": "1.4.3",
+ "description": "AngularJS module for cookies",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/angular/angular.js.git"
+ },
+ "keywords": [
+ "angular",
+ "framework",
+ "browser",
+ "cookies",
+ "client-side"
+ ],
+ "author": "Angular Core Team ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/angular/angular.js/issues"
+ },
+ "homepage": "http://angularjs.org"
+}
diff --git a/awx/ui/client/lib/angular-filters/.bower.json b/awx/ui/client/lib/angular-filters/.bower.json
index 01d1b29785..e64588c093 100644
--- a/awx/ui/client/lib/angular-filters/.bower.json
+++ b/awx/ui/client/lib/angular-filters/.bower.json
@@ -2,30 +2,26 @@
"author": "Francesco Pontillo",
"name": "angular-filters",
"description": "A collection of filters for AngularJS.",
- "version": "1.1.0",
+ "version": "1.1.2",
"homepage": "https://github.com/frapontillo/angular-filters",
"repository": {
"type": "git",
"url": "git://github.com/frapontillo/angular-filters.git"
},
- "main": "./dist/angular-filters.min.js",
+ "main": "./dist/angular-filters.js",
"dependencies": {
- "angular": "1.2.10"
+ "angular": "~1.4.0"
},
"devDependencies": {
- "angular-mocks": "1.2.10"
+ "angular-mocks": "~1.4.0"
},
- "resolutions": {
- "angular": "1.2.10"
- },
- "_release": "1.1.0",
+ "_release": "1.1.2",
"_resolution": {
"type": "version",
- "tag": "1.1.0",
- "commit": "ecd468b898f99786e90c4631835e83ef93a2d743"
+ "tag": "1.1.2",
+ "commit": "61c1501beb057ff75fbc4953e32623104c6cd8ad"
},
"_source": "git://github.com/frapontillo/angular-filters.git",
- "_target": "~1.1.0",
- "_originalSource": "angular-filters",
- "_direct": true
+ "_target": "~1.1.2",
+ "_originalSource": "angular-filters"
}
\ No newline at end of file
diff --git a/awx/ui/client/lib/angular-filters/.travis.yml b/awx/ui/client/lib/angular-filters/.travis.yml
index dc9324bd55..9255758283 100644
--- a/awx/ui/client/lib/angular-filters/.travis.yml
+++ b/awx/ui/client/lib/angular-filters/.travis.yml
@@ -1,12 +1,16 @@
language: node_js
+
node_js:
- - "0.10.15"
+ - '0.10'
- before_install:
- - export DISPLAY=:99.0
- - sh -e /etc/init.d/xvfb start
- - npm install -g grunt-cli karma bower
- - npm install
- - bower install
+ before_script:
+ - export DISPLAY=:99.0
+ - export PHANTOMJS_BIN=/usr/local/phantomjs/bin/phantomjs
+ - sh -e /etc/init.d/xvfb start
+ - sleep 3 # give xvfb some time to start
+ - 'npm install -g bower grunt-cli'
+ - 'npm install'
+ - 'bower install'
- script: "grunt"
\ No newline at end of file
+ script:
+ - grunt
diff --git a/awx/ui/client/lib/angular-filters/CHANGELOG.md b/awx/ui/client/lib/angular-filters/CHANGELOG.md
index 3a84d5bfd3..60fad95f78 100644
--- a/awx/ui/client/lib/angular-filters/CHANGELOG.md
+++ b/awx/ui/client/lib/angular-filters/CHANGELOG.md
@@ -1,6 +1,14 @@
CHANGELOG
=========
+#### v1.1.2 (2015-06-13)
+
+- Updated to angular 1.4.0
+
+#### v1.1.1
+
+- Treat `NaN` as an invalid value for the `default` filter
+
#### v1.1.0
- Added `property` filter
diff --git a/awx/ui/client/lib/angular-filters/Gruntfile.js b/awx/ui/client/lib/angular-filters/Gruntfile.js
index f6f51ba3bb..5c997d8254 100644
--- a/awx/ui/client/lib/angular-filters/Gruntfile.js
+++ b/awx/ui/client/lib/angular-filters/Gruntfile.js
@@ -99,7 +99,7 @@ module.exports = function(grunt) {
}
});
- grunt.registerTask('test', ['jshint', 'karma:unit']);
+ grunt.registerTask('test', ['jshint', 'karma:local']);
grunt.registerTask('test-travis', ['jshint', 'karma:travis']);
grunt.registerTask('build', ['clean', 'ngmin', 'concat', 'uglify', 'clean:temp']);
diff --git a/awx/ui/client/lib/angular-filters/README.md b/awx/ui/client/lib/angular-filters/README.md
index c0dc78db9c..5667149c14 100644
--- a/awx/ui/client/lib/angular-filters/README.md
+++ b/awx/ui/client/lib/angular-filters/README.md
@@ -47,6 +47,7 @@ The `default` filter allows to **specify a default fallback value** if an object
- `null`
- `undefined`
- empty string, `''`
+- `NaN`
Please notice that if a value equals to `0`, the default value won't be returned, as `0` is accepted.
@@ -184,7 +185,7 @@ To contribute, please follow the generic [AngularJS Contributing Guidelines](htt
## License
```
- Copyright 2014 Francesco Pontillo
+ Copyright 2014-2015 Francesco Pontillo
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/awx/ui/client/lib/angular-filters/bower.json b/awx/ui/client/lib/angular-filters/bower.json
index 51426c2e12..a48807293a 100644
--- a/awx/ui/client/lib/angular-filters/bower.json
+++ b/awx/ui/client/lib/angular-filters/bower.json
@@ -2,20 +2,17 @@
"author": "Francesco Pontillo",
"name": "angular-filters",
"description": "A collection of filters for AngularJS.",
- "version": "1.1.0",
+ "version": "1.1.2",
"homepage": "https://github.com/frapontillo/angular-filters",
"repository": {
"type": "git",
"url": "git://github.com/frapontillo/angular-filters.git"
},
- "main": "./dist/angular-filters.min.js",
+ "main": "./dist/angular-filters.js",
"dependencies": {
- "angular": "1.2.10"
+ "angular": "~1.4.0"
},
"devDependencies": {
- "angular-mocks": "1.2.10"
- },
- "resolutions": {
- "angular": "1.2.10"
+ "angular-mocks": "~1.4.0"
}
}
diff --git a/awx/ui/client/lib/angular-filters/dist/angular-filters.js b/awx/ui/client/lib/angular-filters/dist/angular-filters.js
index b6a6e7f663..703f40259f 100644
--- a/awx/ui/client/lib/angular-filters/dist/angular-filters.js
+++ b/awx/ui/client/lib/angular-filters/dist/angular-filters.js
@@ -1,6 +1,6 @@
/**
* A collection of filters for AngularJS.
- * @version v1.1.0 - 2014-01-30
+ * @version v1.1.2 - 2015-06-13
* @author Francesco Pontillo
* @link https://github.com/frapontillo/angular-filters
* @license Apache License 2.0
@@ -19,7 +19,7 @@ angular.module('frapontillo.ex.filters').filter('bool', function () {
// Source: dist/.temp/filters/default/default.js
angular.module('frapontillo.ex.filters').filter('default', function () {
return function (input, value) {
- if (input !== null && input !== undefined && (input !== '' || angular.isNumber(input))) {
+ if (!isNaN(input) && input !== null && input !== undefined && (input !== '' || angular.isNumber(input))) {
return input;
}
return value || '';
diff --git a/awx/ui/client/lib/angular-filters/dist/angular-filters.min.js b/awx/ui/client/lib/angular-filters/dist/angular-filters.min.js
index a4c177364b..9d24b7a4b4 100644
--- a/awx/ui/client/lib/angular-filters/dist/angular-filters.min.js
+++ b/awx/ui/client/lib/angular-filters/dist/angular-filters.min.js
@@ -1,9 +1,9 @@
/**
* A collection of filters for AngularJS.
- * @version v1.1.0 - 2014-01-30
+ * @version v1.1.2 - 2015-06-13
* @author Francesco Pontillo
* @link https://github.com/frapontillo/angular-filters
* @license Apache License 2.0
**/
-"use strict";angular.module("frapontillo.ex.filters",[]),angular.module("frapontillo",["ex.filters"]),angular.module("frapontillo.ex.filters").filter("bool",function(){return function(a,b,c){return a!==!0?c:b}}),angular.module("frapontillo.ex.filters").filter("default",function(){return function(a,b){return null===a||void 0===a||""===a&&!angular.isNumber(a)?b||"":a}}),angular.module("frapontillo.ex.filters").filter("firstNotNull",function(){return function(a){if(a)for(var b=a.length-1,c=0;b>=c;c++)if(void 0!==a[c]&&null!==a[c])return a[c]}}),angular.module("frapontillo.ex.filters").filter("join",function(){return function(a,b){return a?a.join(b):""}}),angular.module("frapontillo.ex.filters").filter("lastNotNull",function(){return function(a){if(a)for(var b=a.length-1,c=b;c>=0;c--)if(void 0!==a[c])return a[c]}}),angular.module("frapontillo.ex.filters").filter("max",function(){return function(a){var b;if(a)for(var c in a)(a[c]>b||void 0===b||null===b)&&(b=a[c]);return b}}),angular.module("frapontillo.ex.filters").filter("min",function(){return function(a){var b;if(a)for(var c in a)(a[c]=c;c++)if(void 0!==a[c]&&null!==a[c])return a[c]}}),angular.module("frapontillo.ex.filters").filter("join",function(){return function(a,b){return a?a.join(b):""}}),angular.module("frapontillo.ex.filters").filter("lastNotNull",function(){return function(a){if(a)for(var b=a.length-1,c=b;c>=0;c--)if(void 0!==a[c])return a[c]}}),angular.module("frapontillo.ex.filters").filter("max",function(){return function(a){var b;if(a)for(var c in a)(a[c]>b||void 0===b||null===b)&&(b=a[c]);return b}}),angular.module("frapontillo.ex.filters").filter("min",function(){return function(a){var b;if(a)for(var c in a)(a[c] 4,
+ args = hasParams ? Array.prototype.slice.call(arguments, 4) : [],
iteration = 0,
- skipApply = (angular.isDefined(invokeApply) && !invokeApply);
+ skipApply = (angular.isDefined(invokeApply) && !invokeApply),
+ deferred = (skipApply ? $$q : $q).defer(),
+ promise = deferred.promise;
count = (angular.isDefined(count)) ? count : 0;
- promise.then(null, null, fn);
+ promise.then(null, null, (!hasParams) ? fn : function() {
+ fn.apply(null, args);
+ });
promise.$$intervalId = nextRepeatId;
@@ -483,7 +474,11 @@ angular.mock.$IntervalProvider = function() {
}
}
- if (!skipApply) $rootScope.$apply();
+ if (skipApply) {
+ $browser.defer.flush();
+ } else {
+ $rootScope.$apply();
+ }
}
repeatFns.push({
@@ -493,7 +488,7 @@ angular.mock.$IntervalProvider = function() {
id: nextRepeatId,
deferred: deferred
});
- repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;});
+ repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;});
nextRepeatId++;
return promise;
@@ -509,7 +504,7 @@ angular.mock.$IntervalProvider = function() {
* @returns {boolean} Returns `true` if the task was successfully cancelled.
*/
$interval.cancel = function(promise) {
- if(!promise) return false;
+ if (!promise) return false;
var fnIndex;
angular.forEach(repeatFns, function(fn, index) {
@@ -542,7 +537,7 @@ angular.mock.$IntervalProvider = function() {
var task = repeatFns[0];
task.fn();
task.nextTime += task.delay;
- repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;});
+ repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;});
}
return millis;
};
@@ -566,20 +561,20 @@ function jsonStringToDate(string) {
tzHour = 0,
tzMin = 0;
if (match[9]) {
- tzHour = int(match[9] + match[10]);
- tzMin = int(match[9] + match[11]);
+ tzHour = toInt(match[9] + match[10]);
+ tzMin = toInt(match[9] + match[11]);
}
- date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
- date.setUTCHours(int(match[4]||0) - tzHour,
- int(match[5]||0) - tzMin,
- int(match[6]||0),
- int(match[7]||0));
+ date.setUTCFullYear(toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
+ date.setUTCHours(toInt(match[4] || 0) - tzHour,
+ toInt(match[5] || 0) - tzMin,
+ toInt(match[6] || 0),
+ toInt(match[7] || 0));
return date;
}
return string;
}
-function int(str) {
+function toInt(str) {
return parseInt(str, 10);
}
@@ -590,9 +585,10 @@ function padNumber(num, digits, trim) {
num = -num;
}
num = '' + num;
- while(num.length < digits) num = '0' + num;
- if (trim)
+ while (num.length < digits) num = '0' + num;
+ if (trim) {
num = num.substr(num.length - digits);
+ }
return neg + num;
}
@@ -634,7 +630,7 @@ function padNumber(num, digits, trim) {
* ```
*
*/
-angular.mock.TzDate = function (offset, timestamp) {
+angular.mock.TzDate = function(offset, timestamp) {
var self = new Date(0);
if (angular.isString(timestamp)) {
var tsStr = timestamp;
@@ -642,17 +638,18 @@ angular.mock.TzDate = function (offset, timestamp) {
self.origDate = jsonStringToDate(timestamp);
timestamp = self.origDate.getTime();
- if (isNaN(timestamp))
+ if (isNaN(timestamp)) {
throw {
name: "Illegal Argument",
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
};
+ }
} else {
self.origDate = new Date(timestamp);
}
var localOffset = new Date(timestamp).getTimezoneOffset();
- self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
+ self.offsetDiff = localOffset * 60 * 1000 - offset * 1000 * 60 * 60;
self.date = new Date(timestamp + self.offsetDiff);
self.getTime = function() {
@@ -774,14 +771,23 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
};
});
- $provide.decorator('$animate', function($delegate, $$asyncCallback) {
+ $provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF',
+ function($delegate, $timeout, $browser, $$rAF) {
var animate = {
- queue : [],
- enabled : $delegate.enabled,
- triggerCallbacks : function() {
- $$asyncCallback.flush();
+ queue: [],
+ cancel: $delegate.cancel,
+ enabled: $delegate.enabled,
+ triggerCallbackEvents: function() {
+ $$rAF.flush();
},
- triggerReflow : function() {
+ triggerCallbackPromise: function() {
+ $timeout.flush(0);
+ },
+ triggerCallbacks: function() {
+ this.triggerCallbackEvents();
+ this.triggerCallbackPromise();
+ },
+ triggerReflow: function() {
angular.forEach(reflowQueue, function(fn) {
fn();
});
@@ -790,19 +796,20 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
};
angular.forEach(
- ['enter','leave','move','addClass','removeClass','setClass'], function(method) {
+ ['animate','enter','leave','move','addClass','removeClass','setClass'], function(method) {
animate[method] = function() {
animate.queue.push({
- event : method,
- element : arguments[0],
- args : arguments
+ event: method,
+ element: arguments[0],
+ options: arguments[arguments.length - 1],
+ args: arguments
});
- $delegate[method].apply($delegate, arguments);
+ return $delegate[method].apply($delegate, arguments);
};
});
return animate;
- });
+ }]);
}]);
@@ -862,13 +869,13 @@ angular.mock.dump = function(object) {
function serializeScope(scope, offset) {
offset = offset || ' ';
var log = [offset + 'Scope(' + scope.$id + '): {'];
- for ( var key in scope ) {
+ for (var key in scope) {
if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) {
log.push(' ' + key + ': ' + angular.toJson(scope[key]));
}
}
var child = scope.$$childHead;
- while(child) {
+ while (child) {
log.push(serializeScope(child, offset + ' '));
child = child.$$nextSibling;
}
@@ -980,6 +987,11 @@ angular.mock.dump = function(object) {
* First we create the controller under test:
*
```js
+ // The module code
+ angular
+ .module('MyApp', [])
+ .controller('MyController', MyController);
+
// The controller code
function MyController($scope, $http) {
var authToken;
@@ -1007,13 +1019,17 @@ angular.mock.dump = function(object) {
```js
// testing controller
describe('MyController', function() {
- var $httpBackend, $rootScope, createController;
+ var $httpBackend, $rootScope, createController, authRequestHandler;
+
+ // Set up the module
+ beforeEach(module('MyApp'));
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
- $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
+ authRequestHandler = $httpBackend.when('GET', '/auth.py')
+ .respond({userId: 'userX'}, {'A-Token': 'xxx'});
// Get hold of a scope (i.e. the root scope)
$rootScope = $injector.get('$rootScope');
@@ -1039,6 +1055,18 @@ angular.mock.dump = function(object) {
});
+ it('should fail authentication', function() {
+
+ // Notice how you can change the response even after it was set
+ authRequestHandler.respond(401, '');
+
+ $httpBackend.expectGET('/auth.py');
+ var controller = createController();
+ $httpBackend.flush();
+ expect($rootScope.status).toBe('Failed...');
+ });
+
+
it('should send msg to server', function() {
var controller = createController();
$httpBackend.flush();
@@ -1061,7 +1089,7 @@ angular.mock.dump = function(object) {
$httpBackend.flush();
$httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
- // check if the header was send, if it wasn't the expectation won't
+ // check if the header was sent, if it wasn't the expectation won't
// match the request and the test will fail
return headers['Authorization'] == 'xxx';
}).respond(201, '');
@@ -1073,7 +1101,7 @@ angular.mock.dump = function(object) {
```
*/
angular.mock.$HttpBackendProvider = function() {
- this.$get = ['$rootScope', createHttpBackendMock];
+ this.$get = ['$rootScope', '$timeout', createHttpBackendMock];
};
/**
@@ -1090,7 +1118,7 @@ angular.mock.$HttpBackendProvider = function() {
* @param {Object=} $browser Auto-flushing enabled if specified
* @return {Object} Instance of $httpBackend mock
*/
-function createHttpBackendMock($rootScope, $delegate, $browser) {
+function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
var definitions = [],
expectations = [],
responses = [],
@@ -1103,7 +1131,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
return function() {
return angular.isNumber(status)
? [status, data, headers, statusText]
- : [200, status, data];
+ : [200, status, data, headers];
};
}
@@ -1120,7 +1148,9 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
}
function wrapResponse(wrapped) {
- if (!$browser && timeout && timeout.then) timeout.then(handleTimeout);
+ if (!$browser && timeout) {
+ timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout);
+ }
return handleResponse;
@@ -1143,14 +1173,16 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
}
if (expectation && expectation.match(method, url)) {
- if (!expectation.matchData(data))
+ if (!expectation.matchData(data)) {
throw new Error('Expected ' + expectation + ' with different data\n' +
'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
+ }
- if (!expectation.matchHeaders(headers))
+ if (!expectation.matchHeaders(headers)) {
throw new Error('Expected ' + expectation + ' with different headers\n' +
'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
prettyPrint(headers));
+ }
expectations.shift();
@@ -1186,32 +1218,39 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* Creates a new backend definition.
*
* @param {string} method HTTP method.
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
* object and returns true if the headers match the current definition.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
- * request is handled.
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*
* - respond –
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers)}`
* – The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (string), response
- * headers (Object), and the text for the status (string).
+ * headers (Object), and the text for the status (string). The respond method returns the
+ * `requestHandler` object for possible overrides.
*/
$httpBackend.when = function(method, url, data, headers) {
var definition = new MockHttpExpectation(method, url, data, headers),
chain = {
respond: function(status, data, headers, statusText) {
+ definition.passThrough = undefined;
definition.response = createResponse(status, data, headers, statusText);
+ return chain;
}
};
if ($browser) {
chain.passThrough = function() {
+ definition.response = undefined;
definition.passThrough = true;
+ return chain;
};
}
@@ -1225,10 +1264,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new backend definition for GET requests. For more info see `when()`.
*
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
/**
@@ -1237,10 +1278,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new backend definition for HEAD requests. For more info see `when()`.
*
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
/**
@@ -1249,10 +1292,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new backend definition for DELETE requests. For more info see `when()`.
*
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
/**
@@ -1261,12 +1306,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new backend definition for POST requests. For more info see `when()`.
*
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
/**
@@ -1275,12 +1322,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new backend definition for PUT requests. For more info see `when()`.
*
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
/**
@@ -1289,9 +1338,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new backend definition for JSONP requests. For more info see `when()`.
*
- * @param {string|RegExp} url HTTP url.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
createShortMethods('when');
@@ -1303,30 +1354,36 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* Creates a new request expectation.
*
* @param {string} method HTTP method.
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
* object and returns true if the headers match the current expectation.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*
* - respond –
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers)}`
* – The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (string), response
- * headers (Object), and the text for the status (string).
+ * headers (Object), and the text for the status (string). The respond method returns the
+ * `requestHandler` object for possible overrides.
*/
$httpBackend.expect = function(method, url, data, headers) {
- var expectation = new MockHttpExpectation(method, url, data, headers);
+ var expectation = new MockHttpExpectation(method, url, data, headers),
+ chain = {
+ respond: function(status, data, headers, statusText) {
+ expectation.response = createResponse(status, data, headers, statusText);
+ return chain;
+ }
+ };
+
expectations.push(expectation);
- return {
- respond: function (status, data, headers, statusText) {
- expectation.response = createResponse(status, data, headers, statusText);
- }
- };
+ return chain;
};
@@ -1336,10 +1393,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new request expectation for GET requests. For more info see `expect()`.
*
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled. See #expect for more info.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled. See #expect for more info.
*/
/**
@@ -1348,10 +1407,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new request expectation for HEAD requests. For more info see `expect()`.
*
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
/**
@@ -1360,10 +1421,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new request expectation for DELETE requests. For more info see `expect()`.
*
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
/**
@@ -1372,13 +1435,15 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new request expectation for POST requests. For more info see `expect()`.
*
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
/**
@@ -1387,13 +1452,15 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new request expectation for PUT requests. For more info see `expect()`.
*
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
/**
@@ -1402,13 +1469,15 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new request expectation for PATCH requests. For more info see `expect()`.
*
- * @param {string|RegExp} url HTTP url.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
/**
@@ -1417,9 +1486,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* @description
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
*
- * @param {string|RegExp} url HTTP url.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
- * request is handled.
+ * @param {string|RegExp|function(string)} url HTTP url or function that receives an url
+ * and returns true if the url matches the current definition.
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
+ * request is handled. You can save this object for later use and invoke `respond` again in
+ * order to change how a matched request is handled.
*/
createShortMethods('expect');
@@ -1434,11 +1505,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* all pending requests will be flushed. If there are no pending requests when the flush method
* is called an exception is thrown (as this typically a sign of programming error).
*/
- $httpBackend.flush = function(count) {
- $rootScope.$digest();
+ $httpBackend.flush = function(count, digest) {
+ if (digest !== false) $rootScope.$digest();
if (!responses.length) throw new Error('No pending request to flush !');
- if (angular.isDefined(count)) {
+ if (angular.isDefined(count) && count !== null) {
while (count--) {
if (!responses.length) throw new Error('No more pending request to flush !');
responses.shift()();
@@ -1448,7 +1519,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
responses.shift()();
}
}
- $httpBackend.verifyNoOutstandingExpectation();
+ $httpBackend.verifyNoOutstandingExpectation(digest);
};
@@ -1466,8 +1537,8 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* afterEach($httpBackend.verifyNoOutstandingExpectation);
* ```
*/
- $httpBackend.verifyNoOutstandingExpectation = function() {
- $rootScope.$digest();
+ $httpBackend.verifyNoOutstandingExpectation = function(digest) {
+ if (digest !== false) $rootScope.$digest();
if (expectations.length) {
throw new Error('Unsatisfied requests: ' + expectations.join(', '));
}
@@ -1511,7 +1582,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
function createShortMethods(prefix) {
- angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) {
+ angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) {
$httpBackend[prefix + method] = function(url, headers) {
return $httpBackend[prefix](method, url, undefined, headers);
};
@@ -1541,6 +1612,7 @@ function MockHttpExpectation(method, url, data, headers) {
this.matchUrl = function(u) {
if (!url) return true;
if (angular.isFunction(url.test)) return url.test(u);
+ if (angular.isFunction(url)) return url(u);
return url == u;
};
@@ -1554,7 +1626,9 @@ function MockHttpExpectation(method, url, data, headers) {
if (angular.isUndefined(data)) return true;
if (data && angular.isFunction(data.test)) return data.test(d);
if (data && angular.isFunction(data)) return data(d);
- if (data && !angular.isString(data)) return angular.equals(data, angular.fromJson(d));
+ if (data && !angular.isString(data)) {
+ return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d));
+ }
return data == d;
};
@@ -1627,7 +1701,7 @@ function MockXhr() {
* that adds a "flush" and "verifyNoPendingTasks" methods.
*/
-angular.mock.$TimeoutDecorator = function($delegate, $browser) {
+angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $browser) {
/**
* @ngdoc method
@@ -1666,9 +1740,9 @@ angular.mock.$TimeoutDecorator = function($delegate, $browser) {
}
return $delegate;
-};
+}];
-angular.mock.$RAFDecorator = function($delegate) {
+angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
var queue = [];
var rafFn = function(fn) {
var index = queue.length;
@@ -1681,34 +1755,20 @@ angular.mock.$RAFDecorator = function($delegate) {
rafFn.supported = $delegate.supported;
rafFn.flush = function() {
- if(queue.length === 0) {
+ if (queue.length === 0) {
throw new Error('No rAF callbacks present');
}
var length = queue.length;
- for(var i=0;i",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/angular/angular.js/issues"
+ },
+ "homepage": "http://angularjs.org"
+}
diff --git a/awx/ui/client/lib/angular-resource/.bower.json b/awx/ui/client/lib/angular-resource/.bower.json
index dbf41c64b7..a5aa59928d 100644
--- a/awx/ui/client/lib/angular-resource/.bower.json
+++ b/awx/ui/client/lib/angular-resource/.bower.json
@@ -1,18 +1,19 @@
{
"name": "angular-resource",
- "version": "1.2.15-build.2398+sha.4bab3d8",
+ "version": "1.4.3",
"main": "./angular-resource.js",
+ "ignore": [],
"dependencies": {
- "angular": "1.2.15-build.2398+sha.4bab3d8"
+ "angular": "1.4.3"
},
"homepage": "https://github.com/angular/bower-angular-resource",
- "_release": "1.2.15-build.2398+sha.4bab3d8",
+ "_release": "1.4.3",
"_resolution": {
"type": "version",
- "tag": "v1.2.15-build.2398+sha.4bab3d8",
- "commit": "c20e7f87b0f788322c1c1920f1646a116389fa8d"
+ "tag": "v1.4.3",
+ "commit": "c971f7735b90685651526daf33a64c04b8539e91"
},
"_source": "git://github.com/angular/bower-angular-resource.git",
- "_target": "~1.2.12",
+ "_target": "~1.4.3",
"_originalSource": "angular-resource"
}
\ No newline at end of file
diff --git a/awx/ui/client/lib/angular-resource/README.md b/awx/ui/client/lib/angular-resource/README.md
index c8ac81468a..f3bd119ce2 100644
--- a/awx/ui/client/lib/angular-resource/README.md
+++ b/awx/ui/client/lib/angular-resource/README.md
@@ -1,12 +1,26 @@
-# bower-angular-resource
+# packaged angular-resource
-This repo is for distribution on `bower`. The source for this module is in the
+This repo is for distribution on `npm` and `bower`. The source for this module is in the
[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngResource).
Please file issues and pull requests against that repo.
## Install
-Install with `bower`:
+You can install this package either with `npm` or with `bower`.
+
+### npm
+
+```shell
+npm install angular-resource
+```
+
+Then add `ngResource` as a dependency for your app:
+
+```javascript
+angular.module('myApp', [require('angular-resource')]);
+```
+
+### bower
```shell
bower install angular-resource
@@ -18,7 +32,7 @@ Add a `
```
-And add `ngResource` as a dependency for your app:
+Then add `ngResource` as a dependency for your app:
```javascript
angular.module('myApp', ['ngResource']);
@@ -33,7 +47,7 @@ Documentation is available on the
The MIT License
-Copyright (c) 2010-2012 Google, Inc. http://angularjs.org
+Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/awx/ui/client/lib/angular-resource/angular-resource.js b/awx/ui/client/lib/angular-resource/angular-resource.js
index 73a31070f3..797f9eeafb 100644
--- a/awx/ui/client/lib/angular-resource/angular-resource.js
+++ b/awx/ui/client/lib/angular-resource/angular-resource.js
@@ -1,6 +1,6 @@
/**
- * @license AngularJS v1.2.15-build.2398+sha.4bab3d8
- * (c) 2010-2014 Google, Inc. http://angularjs.org
+ * @license AngularJS v1.4.3
+ * (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
@@ -10,7 +10,7 @@ var $resourceMinErr = angular.$$minErr('$resource');
// Helper functions and regex to lookup a dotted path on an object
// stopping at undefined/null. The path must be composed of ASCII
// identifiers (just like $parse)
-var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;
+var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;
function isValidDottedPath(path) {
return (path != null && path !== '' && path !== 'hasOwnProperty' &&
@@ -35,7 +35,7 @@ function lookupDottedPath(obj, path) {
function shallowClearAndCopy(src, dst) {
dst = dst || {};
- angular.forEach(dst, function(value, key){
+ angular.forEach(dst, function(value, key) {
delete dst[key];
});
@@ -78,7 +78,19 @@ function shallowClearAndCopy(src, dst) {
*
* Requires the {@link ngResource `ngResource`} module to be installed.
*
- * @param {string} url A parametrized URL template with parameters prefixed by `:` as in
+ * By default, trailing slashes will be stripped from the calculated URLs,
+ * which can pose problems with server backends that do not expect that
+ * behavior. This can be disabled by configuring the `$resourceProvider` like
+ * this:
+ *
+ * ```js
+ app.config(['$resourceProvider', function($resourceProvider) {
+ // Don't strip trailing slashes from calculated URLs
+ $resourceProvider.defaults.stripTrailingSlashes = false;
+ }]);
+ * ```
+ *
+ * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
* `/user/:username`. If you are using a URL with a port number (e.g.
* `http://example.com:8080/api`), it will be respected.
*
@@ -99,12 +111,14 @@ function shallowClearAndCopy(src, dst) {
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
* URL `/path/greet?salutation=Hello`.
*
- * If the parameter value is prefixed with `@` then the value of that parameter is extracted from
- * the data object (useful for non-GET operations).
+ * If the parameter value is prefixed with `@` then the value for that parameter will be extracted
+ * from the corresponding property on the `data` object (provided when calling an action method). For
+ * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam`
+ * will be `data.someProp`.
*
- * @param {Object.
",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/angular/angular.js/issues"
+ },
+ "homepage": "http://angularjs.org"
+}
diff --git a/awx/ui/client/lib/angular-sanitize/.bower.json b/awx/ui/client/lib/angular-sanitize/.bower.json
index 28fa11b724..e1e61e612f 100644
--- a/awx/ui/client/lib/angular-sanitize/.bower.json
+++ b/awx/ui/client/lib/angular-sanitize/.bower.json
@@ -1,19 +1,19 @@
{
"name": "angular-sanitize",
- "version": "1.2.23",
+ "version": "1.4.3",
"main": "./angular-sanitize.js",
+ "ignore": [],
"dependencies": {
- "angular": "1.2.23"
+ "angular": "1.4.3"
},
"homepage": "https://github.com/angular/bower-angular-sanitize",
- "_release": "1.2.23",
+ "_release": "1.4.3",
"_resolution": {
"type": "version",
- "tag": "v1.2.23",
- "commit": "c64ed360a0ee54d4f56da6ab84904478c2423628"
+ "tag": "v1.4.3",
+ "commit": "0367ee4c3f9cb8af5d1da9ec35b71a8b523d9fc0"
},
"_source": "git://github.com/angular/bower-angular-sanitize.git",
- "_target": "~1.2.23",
- "_originalSource": "angular-sanitize",
- "_direct": true
+ "_target": "~1.4.3",
+ "_originalSource": "angular-sanitize"
}
\ No newline at end of file
diff --git a/awx/ui/client/lib/angular-sanitize/README.md b/awx/ui/client/lib/angular-sanitize/README.md
index 585a235ede..b84aaf6dbf 100644
--- a/awx/ui/client/lib/angular-sanitize/README.md
+++ b/awx/ui/client/lib/angular-sanitize/README.md
@@ -1,12 +1,26 @@
-# bower-angular-sanitize
+# packaged angular-sanitize
-This repo is for distribution on `bower`. The source for this module is in the
+This repo is for distribution on `npm` and `bower`. The source for this module is in the
[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngSanitize).
Please file issues and pull requests against that repo.
## Install
-Install with `bower`:
+You can install this package either with `npm` or with `bower`.
+
+### npm
+
+```shell
+npm install angular-sanitize
+```
+
+Then add `ngSanitize` as a dependency for your app:
+
+```javascript
+angular.module('myApp', [require('angular-sanitize')]);
+```
+
+### bower
```shell
bower install angular-sanitize
@@ -18,7 +32,7 @@ Add a `
```
-And add `ngSanitize` as a dependency for your app:
+Then add `ngSanitize` as a dependency for your app:
```javascript
angular.module('myApp', ['ngSanitize']);
@@ -33,7 +47,7 @@ Documentation is available on the
The MIT License
-Copyright (c) 2010-2012 Google, Inc. http://angularjs.org
+Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/awx/ui/client/lib/angular-sanitize/angular-sanitize.js b/awx/ui/client/lib/angular-sanitize/angular-sanitize.js
index 87489d9963..bebe1a72ab 100644
--- a/awx/ui/client/lib/angular-sanitize/angular-sanitize.js
+++ b/awx/ui/client/lib/angular-sanitize/angular-sanitize.js
@@ -1,10 +1,21 @@
/**
- * @license AngularJS v1.2.23
- * (c) 2010-2014 Google, Inc. http://angularjs.org
+ * @license AngularJS v1.4.3
+ * (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
var $sanitizeMinErr = angular.$$minErr('$sanitize');
/**
@@ -45,16 +56,16 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
* @kind function
*
* @description
- * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
+ * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
* then serialized back to properly escaped html string. This means that no unsafe input can make
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
- * browser, won't make it through the sanitizer.
+ * browser, won't make it through the sanitizer. The input may also contain SVG markup.
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
*
- * @param {string} html Html input.
- * @returns {string} Sanitized html.
+ * @param {string} html HTML input.
+ * @returns {string} Sanitized HTML.
*
* @example
@@ -198,6 +209,13 @@ var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
+// SVG Elements
+// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
+// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
+// They can potentially allow for arbitrary javascript to be executed. See #11290
+var svgElements = makeMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," +
+ "hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," +
+ "radialGradient,rect,stop,svg,switch,text,title,tspan,use");
// Special Elements (can contain anything)
var specialElements = makeMap("script,style");
@@ -206,20 +224,46 @@ var validElements = angular.extend({},
voidElements,
blockElements,
inlineElements,
- optionalEndTagElements);
+ optionalEndTagElements,
+ svgElements);
//Attributes that have href and hence need to be sanitized
-var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
-var validAttrs = angular.extend({}, uriAttrs, makeMap(
- 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
- 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
- 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
- 'scope,scrolling,shape,size,span,start,summary,target,title,type,'+
- 'valign,value,vspace,width'));
+var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href");
-function makeMap(str) {
+var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
+ 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
+ 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
+ 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
+ 'valign,value,vspace,width');
+
+// SVG attributes (without "id" and "name" attributes)
+// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
+var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
+ 'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
+ 'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
+ 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
+ 'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
+ 'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
+ 'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
+ 'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
+ 'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
+ 'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
+ 'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
+ 'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
+ 'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
+ 'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
+ 'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
+
+var validAttrs = angular.extend({},
+ uriAttrs,
+ svgAttrs,
+ htmlAttrs);
+
+function makeMap(str, lowercaseKeys) {
var obj = {}, items = str.split(','), i;
- for (i = 0; i < items.length; i++) obj[items[i]] = true;
+ for (i = 0; i < items.length; i++) {
+ obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true;
+ }
return obj;
}
@@ -236,7 +280,7 @@ function makeMap(str) {
* @param {string} html string
* @param {object} handler
*/
-function htmlParser( html, handler ) {
+function htmlParser(html, handler) {
if (typeof html !== 'string') {
if (html === null || typeof html === 'undefined') {
html = '';
@@ -245,52 +289,52 @@ function htmlParser( html, handler ) {
}
}
var index, chars, match, stack = [], last = html, text;
- stack.last = function() { return stack[ stack.length - 1 ]; };
+ stack.last = function() { return stack[stack.length - 1]; };
- while ( html ) {
+ while (html) {
text = '';
chars = true;
// Make sure we're not in a script or style element
- if ( !stack.last() || !specialElements[ stack.last() ] ) {
+ if (!stack.last() || !specialElements[stack.last()]) {
// Comment
- if ( html.indexOf("", index) === index) {
- if (handler.comment) handler.comment( html.substring( 4, index ) );
- html = html.substring( index + 3 );
+ if (index >= 0 && html.lastIndexOf("-->", index) === index) {
+ if (handler.comment) handler.comment(html.substring(4, index));
+ html = html.substring(index + 3);
chars = false;
}
// DOCTYPE
- } else if ( DOCTYPE_REGEXP.test(html) ) {
- match = html.match( DOCTYPE_REGEXP );
+ } else if (DOCTYPE_REGEXP.test(html)) {
+ match = html.match(DOCTYPE_REGEXP);
- if ( match ) {
- html = html.replace( match[0], '');
+ if (match) {
+ html = html.replace(match[0], '');
chars = false;
}
// end tag
- } else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
- match = html.match( END_TAG_REGEXP );
+ } else if (BEGING_END_TAGE_REGEXP.test(html)) {
+ match = html.match(END_TAG_REGEXP);
- if ( match ) {
- html = html.substring( match[0].length );
- match[0].replace( END_TAG_REGEXP, parseEndTag );
+ if (match) {
+ html = html.substring(match[0].length);
+ match[0].replace(END_TAG_REGEXP, parseEndTag);
chars = false;
}
// start tag
- } else if ( BEGIN_TAG_REGEXP.test(html) ) {
- match = html.match( START_TAG_REGEXP );
+ } else if (BEGIN_TAG_REGEXP.test(html)) {
+ match = html.match(START_TAG_REGEXP);
- if ( match ) {
+ if (match) {
// We only have a valid start-tag if there is a '>'.
- if ( match[4] ) {
- html = html.substring( match[0].length );
- match[0].replace( START_TAG_REGEXP, parseStartTag );
+ if (match[4]) {
+ html = html.substring(match[0].length);
+ match[0].replace(START_TAG_REGEXP, parseStartTag);
}
chars = false;
} else {
@@ -300,29 +344,30 @@ function htmlParser( html, handler ) {
}
}
- if ( chars ) {
+ if (chars) {
index = html.indexOf("<");
- text += index < 0 ? html : html.substring( 0, index );
- html = index < 0 ? "" : html.substring( index );
+ text += index < 0 ? html : html.substring(0, index);
+ html = index < 0 ? "" : html.substring(index);
- if (handler.chars) handler.chars( decodeEntities(text) );
+ if (handler.chars) handler.chars(decodeEntities(text));
}
} else {
- html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
- function(all, text){
+ // IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w].
+ html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
+ function(all, text) {
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
- if (handler.chars) handler.chars( decodeEntities(text) );
+ if (handler.chars) handler.chars(decodeEntities(text));
return "";
});
- parseEndTag( "", stack.last() );
+ parseEndTag("", stack.last());
}
- if ( html == last ) {
+ if (html == last) {
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
"of html: {0}", html);
}
@@ -332,22 +377,23 @@ function htmlParser( html, handler ) {
// Clean up any remaining tags
parseEndTag();
- function parseStartTag( tag, tagName, rest, unary ) {
+ function parseStartTag(tag, tagName, rest, unary) {
tagName = angular.lowercase(tagName);
- if ( blockElements[ tagName ] ) {
- while ( stack.last() && inlineElements[ stack.last() ] ) {
- parseEndTag( "", stack.last() );
+ if (blockElements[tagName]) {
+ while (stack.last() && inlineElements[stack.last()]) {
+ parseEndTag("", stack.last());
}
}
- if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
- parseEndTag( "", tagName );
+ if (optionalEndTagElements[tagName] && stack.last() == tagName) {
+ parseEndTag("", tagName);
}
- unary = voidElements[ tagName ] || !!unary;
+ unary = voidElements[tagName] || !!unary;
- if ( !unary )
- stack.push( tagName );
+ if (!unary) {
+ stack.push(tagName);
+ }
var attrs = {};
@@ -360,22 +406,23 @@ function htmlParser( html, handler ) {
attrs[name] = decodeEntities(value);
});
- if (handler.start) handler.start( tagName, attrs, unary );
+ if (handler.start) handler.start(tagName, attrs, unary);
}
- function parseEndTag( tag, tagName ) {
+ function parseEndTag(tag, tagName) {
var pos = 0, i;
tagName = angular.lowercase(tagName);
- if ( tagName )
+ if (tagName) {
// Find the closest opened tag of the same type
- for ( pos = stack.length - 1; pos >= 0; pos-- )
- if ( stack[ pos ] == tagName )
- break;
+ for (pos = stack.length - 1; pos >= 0; pos--) {
+ if (stack[pos] == tagName) break;
+ }
+ }
- if ( pos >= 0 ) {
+ if (pos >= 0) {
// Close all the open elements, up the stack
- for ( i = stack.length - 1; i >= pos; i-- )
- if (handler.end) handler.end( stack[ i ] );
+ for (i = stack.length - 1; i >= pos; i--)
+ if (handler.end) handler.end(stack[i]);
// Remove the open elements from the stack
stack.length = pos;
@@ -384,7 +431,6 @@ function htmlParser( html, handler ) {
}
var hiddenPre=document.createElement("pre");
-var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
/**
* decodes all entities into regular string
* @param value
@@ -393,22 +439,10 @@ var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
function decodeEntities(value) {
if (!value) { return ''; }
- // Note: IE8 does not preserve spaces at the start/end of innerHTML
- // so we must capture them and reattach them afterward
- var parts = spaceRe.exec(value);
- var spaceBefore = parts[1];
- var spaceAfter = parts[3];
- var content = parts[2];
- if (content) {
- hiddenPre.innerHTML=content.replace(/' : '>');
}
},
- end: function(tag){
+ end: function(tag) {
tag = angular.lowercase(tag);
if (!ignore && validElements[tag] === true) {
out('');
@@ -481,7 +515,7 @@ function htmlSanitizeWriter(buf, uriValidator){
ignore = false;
}
},
- chars: function(chars){
+ chars: function(chars) {
if (!ignore) {
out(encodeEntities(chars));
}
@@ -597,8 +631,8 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
*/
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
- /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/,
- MAILTO_REGEXP = /^mailto:/;
+ /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/i,
+ MAILTO_REGEXP = /^mailto:/i;
return function(text, target) {
if (!text) return text;
@@ -610,8 +644,10 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
- // if we did not match ftp/http/mailto then assume mailto
- if (match[2] == match[3]) url = 'mailto:' + url;
+ // if we did not match ftp/http/www/mailto then assume mailto
+ if (!match[2] && !match[4]) {
+ url = (match[3] ? 'http://' : 'mailto:') + url;
+ }
i = match.index;
addText(raw.substr(0, i));
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
@@ -630,13 +666,13 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
function addLink(url, text) {
html.push('');
+ html.push('href="',
+ url.replace(/"/g, '"'),
+ '">');
addText(text);
html.push('');
}
diff --git a/awx/ui/client/lib/angular-sanitize/angular-sanitize.min.js b/awx/ui/client/lib/angular-sanitize/angular-sanitize.min.js
index e10308b6ff..e8c4226487 100644
--- a/awx/ui/client/lib/angular-sanitize/angular-sanitize.min.js
+++ b/awx/ui/client/lib/angular-sanitize/angular-sanitize.min.js
@@ -1,15 +1,16 @@
/*
- AngularJS v1.2.23
- (c) 2010-2014 Google, Inc. http://angularjs.org
+ AngularJS v1.4.3
+ (c) 2010-2015 Google, Inc. http://angularjs.org
License: MIT
*/
-(function(q,g,r){'use strict';function F(a){var d=[];t(d,g.noop).chars(a);return d.join("")}function m(a){var d={};a=a.split(",");var c;for(c=0;c=c;e--)d.end&&d.end(f[e]);f.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,l,f=[],n=a,h;for(f.last=function(){return f[f.length-1]};a;){h="";l=!0;if(f.last()&&y[f.last()])a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(I,"$1").replace(J,"$1");d.chars&&d.chars(s(b));return""}),e("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(d.comment&&d.comment(a.substring(4,
-b)),a=a.substring(b+3),l=!1);else if(z.test(a)){if(b=a.match(z))a=a.replace(b[0],""),l=!1}else if(K.test(a)){if(b=a.match(A))a=a.substring(b[0].length),b[0].replace(A,e),l=!1}else L.test(a)&&((b=a.match(B))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(B,c)),l=!1):(h+="<",a=a.substring(1)));l&&(b=a.indexOf("<"),h+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),d.chars&&d.chars(s(h)))}if(a==n)throw M("badparse",a);n=a}e()}function s(a){if(!a)return"";var d=N.exec(a);a=d[1];var c=d[3];if(d=d[2])p.innerHTML=
-d.replace(//g,">")}function t(a,d){var c=!1,e=g.bind(a,a.push);return{start:function(a,l,f){a=g.lowercase(a);!c&&y[a]&&(c=a);c||!0!==D[a]||(e("<"),e(a),g.forEach(l,function(c,f){var k=
-g.lowercase(f),l="img"===a&&"src"===k||"background"===k;!0!==Q[k]||!0===E[k]&&!d(c,l)||(e(" "),e(f),e('="'),e(C(c)),e('"'))}),e(f?"/>":">"))},end:function(a){a=g.lowercase(a);c||!0!==D[a]||(e(""),e(a),e(">"));a==c&&(c=!1)},chars:function(a){c||e(C(a))}}}var M=g.$$minErr("$sanitize"),B=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,A=/^<\/\s*([\w:-]+)[^>]*>/,H=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,L=/^,
-K=/^<\//,I=/\x3c!--(.*?)--\x3e/g,z=/]*?)>/i,J=/"]/,c=/^mailto:/;return function(e,b){function l(a){a&&k.push(F(a))}function f(a,c){k.push("');l(c);k.push("")}
-if(!e)return e;for(var n,h=e,k=[],m,p;n=h.match(d);)m=n[0],n[2]==n[3]&&(m="mailto:"+m),p=n.index,l(h.substr(0,p)),f(m,n[0].replace(c,"")),h=h.substring(p+n[0].length);l(h);return a(k.join(""))}}])})(window,window.angular);
+(function(n,h,p){'use strict';function E(a){var f=[];r(f,h.noop).chars(a);return f.join("")}function g(a,f){var d={},c=a.split(","),b;for(b=0;b=c;d--)f.end&&f.end(e[d]);e.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,k,e=[],m=a,l;for(e.last=function(){return e[e.length-1]};a;){l="";k=!0;if(e.last()&&w[e.last()])a=a.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*"+e.last()+"[^>]*>","i"),function(a,b){b=b.replace(H,"$1").replace(I,"$1");f.chars&&f.chars(q(b));return""}),c("",e.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",
+b)===b&&(f.comment&&f.comment(a.substring(4,b)),a=a.substring(b+3),k=!1);else if(x.test(a)){if(b=a.match(x))a=a.replace(b[0],""),k=!1}else if(J.test(a)){if(b=a.match(y))a=a.substring(b[0].length),b[0].replace(y,c),k=!1}else K.test(a)&&((b=a.match(z))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(z,d)),k=!1):(l+="<",a=a.substring(1)));k&&(b=a.indexOf("<"),l+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),f.chars&&f.chars(q(l)))}if(a==m)throw L("badparse",a);m=a}c()}function q(a){if(!a)return"";A.innerHTML=
+a.replace(//g,">")}function r(a,f){var d=!1,c=h.bind(a,a.push);return{start:function(a,k,e){a=h.lowercase(a);!d&&w[a]&&(d=a);d||!0!==C[a]||(c("<"),c(a),h.forEach(k,function(d,e){var k=h.lowercase(e),g="img"===a&&"src"===k||
+"background"===k;!0!==O[k]||!0===D[k]&&!f(d,g)||(c(" "),c(e),c('="'),c(B(d)),c('"'))}),c(e?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==C[a]||(c(""),c(a),c(">"));a==d&&(d=!1)},chars:function(a){d||c(B(a))}}}var L=h.$$minErr("$sanitize"),z=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,y=/^<\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^,J=/^<\//,H=/\x3c!--(.*?)--\x3e/g,x=/]*?)>/i,
+I=/"\u201d\u2019]/i,d=/^mailto:/i;return function(c,b){function k(a){a&&g.push(E(a))}function e(a,
+c){g.push("');k(c);g.push("")}if(!c)return c;for(var m,l=c,g=[],n,p;m=l.match(f);)n=m[0],m[2]||m[4]||(n=(m[3]?"http://":"mailto:")+n),p=m.index,k(l.substr(0,p)),e(n,m[0].replace(d,"")),l=l.substring(p+m[0].length);k(l);return a(g.join(""))}}])})(window,window.angular);
//# sourceMappingURL=angular-sanitize.min.js.map
diff --git a/awx/ui/client/lib/angular-sanitize/angular-sanitize.min.js.map b/awx/ui/client/lib/angular-sanitize/angular-sanitize.min.js.map
index 2bc3236e3e..2f360f4df7 100644
--- a/awx/ui/client/lib/angular-sanitize/angular-sanitize.min.js.map
+++ b/awx/ui/client/lib/angular-sanitize/angular-sanitize.min.js.map
@@ -1,8 +1,8 @@
{
"version":3,
"file":"angular-sanitize.min.js",
-"lineCount":14,
-"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkBC,CAAlB,CAA6B,CAkJtCC,QAASA,EAAY,CAACC,CAAD,CAAQ,CAC3B,IAAIC,EAAM,EACGC,EAAAC,CAAmBF,CAAnBE,CAAwBN,CAAAO,KAAxBD,CACbH,MAAA,CAAaA,CAAb,CACA,OAAOC,EAAAI,KAAA,CAAS,EAAT,CAJoB,CAoE7BC,QAASA,EAAO,CAACC,CAAD,CAAM,CAAA,IAChBC,EAAM,EAAIC,EAAAA,CAAQF,CAAAG,MAAA,CAAU,GAAV,CAAtB,KAAsCC,CACtC,KAAKA,CAAL,CAAS,CAAT,CAAYA,CAAZ,CAAgBF,CAAAG,OAAhB,CAA8BD,CAAA,EAA9B,CAAmCH,CAAA,CAAIC,CAAA,CAAME,CAAN,CAAJ,CAAA,CAAgB,CAAA,CACnD,OAAOH,EAHa,CAmBtBK,QAASA,EAAU,CAAEC,CAAF,CAAQC,CAAR,CAAkB,CAgGnCC,QAASA,EAAa,CAAEC,CAAF,CAAOC,CAAP,CAAgBC,CAAhB,CAAsBC,CAAtB,CAA8B,CAClDF,CAAA,CAAUrB,CAAAwB,UAAA,CAAkBH,CAAlB,CACV,IAAKI,CAAA,CAAeJ,CAAf,CAAL,CACE,IAAA,CAAQK,CAAAC,KAAA,EAAR,EAAwBC,CAAA,CAAgBF,CAAAC,KAAA,EAAhB,CAAxB,CAAA,CACEE,CAAA,CAAa,EAAb,CAAiBH,CAAAC,KAAA,EAAjB,CAICG,EAAA,CAAwBT,CAAxB,CAAL,EAA0CK,CAAAC,KAAA,EAA1C,EAA0DN,CAA1D,EACEQ,CAAA,CAAa,EAAb,CAAiBR,CAAjB,CAKF,EAFAE,CAEA,CAFQQ,CAAA,CAAcV,CAAd,CAER,EAFmC,CAAC,CAACE,CAErC,GACEG,CAAAM,KAAA,CAAYX,CAAZ,CAEF,KAAIY,EAAQ,EAEZX,EAAAY,QAAA,CAAaC,CAAb,CACE,QAAQ,CAACC,CAAD,CAAQC,CAAR,CAAcC,CAAd,CAAiCC,CAAjC,CAAoDC,CAApD,CAAmE,CAMzEP,CAAA,CAAMI,CAAN,CAAA,CAAcI,CAAA,CALFH,CAKE,EAJTC,CAIS,EAHTC,CAGS,EAFT,EAES,CAN2D,CAD7E,CASItB,EAAAwB,MAAJ,EAAmBxB,CAAAwB,MAAA,CAAerB,CAAf,CAAwBY,CAAxB,CAA+BV,CAA/B,CA5B+B,CA+BpDM,QAASA,EAAW,CAAET,CAAF,CAAOC,CAAP,CAAiB,CAAA,IAC/BsB,EAAM,CADyB,CACtB7B,CAEb,IADAO,CACA,CADUrB,CAAAwB,UAAA,CAAkBH,CAAlB,CACV,CAEE,IAAMsB,CAAN,CAAYjB,CAAAX,OAAZ,CAA2B,CAA3B,CAAqC,CAArC,EAA8B4B,CAA9B,EACOjB,CAAA,CAAOiB,CAAP,CADP,EACuBtB,CADvB,CAAwCsB,CAAA,EAAxC;AAIF,GAAY,CAAZ,EAAKA,CAAL,CAAgB,CAEd,IAAM7B,CAAN,CAAUY,CAAAX,OAAV,CAAyB,CAAzB,CAA4BD,CAA5B,EAAiC6B,CAAjC,CAAsC7B,CAAA,EAAtC,CACMI,CAAA0B,IAAJ,EAAiB1B,CAAA0B,IAAA,CAAalB,CAAA,CAAOZ,CAAP,CAAb,CAGnBY,EAAAX,OAAA,CAAe4B,CAND,CATmB,CA9HjB,QAApB,GAAI,MAAO1B,EAAX,GAEIA,CAFJ,CACe,IAAb,GAAIA,CAAJ,EAAqC,WAArC,GAAqB,MAAOA,EAA5B,CACS,EADT,CAGS,EAHT,CAGcA,CAJhB,CADmC,KAQ/B4B,CAR+B,CAQxB1C,CARwB,CAQVuB,EAAQ,EARE,CAQEC,EAAOV,CART,CAQe6B,CAGlD,KAFApB,CAAAC,KAEA,CAFaoB,QAAQ,EAAG,CAAE,MAAOrB,EAAA,CAAOA,CAAAX,OAAP,CAAsB,CAAtB,CAAT,CAExB,CAAQE,CAAR,CAAA,CAAe,CACb6B,CAAA,CAAO,EACP3C,EAAA,CAAQ,CAAA,CAGR,IAAMuB,CAAAC,KAAA,EAAN,EAAuBqB,CAAA,CAAiBtB,CAAAC,KAAA,EAAjB,CAAvB,CA0DEV,CASA,CATOA,CAAAiB,QAAA,CAAiBe,MAAJ,CAAW,kBAAX,CAAgCvB,CAAAC,KAAA,EAAhC,CAA+C,QAA/C,CAAyD,GAAzD,CAAb,CACL,QAAQ,CAACuB,CAAD,CAAMJ,CAAN,CAAW,CACjBA,CAAA,CAAOA,CAAAZ,QAAA,CAAaiB,CAAb,CAA6B,IAA7B,CAAAjB,QAAA,CAA2CkB,CAA3C,CAAyD,IAAzD,CAEHlC,EAAAf,MAAJ,EAAmBe,CAAAf,MAAA,CAAesC,CAAA,CAAeK,CAAf,CAAf,CAEnB,OAAO,EALU,CADd,CASP,CAAAjB,CAAA,CAAa,EAAb,CAAiBH,CAAAC,KAAA,EAAjB,CAnEF,KAAyD,CAGvD,GAA8B,CAA9B,GAAKV,CAAAoC,QAAA,CAAa,SAAb,CAAL,CAEER,CAEA,CAFQ5B,CAAAoC,QAAA,CAAa,IAAb,CAAmB,CAAnB,CAER,CAAc,CAAd,EAAKR,CAAL,EAAmB5B,CAAAqC,YAAA,CAAiB,QAAjB,CAAwBT,CAAxB,CAAnB,GAAsDA,CAAtD,GACM3B,CAAAqC,QAEJ,EAFqBrC,CAAAqC,QAAA,CAAiBtC,CAAAuC,UAAA,CAAgB,CAAhB;AAAmBX,CAAnB,CAAjB,CAErB,CADA5B,CACA,CADOA,CAAAuC,UAAA,CAAgBX,CAAhB,CAAwB,CAAxB,CACP,CAAA1C,CAAA,CAAQ,CAAA,CAHV,CAJF,KAUO,IAAKsD,CAAAC,KAAA,CAAoBzC,CAApB,CAAL,CAGL,IAFAmB,CAEA,CAFQnB,CAAAmB,MAAA,CAAYqB,CAAZ,CAER,CACExC,CACA,CADOA,CAAAiB,QAAA,CAAcE,CAAA,CAAM,CAAN,CAAd,CAAwB,EAAxB,CACP,CAAAjC,CAAA,CAAQ,CAAA,CAFV,CAHK,IAQA,IAAKwD,CAAAD,KAAA,CAA4BzC,CAA5B,CAAL,CAGL,IAFAmB,CAEA,CAFQnB,CAAAmB,MAAA,CAAYwB,CAAZ,CAER,CACE3C,CAEA,CAFOA,CAAAuC,UAAA,CAAgBpB,CAAA,CAAM,CAAN,CAAArB,OAAhB,CAEP,CADAqB,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAkB0B,CAAlB,CAAkC/B,CAAlC,CACA,CAAA1B,CAAA,CAAQ,CAAA,CAHV,CAHK,IAUK0D,EAAAH,KAAA,CAAsBzC,CAAtB,CAAL,GAGL,CAFAmB,CAEA,CAFQnB,CAAAmB,MAAA,CAAY0B,CAAZ,CAER,GAEO1B,CAAA,CAAM,CAAN,CAIL,GAHEnB,CACA,CADOA,CAAAuC,UAAA,CAAgBpB,CAAA,CAAM,CAAN,CAAArB,OAAhB,CACP,CAAAqB,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAkB4B,CAAlB,CAAoC3C,CAApC,CAEF,EAAAhB,CAAA,CAAQ,CAAA,CANV,GASE2C,CACA,EADQ,GACR,CAAA7B,CAAA,CAAOA,CAAAuC,UAAA,CAAe,CAAf,CAVT,CAHK,CAiBFrD,EAAL,GACE0C,CAKA,CALQ5B,CAAAoC,QAAA,CAAa,GAAb,CAKR,CAHAP,CAGA,EAHgB,CAAR,CAAAD,CAAA,CAAY5B,CAAZ,CAAmBA,CAAAuC,UAAA,CAAgB,CAAhB,CAAmBX,CAAnB,CAG3B,CAFA5B,CAEA,CAFe,CAAR,CAAA4B,CAAA,CAAY,EAAZ,CAAiB5B,CAAAuC,UAAA,CAAgBX,CAAhB,CAExB,CAAI3B,CAAAf,MAAJ,EAAmBe,CAAAf,MAAA,CAAesC,CAAA,CAAeK,CAAf,CAAf,CANrB,CAhDuD,CAsEzD,GAAK7B,CAAL,EAAaU,CAAb,CACE,KAAMoC,EAAA,CAAgB,UAAhB,CAC4C9C,CAD5C,CAAN,CAGFU,CAAA,CAAOV,CA/EM,CAmFfY,CAAA,EA9FmC,CA0JrCY,QAASA,EAAc,CAACuB,CAAD,CAAQ,CAC7B,GAAI,CAACA,CAAL,CAAc,MAAO,EAIrB,KAAIC,EAAQC,CAAAC,KAAA,CAAaH,CAAb,CACRI,EAAAA,CAAcH,CAAA,CAAM,CAAN,CAClB,KAAII,EAAaJ,CAAA,CAAM,CAAN,CAEjB,IADIK,CACJ,CADcL,CAAA,CAAM,CAAN,CACd,CACEM,CAAAC,UAKA;AALoBF,CAAApC,QAAA,CAAgB,IAAhB,CAAqB,MAArB,CAKpB,CAAAoC,CAAA,CAAU,aAAA,EAAiBC,EAAjB,CACRA,CAAAE,YADQ,CACgBF,CAAAG,UAE5B,OAAON,EAAP,CAAqBE,CAArB,CAA+BD,CAlBF,CA4B/BM,QAASA,EAAc,CAACX,CAAD,CAAQ,CAC7B,MAAOA,EAAA9B,QAAA,CACG,IADH,CACS,OADT,CAAAA,QAAA,CAEG0C,CAFH,CAE0B,QAAS,CAACZ,CAAD,CAAQ,CAC9C,IAAIa,EAAKb,CAAAc,WAAA,CAAiB,CAAjB,CACLC,EAAAA,CAAMf,CAAAc,WAAA,CAAiB,CAAjB,CACV,OAAO,IAAP,EAAgC,IAAhC,EAAiBD,CAAjB,CAAsB,KAAtB,GAA0CE,CAA1C,CAAgD,KAAhD,EAA0D,KAA1D,EAAqE,GAHvB,CAF3C,CAAA7C,QAAA,CAOG8C,CAPH,CAO4B,QAAQ,CAAChB,CAAD,CAAO,CAC9C,MAAO,IAAP,CAAcA,CAAAc,WAAA,CAAiB,CAAjB,CAAd,CAAoC,GADU,CAP3C,CAAA5C,QAAA,CAUG,IAVH,CAUS,MAVT,CAAAA,QAAA,CAWG,IAXH,CAWS,MAXT,CADsB,CAyB/B7B,QAASA,EAAkB,CAACD,CAAD,CAAM6E,CAAN,CAAmB,CAC5C,IAAIC,EAAS,CAAA,CAAb,CACIC,EAAMnF,CAAAoF,KAAA,CAAahF,CAAb,CAAkBA,CAAA4B,KAAlB,CACV,OAAO,OACEU,QAAQ,CAACtB,CAAD,CAAMa,CAAN,CAAaV,CAAb,CAAmB,CAChCH,CAAA,CAAMpB,CAAAwB,UAAA,CAAkBJ,CAAlB,CACD8D,EAAAA,CAAL,EAAelC,CAAA,CAAgB5B,CAAhB,CAAf,GACE8D,CADF,CACW9D,CADX,CAGK8D,EAAL,EAAsC,CAAA,CAAtC,GAAeG,CAAA,CAAcjE,CAAd,CAAf,GACE+D,CAAA,CAAI,GAAJ,CAcA,CAbAA,CAAA,CAAI/D,CAAJ,CAaA,CAZApB,CAAAsF,QAAA,CAAgBrD,CAAhB,CAAuB,QAAQ,CAAC+B,CAAD,CAAQuB,CAAR,CAAY,CACzC,IAAIC;AAAKxF,CAAAwB,UAAA,CAAkB+D,CAAlB,CAAT,CACIE,EAAmB,KAAnBA,GAAWrE,CAAXqE,EAAqC,KAArCA,GAA4BD,CAA5BC,EAAyD,YAAzDA,GAAgDD,CAC3B,EAAA,CAAzB,GAAIE,CAAA,CAAWF,CAAX,CAAJ,EACsB,CAAA,CADtB,GACGG,CAAA,CAASH,CAAT,CADH,EAC8B,CAAAP,CAAA,CAAajB,CAAb,CAAoByB,CAApB,CAD9B,GAEEN,CAAA,CAAI,GAAJ,CAIA,CAHAA,CAAA,CAAII,CAAJ,CAGA,CAFAJ,CAAA,CAAI,IAAJ,CAEA,CADAA,CAAA,CAAIR,CAAA,CAAeX,CAAf,CAAJ,CACA,CAAAmB,CAAA,CAAI,GAAJ,CANF,CAHyC,CAA3C,CAYA,CAAAA,CAAA,CAAI5D,CAAA,CAAQ,IAAR,CAAe,GAAnB,CAfF,CALgC,CAD7B,KAwBAqB,QAAQ,CAACxB,CAAD,CAAK,CACdA,CAAA,CAAMpB,CAAAwB,UAAA,CAAkBJ,CAAlB,CACD8D,EAAL,EAAsC,CAAA,CAAtC,GAAeG,CAAA,CAAcjE,CAAd,CAAf,GACE+D,CAAA,CAAI,IAAJ,CAEA,CADAA,CAAA,CAAI/D,CAAJ,CACA,CAAA+D,CAAA,CAAI,GAAJ,CAHF,CAKI/D,EAAJ,EAAW8D,CAAX,GACEA,CADF,CACW,CAAA,CADX,CAPc,CAxBb,OAmCE/E,QAAQ,CAACA,CAAD,CAAO,CACb+E,CAAL,EACEC,CAAA,CAAIR,CAAA,CAAexE,CAAf,CAAJ,CAFgB,CAnCjB,CAHqC,CAtb9C,IAAI4D,EAAkB/D,CAAA4F,SAAA,CAAiB,WAAjB,CAAtB,CAyJI9B,EACG,wGA1JP,CA2JEF,EAAiB,wBA3JnB,CA4JEzB,EAAc,yEA5JhB,CA6JE0B,EAAmB,IA7JrB;AA8JEF,EAAyB,MA9J3B,CA+JER,EAAiB,qBA/JnB,CAgKEM,EAAiB,qBAhKnB,CAiKEL,EAAe,yBAjKjB,CAkKEwB,EAAwB,iCAlK1B,CAoKEI,EAA0B,gBApK5B,CA6KIjD,EAAetB,CAAA,CAAQ,wBAAR,CAIfoF,EAAAA,CAA8BpF,CAAA,CAAQ,gDAAR,CAC9BqF,EAAAA,CAA+BrF,CAAA,CAAQ,OAAR,CADnC,KAEIqB,EAAyB9B,CAAA+F,OAAA,CAAe,EAAf,CACeD,CADf,CAEeD,CAFf,CAF7B,CAOIpE,EAAgBzB,CAAA+F,OAAA,CAAe,EAAf,CAAmBF,CAAnB,CAAgDpF,CAAA,CAAQ,4KAAR,CAAhD,CAPpB,CAYImB,EAAiB5B,CAAA+F,OAAA,CAAe,EAAf,CAAmBD,CAAnB,CAAiDrF,CAAA,CAAQ,2JAAR,CAAjD,CAZrB;AAkBIuC,EAAkBvC,CAAA,CAAQ,cAAR,CAlBtB,CAoBI4E,EAAgBrF,CAAA+F,OAAA,CAAe,EAAf,CACehE,CADf,CAEeN,CAFf,CAGeG,CAHf,CAIeE,CAJf,CApBpB,CA2BI6D,EAAWlF,CAAA,CAAQ,0CAAR,CA3Bf,CA4BIiF,EAAa1F,CAAA+F,OAAA,CAAe,EAAf,CAAmBJ,CAAnB,CAA6BlF,CAAA,CAC1C,ySAD0C,CAA7B,CA5BjB,CAyMI8D,EAAUyB,QAAAC,cAAA,CAAuB,KAAvB,CAzMd,CA0MI/B,EAAU,wBA2GdlE,EAAAkG,OAAA,CAAe,YAAf,CAA6B,EAA7B,CAAAC,SAAA,CAA0C,WAA1C;AAlWAC,QAA0B,EAAG,CAC3B,IAAAC,KAAA,CAAY,CAAC,eAAD,CAAkB,QAAQ,CAACC,CAAD,CAAgB,CACpD,MAAO,SAAQ,CAACrF,CAAD,CAAO,CACpB,IAAIb,EAAM,EACVY,EAAA,CAAWC,CAAX,CAAiBZ,CAAA,CAAmBD,CAAnB,CAAwB,QAAQ,CAACmG,CAAD,CAAMd,CAAN,CAAe,CAC9D,MAAO,CAAC,SAAA/B,KAAA,CAAe4C,CAAA,CAAcC,CAAd,CAAmBd,CAAnB,CAAf,CADsD,CAA/C,CAAjB,CAGA,OAAOrF,EAAAI,KAAA,CAAS,EAAT,CALa,CAD8B,CAA1C,CADe,CAkW7B,CAwGAR,EAAAkG,OAAA,CAAe,YAAf,CAAAM,OAAA,CAAoC,OAApC,CAA6C,CAAC,WAAD,CAAc,QAAQ,CAACC,CAAD,CAAY,CAAA,IACzEC,EACE,oEAFuE,CAGzEC,EAAgB,UAEpB,OAAO,SAAQ,CAAC7D,CAAD,CAAO8D,CAAP,CAAe,CAoB5BC,QAASA,EAAO,CAAC/D,CAAD,CAAO,CAChBA,CAAL,EAGA7B,CAAAe,KAAA,CAAU9B,CAAA,CAAa4C,CAAb,CAAV,CAJqB,CAOvBgE,QAASA,EAAO,CAACC,CAAD,CAAMjE,CAAN,CAAY,CAC1B7B,CAAAe,KAAA,CAAU,KAAV,CACIhC,EAAAgH,UAAA,CAAkBJ,CAAlB,CAAJ,GACE3F,CAAAe,KAAA,CAAU,UAAV,CAEA,CADAf,CAAAe,KAAA,CAAU4E,CAAV,CACA,CAAA3F,CAAAe,KAAA,CAAU,IAAV,CAHF,CAKAf,EAAAe,KAAA,CAAU,QAAV,CACAf,EAAAe,KAAA,CAAU+E,CAAV,CACA9F,EAAAe,KAAA,CAAU,IAAV,CACA6E,EAAA,CAAQ/D,CAAR,CACA7B,EAAAe,KAAA,CAAU,MAAV,CAX0B,CA3BA;AAC5B,GAAI,CAACc,CAAL,CAAW,MAAOA,EAMlB,KALA,IAAIV,CAAJ,CACI6E,EAAMnE,CADV,CAEI7B,EAAO,EAFX,CAGI8F,CAHJ,CAIIjG,CACJ,CAAQsB,CAAR,CAAgB6E,CAAA7E,MAAA,CAAUsE,CAAV,CAAhB,CAAA,CAEEK,CAMA,CANM3E,CAAA,CAAM,CAAN,CAMN,CAJIA,CAAA,CAAM,CAAN,CAIJ,EAJgBA,CAAA,CAAM,CAAN,CAIhB,GAJ0B2E,CAI1B,CAJgC,SAIhC,CAJ4CA,CAI5C,EAHAjG,CAGA,CAHIsB,CAAAS,MAGJ,CAFAgE,CAAA,CAAQI,CAAAC,OAAA,CAAW,CAAX,CAAcpG,CAAd,CAAR,CAEA,CADAgG,CAAA,CAAQC,CAAR,CAAa3E,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAiByE,CAAjB,CAAgC,EAAhC,CAAb,CACA,CAAAM,CAAA,CAAMA,CAAAzD,UAAA,CAAc1C,CAAd,CAAkBsB,CAAA,CAAM,CAAN,CAAArB,OAAlB,CAER8F,EAAA,CAAQI,CAAR,CACA,OAAOR,EAAA,CAAUxF,CAAAT,KAAA,CAAU,EAAV,CAAV,CAlBqB,CAL+C,CAAlC,CAA7C,CAhlBsC,CAArC,CAAA,CAioBET,MAjoBF,CAioBUA,MAAAC,QAjoBV;",
+"lineCount":15,
+"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkBC,CAAlB,CAA6B,CA6JtCC,QAASA,EAAY,CAACC,CAAD,CAAQ,CAC3B,IAAIC,EAAM,EACGC,EAAAC,CAAmBF,CAAnBE,CAAwBN,CAAAO,KAAxBD,CACbH,MAAA,CAAaA,CAAb,CACA,OAAOC,EAAAI,KAAA,CAAS,EAAT,CAJoB,CAmG7BC,QAASA,EAAO,CAACC,CAAD,CAAMC,CAAN,CAAqB,CAAA,IAC/BC,EAAM,EADyB,CACrBC,EAAQH,CAAAI,MAAA,CAAU,GAAV,CADa,CACGC,CACtC,KAAKA,CAAL,CAAS,CAAT,CAAYA,CAAZ,CAAgBF,CAAAG,OAAhB,CAA8BD,CAAA,EAA9B,CACEH,CAAA,CAAID,CAAA,CAAgBX,CAAAiB,UAAA,CAAkBJ,CAAA,CAAME,CAAN,CAAlB,CAAhB,CAA8CF,CAAA,CAAME,CAAN,CAAlD,CAAA,CAA8D,CAAA,CAEhE,OAAOH,EAL4B,CAqBrCM,QAASA,EAAU,CAACC,CAAD,CAAOC,CAAP,CAAgB,CAiGjCC,QAASA,EAAa,CAACC,CAAD,CAAMC,CAAN,CAAeC,CAAf,CAAqBC,CAArB,CAA4B,CAChDF,CAAA,CAAUvB,CAAAiB,UAAA,CAAkBM,CAAlB,CACV,IAAIG,CAAA,CAAcH,CAAd,CAAJ,CACE,IAAA,CAAOI,CAAAC,KAAA,EAAP,EAAuBC,CAAA,CAAeF,CAAAC,KAAA,EAAf,CAAvB,CAAA,CACEE,CAAA,CAAY,EAAZ,CAAgBH,CAAAC,KAAA,EAAhB,CAIAG,EAAA,CAAuBR,CAAvB,CAAJ,EAAuCI,CAAAC,KAAA,EAAvC,EAAuDL,CAAvD,EACEO,CAAA,CAAY,EAAZ,CAAgBP,CAAhB,CAKF,EAFAE,CAEA,CAFQO,CAAA,CAAaT,CAAb,CAER,EAFiC,CAAEE,CAAAA,CAEnC,GACEE,CAAAM,KAAA,CAAWV,CAAX,CAGF,KAAIW,EAAQ,EAEZV,EAAAW,QAAA,CAAaC,CAAb,CACE,QAAQ,CAACC,CAAD,CAAQC,CAAR,CAAcC,CAAd,CAAiCC,CAAjC,CAAoDC,CAApD,CAAmE,CAMzEP,CAAA,CAAMI,CAAN,CAAA,CAAcI,CAAA,CALFH,CAKE,EAJTC,CAIS,EAHTC,CAGS,EAFT,EAES,CAN2D,CAD7E,CASIrB,EAAAuB,MAAJ,EAAmBvB,CAAAuB,MAAA,CAAcpB,CAAd,CAAuBW,CAAvB,CAA8BT,CAA9B,CA7B6B,CAgClDK,QAASA,EAAW,CAACR,CAAD,CAAMC,CAAN,CAAe,CAAA,IAC7BqB,EAAM,CADuB,CACpB7B,CAEb,IADAQ,CACA,CADUvB,CAAAiB,UAAA,CAAkBM,CAAlB,CACV,CAEE,IAAKqB,CAAL,CAAWjB,CAAAX,OAAX;AAA0B,CAA1B,CAAoC,CAApC,EAA6B4B,CAA7B,EACMjB,CAAA,CAAMiB,CAAN,CADN,EACoBrB,CADpB,CAAuCqB,CAAA,EAAvC,EAKF,GAAW,CAAX,EAAIA,CAAJ,CAAc,CAEZ,IAAK7B,CAAL,CAASY,CAAAX,OAAT,CAAwB,CAAxB,CAA2BD,CAA3B,EAAgC6B,CAAhC,CAAqC7B,CAAA,EAArC,CACMK,CAAAyB,IAAJ,EAAiBzB,CAAAyB,IAAA,CAAYlB,CAAA,CAAMZ,CAAN,CAAZ,CAGnBY,EAAAX,OAAA,CAAe4B,CANH,CAVmB,CAhIf,QAApB,GAAI,MAAOzB,EAAX,GAEIA,CAFJ,CACe,IAAb,GAAIA,CAAJ,EAAqC,WAArC,GAAqB,MAAOA,EAA5B,CACS,EADT,CAGS,EAHT,CAGcA,CAJhB,CADiC,KAQ7B2B,CAR6B,CAQtB3C,CARsB,CAQRwB,EAAQ,EARA,CAQIC,EAAOT,CARX,CAQiB4B,CAGlD,KAFApB,CAAAC,KAEA,CAFaoB,QAAQ,EAAG,CAAE,MAAOrB,EAAA,CAAMA,CAAAX,OAAN,CAAqB,CAArB,CAAT,CAExB,CAAOG,CAAP,CAAA,CAAa,CACX4B,CAAA,CAAO,EACP5C,EAAA,CAAQ,CAAA,CAGR,IAAKwB,CAAAC,KAAA,EAAL,EAAsBqB,CAAA,CAAgBtB,CAAAC,KAAA,EAAhB,CAAtB,CA2DET,CASA,CATOA,CAAAgB,QAAA,CAAa,IAAIe,MAAJ,CAAW,yBAAX,CAAuCvB,CAAAC,KAAA,EAAvC,CAAsD,QAAtD,CAAgE,GAAhE,CAAb,CACL,QAAQ,CAACuB,CAAD,CAAMJ,CAAN,CAAY,CAClBA,CAAA,CAAOA,CAAAZ,QAAA,CAAaiB,CAAb,CAA6B,IAA7B,CAAAjB,QAAA,CAA2CkB,CAA3C,CAAyD,IAAzD,CAEHjC,EAAAjB,MAAJ,EAAmBiB,CAAAjB,MAAA,CAAcuC,CAAA,CAAeK,CAAf,CAAd,CAEnB,OAAO,EALW,CADf,CASP,CAAAjB,CAAA,CAAY,EAAZ,CAAgBH,CAAAC,KAAA,EAAhB,CApEF,KAAqD,CAGnD,GAA6B,CAA7B,GAAIT,CAAAmC,QAAA,CAAa,SAAb,CAAJ,CAEER,CAEA,CAFQ3B,CAAAmC,QAAA,CAAa,IAAb,CAAmB,CAAnB,CAER,CAAa,CAAb,EAAIR,CAAJ,EAAkB3B,CAAAoC,YAAA,CAAiB,QAAjB;AAAwBT,CAAxB,CAAlB,GAAqDA,CAArD,GACM1B,CAAAoC,QAEJ,EAFqBpC,CAAAoC,QAAA,CAAgBrC,CAAAsC,UAAA,CAAe,CAAf,CAAkBX,CAAlB,CAAhB,CAErB,CADA3B,CACA,CADOA,CAAAsC,UAAA,CAAeX,CAAf,CAAuB,CAAvB,CACP,CAAA3C,CAAA,CAAQ,CAAA,CAHV,CAJF,KAUO,IAAIuD,CAAAC,KAAA,CAAoBxC,CAApB,CAAJ,CAGL,IAFAkB,CAEA,CAFQlB,CAAAkB,MAAA,CAAWqB,CAAX,CAER,CACEvC,CACA,CADOA,CAAAgB,QAAA,CAAaE,CAAA,CAAM,CAAN,CAAb,CAAuB,EAAvB,CACP,CAAAlC,CAAA,CAAQ,CAAA,CAFV,CAHK,IAQA,IAAIyD,CAAAD,KAAA,CAA4BxC,CAA5B,CAAJ,CAGL,IAFAkB,CAEA,CAFQlB,CAAAkB,MAAA,CAAWwB,CAAX,CAER,CACE1C,CAEA,CAFOA,CAAAsC,UAAA,CAAepB,CAAA,CAAM,CAAN,CAAArB,OAAf,CAEP,CADAqB,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAiB0B,CAAjB,CAAiC/B,CAAjC,CACA,CAAA3B,CAAA,CAAQ,CAAA,CAHV,CAHK,IAUI2D,EAAAH,KAAA,CAAsBxC,CAAtB,CAAJ,GAGL,CAFAkB,CAEA,CAFQlB,CAAAkB,MAAA,CAAW0B,CAAX,CAER,GAEM1B,CAAA,CAAM,CAAN,CAIJ,GAHElB,CACA,CADOA,CAAAsC,UAAA,CAAepB,CAAA,CAAM,CAAN,CAAArB,OAAf,CACP,CAAAqB,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAiB4B,CAAjB,CAAmC1C,CAAnC,CAEF,EAAAlB,CAAA,CAAQ,CAAA,CANV,GASE4C,CACA,EADQ,GACR,CAAA5B,CAAA,CAAOA,CAAAsC,UAAA,CAAe,CAAf,CAVT,CAHK,CAiBHtD,EAAJ,GACE2C,CAKA,CALQ3B,CAAAmC,QAAA,CAAa,GAAb,CAKR,CAHAP,CAGA,EAHgB,CAAR,CAAAD,CAAA,CAAY3B,CAAZ,CAAmBA,CAAAsC,UAAA,CAAe,CAAf,CAAkBX,CAAlB,CAG3B,CAFA3B,CAEA,CAFe,CAAR,CAAA2B,CAAA,CAAY,EAAZ,CAAiB3B,CAAAsC,UAAA,CAAeX,CAAf,CAExB,CAAI1B,CAAAjB,MAAJ,EAAmBiB,CAAAjB,MAAA,CAAcuC,CAAA,CAAeK,CAAf,CAAd,CANrB,CAhDmD,CAuErD,GAAI5B,CAAJ,EAAYS,CAAZ,CACE,KAAMoC,EAAA,CAAgB,UAAhB,CAC4C7C,CAD5C,CAAN,CAGFS,CAAA,CAAOT,CAhFI,CAoFbW,CAAA,EA/FiC,CA4JnCY,QAASA,EAAc,CAACuB,CAAD,CAAQ,CAC7B,GAAKA,CAAAA,CAAL,CAAc,MAAO,EAErBC,EAAAC,UAAA;AAAsBF,CAAA9B,QAAA,CAAc,IAAd,CAAmB,MAAnB,CAGtB,OAAO+B,EAAAE,YANsB,CAgB/BC,QAASA,EAAc,CAACJ,CAAD,CAAQ,CAC7B,MAAOA,EAAA9B,QAAA,CACG,IADH,CACS,OADT,CAAAA,QAAA,CAEGmC,CAFH,CAE0B,QAAQ,CAACL,CAAD,CAAQ,CAC7C,IAAIM,EAAKN,CAAAO,WAAA,CAAiB,CAAjB,CACLC,EAAAA,CAAMR,CAAAO,WAAA,CAAiB,CAAjB,CACV,OAAO,IAAP,EAAgC,IAAhC,EAAiBD,CAAjB,CAAsB,KAAtB,GAA0CE,CAA1C,CAAgD,KAAhD,EAA0D,KAA1D,EAAqE,GAHxB,CAF1C,CAAAtC,QAAA,CAOGuC,CAPH,CAO4B,QAAQ,CAACT,CAAD,CAAQ,CAC/C,MAAO,IAAP,CAAcA,CAAAO,WAAA,CAAiB,CAAjB,CAAd,CAAoC,GADW,CAP5C,CAAArC,QAAA,CAUG,IAVH,CAUS,MAVT,CAAAA,QAAA,CAWG,IAXH,CAWS,MAXT,CADsB,CAyB/B9B,QAASA,EAAkB,CAACD,CAAD,CAAMuE,CAAN,CAAoB,CAC7C,IAAIC,EAAS,CAAA,CAAb,CACIC,EAAM7E,CAAA8E,KAAA,CAAa1E,CAAb,CAAkBA,CAAA6B,KAAlB,CACV,OAAO,CACLU,MAAOA,QAAQ,CAACrB,CAAD,CAAMY,CAAN,CAAaT,CAAb,CAAoB,CACjCH,CAAA,CAAMtB,CAAAiB,UAAA,CAAkBK,CAAlB,CACDsD,EAAAA,CAAL,EAAe3B,CAAA,CAAgB3B,CAAhB,CAAf,GACEsD,CADF,CACWtD,CADX,CAGKsD,EAAL,EAAsC,CAAA,CAAtC,GAAeG,CAAA,CAAczD,CAAd,CAAf,GACEuD,CAAA,CAAI,GAAJ,CAcA,CAbAA,CAAA,CAAIvD,CAAJ,CAaA,CAZAtB,CAAAgF,QAAA,CAAgB9C,CAAhB,CAAuB,QAAQ,CAAC+B,CAAD,CAAQgB,CAAR,CAAa,CAC1C,IAAIC,EAAKlF,CAAAiB,UAAA,CAAkBgE,CAAlB,CAAT,CACIE,EAAmB,KAAnBA,GAAW7D,CAAX6D,EAAqC,KAArCA,GAA4BD,CAA5BC;AAAyD,YAAzDA,GAAgDD,CAC3B,EAAA,CAAzB,GAAIE,CAAA,CAAWF,CAAX,CAAJ,EACsB,CAAA,CADtB,GACGG,CAAA,CAASH,CAAT,CADH,EAC8B,CAAAP,CAAA,CAAaV,CAAb,CAAoBkB,CAApB,CAD9B,GAEEN,CAAA,CAAI,GAAJ,CAIA,CAHAA,CAAA,CAAII,CAAJ,CAGA,CAFAJ,CAAA,CAAI,IAAJ,CAEA,CADAA,CAAA,CAAIR,CAAA,CAAeJ,CAAf,CAAJ,CACA,CAAAY,CAAA,CAAI,GAAJ,CANF,CAH0C,CAA5C,CAYA,CAAAA,CAAA,CAAIpD,CAAA,CAAQ,IAAR,CAAe,GAAnB,CAfF,CALiC,CAD9B,CAwBLoB,IAAKA,QAAQ,CAACvB,CAAD,CAAM,CACfA,CAAA,CAAMtB,CAAAiB,UAAA,CAAkBK,CAAlB,CACDsD,EAAL,EAAsC,CAAA,CAAtC,GAAeG,CAAA,CAAczD,CAAd,CAAf,GACEuD,CAAA,CAAI,IAAJ,CAEA,CADAA,CAAA,CAAIvD,CAAJ,CACA,CAAAuD,CAAA,CAAI,GAAJ,CAHF,CAKIvD,EAAJ,EAAWsD,CAAX,GACEA,CADF,CACW,CAAA,CADX,CAPe,CAxBd,CAmCLzE,MAAOA,QAAQ,CAACA,CAAD,CAAQ,CACdyE,CAAL,EACEC,CAAA,CAAIR,CAAA,CAAelE,CAAf,CAAJ,CAFiB,CAnClB,CAHsC,CA7c/C,IAAI6D,EAAkBhE,CAAAsF,SAAA,CAAiB,WAAjB,CAAtB,CAyJIvB,EACG,wGA1JP,CA2JEF,EAAiB,wBA3JnB,CA4JEzB,EAAc,yEA5JhB,CA6JE0B,EAAmB,IA7JrB,CA8JEF,EAAyB,MA9J3B,CA+JER,EAAiB,qBA/JnB,CAgKEM,EAAiB,qBAhKnB;AAiKEL,EAAe,yBAjKjB,CAkKEiB,EAAwB,iCAlK1B,CAoKEI,EAA0B,gBApK5B,CA6KI1C,EAAevB,CAAA,CAAQ,wBAAR,CAIf8E,EAAAA,CAA8B9E,CAAA,CAAQ,gDAAR,CAC9B+E,EAAAA,CAA+B/E,CAAA,CAAQ,OAAR,CADnC,KAEIsB,EAAyB/B,CAAAyF,OAAA,CAAe,EAAf,CACeD,CADf,CAEeD,CAFf,CAF7B,CAOI7D,EAAgB1B,CAAAyF,OAAA,CAAe,EAAf,CAAmBF,CAAnB,CAAgD9E,CAAA,CAAQ,4KAAR,CAAhD,CAPpB,CAYIoB,EAAiB7B,CAAAyF,OAAA,CAAe,EAAf,CAAmBD,CAAnB,CAAiD/E,CAAA,CAAQ,2JAAR,CAAjD,CAQjBiF;CAAAA,CAAcjF,CAAA,CAAQ,4NAAR,CAKlB,KAAIwC,EAAkBxC,CAAA,CAAQ,cAAR,CAAtB,CAEIsE,EAAgB/E,CAAAyF,OAAA,CAAe,EAAf,CACezD,CADf,CAEeN,CAFf,CAGeG,CAHf,CAIeE,CAJf,CAKe2D,CALf,CAFpB,CAUIL,EAAW5E,CAAA,CAAQ,qDAAR,CAEXkF,EAAAA,CAAYlF,CAAA,CAAQ,kTAAR,CAQZmF;CAAAA,CAAWnF,CAAA,CAAQ,guCAAR;AAcoE,CAAA,CAdpE,CAgBf,KAAI2E,EAAapF,CAAAyF,OAAA,CAAe,EAAf,CACeJ,CADf,CAEeO,CAFf,CAGeD,CAHf,CAAjB,CAgLIzB,EAAU2B,QAAAC,cAAA,CAAuB,KAAvB,CA+Fd9F,EAAA+F,OAAA,CAAe,YAAf,CAA6B,EAA7B,CAAAC,SAAA,CAA0C,WAA1C,CAzXAC,QAA0B,EAAG,CAC3B,IAAAC,KAAA,CAAY,CAAC,eAAD,CAAkB,QAAQ,CAACC,CAAD,CAAgB,CACpD,MAAO,SAAQ,CAAChF,CAAD,CAAO,CACpB,IAAIf,EAAM,EACVc,EAAA,CAAWC,CAAX,CAAiBd,CAAA,CAAmBD,CAAnB,CAAwB,QAAQ,CAACgG,CAAD,CAAMjB,CAAN,CAAe,CAC9D,MAAO,CAAC,SAAAxB,KAAA,CAAewC,CAAA,CAAcC,CAAd,CAAmBjB,CAAnB,CAAf,CADsD,CAA/C,CAAjB,CAGA,OAAO/E,EAAAI,KAAA,CAAS,EAAT,CALa,CAD8B,CAA1C,CADe,CAyX7B,CAwGAR,EAAA+F,OAAA,CAAe,YAAf,CAAAM,OAAA,CAAoC,OAApC,CAA6C,CAAC,WAAD,CAAc,QAAQ,CAACC,CAAD,CAAY,CAAA,IACzEC,EACE,yFAFuE,CAGzEC,EAAgB,WAEpB,OAAO,SAAQ,CAACzD,CAAD,CAAO0D,CAAP,CAAe,CAsB5BC,QAASA,EAAO,CAAC3D,CAAD,CAAO,CAChBA,CAAL,EAGA5B,CAAAc,KAAA,CAAU/B,CAAA,CAAa6C,CAAb,CAAV,CAJqB,CAOvB4D,QAASA,EAAO,CAACC,CAAD;AAAM7D,CAAN,CAAY,CAC1B5B,CAAAc,KAAA,CAAU,KAAV,CACIjC,EAAA6G,UAAA,CAAkBJ,CAAlB,CAAJ,EACEtF,CAAAc,KAAA,CAAU,UAAV,CACUwE,CADV,CAEU,IAFV,CAIFtF,EAAAc,KAAA,CAAU,QAAV,CACU2E,CAAAzE,QAAA,CAAY,IAAZ,CAAkB,QAAlB,CADV,CAEU,IAFV,CAGAuE,EAAA,CAAQ3D,CAAR,CACA5B,EAAAc,KAAA,CAAU,MAAV,CAX0B,CA5B5B,GAAKc,CAAAA,CAAL,CAAW,MAAOA,EAMlB,KALA,IAAIV,CAAJ,CACIyE,EAAM/D,CADV,CAEI5B,EAAO,EAFX,CAGIyF,CAHJ,CAII7F,CACJ,CAAQsB,CAAR,CAAgByE,CAAAzE,MAAA,CAAUkE,CAAV,CAAhB,CAAA,CAEEK,CAQA,CARMvE,CAAA,CAAM,CAAN,CAQN,CANKA,CAAA,CAAM,CAAN,CAML,EANkBA,CAAA,CAAM,CAAN,CAMlB,GALEuE,CAKF,EALSvE,CAAA,CAAM,CAAN,CAAA,CAAW,SAAX,CAAuB,SAKhC,EAL6CuE,CAK7C,EAHA7F,CAGA,CAHIsB,CAAAS,MAGJ,CAFA4D,CAAA,CAAQI,CAAAC,OAAA,CAAW,CAAX,CAAchG,CAAd,CAAR,CAEA,CADA4F,CAAA,CAAQC,CAAR,CAAavE,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAiBqE,CAAjB,CAAgC,EAAhC,CAAb,CACA,CAAAM,CAAA,CAAMA,CAAArD,UAAA,CAAc1C,CAAd,CAAkBsB,CAAA,CAAM,CAAN,CAAArB,OAAlB,CAER0F,EAAA,CAAQI,CAAR,CACA,OAAOR,EAAA,CAAUnF,CAAAX,KAAA,CAAU,EAAV,CAAV,CApBqB,CAL+C,CAAlC,CAA7C,CAlnBsC,CAArC,CAAD,CAqqBGT,MArqBH,CAqqBWA,MAAAC,QArqBX;",
"sources":["angular-sanitize.js"],
-"names":["window","angular","undefined","sanitizeText","chars","buf","htmlSanitizeWriter","writer","noop","join","makeMap","str","obj","items","split","i","length","htmlParser","html","handler","parseStartTag","tag","tagName","rest","unary","lowercase","blockElements","stack","last","inlineElements","parseEndTag","optionalEndTagElements","voidElements","push","attrs","replace","ATTR_REGEXP","match","name","doubleQuotedValue","singleQuotedValue","unquotedValue","decodeEntities","start","pos","end","index","text","stack.last","specialElements","RegExp","all","COMMENT_REGEXP","CDATA_REGEXP","indexOf","lastIndexOf","comment","substring","DOCTYPE_REGEXP","test","BEGING_END_TAGE_REGEXP","END_TAG_REGEXP","BEGIN_TAG_REGEXP","START_TAG_REGEXP","$sanitizeMinErr","value","parts","spaceRe","exec","spaceBefore","spaceAfter","content","hiddenPre","innerHTML","textContent","innerText","encodeEntities","SURROGATE_PAIR_REGEXP","hi","charCodeAt","low","NON_ALPHANUMERIC_REGEXP","uriValidator","ignore","out","bind","validElements","forEach","key","lkey","isImage","validAttrs","uriAttrs","$$minErr","optionalEndTagBlockElements","optionalEndTagInlineElements","extend","document","createElement","module","provider","$SanitizeProvider","$get","$$sanitizeUri","uri","filter","$sanitize","LINKY_URL_REGEXP","MAILTO_REGEXP","target","addText","addLink","url","isDefined","raw","substr"]
+"names":["window","angular","undefined","sanitizeText","chars","buf","htmlSanitizeWriter","writer","noop","join","makeMap","str","lowercaseKeys","obj","items","split","i","length","lowercase","htmlParser","html","handler","parseStartTag","tag","tagName","rest","unary","blockElements","stack","last","inlineElements","parseEndTag","optionalEndTagElements","voidElements","push","attrs","replace","ATTR_REGEXP","match","name","doubleQuotedValue","singleQuotedValue","unquotedValue","decodeEntities","start","pos","end","index","text","stack.last","specialElements","RegExp","all","COMMENT_REGEXP","CDATA_REGEXP","indexOf","lastIndexOf","comment","substring","DOCTYPE_REGEXP","test","BEGING_END_TAGE_REGEXP","END_TAG_REGEXP","BEGIN_TAG_REGEXP","START_TAG_REGEXP","$sanitizeMinErr","value","hiddenPre","innerHTML","textContent","encodeEntities","SURROGATE_PAIR_REGEXP","hi","charCodeAt","low","NON_ALPHANUMERIC_REGEXP","uriValidator","ignore","out","bind","validElements","forEach","key","lkey","isImage","validAttrs","uriAttrs","$$minErr","optionalEndTagBlockElements","optionalEndTagInlineElements","extend","svgElements","htmlAttrs","svgAttrs","document","createElement","module","provider","$SanitizeProvider","$get","$$sanitizeUri","uri","filter","$sanitize","LINKY_URL_REGEXP","MAILTO_REGEXP","target","addText","addLink","url","isDefined","raw","substr"]
}
diff --git a/awx/ui/client/lib/angular-sanitize/bower.json b/awx/ui/client/lib/angular-sanitize/bower.json
index 648d5e99d9..ffa9554222 100644
--- a/awx/ui/client/lib/angular-sanitize/bower.json
+++ b/awx/ui/client/lib/angular-sanitize/bower.json
@@ -1,8 +1,9 @@
{
"name": "angular-sanitize",
- "version": "1.2.23",
+ "version": "1.4.3",
"main": "./angular-sanitize.js",
+ "ignore": [],
"dependencies": {
- "angular": "1.2.23"
+ "angular": "1.4.3"
}
}
diff --git a/awx/ui/client/lib/angular-sanitize/index.js b/awx/ui/client/lib/angular-sanitize/index.js
new file mode 100644
index 0000000000..dd5d22e4a5
--- /dev/null
+++ b/awx/ui/client/lib/angular-sanitize/index.js
@@ -0,0 +1,2 @@
+require('./angular-sanitize');
+module.exports = 'ngSanitize';
diff --git a/awx/ui/client/lib/angular-sanitize/package.json b/awx/ui/client/lib/angular-sanitize/package.json
new file mode 100644
index 0000000000..d8ee360019
--- /dev/null
+++ b/awx/ui/client/lib/angular-sanitize/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "angular-sanitize",
+ "version": "1.4.3",
+ "description": "AngularJS module for sanitizing HTML",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/angular/angular.js.git"
+ },
+ "keywords": [
+ "angular",
+ "framework",
+ "browser",
+ "html",
+ "client-side"
+ ],
+ "author": "Angular Core Team ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/angular/angular.js/issues"
+ },
+ "homepage": "http://angularjs.org"
+}
diff --git a/awx/ui/client/lib/angular/.bower.json b/awx/ui/client/lib/angular/.bower.json
index 8824a203bc..3aafa83d22 100644
--- a/awx/ui/client/lib/angular/.bower.json
+++ b/awx/ui/client/lib/angular/.bower.json
@@ -1,16 +1,17 @@
{
"name": "angular",
- "version": "1.2.23",
+ "version": "1.4.3",
"main": "./angular.js",
+ "ignore": [],
"dependencies": {},
"homepage": "https://github.com/angular/bower-angular",
- "_release": "1.2.23",
+ "_release": "1.4.3",
"_resolution": {
"type": "version",
- "tag": "v1.2.23",
- "commit": "53716560a512e598c32c5dbc70b2e41a793443e9"
+ "tag": "v1.4.3",
+ "commit": "dbd689e8103a6366e53e1f6786727f7c65ccfd75"
},
"_source": "git://github.com/angular/bower-angular.git",
- "_target": "1.2.23",
+ "_target": "~1.4.3",
"_originalSource": "angular"
}
\ No newline at end of file
diff --git a/awx/ui/client/lib/angular/README.md b/awx/ui/client/lib/angular/README.md
index fc0c09987e..d1bc0eddf4 100644
--- a/awx/ui/client/lib/angular/README.md
+++ b/awx/ui/client/lib/angular/README.md
@@ -1,18 +1,34 @@
-# bower-angular
+# packaged angular
-This repo is for distribution on `bower`. The source for this module is in the
+This repo is for distribution on `npm` and `bower`. The source for this module is in the
[main AngularJS repo](https://github.com/angular/angular.js).
Please file issues and pull requests against that repo.
## Install
-Install with `bower`:
+You can install this package either with `npm` or with `bower`.
+
+### npm
+
+```shell
+npm install angular
+```
+
+Then add a `
+```
+
+Or `require('angular')` from your code.
+
+### bower
```shell
bower install angular
```
-Add a `
@@ -27,7 +43,7 @@ Documentation is available on the
The MIT License
-Copyright (c) 2010-2012 Google, Inc. http://angularjs.org
+Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/awx/ui/client/lib/angular/angular-csp.css b/awx/ui/client/lib/angular/angular-csp.css
index 3abb3a0e66..f3cd926cb3 100644
--- a/awx/ui/client/lib/angular/angular-csp.css
+++ b/awx/ui/client/lib/angular/angular-csp.css
@@ -4,7 +4,7 @@
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],
.ng-cloak, .x-ng-cloak,
-.ng-hide {
+.ng-hide:not(.ng-hide-animate) {
display: none !important;
}
@@ -12,13 +12,10 @@ ng\:form {
display: block;
}
-.ng-animate-block-transitions {
- transition:0s all!important;
- -webkit-transition:0s all!important;
+.ng-animate-shim {
+ visibility:hidden;
}
-/* show the element during a show/hide animation when the
- * animation is ongoing, but the .ng-hide class is active */
-.ng-hide-add-active, .ng-hide-remove {
- display: block!important;
+.ng-anchor {
+ position:absolute;
}
diff --git a/awx/ui/client/lib/angular/angular.js b/awx/ui/client/lib/angular/angular.js
index 59f7a96768..f7442c0b03 100644
--- a/awx/ui/client/lib/angular/angular.js
+++ b/awx/ui/client/lib/angular/angular.js
@@ -1,6 +1,6 @@
/**
- * @license AngularJS v1.2.23
- * (c) 2010-2014 Google, Inc. http://angularjs.org
+ * @license AngularJS v1.4.3
+ * (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document, undefined) {'use strict';
@@ -30,138 +30,139 @@
* should all be static strings, not variables or general expressions.
*
* @param {string} module The namespace to use for the new minErr instance.
+ * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
+ * error from returned function, for cases when a particular type of error is useful.
* @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
*/
-function minErr(module) {
- return function () {
- var code = arguments[0],
- prefix = '[' + (module ? module + ':' : '') + code + '] ',
- template = arguments[1],
- templateArgs = arguments,
- stringify = function (obj) {
- if (typeof obj === 'function') {
- return obj.toString().replace(/ \{[\s\S]*$/, '');
- } else if (typeof obj === 'undefined') {
- return 'undefined';
- } else if (typeof obj !== 'string') {
- return JSON.stringify(obj);
- }
- return obj;
- },
- message, i;
+function minErr(module, ErrorConstructor) {
+ ErrorConstructor = ErrorConstructor || Error;
+ return function() {
+ var SKIP_INDEXES = 2;
- message = prefix + template.replace(/\{\d+\}/g, function (match) {
- var index = +match.slice(1, -1), arg;
+ var templateArgs = arguments,
+ code = templateArgs[0],
+ message = '[' + (module ? module + ':' : '') + code + '] ',
+ template = templateArgs[1],
+ paramPrefix, i;
- if (index + 2 < templateArgs.length) {
- arg = templateArgs[index + 2];
- if (typeof arg === 'function') {
- return arg.toString().replace(/ ?\{[\s\S]*$/, '');
- } else if (typeof arg === 'undefined') {
- return 'undefined';
- } else if (typeof arg !== 'string') {
- return toJson(arg);
- }
- return arg;
+ message += template.replace(/\{\d+\}/g, function(match) {
+ var index = +match.slice(1, -1),
+ shiftedIndex = index + SKIP_INDEXES;
+
+ if (shiftedIndex < templateArgs.length) {
+ return toDebugString(templateArgs[shiftedIndex]);
}
+
return match;
});
- message = message + '\nhttp://errors.angularjs.org/1.2.23/' +
+ message += '\nhttp://errors.angularjs.org/1.4.3/' +
(module ? module + '/' : '') + code;
- for (i = 2; i < arguments.length; i++) {
- message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
- encodeURIComponent(stringify(arguments[i]));
+
+ for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
+ message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
+ encodeURIComponent(toDebugString(templateArgs[i]));
}
- return new Error(message);
+ return new ErrorConstructor(message);
};
}
/* We need to tell jshint what variables are being exported */
/* global angular: true,
- msie: true,
- jqLite: true,
- jQuery: true,
- slice: true,
- push: true,
- toString: true,
- ngMinErr: true,
- angularModule: true,
- nodeName_: true,
- uid: true,
- VALIDITY_STATE_PROPERTY: true,
+ msie: true,
+ jqLite: true,
+ jQuery: true,
+ slice: true,
+ splice: true,
+ push: true,
+ toString: true,
+ ngMinErr: true,
+ angularModule: true,
+ uid: true,
+ REGEX_STRING_REGEXP: true,
+ VALIDITY_STATE_PROPERTY: true,
- lowercase: true,
- uppercase: true,
- manualLowercase: true,
- manualUppercase: true,
- nodeName_: true,
- isArrayLike: true,
- forEach: true,
- sortedKeys: true,
- forEachSorted: true,
- reverseParams: true,
- nextUid: true,
- setHashKey: true,
- extend: true,
- int: true,
- inherit: true,
- noop: true,
- identity: true,
- valueFn: true,
- isUndefined: true,
- isDefined: true,
- isObject: true,
- isString: true,
- isNumber: true,
- isDate: true,
- isArray: true,
- isFunction: true,
- isRegExp: true,
- isWindow: true,
- isScope: true,
- isFile: true,
- isBlob: true,
- isBoolean: true,
- isPromiseLike: true,
- trim: true,
- isElement: true,
- makeMap: true,
- map: true,
- size: true,
- includes: true,
- indexOf: true,
- arrayRemove: true,
- isLeafNode: true,
- copy: true,
- shallowCopy: true,
- equals: true,
- csp: true,
- concat: true,
- sliceArgs: true,
- bind: true,
- toJsonReplacer: true,
- toJson: true,
- fromJson: true,
- toBoolean: true,
- startingTag: true,
- tryDecodeURIComponent: true,
- parseKeyValue: true,
- toKeyValue: true,
- encodeUriSegment: true,
- encodeUriQuery: true,
- angularInit: true,
- bootstrap: true,
- snake_case: true,
- bindJQuery: true,
- assertArg: true,
- assertArgFn: true,
- assertNotHasOwnProperty: true,
- getter: true,
- getBlockElements: true,
- hasOwnProperty: true,
+ lowercase: true,
+ uppercase: true,
+ manualLowercase: true,
+ manualUppercase: true,
+ nodeName_: true,
+ isArrayLike: true,
+ forEach: true,
+ forEachSorted: true,
+ reverseParams: true,
+ nextUid: true,
+ setHashKey: true,
+ extend: true,
+ toInt: true,
+ inherit: true,
+ merge: true,
+ noop: true,
+ identity: true,
+ valueFn: true,
+ isUndefined: true,
+ isDefined: true,
+ isObject: true,
+ isBlankObject: true,
+ isString: true,
+ isNumber: true,
+ isDate: true,
+ isArray: true,
+ isFunction: true,
+ isRegExp: true,
+ isWindow: true,
+ isScope: true,
+ isFile: true,
+ isFormData: true,
+ isBlob: true,
+ isBoolean: true,
+ isPromiseLike: true,
+ trim: true,
+ escapeForRegexp: true,
+ isElement: true,
+ makeMap: true,
+ includes: true,
+ arrayRemove: true,
+ copy: true,
+ shallowCopy: true,
+ equals: true,
+ csp: true,
+ jq: true,
+ concat: true,
+ sliceArgs: true,
+ bind: true,
+ toJsonReplacer: true,
+ toJson: true,
+ fromJson: true,
+ convertTimezoneToLocal: true,
+ timezoneToOffset: true,
+ startingTag: true,
+ tryDecodeURIComponent: true,
+ parseKeyValue: true,
+ toKeyValue: true,
+ encodeUriSegment: true,
+ encodeUriQuery: true,
+ angularInit: true,
+ bootstrap: true,
+ getTestability: true,
+ snake_case: true,
+ bindJQuery: true,
+ assertArg: true,
+ assertArgFn: true,
+ assertNotHasOwnProperty: true,
+ getter: true,
+ getBlockNodes: true,
+ hasOwnProperty: true,
+ createMap: true,
+
+ NODE_TYPE_ELEMENT: true,
+ NODE_TYPE_ATTRIBUTE: true,
+ NODE_TYPE_TEXT: true,
+ NODE_TYPE_COMMENT: true,
+ NODE_TYPE_DOCUMENT: true,
+ NODE_TYPE_DOCUMENT_FRAGMENT: true,
*/
////////////////////////////////////
@@ -181,6 +182,8 @@ function minErr(module) {
*
*/
+var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
+
// The name of a form control's ValidityState property.
// This is used so that it's possible for internal tests to create mock ValidityStates.
var VALIDITY_STATE_PROPERTY = 'validity';
@@ -195,7 +198,7 @@ var VALIDITY_STATE_PROPERTY = 'validity';
* @param {string} string String to be converted to lowercase.
* @returns {string} Lowercased string.
*/
-var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
+var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
@@ -208,7 +211,7 @@ var hasOwnProperty = Object.prototype.hasOwnProperty;
* @param {string} string String to be converted to uppercase.
* @returns {string} Uppercased string.
*/
-var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;};
+var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
var manualLowercase = function(s) {
@@ -234,29 +237,27 @@ if ('i' !== 'I'.toLowerCase()) {
}
-var /** holds major version number for IE or NaN for real browsers */
- msie,
+var
+ msie, // holds major version number for IE, or NaN if UA is not IE.
jqLite, // delay binding since jQuery could be loaded after us.
jQuery, // delay binding
slice = [].slice,
+ splice = [].splice,
push = [].push,
toString = Object.prototype.toString,
+ getPrototypeOf = Object.getPrototypeOf,
ngMinErr = minErr('ng'),
/** @name angular */
angular = window.angular || (window.angular = {}),
angularModule,
- nodeName_,
- uid = ['0', '0', '0'];
+ uid = 0;
/**
- * IE 11 changed the format of the UserAgent string.
- * See http://msdn.microsoft.com/en-us/library/ms537503.aspx
+ * documentMode is an IE-only property
+ * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
*/
-msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
-if (isNaN(msie)) {
- msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
-}
+msie = document.documentMode;
/**
@@ -270,9 +271,11 @@ function isArrayLike(obj) {
return false;
}
- var length = obj.length;
+ // Support: iOS 8.2 (not reproducible in simulator)
+ // "length" in obj used to prevent JIT error (gh-11508)
+ var length = "length" in Object(obj) && obj.length;
- if (obj.nodeType === 1 && length) {
+ if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
return true;
}
@@ -288,12 +291,17 @@ function isArrayLike(obj) {
*
* @description
* Invokes the `iterator` function once for each item in `obj` collection, which can be either an
- * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value`
- * is the value of an object property or an array element and `key` is the object property key or
- * array element index. Specifying a `context` for the function is optional.
+ * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
+ * is the value of an object property or an array element, `key` is the object property key or
+ * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
*
* It is worth noting that `.forEach` does not iterate over inherited properties because it filters
* using the `hasOwnProperty` method.
+ *
+ * Unlike ES262's
+ * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
+ * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
+ * return the value provided.
*
```js
var values = {name: 'misko', gender: 'male'};
@@ -309,27 +317,44 @@ function isArrayLike(obj) {
* @param {Object=} context Object to become context (`this`) for the iterator function.
* @returns {Object|Array} Reference to `obj`.
*/
+
function forEach(obj, iterator, context) {
- var key;
+ var key, length;
if (obj) {
if (isFunction(obj)) {
for (key in obj) {
// Need to check if hasOwnProperty exists,
// as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
- iterator.call(context, obj[key], key);
+ iterator.call(context, obj[key], key, obj);
}
}
} else if (isArray(obj) || isArrayLike(obj)) {
- for (key = 0; key < obj.length; key++) {
- iterator.call(context, obj[key], key);
+ var isPrimitive = typeof obj !== 'object';
+ for (key = 0, length = obj.length; key < length; key++) {
+ if (isPrimitive || key in obj) {
+ iterator.call(context, obj[key], key, obj);
+ }
}
} else if (obj.forEach && obj.forEach !== forEach) {
- obj.forEach(iterator, context);
- } else {
+ obj.forEach(iterator, context, obj);
+ } else if (isBlankObject(obj)) {
+ // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
+ for (key in obj) {
+ iterator.call(context, obj[key], key, obj);
+ }
+ } else if (typeof obj.hasOwnProperty === 'function') {
+ // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
for (key in obj) {
if (obj.hasOwnProperty(key)) {
- iterator.call(context, obj[key], key);
+ iterator.call(context, obj[key], key, obj);
+ }
+ }
+ } else {
+ // Slow path for objects which do not have a method `hasOwnProperty`
+ for (key in obj) {
+ if (hasOwnProperty.call(obj, key)) {
+ iterator.call(context, obj[key], key, obj);
}
}
}
@@ -337,19 +362,9 @@ function forEach(obj, iterator, context) {
return obj;
}
-function sortedKeys(obj) {
- var keys = [];
- for (var key in obj) {
- if (obj.hasOwnProperty(key)) {
- keys.push(key);
- }
- }
- return keys.sort();
-}
-
function forEachSorted(obj, iterator, context) {
- var keys = sortedKeys(obj);
- for ( var i = 0; i < keys.length; i++) {
+ var keys = Object.keys(obj).sort();
+ for (var i = 0; i < keys.length; i++) {
iterator.call(context, obj[keys[i]], keys[i]);
}
return keys;
@@ -366,33 +381,17 @@ function reverseParams(iteratorFn) {
}
/**
- * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
- * characters such as '012ABC'. The reason why we are not using simply a number counter is that
- * the number string gets longer over time, and it can also overflow, where as the nextId
- * will grow much slower, it is a string, and it will never overflow.
+ * A consistent way of creating unique IDs in angular.
*
- * @returns {string} an unique alpha-numeric string
+ * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
+ * we hit number precision issues in JavaScript.
+ *
+ * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
+ *
+ * @returns {number} an unique alpha-numeric string
*/
function nextUid() {
- var index = uid.length;
- var digit;
-
- while(index) {
- index--;
- digit = uid[index].charCodeAt(0);
- if (digit == 57 /*'9'*/) {
- uid[index] = 'A';
- return uid.join('');
- }
- if (digit == 90 /*'Z'*/) {
- uid[index] = '0';
- } else {
- uid[index] = String.fromCharCode(digit + 1);
- return uid.join('');
- }
- }
- uid.unshift('0');
- return uid.join('');
+ return ++uid;
}
@@ -404,12 +403,40 @@ function nextUid() {
function setHashKey(obj, h) {
if (h) {
obj.$$hashKey = h;
- }
- else {
+ } else {
delete obj.$$hashKey;
}
}
+
+function baseExtend(dst, objs, deep) {
+ var h = dst.$$hashKey;
+
+ for (var i = 0, ii = objs.length; i < ii; ++i) {
+ var obj = objs[i];
+ if (!isObject(obj) && !isFunction(obj)) continue;
+ var keys = Object.keys(obj);
+ for (var j = 0, jj = keys.length; j < jj; j++) {
+ var key = keys[j];
+ var src = obj[key];
+
+ if (deep && isObject(src)) {
+ if (isDate(src)) {
+ dst[key] = new Date(src.valueOf());
+ } else {
+ if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
+ baseExtend(dst[key], [src], true);
+ }
+ } else {
+ dst[key] = src;
+ }
+ }
+ }
+
+ setHashKey(dst, h);
+ return dst;
+}
+
/**
* @ngdoc function
* @name angular.extend
@@ -417,34 +444,53 @@ function setHashKey(obj, h) {
* @kind function
*
* @description
- * Extends the destination object `dst` by copying all of the properties from the `src` object(s)
- * to `dst`. You can specify multiple `src` objects.
+ * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
+ * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
+ * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
+ *
+ * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
+ * {@link angular.merge} for this.
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
* @returns {Object} Reference to `dst`.
*/
function extend(dst) {
- var h = dst.$$hashKey;
- forEach(arguments, function(obj) {
- if (obj !== dst) {
- forEach(obj, function(value, key) {
- dst[key] = value;
- });
- }
- });
-
- setHashKey(dst,h);
- return dst;
+ return baseExtend(dst, slice.call(arguments, 1), false);
}
-function int(str) {
+
+/**
+* @ngdoc function
+* @name angular.merge
+* @module ng
+* @kind function
+*
+* @description
+* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
+* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
+* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
+*
+* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
+* objects, performing a deep copy.
+*
+* @param {Object} dst Destination object.
+* @param {...Object} src Source object(s).
+* @returns {Object} Reference to `dst`.
+*/
+function merge(dst) {
+ return baseExtend(dst, slice.call(arguments, 1), true);
+}
+
+
+
+function toInt(str) {
return parseInt(str, 10);
}
function inherit(parent, extra) {
- return extend(new (extend(function() {}, {prototype:parent}))(), extra);
+ return extend(Object.create(parent), extra);
}
/**
@@ -482,6 +528,8 @@ noop.$inject = [];
return (transformationFn || angular.identity)(value);
};
```
+ * @param {*} value to be returned.
+ * @returns {*} the value passed in.
*/
function identity($) {return $;}
identity.$inject = [];
@@ -489,6 +537,11 @@ identity.$inject = [];
function valueFn(value) {return function() {return value;};}
+function hasCustomToString(obj) {
+ return isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
+}
+
+
/**
* @ngdoc function
* @name angular.isUndefined
@@ -501,7 +554,7 @@ function valueFn(value) {return function() {return value;};}
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is undefined.
*/
-function isUndefined(value){return typeof value === 'undefined';}
+function isUndefined(value) {return typeof value === 'undefined';}
/**
@@ -516,7 +569,7 @@ function isUndefined(value){return typeof value === 'undefined';}
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is defined.
*/
-function isDefined(value){return typeof value !== 'undefined';}
+function isDefined(value) {return typeof value !== 'undefined';}
/**
@@ -532,7 +585,20 @@ function isDefined(value){return typeof value !== 'undefined';}
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is an `Object` but not `null`.
*/
-function isObject(value){return value != null && typeof value === 'object';}
+function isObject(value) {
+ // http://jsperf.com/isobject4
+ return value !== null && typeof value === 'object';
+}
+
+
+/**
+ * Determine if a value is an object with a null prototype
+ *
+ * @returns {boolean} True if `value` is an `Object` with a null prototype
+ */
+function isBlankObject(value) {
+ return value !== null && typeof value === 'object' && !getPrototypeOf(value);
+}
/**
@@ -547,7 +613,7 @@ function isObject(value){return value != null && typeof value === 'object';}
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `String`.
*/
-function isString(value){return typeof value === 'string';}
+function isString(value) {return typeof value === 'string';}
/**
@@ -559,10 +625,16 @@ function isString(value){return typeof value === 'string';}
* @description
* Determines if a reference is a `Number`.
*
+ * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
+ *
+ * If you wish to exclude these then you can use the native
+ * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
+ * method.
+ *
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Number`.
*/
-function isNumber(value){return typeof value === 'number';}
+function isNumber(value) {return typeof value === 'number';}
/**
@@ -594,14 +666,7 @@ function isDate(value) {
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is an `Array`.
*/
-var isArray = (function() {
- if (!isFunction(Array.isArray)) {
- return function(value) {
- return toString.call(value) === '[object Array]';
- };
- }
- return Array.isArray;
-})();
+var isArray = Array.isArray;
/**
* @ngdoc function
@@ -615,7 +680,7 @@ var isArray = (function() {
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Function`.
*/
-function isFunction(value){return typeof value === 'function';}
+function isFunction(value) {return typeof value === 'function';}
/**
@@ -638,7 +703,7 @@ function isRegExp(value) {
* @returns {boolean} True if `obj` is a window obj.
*/
function isWindow(obj) {
- return obj && obj.document && obj.location && obj.alert && obj.setInterval;
+ return obj && obj.window === obj;
}
@@ -652,6 +717,11 @@ function isFile(obj) {
}
+function isFormData(obj) {
+ return toString.call(obj) === '[object FormData]';
+}
+
+
function isBlob(obj) {
return toString.call(obj) === '[object Blob]';
}
@@ -667,19 +737,23 @@ function isPromiseLike(obj) {
}
-var trim = (function() {
- // native trim is way faster: http://jsperf.com/angular-trim-test
- // but IE doesn't have it... :-(
- // TODO: we should move this into IE/ES5 polyfill
- if (!String.prototype.trim) {
- return function(value) {
- return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value;
- };
- }
- return function(value) {
- return isString(value) ? value.trim() : value;
- };
-})();
+var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/;
+function isTypedArray(value) {
+ return TYPED_ARRAY_REGEXP.test(toString.call(value));
+}
+
+
+var trim = function(value) {
+ return isString(value) ? value.trim() : value;
+};
+
+// Copied from:
+// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
+// Prereq: s is a string.
+var escapeForRegexp = function(s) {
+ return s.replace(/([-()\[\]{}+?*.$\^|,:#=0)
+ var index = array.indexOf(value);
+ if (index >= 0) {
array.splice(index, 1);
- return value;
-}
-
-function isLeafNode (node) {
- if (node) {
- switch (node.nodeName) {
- case "OPTION":
- case "PRE":
- case "TITLE":
- return true;
- }
}
- return false;
+ return index;
}
/**
@@ -803,7 +813,7 @@ function isLeafNode (node) {
* Creates a deep copy of `source`, which should be an object or an array.
*
* * If no destination is supplied, a copy of the object or array is created.
- * * If a destination is provided, all of its elements (for array) or properties (for objects)
+ * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
* are deleted and then all elements/properties from the source are copied to it.
* * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
* * If `source` is identical to 'destination' an exception will be thrown.
@@ -856,19 +866,40 @@ function copy(source, destination, stackSource, stackDest) {
throw ngMinErr('cpws',
"Can't copy! Making copies of Window or Scope instances is not supported.");
}
+ if (isTypedArray(destination)) {
+ throw ngMinErr('cpta',
+ "Can't copy! TypedArray destination cannot be mutated.");
+ }
if (!destination) {
destination = source;
- if (source) {
+ if (isObject(source)) {
+ var index;
+ if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
+ return stackDest[index];
+ }
+
+ // TypedArray, Date and RegExp have specific copy functionality and must be
+ // pushed onto the stack before returning.
+ // Array and other objects create the base object and recurse to copy child
+ // objects. The array/object will be pushed onto the stack when recursed.
if (isArray(source)) {
- destination = copy(source, [], stackSource, stackDest);
+ return copy(source, [], stackSource, stackDest);
+ } else if (isTypedArray(source)) {
+ destination = new source.constructor(source);
} else if (isDate(source)) {
destination = new Date(source.getTime());
} else if (isRegExp(source)) {
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
destination.lastIndex = source.lastIndex;
- } else if (isObject(source)) {
- destination = copy(source, {}, stackSource, stackDest);
+ } else {
+ var emptyObject = Object.create(getPrototypeOf(source));
+ return copy(source, emptyObject, stackSource, stackDest);
+ }
+
+ if (stackDest) {
+ stackSource.push(source);
+ stackDest.push(destination);
}
}
} else {
@@ -879,23 +910,15 @@ function copy(source, destination, stackSource, stackDest) {
stackDest = stackDest || [];
if (isObject(source)) {
- var index = indexOf(stackSource, source);
- if (index !== -1) return stackDest[index];
-
stackSource.push(source);
stackDest.push(destination);
}
- var result;
+ var result, key;
if (isArray(source)) {
destination.length = 0;
- for ( var i = 0; i < source.length; i++) {
- result = copy(source[i], null, stackSource, stackDest);
- if (isObject(source[i])) {
- stackSource.push(source[i]);
- stackDest.push(result);
- }
- destination.push(result);
+ for (var i = 0; i < source.length; i++) {
+ destination.push(copy(source[i], null, stackSource, stackDest));
}
} else {
var h = destination.$$hashKey;
@@ -906,36 +929,49 @@ function copy(source, destination, stackSource, stackDest) {
delete destination[key];
});
}
- for ( var key in source) {
- result = copy(source[key], null, stackSource, stackDest);
- if (isObject(source[key])) {
- stackSource.push(source[key]);
- stackDest.push(result);
+ if (isBlankObject(source)) {
+ // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
+ for (key in source) {
+ destination[key] = copy(source[key], null, stackSource, stackDest);
+ }
+ } else if (source && typeof source.hasOwnProperty === 'function') {
+ // Slow path, which must rely on hasOwnProperty
+ for (key in source) {
+ if (source.hasOwnProperty(key)) {
+ destination[key] = copy(source[key], null, stackSource, stackDest);
+ }
+ }
+ } else {
+ // Slowest path --- hasOwnProperty can't be called as a method
+ for (key in source) {
+ if (hasOwnProperty.call(source, key)) {
+ destination[key] = copy(source[key], null, stackSource, stackDest);
+ }
}
- destination[key] = result;
}
setHashKey(destination,h);
}
-
}
return destination;
}
/**
- * Creates a shallow copy of an object, an array or a primitive
+ * Creates a shallow copy of an object, an array or a primitive.
+ *
+ * Assumes that there are no proto properties for objects.
*/
function shallowCopy(src, dst) {
if (isArray(src)) {
dst = dst || [];
- for ( var i = 0; i < src.length; i++) {
+ for (var i = 0, ii = src.length; i < ii; i++) {
dst[i] = src[i];
}
} else if (isObject(src)) {
dst = dst || {};
for (var key in src) {
- if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
+ if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
dst[key] = src[key];
}
}
@@ -984,26 +1020,27 @@ function equals(o1, o2) {
if (isArray(o1)) {
if (!isArray(o2)) return false;
if ((length = o1.length) == o2.length) {
- for(key=0; key
+
+ ...
+ ...
+
+ ```
+ * @example
+ * This example shows how to use a jQuery based library of a different name.
+ * The library name must be available at the top most 'window'.
+ ```html
+
+
+ ...
+ ...
+
+ ```
+ */
+var jq = function() {
+ if (isDefined(jq.name_)) return jq.name_;
+ var el;
+ var i, ii = ngAttrPrefixes.length, prefix, name;
+ for (i = 0; i < ii; ++i) {
+ prefix = ngAttrPrefixes[i];
+ if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
+ name = el.getAttribute(prefix + 'jq');
+ break;
+ }
+ }
+ return (jq.name_ = name);
+};
function concat(array1, array2, index) {
return array1.concat(slice.call(array2, index));
@@ -1070,7 +1158,7 @@ function bind(self, fn) {
return curryArgs.length
? function() {
return arguments.length
- ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0)))
+ ? fn.apply(self, concat(curryArgs, arguments, 0))
: fn.apply(self, curryArgs);
}
: function() {
@@ -1088,7 +1176,7 @@ function bind(self, fn) {
function toJsonReplacer(key, value) {
var val = value;
- if (typeof key === 'string' && key.charAt(0) === '$') {
+ if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
val = undefined;
} else if (isWindow(value)) {
val = '$WINDOW';
@@ -1109,16 +1197,20 @@ function toJsonReplacer(key, value) {
* @kind function
*
* @description
- * Serializes input into a JSON-formatted string. Properties with leading $ characters will be
+ * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
* stripped since angular uses this notation internally.
*
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
- * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
+ * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
+ * If set to an integer, the JSON output will contain that many spaces per indentation.
* @returns {string|undefined} JSON-ified string representing `obj`.
*/
function toJson(obj, pretty) {
if (typeof obj === 'undefined') return undefined;
- return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
+ if (!isNumber(pretty)) {
+ pretty = pretty ? 2 : null;
+ }
+ return JSON.stringify(obj, toJsonReplacer, pretty);
}
@@ -1132,7 +1224,7 @@ function toJson(obj, pretty) {
* Deserializes a JSON string.
*
* @param {string} json JSON string to deserialize.
- * @returns {Object|Array|string|number} Deserialized thingy.
+ * @returns {Object|Array|string|number} Deserialized JSON string.
*/
function fromJson(json) {
return isString(json)
@@ -1141,18 +1233,26 @@ function fromJson(json) {
}
-function toBoolean(value) {
- if (typeof value === 'function') {
- value = true;
- } else if (value && value.length !== 0) {
- var v = lowercase("" + value);
- value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
- } else {
- value = false;
- }
- return value;
+function timezoneToOffset(timezone, fallback) {
+ var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
+ return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
}
+
+function addDateMinutes(date, minutes) {
+ date = new Date(date.getTime());
+ date.setMinutes(date.getMinutes() + minutes);
+ return date;
+}
+
+
+function convertTimezoneToLocal(date, timezone, reverse) {
+ reverse = reverse ? -1 : 1;
+ var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
+ return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
+}
+
+
/**
* @returns {string} Returns the string representation of the element.
*/
@@ -1162,16 +1262,14 @@ function startingTag(element) {
// turns out IE does not let you set .html() on elements which
// are not allowed to have children. So we just ignore it.
element.empty();
- } catch(e) {}
- // As Per DOM Standards
- var TEXT_NODE = 3;
+ } catch (e) {}
var elemHtml = jqLite('
').append(element).html();
try {
- return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
+ return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
elemHtml.
match(/^(<[^>]+>)/)[1].
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
- } catch(e) {
+ } catch (e) {
return lowercase(elemHtml);
}
@@ -1191,7 +1289,7 @@ function startingTag(element) {
function tryDecodeURIComponent(value) {
try {
return decodeURIComponent(value);
- } catch(e) {
+ } catch (e) {
// Ignore any invalid uri component
}
}
@@ -1204,14 +1302,14 @@ function tryDecodeURIComponent(value) {
function parseKeyValue(/**string*/keyValue) {
var obj = {}, key_value, key;
forEach((keyValue || "").split('&'), function(keyValue) {
- if ( keyValue ) {
+ if (keyValue) {
key_value = keyValue.replace(/\+/g,'%20').split('=');
key = tryDecodeURIComponent(key_value[0]);
- if ( isDefined(key) ) {
+ if (isDefined(key)) {
var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
if (!hasOwnProperty.call(obj, key)) {
obj[key] = val;
- } else if(isArray(obj[key])) {
+ } else if (isArray(obj[key])) {
obj[key].push(val);
} else {
obj[key] = [obj[key],val];
@@ -1275,9 +1373,22 @@ function encodeUriQuery(val, pctEncodeSpaces) {
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
+ replace(/%3B/gi, ';').
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
}
+var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
+
+function getNgAttribute(element, ngAttr) {
+ var attr, i, ii = ngAttrPrefixes.length;
+ for (i = 0; i < ii; ++i) {
+ attr = ngAttrPrefixes[i] + ngAttr;
+ if (isString(attr = element.getAttribute(attr))) {
+ return attr;
+ }
+ }
+ return null;
+}
/**
* @ngdoc directive
@@ -1287,6 +1398,11 @@ function encodeUriQuery(val, pctEncodeSpaces) {
* @element ANY
* @param {angular.Module} ngApp an optional application
* {@link angular.module module} name to load.
+ * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
+ * created in "strict-di" mode. This means that the application will fail to invoke functions which
+ * do not use explicit function annotation (and are thus unsuitable for minification), as described
+ * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
+ * tracking down the root of these bugs.
*
* @description
*
@@ -1300,7 +1416,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
* {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
*
* You can specify an **AngularJS module** to be used as the root module for the application. This
- * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and
+ * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
* should contain the application code needed or have dependencies on other modules that will
* contain the code. See {@link angular.module} for more information.
*
@@ -1308,7 +1424,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
* document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
* would not be resolved to `3`.
*
- * `ngApp` is the easiest, and most common, way to bootstrap an application.
+ * `ngApp` is the easiest, and most common way to bootstrap an application.
*
@@ -1324,48 +1440,109 @@ function encodeUriQuery(val, pctEncodeSpaces) {
*
+ * Using `ngStrictDi`, you would see something like this:
+ *
+
+
+
+
+ I can add: {{a}} + {{b}} = {{ a+b }}
+
+
This renders because the controller does not fail to
+ instantiate, by using explicit annotation style (see
+ script.js for details)
+
+
+
+
+ Name:
+ Hello, {{name}}!
+
+
This renders because the controller does not fail to
+ instantiate, by using explicit annotation style
+ (see script.js for details)
+
+
+
+
+ I can add: {{a}} + {{b}} = {{ a+b }}
+
+
The controller could not be instantiated, due to relying
+ on automatic function annotations (which are disabled in
+ strict mode). As such, the content of this section is not
+ interpolated, and there should be an error in your web console.
+
+
+
+
+
+ angular.module('ngAppStrictDemo', [])
+ // BadController will fail to instantiate, due to relying on automatic function annotation,
+ // rather than an explicit annotation
+ .controller('BadController', function($scope) {
+ $scope.a = 1;
+ $scope.b = 2;
+ })
+ // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
+ // due to using explicit annotations using the array style and $inject property, respectively.
+ .controller('GoodController1', ['$scope', function($scope) {
+ $scope.a = 1;
+ $scope.b = 2;
+ }])
+ .controller('GoodController2', GoodController2);
+ function GoodController2($scope) {
+ $scope.name = "World";
+ }
+ GoodController2.$inject = ['$scope'];
+
+
+ div[ng-controller] {
+ margin-bottom: 1em;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ border: 1px solid;
+ padding: .5em;
+ }
+ div[ng-controller^=Good] {
+ border-color: #d6e9c6;
+ background-color: #dff0d8;
+ color: #3c763d;
+ }
+ div[ng-controller^=Bad] {
+ border-color: #ebccd1;
+ background-color: #f2dede;
+ color: #a94442;
+ margin-bottom: 0;
+ }
+
+
*/
function angularInit(element, bootstrap) {
- var elements = [element],
- appElement,
+ var appElement,
module,
- names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
- NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
+ config = {};
- function append(element) {
- element && elements.push(element);
- }
+ // The element `element` has priority over any other element
+ forEach(ngAttrPrefixes, function(prefix) {
+ var name = prefix + 'app';
- forEach(names, function(name) {
- names[name] = true;
- append(document.getElementById(name));
- name = name.replace(':', '\\:');
- if (element.querySelectorAll) {
- forEach(element.querySelectorAll('.' + name), append);
- forEach(element.querySelectorAll('.' + name + '\\:'), append);
- forEach(element.querySelectorAll('[' + name + ']'), append);
+ if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
+ appElement = element;
+ module = element.getAttribute(name);
}
});
+ forEach(ngAttrPrefixes, function(prefix) {
+ var name = prefix + 'app';
+ var candidate;
- forEach(elements, function(element) {
- if (!appElement) {
- var className = ' ' + element.className + ' ';
- var match = NG_APP_CLASS_REGEXP.exec(className);
- if (match) {
- appElement = element;
- module = (match[2] || '').replace(/\s+/g, ',');
- } else {
- forEach(element.attributes, function(attr) {
- if (!appElement && names[attr.name]) {
- appElement = element;
- module = attr.value;
- }
- });
- }
+ if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
+ appElement = candidate;
+ module = candidate.getAttribute(name);
}
});
if (appElement) {
- bootstrap(appElement, module ? [module] : []);
+ config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
+ bootstrap(appElement, module ? [module] : [], config);
}
}
@@ -1378,7 +1555,7 @@ function angularInit(element, bootstrap) {
*
* See: {@link guide/bootstrap Bootstrap}
*
- * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually.
+ * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
* They must use {@link ng.directive:ngApp ngApp}.
*
* Angular will detect if it has been loaded into the browser more than once and only allow the
@@ -1386,44 +1563,45 @@ function angularInit(element, bootstrap) {
* each of the subsequent scripts. This prevents strange results in applications, where otherwise
* multiple instances of Angular try to work on the DOM.
*
- *
- *
- *
- *
- *
- *
- *
{{heading}}
- *
- *
- *
{{fill}}
- *
- *
+ * ```html
+ *
+ *
+ *
+ *
+ * {{greeting}}
*
- *
- *
- * var app = angular.module('multi-bootstrap', [])
*
- * .controller('BrokenTable', function($scope) {
- * $scope.headings = ['One', 'Two', 'Three'];
- * $scope.fillings = [[1, 2, 3], ['A', 'B', 'C'], [7, 8, 9]];
- * });
- *
- *
- * it('should only insert one table cell for each item in $scope.fillings', function() {
- * expect(element.all(by.css('td')).count())
- * .toBe(9);
- * });
- *
- *
+ *
+ *
+ *
+ *
+ * ```
*
* @param {DOMElement} element DOM element which is the root of angular application.
* @param {Array=} modules an array of modules to load into the application.
* Each item in the array should be the name of a predefined module or a (DI annotated)
- * function that will be invoked by the injector as a run block.
+ * function that will be invoked by the injector as a `config` block.
* See: {@link angular.module modules}
+ * @param {Object=} config an object for defining configuration options for the application. The
+ * following keys are supported:
+ *
+ * * `strictDi` - disable automatic function annotation for the application. This is meant to
+ * assist in finding bugs which break minified code. Defaults to `false`.
+ *
* @returns {auto.$injector} Returns the newly created injector for this app.
*/
-function bootstrap(element, modules) {
+function bootstrap(element, modules, config) {
+ if (!isObject(config)) config = {};
+ var defaultConfig = {
+ strictDi: false
+ };
+ config = extend(defaultConfig, config);
var doBootstrap = function() {
element = jqLite(element);
@@ -1440,10 +1618,18 @@ function bootstrap(element, modules) {
modules.unshift(['$provide', function($provide) {
$provide.value('$rootElement', element);
}]);
+
+ if (config.debugInfoEnabled) {
+ // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
+ modules.push(['$compileProvider', function($compileProvider) {
+ $compileProvider.debugInfoEnabled(true);
+ }]);
+ }
+
modules.unshift('ng');
- var injector = createInjector(modules);
- injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
- function(scope, element, compile, injector, animate) {
+ var injector = createInjector(modules, config.strictDi);
+ injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
+ function bootstrapApply(scope, element, compile, injector) {
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
@@ -1453,8 +1639,14 @@ function bootstrap(element, modules) {
return injector;
};
+ var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
+ if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
+ config.debugInfoEnabled = true;
+ window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
+ }
+
if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
return doBootstrap();
}
@@ -1464,8 +1656,44 @@ function bootstrap(element, modules) {
forEach(extraModules, function(module) {
modules.push(module);
});
- doBootstrap();
+ return doBootstrap();
};
+
+ if (isFunction(angular.resumeDeferredBootstrap)) {
+ angular.resumeDeferredBootstrap();
+ }
+}
+
+/**
+ * @ngdoc function
+ * @name angular.reloadWithDebugInfo
+ * @module ng
+ * @description
+ * Use this function to reload the current application with debug information turned on.
+ * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
+ *
+ * See {@link ng.$compileProvider#debugInfoEnabled} for more.
+ */
+function reloadWithDebugInfo() {
+ window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
+ window.location.reload();
+}
+
+/**
+ * @name angular.getTestability
+ * @module ng
+ * @description
+ * Get the testability service for the instance of Angular on the given
+ * element.
+ * @param {DOMElement} element DOM element which is the root of angular application.
+ */
+function getTestability(rootElement) {
+ var injector = angular.element(rootElement).injector();
+ if (!injector) {
+ throw ngMinErr('test',
+ 'no injector found for element argument to getTestability');
+ }
+ return injector.get('$$testability');
}
var SNAKE_CASE_REGEXP = /[A-Z]/g;
@@ -1476,11 +1704,26 @@ function snake_case(name, separator) {
});
}
+var bindJQueryFired = false;
+var skipDestroyOnNextJQueryCleanData;
function bindJQuery() {
+ var originalCleanData;
+
+ if (bindJQueryFired) {
+ return;
+ }
+
// bind to jQuery if present;
- jQuery = window.jQuery;
+ var jqName = jq();
+ jQuery = window.jQuery; // use default jQuery.
+ if (isDefined(jqName)) { // `ngJq` present
+ jQuery = jqName === null ? undefined : window[jqName]; // if empty; use jqLite. if not empty, use jQuery specified by `ngJq`.
+ }
+
// Use jQuery if it exists with proper functionality, otherwise default to us.
- // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support.
+ // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
+ // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
+ // versions. It will not work for sure with jQuery <1.7, though.
if (jQuery && jQuery.fn.on) {
jqLite = jQuery;
extend(jQuery.fn, {
@@ -1490,15 +1733,33 @@ function bindJQuery() {
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
});
- // Method signature:
- // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments)
- jqLitePatchJQueryRemove('remove', true, true, false);
- jqLitePatchJQueryRemove('empty', false, false, false);
- jqLitePatchJQueryRemove('html', false, false, true);
+
+ // All nodes removed from the DOM via various jQuery APIs like .remove()
+ // are passed through jQuery.cleanData. Monkey-patch this method to fire
+ // the $destroy event on all removed nodes.
+ originalCleanData = jQuery.cleanData;
+ jQuery.cleanData = function(elems) {
+ var events;
+ if (!skipDestroyOnNextJQueryCleanData) {
+ for (var i = 0, elem; (elem = elems[i]) != null; i++) {
+ events = jQuery._data(elem, "events");
+ if (events && events.$destroy) {
+ jQuery(elem).triggerHandler('$destroy');
+ }
+ }
+ } else {
+ skipDestroyOnNextJQueryCleanData = false;
+ }
+ originalCleanData(elems);
+ };
} else {
jqLite = JQLite;
}
+
angular.element = jqLite;
+
+ // Prevent double-proxying.
+ bindJQueryFired = true;
}
/**
@@ -1562,27 +1823,47 @@ function getter(obj, path, bindFnToScope) {
/**
* Return the DOM siblings between the first and last node in the given array.
* @param {Array} array like object
- * @returns {DOMElement} object containing the elements
+ * @returns {jqLite} jqLite collection containing the nodes
*/
-function getBlockElements(nodes) {
- var startNode = nodes[0],
- endNode = nodes[nodes.length - 1];
- if (startNode === endNode) {
- return jqLite(startNode);
- }
-
- var element = startNode;
- var elements = [element];
+function getBlockNodes(nodes) {
+ // TODO(perf): just check if all items in `nodes` are siblings and if they are return the original
+ // collection, otherwise update the original collection.
+ var node = nodes[0];
+ var endNode = nodes[nodes.length - 1];
+ var blockNodes = [node];
do {
- element = element.nextSibling;
- if (!element) break;
- elements.push(element);
- } while (element !== endNode);
+ node = node.nextSibling;
+ if (!node) break;
+ blockNodes.push(node);
+ } while (node !== endNode);
- return jqLite(elements);
+ return jqLite(blockNodes);
}
+
+/**
+ * Creates a new object without a prototype. This object is useful for lookup without having to
+ * guard against prototypically inherited properties via hasOwnProperty.
+ *
+ * Related micro-benchmarks:
+ * - http://jsperf.com/object-create2
+ * - http://jsperf.com/proto-map-lookup/2
+ * - http://jsperf.com/for-in-vs-object-keys2
+ *
+ * @returns {Object}
+ */
+function createMap() {
+ return Object.create(null);
+}
+
+var NODE_TYPE_ELEMENT = 1;
+var NODE_TYPE_ATTRIBUTE = 2;
+var NODE_TYPE_TEXT = 3;
+var NODE_TYPE_COMMENT = 8;
+var NODE_TYPE_DOCUMENT = 9;
+var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
+
/**
* @ngdoc type
* @name angular.Module
@@ -1682,15 +1963,19 @@ function setupModuleLoader(window) {
/** @type {!Array.>} */
var invokeQueue = [];
+ /** @type {!Array.} */
+ var configBlocks = [];
+
/** @type {!Array.} */
var runBlocks = [];
- var config = invokeLater('$injector', 'invoke');
+ var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
/** @type {angular.Module} */
var moduleInstance = {
// Private state
_invokeQueue: invokeQueue,
+ _configBlocks: configBlocks,
_runBlocks: runBlocks,
/**
@@ -1725,7 +2010,7 @@ function setupModuleLoader(window) {
* @description
* See {@link auto.$provide#provider $provide.provider()}.
*/
- provider: invokeLater('$provide', 'provider'),
+ provider: invokeLaterAndSetModuleName('$provide', 'provider'),
/**
* @ngdoc method
@@ -1736,7 +2021,7 @@ function setupModuleLoader(window) {
* @description
* See {@link auto.$provide#factory $provide.factory()}.
*/
- factory: invokeLater('$provide', 'factory'),
+ factory: invokeLaterAndSetModuleName('$provide', 'factory'),
/**
* @ngdoc method
@@ -1747,7 +2032,7 @@ function setupModuleLoader(window) {
* @description
* See {@link auto.$provide#service $provide.service()}.
*/
- service: invokeLater('$provide', 'service'),
+ service: invokeLaterAndSetModuleName('$provide', 'service'),
/**
* @ngdoc method
@@ -1772,6 +2057,18 @@ function setupModuleLoader(window) {
*/
constant: invokeLater('$provide', 'constant', 'unshift'),
+ /**
+ * @ngdoc method
+ * @name angular.Module#decorator
+ * @module ng
+ * @param {string} The name of the service to decorate.
+ * @param {Function} This function will be invoked when the service needs to be
+ * instantiated and should return the decorated service instance.
+ * @description
+ * See {@link auto.$provide#decorator $provide.decorator()}.
+ */
+ decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
+
/**
* @ngdoc method
* @name angular.Module#animation
@@ -1785,7 +2082,7 @@ function setupModuleLoader(window) {
*
*
* Defines an animation hook that can be later used with
- * {@link ngAnimate.$animate $animate} service and directives that use this service.
+ * {@link $animate $animate} service and directives that use this service.
*
* ```js
* module.animation('.animation-name', function($inject1, $inject2) {
@@ -1801,21 +2098,28 @@ function setupModuleLoader(window) {
* })
* ```
*
- * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
+ * See {@link ng.$animateProvider#register $animateProvider.register()} and
* {@link ngAnimate ngAnimate module} for more information.
*/
- animation: invokeLater('$animateProvider', 'register'),
+ animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#filter
* @module ng
- * @param {string} name Filter name.
+ * @param {string} name Filter name - this must be a valid angular expression identifier
* @param {Function} filterFactory Factory function for creating new instance of filter.
* @description
* See {@link ng.$filterProvider#register $filterProvider.register()}.
+ *
+ *
+ * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
+ * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
+ * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
+ * (`myapp_subsection_filterx`).
+ *
*
- * To use jQuery, simply load it before `DOMContentLoaded` event fired.
+ * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
*
*
**Note:** all element references in Angular are always wrapped with jQuery or
* jqLite; they are never raw DOM references.
@@ -2153,13 +2546,14 @@ function publishExternalAPI(angular){
* - [`addClass()`](http://api.jquery.com/addClass/)
* - [`after()`](http://api.jquery.com/after/)
* - [`append()`](http://api.jquery.com/append/)
- * - [`attr()`](http://api.jquery.com/attr/)
+ * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
* - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
* - [`clone()`](http://api.jquery.com/clone/)
* - [`contents()`](http://api.jquery.com/contents/)
- * - [`css()`](http://api.jquery.com/css/)
+ * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'.
* - [`data()`](http://api.jquery.com/data/)
+ * - [`detach()`](http://api.jquery.com/detach/)
* - [`empty()`](http://api.jquery.com/empty/)
* - [`eq()`](http://api.jquery.com/eq/)
* - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
@@ -2200,10 +2594,12 @@ function publishExternalAPI(angular){
* `'ngModel'`).
* - `injector()` - retrieves the injector of the current element or its parent.
* - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
- * element or its parent.
+ * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
+ * be enabled.
* - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
* current element. This getter should be used only on elements that contain a directive which starts a new isolate
* scope. Calling `scope()` on this element always returns the original non-isolate scope.
+ * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
* - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
* parent element is reached.
*
@@ -2215,17 +2611,17 @@ JQLite.expando = 'ng339';
var jqCache = JQLite.cache = {},
jqId = 1,
- addEventListenerFn = (window.document.addEventListener
- ? function(element, type, fn) {element.addEventListener(type, fn, false);}
- : function(element, type, fn) {element.attachEvent('on' + type, fn);}),
- removeEventListenerFn = (window.document.removeEventListener
- ? function(element, type, fn) {element.removeEventListener(type, fn, false); }
- : function(element, type, fn) {element.detachEvent('on' + type, fn); });
+ addEventListenerFn = function(element, type, fn) {
+ element.addEventListener(type, fn, false);
+ },
+ removeEventListenerFn = function(element, type, fn) {
+ element.removeEventListener(type, fn, false);
+ };
/*
* !!! This is an undocumented "private" function !!!
*/
-var jqData = JQLite._data = function(node) {
+JQLite._data = function(node) {
//jQuery always returns an object on cache miss
return this.cache[node[this.expando]] || {};
};
@@ -2235,6 +2631,7 @@ function jqNextId() { return ++jqId; }
var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
var MOZ_HACK_REGEXP = /^moz([A-Z])/;
+var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
var jqLiteMinErr = minErr('jqLite');
/**
@@ -2250,49 +2647,6 @@ function camelCase(name) {
replace(MOZ_HACK_REGEXP, 'Moz$1');
}
-/////////////////////////////////////////////
-// jQuery mutation patch
-//
-// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a
-// $destroy event on all DOM nodes being removed.
-//
-/////////////////////////////////////////////
-
-function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) {
- var originalJqFn = jQuery.fn[name];
- originalJqFn = originalJqFn.$original || originalJqFn;
- removePatch.$original = originalJqFn;
- jQuery.fn[name] = removePatch;
-
- function removePatch(param) {
- // jshint -W040
- var list = filterElems && param ? [this.filter(param)] : [this],
- fireEvent = dispatchThis,
- set, setIndex, setLength,
- element, childIndex, childLength, children;
-
- if (!getterIfNoArguments || param != null) {
- while(list.length) {
- set = list.shift();
- for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
- element = jqLite(set[setIndex]);
- if (fireEvent) {
- element.triggerHandler('$destroy');
- } else {
- fireEvent = !fireEvent;
- }
- for(childIndex = 0, childLength = (children = element.children()).length;
- childIndex < childLength;
- childIndex++) {
- list.push(jQuery(children[childIndex]));
- }
- }
- }
- }
- return originalJqFn.apply(this, arguments);
- }
-}
-
var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/;
var HTML_REGEXP = /<|?\w+;/;
var TAG_NAME_REGEXP = /<([\w:]+)/;
@@ -2312,26 +2666,39 @@ wrapMap.optgroup = wrapMap.option;
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;
+
function jqLiteIsTextNode(html) {
return !HTML_REGEXP.test(html);
}
+function jqLiteAcceptsData(node) {
+ // The window object can accept data but has no nodeType
+ // Otherwise we are only interested in elements (1) and documents (9)
+ var nodeType = node.nodeType;
+ return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
+}
+
+function jqLiteHasData(node) {
+ for (var key in jqCache[node.ng339]) {
+ return true;
+ }
+ return false;
+}
+
function jqLiteBuildFragment(html, context) {
- var elem, tmp, tag, wrap,
+ var tmp, tag, wrap,
fragment = context.createDocumentFragment(),
- nodes = [], i, j, jj;
+ nodes = [], i;
if (jqLiteIsTextNode(html)) {
// Convert non-html into a text node
nodes.push(context.createTextNode(html));
} else {
- tmp = fragment.appendChild(context.createElement('div'));
// Convert html into DOM nodes
+ tmp = tmp || fragment.appendChild(context.createElement("div"));
tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
wrap = wrapMap[tag] || wrapMap._default;
- tmp.innerHTML = '
' +
- wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1>$2>") + wrap[2];
- tmp.removeChild(tmp.firstChild);
+ tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1>$2>") + wrap[2];
// Descend through wrappers to the right content
i = wrap[0];
@@ -2339,7 +2706,7 @@ function jqLiteBuildFragment(html, context) {
tmp = tmp.lastChild;
}
- for (j=0, jj=tmp.childNodes.length; j 0) {
+ return;
+ }
}
+
+ removeEventListenerFn(element, type, handle);
+ delete events[type];
});
}
}
function jqLiteRemoveData(element, name) {
- var expandoId = element.ng339,
- expandoStore = jqCache[expandoId];
+ var expandoId = element.ng339;
+ var expandoStore = expandoId && jqCache[expandoId];
if (expandoStore) {
if (name) {
- delete jqCache[expandoId].data[name];
+ delete expandoStore.data[name];
return;
}
if (expandoStore.handle) {
- expandoStore.events.$destroy && expandoStore.handle({}, '$destroy');
+ if (expandoStore.events.$destroy) {
+ expandoStore.handle({}, '$destroy');
+ }
jqLiteOff(element);
}
delete jqCache[expandoId];
@@ -2441,43 +2831,42 @@ function jqLiteRemoveData(element, name) {
}
}
-function jqLiteExpandoStore(element, key, value) {
- var expandoId = element.ng339,
- expandoStore = jqCache[expandoId || -1];
- if (isDefined(value)) {
- if (!expandoStore) {
- element.ng339 = expandoId = jqNextId();
- expandoStore = jqCache[expandoId] = {};
- }
- expandoStore[key] = value;
- } else {
- return expandoStore && expandoStore[key];
+function jqLiteExpandoStore(element, createIfNecessary) {
+ var expandoId = element.ng339,
+ expandoStore = expandoId && jqCache[expandoId];
+
+ if (createIfNecessary && !expandoStore) {
+ element.ng339 = expandoId = jqNextId();
+ expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
}
+
+ return expandoStore;
}
+
function jqLiteData(element, key, value) {
- var data = jqLiteExpandoStore(element, 'data'),
- isSetter = isDefined(value),
- keyDefined = !isSetter && isDefined(key),
- isSimpleGetter = keyDefined && !isObject(key);
+ if (jqLiteAcceptsData(element)) {
- if (!data && !isSimpleGetter) {
- jqLiteExpandoStore(element, 'data', data = {});
- }
+ var isSimpleSetter = isDefined(value);
+ var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
+ var massGetter = !key;
+ var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
+ var data = expandoStore && expandoStore.data;
- if (isSetter) {
- data[key] = value;
- } else {
- if (keyDefined) {
- if (isSimpleGetter) {
- // don't create data in this case.
- return data && data[key];
- } else {
- extend(data, key);
- }
+ if (isSimpleSetter) { // data('key', value)
+ data[key] = value;
} else {
- return data;
+ if (massGetter) { // data()
+ return data;
+ } else {
+ if (isSimpleGetter) { // data('key')
+ // don't force creation of expandoStore if it doesn't exist yet
+ return data && data[key];
+ } else { // mass-setter: data({key1: val1, key2: val2})
+ extend(data, key);
+ }
+ }
}
}
}
@@ -2485,7 +2874,7 @@ function jqLiteData(element, key, value) {
function jqLiteHasClass(element, selector) {
if (!element.getAttribute) return false;
return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
- indexOf( " " + selector + " " ) > -1);
+ indexOf(" " + selector + " ") > -1);
}
function jqLiteRemoveClass(element, cssClasses) {
@@ -2516,25 +2905,41 @@ function jqLiteAddClass(element, cssClasses) {
}
}
+
function jqLiteAddNodes(root, elements) {
+ // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
+
if (elements) {
- elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements))
- ? elements
- : [ elements ];
- for(var i=0; i < elements.length; i++) {
- root.push(elements[i]);
+
+ // if a Node (the most common case)
+ if (elements.nodeType) {
+ root[root.length++] = elements;
+ } else {
+ var length = elements.length;
+
+ // if an Array or NodeList and not a Window
+ if (typeof length === 'number' && elements.window !== elements) {
+ if (length) {
+ for (var i = 0; i < length; i++) {
+ root[root.length++] = elements[i];
+ }
+ }
+ } else {
+ root[root.length++] = elements;
+ }
}
}
}
+
function jqLiteController(element, name) {
- return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
+ return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
}
function jqLiteInheritedData(element, name, value) {
// if element is the document object work with the html element instead
// this makes $(document).scope() possible
- if(element.nodeType == 9) {
+ if (element.nodeType == NODE_TYPE_DOCUMENT) {
element = element.documentElement;
}
var names = isArray(name) ? name : [name];
@@ -2547,19 +2952,37 @@ function jqLiteInheritedData(element, name, value) {
// If dealing with a document fragment node with a host element, and no parent, use the host
// element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
// to lookup parent controllers.
- element = element.parentNode || (element.nodeType === 11 && element.host);
+ element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
}
}
function jqLiteEmpty(element) {
- for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
- jqLiteDealoc(childNodes[i]);
- }
+ jqLiteDealoc(element, true);
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
+function jqLiteRemove(element, keepData) {
+ if (!keepData) jqLiteDealoc(element);
+ var parent = element.parentNode;
+ if (parent) parent.removeChild(element);
+}
+
+
+function jqLiteDocumentLoaded(action, win) {
+ win = win || window;
+ if (win.document.readyState === 'complete') {
+ // Force the action to be run async for consistent behaviour
+ // from the action's point of view
+ // i.e. it will definitely not be in a $apply
+ win.setTimeout(action);
+ } else {
+ // No need to unbind this handler as load is only ever called once
+ jqLite(win).on('load', action);
+ }
+}
+
//////////////////////////////////////////
// Functions which are declared directly.
//////////////////////////////////////////
@@ -2573,8 +2996,8 @@ var JQLitePrototype = JQLite.prototype = {
fn();
}
- // check if document already is loaded
- if (document.readyState === 'complete'){
+ // check if document is already loaded
+ if (document.readyState === 'complete') {
setTimeout(trigger);
} else {
this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
@@ -2586,7 +3009,7 @@ var JQLitePrototype = JQLite.prototype = {
},
toString: function() {
var value = [];
- forEach(this, function(e){ value.push('' + e);});
+ forEach(this, function(e) { value.push('' + e);});
return '[' + value.join(', ') + ']';
},
@@ -2611,20 +3034,33 @@ forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','),
});
var BOOLEAN_ELEMENTS = {};
forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
- BOOLEAN_ELEMENTS[uppercase(value)] = true;
+ BOOLEAN_ELEMENTS[value] = true;
});
+var ALIASED_ATTR = {
+ 'ngMinlength': 'minlength',
+ 'ngMaxlength': 'maxlength',
+ 'ngMin': 'min',
+ 'ngMax': 'max',
+ 'ngPattern': 'pattern'
+};
function getBooleanAttrName(element, name) {
// check dom last since we will most likely fail on name
var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
// booleanAttr is here twice to minimize DOM access
- return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr;
+ return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
+}
+
+function getAliasedAttrName(element, name) {
+ var nodeName = element.nodeName;
+ return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name];
}
forEach({
data: jqLiteData,
- removeData: jqLiteRemoveData
+ removeData: jqLiteRemoveData,
+ hasData: jqLiteHasData
}, function(fn, name) {
JQLite[name] = fn;
});
@@ -2649,7 +3085,7 @@ forEach({
return jqLiteInheritedData(element, '$injector');
},
- removeAttr: function(element,name) {
+ removeAttr: function(element, name) {
element.removeAttribute(name);
},
@@ -2661,26 +3097,15 @@ forEach({
if (isDefined(value)) {
element.style[name] = value;
} else {
- var val;
-
- if (msie <= 8) {
- // this is some IE specific weirdness that jQuery 1.6.4 does not sure why
- val = element.currentStyle && element.currentStyle[name];
- if (val === '') val = 'auto';
- }
-
- val = val || element.style[name];
-
- if (msie <= 8) {
- // jquery weirdness :-/
- val = (val === '') ? undefined : val;
- }
-
- return val;
+ return element.style[name];
}
},
- attr: function(element, name, value){
+ attr: function(element, name, value) {
+ var nodeType = element.nodeType;
+ if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
+ return;
+ }
var lowercasedName = lowercase(name);
if (BOOLEAN_ATTR[lowercasedName]) {
if (isDefined(value)) {
@@ -2693,7 +3118,7 @@ forEach({
}
} else {
return (element[name] ||
- (element.attributes.getNamedItem(name)|| noop).specified)
+ (element.attributes.getNamedItem(name) || noop).specified)
? lowercasedName
: undefined;
}
@@ -2717,31 +3142,23 @@ forEach({
},
text: (function() {
- var NODE_TYPE_TEXT_PROPERTY = [];
- if (msie < 9) {
- NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/
- NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/
- } else {
- NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/
- NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/
- }
getText.$dv = '';
return getText;
function getText(element, value) {
- var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType];
if (isUndefined(value)) {
- return textProp ? element[textProp] : '';
+ var nodeType = element.nodeType;
+ return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
}
- element[textProp] = value;
+ element.textContent = value;
}
})(),
val: function(element, value) {
if (isUndefined(value)) {
- if (nodeName_(element) === 'SELECT' && element.multiple) {
+ if (element.multiple && nodeName_(element) === 'select') {
var result = [];
- forEach(element.options, function (option) {
+ forEach(element.options, function(option) {
if (option.selected) {
result.push(option.value || option.text);
}
@@ -2757,14 +3174,12 @@ forEach({
if (isUndefined(value)) {
return element.innerHTML;
}
- for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
- jqLiteDealoc(childNodes[i]);
- }
+ jqLiteDealoc(element, true);
element.innerHTML = value;
},
empty: jqLiteEmpty
-}, function(fn, name){
+}, function(fn, name) {
/**
* Properties: writes return selection, reads return first value
*/
@@ -2816,57 +3231,50 @@ forEach({
});
function createEventHandler(element, events) {
- var eventHandler = function (event, type) {
- if (!event.preventDefault) {
- event.preventDefault = function() {
- event.returnValue = false; //ie
- };
- }
-
- if (!event.stopPropagation) {
- event.stopPropagation = function() {
- event.cancelBubble = true; //ie
- };
- }
-
- if (!event.target) {
- event.target = event.srcElement || document;
- }
-
- if (isUndefined(event.defaultPrevented)) {
- var prevent = event.preventDefault;
- event.preventDefault = function() {
- event.defaultPrevented = true;
- prevent.call(event);
- };
- event.defaultPrevented = false;
- }
-
+ var eventHandler = function(event, type) {
+ // jQuery specific api
event.isDefaultPrevented = function() {
- return event.defaultPrevented || event.returnValue === false;
+ return event.defaultPrevented;
+ };
+
+ var eventFns = events[type || event.type];
+ var eventFnsLength = eventFns ? eventFns.length : 0;
+
+ if (!eventFnsLength) return;
+
+ if (isUndefined(event.immediatePropagationStopped)) {
+ var originalStopImmediatePropagation = event.stopImmediatePropagation;
+ event.stopImmediatePropagation = function() {
+ event.immediatePropagationStopped = true;
+
+ if (event.stopPropagation) {
+ event.stopPropagation();
+ }
+
+ if (originalStopImmediatePropagation) {
+ originalStopImmediatePropagation.call(event);
+ }
+ };
+ }
+
+ event.isImmediatePropagationStopped = function() {
+ return event.immediatePropagationStopped === true;
};
// Copy event handlers in case event handlers array is modified during execution.
- var eventHandlersCopy = shallowCopy(events[type || event.type] || []);
+ if ((eventFnsLength > 1)) {
+ eventFns = shallowCopy(eventFns);
+ }
- forEach(eventHandlersCopy, function(fn) {
- fn.call(element, event);
- });
-
- // Remove monkey-patched methods (IE),
- // as they would cause memory leaks in IE8.
- if (msie <= 8) {
- // IE7/8 does not allow to delete property on native object
- event.preventDefault = null;
- event.stopPropagation = null;
- event.isDefaultPrevented = null;
- } else {
- // It shouldn't affect normal browsers (native methods are defined on prototype).
- delete event.preventDefault;
- delete event.stopPropagation;
- delete event.isDefaultPrevented;
+ for (var i = 0; i < eventFnsLength; i++) {
+ if (!event.isImmediatePropagationStopped()) {
+ eventFns[i].call(element, event);
+ }
}
};
+
+ // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
+ // events on `element`
eventHandler.elem = element;
return eventHandler;
}
@@ -2879,68 +3287,56 @@ function createEventHandler(element, events) {
forEach({
removeData: jqLiteRemoveData,
- dealoc: jqLiteDealoc,
-
- on: function onFn(element, type, fn, unsupported){
+ on: function jqLiteOn(element, type, fn, unsupported) {
if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
- var events = jqLiteExpandoStore(element, 'events'),
- handle = jqLiteExpandoStore(element, 'handle');
+ // Do not add event handlers to non-elements because they will not be cleaned up.
+ if (!jqLiteAcceptsData(element)) {
+ return;
+ }
- if (!events) jqLiteExpandoStore(element, 'events', events = {});
- if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events));
+ var expandoStore = jqLiteExpandoStore(element, true);
+ var events = expandoStore.events;
+ var handle = expandoStore.handle;
- forEach(type.split(' '), function(type){
+ if (!handle) {
+ handle = expandoStore.handle = createEventHandler(element, events);
+ }
+
+ // http://jsperf.com/string-indexof-vs-split
+ var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
+ var i = types.length;
+
+ while (i--) {
+ type = types[i];
var eventFns = events[type];
if (!eventFns) {
- if (type == 'mouseenter' || type == 'mouseleave') {
- var contains = document.body.contains || document.body.compareDocumentPosition ?
- function( a, b ) {
- // jshint bitwise: false
- var adown = a.nodeType === 9 ? a.documentElement : a,
- bup = b && b.parentNode;
- return a === bup || !!( bup && bup.nodeType === 1 && (
- adown.contains ?
- adown.contains( bup ) :
- a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
- ));
- } :
- function( a, b ) {
- if ( b ) {
- while ( (b = b.parentNode) ) {
- if ( b === a ) {
- return true;
- }
- }
- }
- return false;
- };
-
- events[type] = [];
+ events[type] = [];
+ if (type === 'mouseenter' || type === 'mouseleave') {
// Refer to jQuery's implementation of mouseenter & mouseleave
// Read about mouseenter and mouseleave:
// http://www.quirksmode.org/js/events_mouse.html#link8
- var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"};
- onFn(element, eventmap[type], function(event) {
+ jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) {
var target = this, related = event.relatedTarget;
// For mousenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
- if ( !related || (related !== target && !contains(target, related)) ){
+ if (!related || (related !== target && !target.contains(related))) {
handle(event, type);
}
});
} else {
- addEventListenerFn(element, type, handle);
- events[type] = [];
+ if (type !== '$destroy') {
+ addEventListenerFn(element, type, handle);
+ }
}
eventFns = events[type];
}
eventFns.push(fn);
- });
+ }
},
off: jqLiteOff,
@@ -2961,7 +3357,7 @@ forEach({
replaceWith: function(element, replaceNode) {
var index, parent = element.parentNode;
jqLiteDealoc(element);
- forEach(new JQLite(replaceNode), function(node){
+ forEach(new JQLite(replaceNode), function(node) {
if (index) {
parent.insertBefore(node, index.nextSibling);
} else {
@@ -2973,9 +3369,10 @@ forEach({
children: function(element) {
var children = [];
- forEach(element.childNodes, function(element){
- if (element.nodeType === 1)
+ forEach(element.childNodes, function(element) {
+ if (element.nodeType === NODE_TYPE_ELEMENT) {
children.push(element);
+ }
});
return children;
},
@@ -2985,24 +3382,28 @@ forEach({
},
append: function(element, node) {
- forEach(new JQLite(node), function(child){
- if (element.nodeType === 1 || element.nodeType === 11) {
- element.appendChild(child);
- }
- });
+ var nodeType = element.nodeType;
+ if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
+
+ node = new JQLite(node);
+
+ for (var i = 0, ii = node.length; i < ii; i++) {
+ var child = node[i];
+ element.appendChild(child);
+ }
},
prepend: function(element, node) {
- if (element.nodeType === 1) {
+ if (element.nodeType === NODE_TYPE_ELEMENT) {
var index = element.firstChild;
- forEach(new JQLite(node), function(child){
+ forEach(new JQLite(node), function(child) {
element.insertBefore(child, index);
});
}
},
wrap: function(element, wrapNode) {
- wrapNode = jqLite(wrapNode)[0];
+ wrapNode = jqLite(wrapNode).eq(0).clone()[0];
var parent = element.parentNode;
if (parent) {
parent.replaceChild(wrapNode, element);
@@ -3010,18 +3411,21 @@ forEach({
wrapNode.appendChild(element);
},
- remove: function(element) {
- jqLiteDealoc(element);
- var parent = element.parentNode;
- if (parent) parent.removeChild(element);
+ remove: jqLiteRemove,
+
+ detach: function(element) {
+ jqLiteRemove(element, true);
},
after: function(element, newElement) {
var index = element, parent = element.parentNode;
- forEach(new JQLite(newElement), function(node){
+ newElement = new JQLite(newElement);
+
+ for (var i = 0, ii = newElement.length; i < ii; i++) {
+ var node = newElement[i];
parent.insertBefore(node, index.nextSibling);
index = node;
- });
+ }
},
addClass: jqLiteAddClass,
@@ -3029,7 +3433,7 @@ forEach({
toggleClass: function(element, selector, condition) {
if (selector) {
- forEach(selector.split(' '), function(className){
+ forEach(selector.split(' '), function(className) {
var classCondition = condition;
if (isUndefined(classCondition)) {
classCondition = !jqLiteHasClass(element, className);
@@ -3041,20 +3445,11 @@ forEach({
parent: function(element) {
var parent = element.parentNode;
- return parent && parent.nodeType !== 11 ? parent : null;
+ return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
},
next: function(element) {
- if (element.nextElementSibling) {
- return element.nextElementSibling;
- }
-
- // IE8 doesn't have nextElementSibling
- var elm = element.nextSibling;
- while (elm != null && elm.nodeType !== 1) {
- elm = elm.nextSibling;
- }
- return elm;
+ return element.nextElementSibling;
},
find: function(element, selector) {
@@ -3071,14 +3466,17 @@ forEach({
var dummyEvent, eventFnsCopy, handlerArgs;
var eventName = event.type || event;
- var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName];
+ var expandoStore = jqLiteExpandoStore(element);
+ var events = expandoStore && expandoStore.events;
+ var eventFns = events && events[eventName];
if (eventFns) {
-
// Create a dummy event to pass to the handlers
dummyEvent = {
preventDefault: function() { this.defaultPrevented = true; },
isDefaultPrevented: function() { return this.defaultPrevented === true; },
+ stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
+ isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
stopPropagation: noop,
type: eventName,
target: element
@@ -3094,18 +3492,20 @@ forEach({
handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
forEach(eventFnsCopy, function(fn) {
- fn.apply(element, handlerArgs);
+ if (!dummyEvent.isImmediatePropagationStopped()) {
+ fn.apply(element, handlerArgs);
+ }
});
-
}
}
-}, function(fn, name){
+}, function(fn, name) {
/**
* chaining functions
*/
JQLite.prototype[name] = function(arg1, arg2, arg3) {
var value;
- for(var i=0; i < this.length; i++) {
+
+ for (var i = 0, ii = this.length; i < ii; i++) {
if (isUndefined(value)) {
value = fn(this[i], arg1, arg2, arg3);
if (isDefined(value)) {
@@ -3124,6 +3524,27 @@ forEach({
JQLite.prototype.unbind = JQLite.prototype.off;
});
+
+// Provider for private $$jqLite service
+function $$jqLiteProvider() {
+ this.$get = function $$jqLite() {
+ return extend(JQLite, {
+ hasClass: function(node, classes) {
+ if (node.attr) node = node[0];
+ return jqLiteHasClass(node, classes);
+ },
+ addClass: function(node, classes) {
+ if (node.attr) node = node[0];
+ return jqLiteAddClass(node, classes);
+ },
+ removeClass: function(node, classes) {
+ if (node.attr) node = node[0];
+ return jqLiteRemoveClass(node, classes);
+ }
+ });
+ };
+}
+
/**
* Computes a hash of an 'obj'.
* Hash of a:
@@ -3137,21 +3558,23 @@ forEach({
* The resulting string key is in 'type:hashKey' format.
*/
function hashKey(obj, nextUidFn) {
- var objType = typeof obj,
- key;
+ var key = obj && obj.$$hashKey;
- if (objType == 'function' || (objType == 'object' && obj !== null)) {
- if (typeof (key = obj.$$hashKey) == 'function') {
- // must invoke on object to keep the right this
+ if (key) {
+ if (typeof key === 'function') {
key = obj.$$hashKey();
- } else if (key === undefined) {
- key = obj.$$hashKey = (nextUidFn || nextUid)();
}
- } else {
- key = obj;
+ return key;
}
- return objType + ':' + key;
+ var objType = typeof obj;
+ if (objType == 'function' || (objType == 'object' && obj !== null)) {
+ key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
+ } else {
+ key = objType + ':' + obj;
+ }
+
+ return key;
}
/**
@@ -3195,6 +3618,12 @@ HashMap.prototype = {
}
};
+var $$HashMapProvider = [function() {
+ this.$get = [function() {
+ return HashMap;
+ }];
+}];
+
/**
* @ngdoc function
* @module ng
@@ -3202,13 +3631,14 @@ HashMap.prototype = {
* @kind function
*
* @description
- * Creates an injector function that can be used for retrieving services as well as for
+ * Creates an injector object that can be used for retrieving services as well as for
* dependency injection (see {@link guide/di dependency injection}).
*
-
* @param {Array.} modules A list of module functions or their aliases. See
- * {@link angular.module}. The `ng` module must be explicitly added.
- * @returns {function()} Injector function. See {@link auto.$injector $injector}.
+ * {@link angular.module}. The `ng` module must be explicitly added.
+ * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
+ * disallows argument name annotation inference.
+ * @returns {injector} Injector object. See {@link auto.$injector $injector}.
*
* @example
* Typical usage
@@ -3218,7 +3648,7 @@ HashMap.prototype = {
*
* // use the injector to kick off your application
* // use the type inference to auto inject arguments, or use implicit injection
- * $injector.invoke(function($rootScope, $compile, $document){
+ * $injector.invoke(function($rootScope, $compile, $document) {
* $compile($document)($rootScope);
* $rootScope.$digest();
* });
@@ -3261,7 +3691,19 @@ var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var $injectorMinErr = minErr('$injector');
-function annotate(fn) {
+
+function anonFn(fn) {
+ // For anonymous functions, showing at the very least the function signature can help in
+ // debugging.
+ var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
+ args = fnText.match(FN_ARGS);
+ if (args) {
+ return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
+ }
+ return 'fn';
+}
+
+function annotate(fn, strictDi, name) {
var $inject,
fnText,
argDecl,
@@ -3271,10 +3713,17 @@ function annotate(fn) {
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
+ if (strictDi) {
+ if (!isString(name) || !name) {
+ name = fn.name || anonFn(fn);
+ }
+ throw $injectorMinErr('strictdi',
+ '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
+ }
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
- forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
- arg.replace(FN_ARG, function(all, underscore, name){
+ forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
+ arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
@@ -3296,7 +3745,6 @@ function annotate(fn) {
/**
* @ngdoc service
* @name $injector
- * @kind function
*
* @description
*
@@ -3309,9 +3757,9 @@ function annotate(fn) {
* ```js
* var $injector = angular.injector();
* expect($injector.get('$injector')).toBe($injector);
- * expect($injector.invoke(function($injector){
+ * expect($injector.invoke(function($injector) {
* return $injector;
- * }).toBe($injector);
+ * })).toBe($injector);
* ```
*
* # Injection Function Annotation
@@ -3335,8 +3783,10 @@ function annotate(fn) {
* ## Inference
*
* In JavaScript calling `toString()` on a function returns the function definition. The definition
- * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with
- * minification, and obfuscation tools since these tools change the argument names.
+ * can then be parsed and the function arguments can be extracted. This method of discovering
+ * annotations is disallowed when the injector is in strict mode.
+ * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
+ * argument names.
*
* ## `$inject` Annotation
* By adding an `$inject` property onto a function the injection parameters can be specified.
@@ -3353,6 +3803,7 @@ function annotate(fn) {
* Return an instance of the service.
*
* @param {string} name The name of the instance to retrieve.
+ * @param {string=} caller An optional string to provide the origin of the function call for error messages.
* @return {*} The instance.
*/
@@ -3363,8 +3814,8 @@ function annotate(fn) {
* @description
* Invoke the method and supply the method arguments from the `$injector`.
*
- * @param {!Function} fn The function to invoke. Function parameters are injected according to the
- * {@link guide/di $inject Annotation} rules.
+ * @param {Function|Array.} fn The injectable function to invoke. Function parameters are
+ * injected according to the {@link guide/di $inject Annotation} rules.
* @param {Object=} self The `this` for the invoked method.
* @param {Object=} locals Optional object. If preset then any argument names are read from this
* object first, before the `$injector` is consulted.
@@ -3378,8 +3829,8 @@ function annotate(fn) {
* @description
* Allows the user to query if the particular service exists.
*
- * @param {string} Name of the service to query.
- * @returns {boolean} returns true if injector has given service.
+ * @param {string} name Name of the service to query.
+ * @returns {boolean} `true` if injector has given service.
*/
/**
@@ -3421,6 +3872,8 @@ function annotate(fn) {
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
* ```
*
+ * You can disallow this method by using strict injection mode.
+ *
* This method does not work with code minification / obfuscation. For this reason the following
* annotation strategies are supported.
*
@@ -3473,6 +3926,8 @@ function annotate(fn) {
* @param {Function|Array.} fn Function for which dependent service names need to
* be retrieved as described above.
*
+ * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
+ *
* @returns {Array.} The names of the services which the function requires.
*/
@@ -3627,8 +4082,8 @@ function annotate(fn) {
* configure your service in a provider.
*
* @param {string} name The name of the instance.
- * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand
- * for `$provide.provider(name, {$get: $getFn})`.
+ * @param {Function|Array.} $getFn The injectable $getFn for the instance creation.
+ * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
* @returns {Object} registered provider instance
*
* @example
@@ -3663,7 +4118,8 @@ function annotate(fn) {
* as a type/class.
*
* @param {string} name The name of the instance.
- * @param {Function} constructor A class (constructor function) that will be instantiated.
+ * @param {Function|Array.} constructor An injectable class (constructor function)
+ * that will be instantiated.
* @returns {Object} registered provider instance
*
* @example
@@ -3762,7 +4218,7 @@ function annotate(fn) {
* object which replaces or wraps and delegates to the original service.
*
* @param {string} name The name of the service to decorate.
- * @param {function()} decorator This function will be invoked when the service needs to be
+ * @param {Function|Array.} decorator This function will be invoked when the service needs to be
* instantiated and should return the decorated service instance. The function is called using
* the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
* Local injection arguments:
@@ -3782,7 +4238,8 @@ function annotate(fn) {
*/
-function createInjector(modulesToLoad) {
+function createInjector(modulesToLoad, strictDi) {
+ strictDi = (strictDi === true);
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
@@ -3798,18 +4255,21 @@ function createInjector(modulesToLoad) {
}
},
providerInjector = (providerCache.$injector =
- createInternalInjector(providerCache, function() {
+ createInternalInjector(providerCache, function(serviceName, caller) {
+ if (angular.isString(caller)) {
+ path.push(caller);
+ }
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
- createInternalInjector(instanceCache, function(servicename) {
- var provider = providerInjector.get(servicename + providerSuffix);
- return instanceInjector.invoke(provider.$get, provider);
+ createInternalInjector(instanceCache, function(serviceName, caller) {
+ var provider = providerInjector.get(serviceName + providerSuffix, caller);
+ return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
}));
- forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
+ forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
return instanceInjector;
@@ -3838,7 +4298,21 @@ function createInjector(modulesToLoad) {
return providerCache[name + providerSuffix] = provider_;
}
- function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
+ function enforceReturnValue(name, factory) {
+ return function enforcedReturnValue() {
+ var result = instanceInjector.invoke(factory, this);
+ if (isUndefined(result)) {
+ throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
+ }
+ return result;
+ };
+ }
+
+ function factory(name, factoryFn, enforce) {
+ return provider(name, {
+ $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
+ });
+ }
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
@@ -3846,7 +4320,7 @@ function createInjector(modulesToLoad) {
}]);
}
- function value(name, val) { return factory(name, valueFn(val)); }
+ function value(name, val) { return factory(name, valueFn(val), false); }
function constant(name, value) {
assertNotHasOwnProperty(name, 'constant');
@@ -3867,23 +4341,28 @@ function createInjector(modulesToLoad) {
////////////////////////////////////
// Module Loading
////////////////////////////////////
- function loadModules(modulesToLoad){
- var runBlocks = [], moduleFn, invokeQueue, i, ii;
+ function loadModules(modulesToLoad) {
+ var runBlocks = [], moduleFn;
forEach(modulesToLoad, function(module) {
if (loadedModules.get(module)) return;
loadedModules.put(module, true);
+ function runInvokeQueue(queue) {
+ var i, ii;
+ for (i = 0, ii = queue.length; i < ii; i++) {
+ var invokeArgs = queue[i],
+ provider = providerInjector.get(invokeArgs[0]);
+
+ provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
+ }
+ }
+
try {
if (isString(module)) {
moduleFn = angularModule(module);
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
-
- for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
- var invokeArgs = invokeQueue[i],
- provider = providerInjector.get(invokeArgs[0]);
-
- provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
- }
+ runInvokeQueue(moduleFn._invokeQueue);
+ runInvokeQueue(moduleFn._configBlocks);
} else if (isFunction(module)) {
runBlocks.push(providerInjector.invoke(module));
} else if (isArray(module)) {
@@ -3916,7 +4395,7 @@ function createInjector(modulesToLoad) {
function createInternalInjector(cache, factory) {
- function getService(serviceName) {
+ function getService(serviceName, caller) {
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
@@ -3927,7 +4406,7 @@ function createInjector(modulesToLoad) {
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
- return cache[serviceName] = factory(serviceName);
+ return cache[serviceName] = factory(serviceName, caller);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
@@ -3939,13 +4418,18 @@ function createInjector(modulesToLoad) {
}
}
- function invoke(fn, self, locals){
+ function invoke(fn, self, locals, serviceName) {
+ if (typeof locals === 'string') {
+ serviceName = locals;
+ locals = null;
+ }
+
var args = [],
- $inject = annotate(fn),
+ $inject = createInjector.$$annotate(fn, strictDi, serviceName),
length, i,
key;
- for(i = 0, length = $inject.length; i < length; i++) {
+ for (i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
if (typeof key !== 'string') {
throw $injectorMinErr('itkn',
@@ -3954,7 +4438,7 @@ function createInjector(modulesToLoad) {
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
- : getService(key)
+ : getService(key, serviceName)
);
}
if (isArray(fn)) {
@@ -3966,15 +4450,12 @@ function createInjector(modulesToLoad) {
return fn.apply(self, args);
}
- function instantiate(Type, locals) {
- var Constructor = function() {},
- instance, returnedValue;
-
+ function instantiate(Type, locals, serviceName) {
// Check if Type is annotated and use just the given function at n-1 as parameter
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
- Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
- instance = new Constructor();
- returnedValue = invoke(Type, instance, locals);
+ // Object creation: http://jsperf.com/create-constructor/2
+ var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
+ var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
@@ -3983,7 +4464,7 @@ function createInjector(modulesToLoad) {
invoke: invoke,
instantiate: instantiate,
get: getService,
- annotate: annotate,
+ annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
@@ -3991,100 +4472,272 @@ function createInjector(modulesToLoad) {
}
}
+createInjector.$$annotate = annotate;
+
/**
- * @ngdoc service
- * @name $anchorScroll
- * @kind function
- * @requires $window
- * @requires $location
- * @requires $rootScope
+ * @ngdoc provider
+ * @name $anchorScrollProvider
*
* @description
- * When called, it checks current value of `$location.hash()` and scrolls to the related element,
- * according to rules specified in
- * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
- *
- * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor.
- * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
- *
- * @example
-
-
-
-
-
- function ScrollCtrl($scope, $location, $anchorScroll) {
- $scope.gotoBottom = function (){
- // set the location.hash to the id of
- // the element you wish to scroll to.
- $location.hash('bottom');
-
- // call $anchorScroll()
- $anchorScroll();
- };
- }
-
-
- #scrollArea {
- height: 350px;
- overflow: auto;
- }
-
- #bottom {
- display: block;
- margin-top: 2000px;
- }
-
-
+ * Use `$anchorScrollProvider` to disable automatic scrolling whenever
+ * {@link ng.$location#hash $location.hash()} changes.
*/
function $AnchorScrollProvider() {
var autoScrollingEnabled = true;
+ /**
+ * @ngdoc method
+ * @name $anchorScrollProvider#disableAutoScrolling
+ *
+ * @description
+ * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
+ * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.
+ * Use this method to disable automatic scrolling.
+ *
+ * If automatic scrolling is disabled, one must explicitly call
+ * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
+ * current hash.
+ */
this.disableAutoScrolling = function() {
autoScrollingEnabled = false;
};
+ /**
+ * @ngdoc service
+ * @name $anchorScroll
+ * @kind function
+ * @requires $window
+ * @requires $location
+ * @requires $rootScope
+ *
+ * @description
+ * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
+ * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
+ * in the
+ * [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
+ *
+ * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
+ * match any anchor whenever it changes. This can be disabled by calling
+ * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
+ *
+ * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
+ * vertical scroll-offset (either fixed or dynamic).
+ *
+ * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
+ * {@link ng.$location#hash $location.hash()} will be used.
+ *
+ * @property {(number|function|jqLite)} yOffset
+ * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
+ * positioned elements at the top of the page, such as navbars, headers etc.
+ *
+ * `yOffset` can be specified in various ways:
+ * - **number**: A fixed number of pixels to be used as offset.
+ * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
+ * a number representing the offset (in pixels).
+ * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
+ * the top of the page to the element's bottom will be used as offset.
+ * **Note**: The element will be taken into account only as long as its `position` is set to
+ * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
+ * their height and/or positioning according to the viewport's size.
+ *
+ *
+ *
+ * In order for `yOffset` to work properly, scrolling should take place on the document's root and
+ * not some child element.
+ *
+
+
+ angular.module('anchorScrollOffsetExample', [])
+ .run(['$anchorScroll', function($anchorScroll) {
+ $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
+ }])
+ .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
+ function ($anchorScroll, $location, $scope) {
+ $scope.gotoAnchor = function(x) {
+ var newHash = 'anchor' + x;
+ if ($location.hash() !== newHash) {
+ // set the $location.hash to `newHash` and
+ // $anchorScroll will automatically scroll to it
+ $location.hash('anchor' + x);
+ } else {
+ // call $anchorScroll() explicitly,
+ // since $location.hash hasn't changed
+ $anchorScroll();
+ }
+ };
+ }
+ ]);
+
+
+ body {
+ padding-top: 50px;
+ }
+
+ .anchor {
+ border: 2px dashed DarkOrchid;
+ padding: 10px 10px 200px 10px;
+ }
+
+ .fixed-header {
+ background-color: rgba(0, 0, 0, 0.2);
+ height: 50px;
+ position: fixed;
+ top: 0; left: 0; right: 0;
+ }
+
+ .fixed-header > a {
+ display: inline-block;
+ margin: 5px 15px;
+ }
+
+
+ */
this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
var document = $window.document;
- // helper function to get first anchor from a NodeList
- // can't use filter.filter, as it accepts only instances of Array
- // and IE can't convert NodeList to an array using [].slice
- // TODO(vojta): use filter if we change it to accept lists as well
+ // Helper function to get first anchor from a NodeList
+ // (using `Array#some()` instead of `angular#forEach()` since it's more performant
+ // and working in all supported browsers.)
function getFirstAnchor(list) {
var result = null;
- forEach(list, function(element) {
- if (!result && lowercase(element.nodeName) === 'a') result = element;
+ Array.prototype.some.call(list, function(element) {
+ if (nodeName_(element) === 'a') {
+ result = element;
+ return true;
+ }
});
return result;
}
- function scroll() {
- var hash = $location.hash(), elm;
+ function getYOffset() {
+
+ var offset = scroll.yOffset;
+
+ if (isFunction(offset)) {
+ offset = offset();
+ } else if (isElement(offset)) {
+ var elem = offset[0];
+ var style = $window.getComputedStyle(elem);
+ if (style.position !== 'fixed') {
+ offset = 0;
+ } else {
+ offset = elem.getBoundingClientRect().bottom;
+ }
+ } else if (!isNumber(offset)) {
+ offset = 0;
+ }
+
+ return offset;
+ }
+
+ function scrollTo(elem) {
+ if (elem) {
+ elem.scrollIntoView();
+
+ var offset = getYOffset();
+
+ if (offset) {
+ // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
+ // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
+ // top of the viewport.
+ //
+ // IF the number of pixels from the top of `elem` to the end of the page's content is less
+ // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
+ // way down the page.
+ //
+ // This is often the case for elements near the bottom of the page.
+ //
+ // In such cases we do not need to scroll the whole `offset` up, just the difference between
+ // the top of the element and the offset, which is enough to align the top of `elem` at the
+ // desired position.
+ var elemTop = elem.getBoundingClientRect().top;
+ $window.scrollBy(0, elemTop - offset);
+ }
+ } else {
+ $window.scrollTo(0, 0);
+ }
+ }
+
+ function scroll(hash) {
+ hash = isString(hash) ? hash : $location.hash();
+ var elm;
// empty hash, scroll to the top of the page
- if (!hash) $window.scrollTo(0, 0);
+ if (!hash) scrollTo(null);
// element with given id
- else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
+ else if ((elm = document.getElementById(hash))) scrollTo(elm);
// first anchor with given name :-D
- else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
+ else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
// no element and hash == 'top', scroll to the top of the page
- else if (hash === 'top') $window.scrollTo(0, 0);
+ else if (hash === 'top') scrollTo(null);
}
// does not scroll when user clicks on anchor link that is currently on
// (no url change, no $location.hash() change), browser native does scroll
if (autoScrollingEnabled) {
$rootScope.$watch(function autoScrollWatch() {return $location.hash();},
- function autoScrollWatchAction() {
- $rootScope.$evalAsync(scroll);
+ function autoScrollWatchAction(newVal, oldVal) {
+ // skip the initial scroll if $location.hash is empty
+ if (newVal === oldVal && newVal === '') return;
+
+ jqLiteDocumentLoaded(function() {
+ $rootScope.$evalAsync(scroll);
+ });
});
}
@@ -4093,6 +4746,168 @@ function $AnchorScrollProvider() {
}
var $animateMinErr = minErr('$animate');
+var ELEMENT_NODE = 1;
+var NG_ANIMATE_CLASSNAME = 'ng-animate';
+
+function mergeClasses(a,b) {
+ if (!a && !b) return '';
+ if (!a) return b;
+ if (!b) return a;
+ if (isArray(a)) a = a.join(' ');
+ if (isArray(b)) b = b.join(' ');
+ return a + ' ' + b;
+}
+
+function extractElementNode(element) {
+ for (var i = 0; i < element.length; i++) {
+ var elm = element[i];
+ if (elm.nodeType === ELEMENT_NODE) {
+ return elm;
+ }
+ }
+}
+
+function splitClasses(classes) {
+ if (isString(classes)) {
+ classes = classes.split(' ');
+ }
+
+ // Use createMap() to prevent class assumptions involving property names in
+ // Object.prototype
+ var obj = createMap();
+ forEach(classes, function(klass) {
+ // sometimes the split leaves empty string values
+ // incase extra spaces were applied to the options
+ if (klass.length) {
+ obj[klass] = true;
+ }
+ });
+ return obj;
+}
+
+// if any other type of options value besides an Object value is
+// passed into the $animate.method() animation then this helper code
+// will be run which will ignore it. While this patch is not the
+// greatest solution to this, a lot of existing plugins depend on
+// $animate to either call the callback (< 1.2) or return a promise
+// that can be changed. This helper function ensures that the options
+// are wiped clean incase a callback function is provided.
+function prepareAnimateOptions(options) {
+ return isObject(options)
+ ? options
+ : {};
+}
+
+var $$CoreAnimateRunnerProvider = function() {
+ this.$get = ['$q', '$$rAF', function($q, $$rAF) {
+ function AnimateRunner() {}
+ AnimateRunner.all = noop;
+ AnimateRunner.chain = noop;
+ AnimateRunner.prototype = {
+ end: noop,
+ cancel: noop,
+ resume: noop,
+ pause: noop,
+ complete: noop,
+ then: function(pass, fail) {
+ return $q(function(resolve) {
+ $$rAF(function() {
+ resolve();
+ });
+ }).then(pass, fail);
+ }
+ };
+ return AnimateRunner;
+ }];
+};
+
+// this is prefixed with Core since it conflicts with
+// the animateQueueProvider defined in ngAnimate/animateQueue.js
+var $$CoreAnimateQueueProvider = function() {
+ var postDigestQueue = new HashMap();
+ var postDigestElements = [];
+
+ this.$get = ['$$AnimateRunner', '$rootScope',
+ function($$AnimateRunner, $rootScope) {
+ return {
+ enabled: noop,
+ on: noop,
+ off: noop,
+ pin: noop,
+
+ push: function(element, event, options, domOperation) {
+ domOperation && domOperation();
+
+ options = options || {};
+ options.from && element.css(options.from);
+ options.to && element.css(options.to);
+
+ if (options.addClass || options.removeClass) {
+ addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
+ }
+
+ return new $$AnimateRunner(); // jshint ignore:line
+ }
+ };
+
+ function addRemoveClassesPostDigest(element, add, remove) {
+ var data = postDigestQueue.get(element);
+ var classVal;
+
+ if (!data) {
+ postDigestQueue.put(element, data = {});
+ postDigestElements.push(element);
+ }
+
+ if (add) {
+ forEach(add.split(' '), function(className) {
+ if (className) {
+ data[className] = true;
+ }
+ });
+ }
+
+ if (remove) {
+ forEach(remove.split(' '), function(className) {
+ if (className) {
+ data[className] = false;
+ }
+ });
+ }
+
+ if (postDigestElements.length > 1) return;
+
+ $rootScope.$$postDigest(function() {
+ forEach(postDigestElements, function(element) {
+ var data = postDigestQueue.get(element);
+ if (data) {
+ var existing = splitClasses(element.attr('class'));
+ var toAdd = '';
+ var toRemove = '';
+ forEach(data, function(status, className) {
+ var hasClass = !!existing[className];
+ if (status !== hasClass) {
+ if (status) {
+ toAdd += (toAdd.length ? ' ' : '') + className;
+ } else {
+ toRemove += (toRemove.length ? ' ' : '') + className;
+ }
+ }
+ });
+
+ forEach(element, function(elm) {
+ toAdd && jqLiteAddClass(elm, toAdd);
+ toRemove && jqLiteRemoveClass(elm, toRemove);
+ });
+ postDigestQueue.remove(element);
+ }
+ });
+
+ postDigestElements.length = 0;
+ });
+ }
+ }];
+};
/**
* @ngdoc provider
@@ -4100,20 +4915,18 @@ var $animateMinErr = minErr('$animate');
*
* @description
* Default implementation of $animate that doesn't perform any animations, instead just
- * synchronously performs DOM
- * updates and calls done() callbacks.
+ * synchronously performs DOM updates and resolves the returned runner promise.
*
- * In order to enable animations the ngAnimate module has to be loaded.
+ * In order to enable animations the `ngAnimate` module has to be loaded.
*
- * To see the functional implementation check out src/ngAnimate/animate.js
+ * To see the functional implementation check out `src/ngAnimate/animate.js`.
*/
var $AnimateProvider = ['$provide', function($provide) {
+ var provider = this;
+ this.$$registeredAnimations = Object.create(null);
- this.$$selectors = {};
-
-
- /**
+ /**
* @ngdoc method
* @name $animateProvider#register
*
@@ -4122,33 +4935,43 @@ var $AnimateProvider = ['$provide', function($provide) {
* animation object which contains callback functions for each event that is expected to be
* animated.
*
- * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction`
- * must be called once the element animation is complete. If a function is returned then the
- * animation service will use this function to cancel the animation whenever a cancel event is
- * triggered.
+ * * `eventFn`: `function(element, ... , doneFunction, options)`
+ * The element to animate, the `doneFunction` and the options fed into the animation. Depending
+ * on the type of animation additional arguments will be injected into the animation function. The
+ * list below explains the function signatures for the different animation methods:
*
+ * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
+ * - addClass: function(element, addedClasses, doneFunction, options)
+ * - removeClass: function(element, removedClasses, doneFunction, options)
+ * - enter, leave, move: function(element, doneFunction, options)
+ * - animate: function(element, fromStyles, toStyles, doneFunction, options)
+ *
+ * Make sure to trigger the `doneFunction` once the animation is fully complete.
*
* ```js
* return {
- * eventFn : function(element, done) {
- * //code to run the animation
- * //once complete, then run done()
- * return function cancellationFunction() {
- * //code to cancel the animation
- * }
- * }
- * }
+ * //enter, leave, move signature
+ * eventFn : function(element, done, options) {
+ * //code to run the animation
+ * //once complete, then run done()
+ * return function endFunction(wasCancelled) {
+ * //code to cancel the animation
+ * }
+ * }
+ * }
* ```
*
- * @param {string} name The name of the animation.
+ * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
* @param {Function} factory The factory function that will be executed to return the animation
* object.
*/
this.register = function(name, factory) {
+ if (name && name.charAt(0) !== '.') {
+ throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name);
+ }
+
var key = name + '-animation';
- if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel',
- "Expecting class selector starting with '.' got '{0}'.", name);
- this.$$selectors[name.substr(1)] = key;
+ provider.$$registeredAnimations[name.substr(1)] = key;
$provide.factory(key, factory);
};
@@ -4159,86 +4982,203 @@ var $AnimateProvider = ['$provide', function($provide) {
* @description
* Sets and/or returns the CSS class regular expression that is checked when performing
* an animation. Upon bootstrap the classNameFilter value is not set at all and will
- * therefore enable $animate to attempt to perform an animation on any element.
- * When setting the classNameFilter value, animations will only be performed on elements
+ * therefore enable $animate to attempt to perform an animation on any element that is triggered.
+ * When setting the `classNameFilter` value, animations will only be performed on elements
* that successfully match the filter expression. This in turn can boost performance
* for low-powered devices as well as applications containing a lot of structural operations.
* @param {RegExp=} expression The className expression which will be checked against all animations
* @return {RegExp} The current CSS className expression value. If null then there is no expression value
*/
this.classNameFilter = function(expression) {
- if(arguments.length === 1) {
+ if (arguments.length === 1) {
this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
+ if (this.$$classNameFilter) {
+ var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)");
+ if (reservedRegex.test(this.$$classNameFilter.toString())) {
+ throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
+
+ }
+ }
}
return this.$$classNameFilter;
};
- this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) {
-
- function async(fn) {
- fn && $$asyncCallback(fn);
+ this.$get = ['$$animateQueue', function($$animateQueue) {
+ function domInsert(element, parentElement, afterElement) {
+ // if for some reason the previous element was removed
+ // from the dom sometime before this code runs then let's
+ // just stick to using the parent element as the anchor
+ if (afterElement) {
+ var afterNode = extractElementNode(afterElement);
+ if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
+ afterElement = null;
+ }
+ }
+ afterElement ? afterElement.after(element) : parentElement.prepend(element);
}
/**
- *
* @ngdoc service
* @name $animate
- * @description The $animate service provides rudimentary DOM manipulation functions to
- * insert, remove and move elements within the DOM, as well as adding and removing classes.
- * This service is the core service used by the ngAnimate $animator service which provides
- * high-level animation hooks for CSS and JavaScript.
+ * @description The $animate service exposes a series of DOM utility methods that provide support
+ * for animation hooks. The default behavior is the application of DOM operations, however,
+ * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
+ * to ensure that animation runs with the triggered DOM operation.
*
- * $animate is available in the AngularJS core, however, the ngAnimate module must be included
- * to enable full out animation support. Otherwise, $animate will only perform simple DOM
- * manipulation operations.
+ * By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't
+ * included and only when it is active then the animation hooks that `$animate` triggers will be
+ * functional. Once active then all structural `ng-` directives will trigger animations as they perform
+ * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
+ * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
*
- * To learn more about enabling animation support, click here to visit the {@link ngAnimate
- * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service
- * page}.
+ * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
+ *
+ * To learn more about enabling animation support, click here to visit the
+ * {@link ngAnimate ngAnimate module page}.
*/
return {
+ // we don't call it directly since non-existant arguments may
+ // be interpreted as null within the sub enabled function
+
+ /**
+ *
+ * @ngdoc method
+ * @name $animate#on
+ * @kind function
+ * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
+ * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
+ * is fired with the following params:
+ *
+ * ```js
+ * $animate.on('enter', container,
+ * function callback(element, phase) {
+ * // cool we detected an enter animation within the container
+ * }
+ * );
+ * ```
+ *
+ * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
+ * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
+ * as well as among its children
+ * @param {Function} callback the callback function that will be fired when the listener is triggered
+ *
+ * The arguments present in the callback function are:
+ * * `element` - The captured DOM element that the animation was fired on.
+ * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
+ */
+ on: $$animateQueue.on,
+
+ /**
+ *
+ * @ngdoc method
+ * @name $animate#off
+ * @kind function
+ * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
+ * can be used in three different ways depending on the arguments:
+ *
+ * ```js
+ * // remove all the animation event listeners listening for `enter`
+ * $animate.off('enter');
+ *
+ * // remove all the animation event listeners listening for `enter` on the given element and its children
+ * $animate.off('enter', container);
+ *
+ * // remove the event listener function provided by `listenerFn` that is set
+ * // to listen for `enter` on the given `element` as well as its children
+ * $animate.off('enter', container, callback);
+ * ```
+ *
+ * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
+ * @param {DOMElement=} container the container element the event listener was placed on
+ * @param {Function=} callback the callback function that was registered as the listener
+ */
+ off: $$animateQueue.off,
+
+ /**
+ * @ngdoc method
+ * @name $animate#pin
+ * @kind function
+ * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
+ * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
+ * element despite being outside the realm of the application or within another application. Say for example if the application
+ * was bootstrapped on an element that is somewhere inside of the `` tag, but we wanted to allow for an element to be situated
+ * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
+ * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
+ *
+ * Note that this feature is only active when the `ngAnimate` module is used.
+ *
+ * @param {DOMElement} element the external element that will be pinned
+ * @param {DOMElement} parentElement the host parent element that will be associated with the external element
+ */
+ pin: $$animateQueue.pin,
+
+ /**
+ *
+ * @ngdoc method
+ * @name $animate#enabled
+ * @kind function
+ * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
+ * function can be called in four ways:
+ *
+ * ```js
+ * // returns true or false
+ * $animate.enabled();
+ *
+ * // changes the enabled state for all animations
+ * $animate.enabled(false);
+ * $animate.enabled(true);
+ *
+ * // returns true or false if animations are enabled for an element
+ * $animate.enabled(element);
+ *
+ * // changes the enabled state for an element and its children
+ * $animate.enabled(element, true);
+ * $animate.enabled(element, false);
+ * ```
+ *
+ * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
+ * @param {boolean=} enabled whether or not the animations will be enabled for the element
+ *
+ * @return {boolean} whether or not animations are enabled
+ */
+ enabled: $$animateQueue.enabled,
+
+ /**
+ * @ngdoc method
+ * @name $animate#cancel
+ * @kind function
+ * @description Cancels the provided animation.
+ *
+ * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
+ */
+ cancel: function(runner) {
+ runner.end && runner.end();
+ },
/**
*
* @ngdoc method
* @name $animate#enter
* @kind function
- * @description Inserts the element into the DOM either after the `after` element or within
- * the `parent` element. Once complete, the done() callback will be fired (if provided).
+ * @description Inserts the element into the DOM either after the `after` element (if provided) or
+ * as the first child within the `parent` element and then triggers an animation.
+ * A promise is returned that will be resolved during the next digest once the animation
+ * has completed.
+ *
* @param {DOMElement} element the element which will be inserted into the DOM
* @param {DOMElement} parent the parent element which will append the element as
- * a child (if the after element is not present)
- * @param {DOMElement} after the sibling element which will append the element
- * after itself
- * @param {Function=} done callback function that will be called after the element has been
- * inserted into the DOM
- */
- enter : function(element, parent, after, done) {
- if (after) {
- after.after(element);
- } else {
- if (!parent || !parent[0]) {
- parent = after.parent();
- }
- parent.append(element);
- }
- async(done);
- },
-
- /**
+ * a child (so long as the after element is not present)
+ * @param {DOMElement=} after the sibling element after which the element will be appended
+ * @param {object=} options an optional collection of options/styles that will be applied to the element
*
- * @ngdoc method
- * @name $animate#leave
- * @kind function
- * @description Removes the element from the DOM. Once complete, the done() callback will be
- * fired (if provided).
- * @param {DOMElement} element the element which will be removed from the DOM
- * @param {Function=} done callback function that will be called after the element has been
- * removed from the DOM
+ * @return {Promise} the animation callback promise
*/
- leave : function(element, done) {
- element.remove();
- async(done);
+ enter: function(element, parent, after, options) {
+ parent = parent && jqLite(parent);
+ after = after && jqLite(after);
+ parent = parent || after.parent();
+ domInsert(element, parent, after);
+ return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
},
/**
@@ -4246,99 +5186,155 @@ var $AnimateProvider = ['$provide', function($provide) {
* @ngdoc method
* @name $animate#move
* @kind function
- * @description Moves the position of the provided element within the DOM to be placed
- * either after the `after` element or inside of the `parent` element. Once complete, the
- * done() callback will be fired (if provided).
+ * @description Inserts (moves) the element into its new position in the DOM either after
+ * the `after` element (if provided) or as the first child within the `parent` element
+ * and then triggers an animation. A promise is returned that will be resolved
+ * during the next digest once the animation has completed.
*
- * @param {DOMElement} element the element which will be moved around within the
- * DOM
- * @param {DOMElement} parent the parent element where the element will be
- * inserted into (if the after element is not present)
- * @param {DOMElement} after the sibling element where the element will be
- * positioned next to
- * @param {Function=} done the callback function (if provided) that will be fired after the
- * element has been moved to its new position
+ * @param {DOMElement} element the element which will be moved into the new DOM position
+ * @param {DOMElement} parent the parent element which will append the element as
+ * a child (so long as the after element is not present)
+ * @param {DOMElement=} after the sibling element after which the element will be appended
+ * @param {object=} options an optional collection of options/styles that will be applied to the element
+ *
+ * @return {Promise} the animation callback promise
*/
- move : function(element, parent, after, done) {
- // Do not remove element before insert. Removing will cause data associated with the
- // element to be dropped. Insert will implicitly do the remove.
- this.enter(element, parent, after, done);
+ move: function(element, parent, after, options) {
+ parent = parent && jqLite(parent);
+ after = after && jqLite(after);
+ parent = parent || after.parent();
+ domInsert(element, parent, after);
+ return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
},
/**
+ * @ngdoc method
+ * @name $animate#leave
+ * @kind function
+ * @description Triggers an animation and then removes the element from the DOM.
+ * When the function is called a promise is returned that will be resolved during the next
+ * digest once the animation has completed.
*
+ * @param {DOMElement} element the element which will be removed from the DOM
+ * @param {object=} options an optional collection of options/styles that will be applied to the element
+ *
+ * @return {Promise} the animation callback promise
+ */
+ leave: function(element, options) {
+ return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
+ element.remove();
+ });
+ },
+
+ /**
* @ngdoc method
* @name $animate#addClass
* @kind function
- * @description Adds the provided className CSS class value to the provided element. Once
- * complete, the done() callback will be fired (if provided).
- * @param {DOMElement} element the element which will have the className value
- * added to it
- * @param {string} className the CSS class which will be added to the element
- * @param {Function=} done the callback function (if provided) that will be fired after the
- * className value has been added to the element
+ *
+ * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
+ * execution, the addClass operation will only be handled after the next digest and it will not trigger an
+ * animation if element already contains the CSS class or if the class is removed at a later step.
+ * Note that class-based animations are treated differently compared to structural animations
+ * (like enter, move and leave) since the CSS classes may be added/removed at different points
+ * depending if CSS or JavaScript animations are used.
+ *
+ * @param {DOMElement} element the element which the CSS classes will be applied to
+ * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
+ * @param {object=} options an optional collection of options/styles that will be applied to the element
+ *
+ * @return {Promise} the animation callback promise
*/
- addClass : function(element, className, done) {
- className = isString(className) ?
- className :
- isArray(className) ? className.join(' ') : '';
- forEach(element, function (element) {
- jqLiteAddClass(element, className);
- });
- async(done);
+ addClass: function(element, className, options) {
+ options = prepareAnimateOptions(options);
+ options.addClass = mergeClasses(options.addclass, className);
+ return $$animateQueue.push(element, 'addClass', options);
},
/**
- *
* @ngdoc method
* @name $animate#removeClass
* @kind function
- * @description Removes the provided className CSS class value from the provided element.
- * Once complete, the done() callback will be fired (if provided).
- * @param {DOMElement} element the element which will have the className value
- * removed from it
- * @param {string} className the CSS class which will be removed from the element
- * @param {Function=} done the callback function (if provided) that will be fired after the
- * className value has been removed from the element
+ *
+ * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
+ * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
+ * animation if element does not contain the CSS class or if the class is added at a later step.
+ * Note that class-based animations are treated differently compared to structural animations
+ * (like enter, move and leave) since the CSS classes may be added/removed at different points
+ * depending if CSS or JavaScript animations are used.
+ *
+ * @param {DOMElement} element the element which the CSS classes will be applied to
+ * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
+ * @param {object=} options an optional collection of options/styles that will be applied to the element
+ *
+ * @return {Promise} the animation callback promise
*/
- removeClass : function(element, className, done) {
- className = isString(className) ?
- className :
- isArray(className) ? className.join(' ') : '';
- forEach(element, function (element) {
- jqLiteRemoveClass(element, className);
- });
- async(done);
+ removeClass: function(element, className, options) {
+ options = prepareAnimateOptions(options);
+ options.removeClass = mergeClasses(options.removeClass, className);
+ return $$animateQueue.push(element, 'removeClass', options);
},
/**
- *
* @ngdoc method
* @name $animate#setClass
* @kind function
- * @description Adds and/or removes the given CSS classes to and from the element.
- * Once complete, the done() callback will be fired (if provided).
- * @param {DOMElement} element the element which will have its CSS classes changed
- * removed from it
- * @param {string} add the CSS classes which will be added to the element
- * @param {string} remove the CSS class which will be removed from the element
- * @param {Function=} done the callback function (if provided) that will be fired after the
- * CSS classes have been set on the element
+ *
+ * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
+ * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
+ * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
+ * passed. Note that class-based animations are treated differently compared to structural animations
+ * (like enter, move and leave) since the CSS classes may be added/removed at different points
+ * depending if CSS or JavaScript animations are used.
+ *
+ * @param {DOMElement} element the element which the CSS classes will be applied to
+ * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
+ * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
+ * @param {object=} options an optional collection of options/styles that will be applied to the element
+ *
+ * @return {Promise} the animation callback promise
*/
- setClass : function(element, add, remove, done) {
- forEach(element, function (element) {
- jqLiteAddClass(element, add);
- jqLiteRemoveClass(element, remove);
- });
- async(done);
+ setClass: function(element, add, remove, options) {
+ options = prepareAnimateOptions(options);
+ options.addClass = mergeClasses(options.addClass, add);
+ options.removeClass = mergeClasses(options.removeClass, remove);
+ return $$animateQueue.push(element, 'setClass', options);
},
- enabled : noop
+ /**
+ * @ngdoc method
+ * @name $animate#animate
+ * @kind function
+ *
+ * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
+ * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
+ * on the provided styles. For example, if a transition animation is set for the given className then the provided from and
+ * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
+ * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
+ *
+ * @param {DOMElement} element the element which the CSS styles will be applied to
+ * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
+ * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
+ * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
+ * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
+ * (Note that if no animation is detected then this value will not be appplied to the element.)
+ * @param {object=} options an optional collection of options/styles that will be applied to the element
+ *
+ * @return {Promise} the animation callback promise
+ */
+ animate: function(element, from, to, className, options) {
+ options = prepareAnimateOptions(options);
+ options.from = options.from ? extend(options.from, from) : from;
+ options.to = options.to ? extend(options.to, to) : to;
+
+ className = className || 'ng-inline-animate';
+ options.tempClasses = mergeClasses(options.tempClasses, className);
+ return $$animateQueue.push(element, 'animate', options);
+ }
};
}];
}];
-function $$AsyncCallbackProvider(){
+function $$AsyncCallbackProvider() {
this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) {
return $$rAF.supported
? function(fn) { return $$rAF(fn); }
@@ -4348,6 +5344,8 @@ function $$AsyncCallbackProvider(){
}];
}
+/* global stripHash: true */
+
/**
* ! This is a private undocumented service !
*
@@ -4366,8 +5364,7 @@ function $$AsyncCallbackProvider(){
/**
* @param {object} window The global window object.
* @param {object} document jQuery wrapped document.
- * @param {function()} XHR XMLHttpRequest constructor.
- * @param {object} $log console.log or an object with the same interface.
+ * @param {object} $log window.console or an object with the same interface.
* @param {object} $sniffer $sniffer service
*/
function Browser(window, document, $log, $sniffer) {
@@ -4398,7 +5395,7 @@ function Browser(window, document, $log, $sniffer) {
} finally {
outstandingRequestCount--;
if (outstandingRequestCount === 0) {
- while(outstandingRequestCallbacks.length) {
+ while (outstandingRequestCallbacks.length) {
try {
outstandingRequestCallbacks.pop()();
} catch (e) {
@@ -4409,6 +5406,11 @@ function Browser(window, document, $log, $sniffer) {
}
}
+ function getHash(url) {
+ var index = url.indexOf('#');
+ return index === -1 ? '' : url.substr(index);
+ }
+
/**
* @private
* Note: this method is used only by scenario runner
@@ -4416,11 +5418,6 @@ function Browser(window, document, $log, $sniffer) {
* @param {function()} callback Function that will be called when no outstanding request
*/
self.notifyWhenNoOutstandingRequests = function(callback) {
- // force browser to execute all pollFns - this is needed so that cookies and other pollers fire
- // at some deterministic time in respect to the test runner's actions. Leaving things up to the
- // regular poller would result in flaky tests.
- forEach(pollFns, function(pollFn){ pollFn(); });
-
if (outstandingRequestCount === 0) {
callback();
} else {
@@ -4428,51 +5425,17 @@ function Browser(window, document, $log, $sniffer) {
}
};
- //////////////////////////////////////////////////////////////
- // Poll Watcher API
- //////////////////////////////////////////////////////////////
- var pollFns = [],
- pollTimeout;
-
- /**
- * @name $browser#addPollFn
- *
- * @param {function()} fn Poll function to add
- *
- * @description
- * Adds a function to the list of functions that poller periodically executes,
- * and starts polling if not started yet.
- *
- * @returns {function()} the added function
- */
- self.addPollFn = function(fn) {
- if (isUndefined(pollTimeout)) startPoller(100, setTimeout);
- pollFns.push(fn);
- return fn;
- };
-
- /**
- * @param {number} interval How often should browser call poll functions (ms)
- * @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
- *
- * @description
- * Configures the poller to run in the specified intervals, using the specified
- * setTimeout fn and kicks it off.
- */
- function startPoller(interval, setTimeout) {
- (function check() {
- forEach(pollFns, function(pollFn){ pollFn(); });
- pollTimeout = setTimeout(check, interval);
- })();
- }
-
//////////////////////////////////////////////////////////////
// URL API
//////////////////////////////////////////////////////////////
- var lastBrowserUrl = location.href,
+ var cachedState, lastHistoryState,
+ lastBrowserUrl = location.href,
baseElement = document.find('base'),
- newLocation = null;
+ reloadLocation = null;
+
+ cacheState();
+ lastHistoryState = cachedState;
/**
* @name $browser#url
@@ -4491,52 +5454,118 @@ function Browser(window, document, $log, $sniffer) {
* {@link ng.$location $location service} to change url.
*
* @param {string} url New url (when used as setter)
- * @param {boolean=} replace Should new url replace current history record ?
+ * @param {boolean=} replace Should new url replace current history record?
+ * @param {object=} state object to use with pushState/replaceState
*/
- self.url = function(url, replace) {
+ self.url = function(url, replace, state) {
+ // In modern browsers `history.state` is `null` by default; treating it separately
+ // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
+ // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
+ if (isUndefined(state)) {
+ state = null;
+ }
+
// Android Browser BFCache causes location, history reference to become stale.
if (location !== window.location) location = window.location;
if (history !== window.history) history = window.history;
// setter
if (url) {
- if (lastBrowserUrl == url) return;
+ var sameState = lastHistoryState === state;
+
+ // Don't change anything if previous and current URLs and states match. This also prevents
+ // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
+ // See https://github.com/angular/angular.js/commit/ffb2701
+ if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
+ return self;
+ }
+ var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
lastBrowserUrl = url;
- if ($sniffer.history) {
- if (replace) history.replaceState(null, '', url);
- else {
- history.pushState(null, '', url);
- // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
- baseElement.attr('href', baseElement.attr('href'));
- }
+ lastHistoryState = state;
+ // Don't use history API if only the hash changed
+ // due to a bug in IE10/IE11 which leads
+ // to not firing a `hashchange` nor `popstate` event
+ // in some cases (see #9143).
+ if ($sniffer.history && (!sameBase || !sameState)) {
+ history[replace ? 'replaceState' : 'pushState'](state, '', url);
+ cacheState();
+ // Do the assignment again so that those two variables are referentially identical.
+ lastHistoryState = cachedState;
} else {
- newLocation = url;
+ if (!sameBase || reloadLocation) {
+ reloadLocation = url;
+ }
if (replace) {
location.replace(url);
- } else {
+ } else if (!sameBase) {
location.href = url;
+ } else {
+ location.hash = getHash(url);
}
}
return self;
// getter
} else {
- // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href
- // methods not updating location.href synchronously.
+ // - reloadLocation is needed as browsers don't allow to read out
+ // the new location.href if a reload happened.
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
- return newLocation || location.href.replace(/%27/g,"'");
+ return reloadLocation || location.href.replace(/%27/g,"'");
}
};
+ /**
+ * @name $browser#state
+ *
+ * @description
+ * This method is a getter.
+ *
+ * Return history.state or null if history.state is undefined.
+ *
+ * @returns {object} state
+ */
+ self.state = function() {
+ return cachedState;
+ };
+
var urlChangeListeners = [],
urlChangeInit = false;
+ function cacheStateAndFireUrlChange() {
+ cacheState();
+ fireUrlChange();
+ }
+
+ function getCurrentState() {
+ try {
+ return history.state;
+ } catch (e) {
+ // MSIE can reportedly throw when there is no state (UNCONFIRMED).
+ }
+ }
+
+ // This variable should be used *only* inside the cacheState function.
+ var lastCachedState = null;
+ function cacheState() {
+ // This should be the only place in $browser where `history.state` is read.
+ cachedState = getCurrentState();
+ cachedState = isUndefined(cachedState) ? null : cachedState;
+
+ // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
+ if (equals(cachedState, lastCachedState)) {
+ cachedState = lastCachedState;
+ }
+ lastCachedState = cachedState;
+ }
+
function fireUrlChange() {
- newLocation = null;
- if (lastBrowserUrl == self.url()) return;
+ if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
+ return;
+ }
lastBrowserUrl = self.url();
+ lastHistoryState = cachedState;
forEach(urlChangeListeners, function(listener) {
- listener(self.url());
+ listener(self.url(), cachedState);
});
}
@@ -4569,11 +5598,9 @@ function Browser(window, document, $log, $sniffer) {
// changed by push/replaceState
// html5 history api - popstate event
- if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange);
+ if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
// hashchange event
- if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange);
- // polling
- else self.addPollFn(fireUrlChange);
+ jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
urlChangeInit = true;
}
@@ -4582,6 +5609,23 @@ function Browser(window, document, $log, $sniffer) {
return callback;
};
+ /**
+ * @private
+ * Remove popstate and hashchange handler from window.
+ *
+ * NOTE: this api is intended for use only by $rootScope.
+ */
+ self.$$applicationDestroyed = function() {
+ jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
+ };
+
+ /**
+ * Checks whether the url has changed outside of Angular.
+ * Needs to be exported to be able to check for changes that have been done in sync,
+ * as hashchange/popstate events fire in async.
+ */
+ self.$$checkUrlChange = fireUrlChange;
+
//////////////////////////////////////////////////////////////
// Misc API
//////////////////////////////////////////////////////////////
@@ -4600,82 +5644,6 @@ function Browser(window, document, $log, $sniffer) {
return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
};
- //////////////////////////////////////////////////////////////
- // Cookies API
- //////////////////////////////////////////////////////////////
- var lastCookies = {};
- var lastCookieString = '';
- var cookiePath = self.baseHref();
-
- /**
- * @name $browser#cookies
- *
- * @param {string=} name Cookie name
- * @param {string=} value Cookie value
- *
- * @description
- * The cookies method provides a 'private' low level access to browser cookies.
- * It is not meant to be used directly, use the $cookie service instead.
- *
- * The return values vary depending on the arguments that the method was called with as follows:
- *
- * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify
- * it
- * - cookies(name, value) -> set name to value, if value is undefined delete the cookie
- * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that
- * way)
- *
- * @returns {Object} Hash of all cookies (if called without any parameter)
- */
- self.cookies = function(name, value) {
- /* global escape: false, unescape: false */
- var cookieLength, cookieArray, cookie, i, index;
-
- if (name) {
- if (value === undefined) {
- rawDocument.cookie = escape(name) + "=;path=" + cookiePath +
- ";expires=Thu, 01 Jan 1970 00:00:00 GMT";
- } else {
- if (isString(value)) {
- cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) +
- ';path=' + cookiePath).length + 1;
-
- // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
- // - 300 cookies
- // - 20 cookies per unique domain
- // - 4096 bytes per cookie
- if (cookieLength > 4096) {
- $log.warn("Cookie '"+ name +
- "' possibly not set or overflowed because it was too large ("+
- cookieLength + " > 4096 bytes)!");
- }
- }
- }
- } else {
- if (rawDocument.cookie !== lastCookieString) {
- lastCookieString = rawDocument.cookie;
- cookieArray = lastCookieString.split("; ");
- lastCookies = {};
-
- for (i = 0; i < cookieArray.length; i++) {
- cookie = cookieArray[i];
- index = cookie.indexOf('=');
- if (index > 0) { //ignore nameless cookies
- name = unescape(cookie.substring(0, index));
- // the first value that is seen for a cookie is the most
- // specific one. values for the same cookie name that
- // follow are for less specific paths.
- if (lastCookies[name] === undefined) {
- lastCookies[name] = unescape(cookie.substring(index + 1));
- }
- }
- }
- }
- return lastCookies;
- }
- };
-
-
/**
* @name $browser#defer
* @param {function()} fn A function, who's execution should be deferred.
@@ -4724,9 +5692,9 @@ function Browser(window, document, $log, $sniffer) {
}
-function $BrowserProvider(){
+function $BrowserProvider() {
this.$get = ['$window', '$log', '$sniffer', '$document',
- function( $window, $log, $sniffer, $document){
+ function($window, $log, $sniffer, $document) {
return new Browser($window, $document, $log, $sniffer);
}];
}
@@ -4890,13 +5858,13 @@ function $CacheFactoryProvider() {
* @returns {*} the value stored.
*/
put: function(key, value) {
+ if (isUndefined(value)) return;
if (capacity < Number.MAX_VALUE) {
var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
refresh(lruEntry);
}
- if (isUndefined(value)) return;
if (!(key in data)) size++;
data[key] = value;
@@ -5100,9 +6068,10 @@ function $CacheFactoryProvider() {
* ```
*
* **Note:** the `script` tag containing the template does not need to be included in the `head` of
- * the document, but it must be below the `ng-app` definition.
+ * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
+ * element with ng-app attribute), otherwise the template will be ignored.
*
- * Adding via the $templateCache service:
+ * Adding via the `$templateCache` service:
*
* ```js
* var myApp = angular.module('myApp', []);
@@ -5130,6 +6099,17 @@ function $TemplateCacheProvider() {
}];
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
*
* DOM-related variables:
@@ -5191,9 +6171,11 @@ function $TemplateCacheProvider() {
* // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
* transclude: false,
* restrict: 'A',
+ * templateNamespace: 'html',
* scope: false,
* controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
- * controllerAs: 'stringAlias',
+ * controllerAs: 'stringIdentifier',
+ * bindToController: false,
* require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
* compile: function compile(tElement, tAttrs, transclude) {
* return {
@@ -5241,6 +6223,13 @@ function $TemplateCacheProvider() {
* The directive definition object provides instructions to the {@link ng.$compile
* compiler}. The attributes are:
*
+ * #### `multiElement`
+ * When this property is set to true, the HTML compiler will collect DOM nodes between
+ * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
+ * together as the directive elements. It is recommended that this feature be used on directives
+ * which are not strictly behavioural (such as {@link ngClick}), and which
+ * do not manipulate or replace child nodes (such as {@link ngInclude}).
+ *
* #### `priority`
* When there are multiple directives defined on a single DOM element, sometimes it
* is necessary to specify the order in which the directives are applied. The `priority` is used
@@ -5252,7 +6241,8 @@ function $TemplateCacheProvider() {
* #### `terminal`
* If set to true then the current `priority` will be the last set of directives
* which will execute (any directives at the current priority will still execute
- * as the order of execution on same `priority` is undefined).
+ * as the order of execution on same `priority` is undefined). Note that expressions
+ * and other directives used in the directive's template will also be excluded from execution.
*
* #### `scope`
* **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the
@@ -5285,7 +6275,9 @@ function $TemplateCacheProvider() {
* value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
* in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
* scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
- * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional.
+ * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
+ * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
+ * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
*
* * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
* If no `attr` name is specified then the attribute name is assumed to be the same as the
@@ -5298,6 +6290,10 @@ function $TemplateCacheProvider() {
* by calling the `localFn` as `localFn({amount: 22})`.
*
*
+ * #### `bindToController`
+ * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
+ * allow a component to have its properties bound to the controller, rather than to scope. When the controller
+ * is instantiated, the initial values of the isolate scope bindings are already available.
*
* #### `controller`
* Controller constructor function. The controller is instantiated before the
@@ -5308,40 +6304,66 @@ function $TemplateCacheProvider() {
* * `$scope` - Current scope associated with the element
* * `$element` - Current element
* * `$attrs` - Current attributes object for the element
- * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope.
- * The scope can be overridden by an optional first argument.
- * `function([scope], cloneLinkingFn)`.
+ * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
+ * `function([scope], cloneLinkingFn, futureParentElement)`.
+ * * `scope`: optional argument to override the scope.
+ * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
+ * * `futureParentElement`:
+ * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
+ * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
+ * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
+ * and when the `cloneLinkinFn` is passed,
+ * as those elements need to created and cloned in a special way when they are defined outside their
+ * usual containers (e.g. like `
*
- *
+ *
* **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
* e.g. does not know about the right outer scope. Please use the transclude function that is passed
* to the link function instead.
@@ -5465,15 +6495,23 @@ function $TemplateCacheProvider() {
* * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
* between all directive linking functions.
*
- * * `controller` - a controller instance - A controller instance if at least one directive on the
- * element defines a controller. The controller is shared among all the directives, which allows
- * the directives to use the controllers as a communication channel.
+ * * `controller` - the directive's required controller instance(s) - Instances are shared
+ * among all directives, which allows the directives to use the controllers as a communication
+ * channel. The exact value depends on the directive's `require` property:
+ * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
+ * * `string`: the controller instance
+ * * `array`: array of controller instances
+ *
+ * If a required controller cannot be found, and it is optional, the instance is `null`,
+ * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
+ *
+ * Note that you can also require the directive's own controller - it will be made available like
+ * like any other controller.
*
* * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
- * The scope can be overridden by an optional first argument. This is the same as the `$transclude`
- * parameter of directive controllers.
- * `function([scope], cloneLinkingFn)`.
- *
+ * This is the same as the `$transclude`
+ * parameter of directive controllers, see there for details.
+ * `function([scope], cloneLinkingFn, futureParentElement)`.
*
* #### Pre-linking function
*
@@ -5482,9 +6520,130 @@ function $TemplateCacheProvider() {
*
* #### Post-linking function
*
- * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function.
+ * Executed after the child elements are linked.
+ *
+ * Note that child elements that contain `templateUrl` directives will not have been compiled
+ * and linked since they are waiting for their template to load asynchronously and their own
+ * compilation and linking has been suspended until that occurs.
+ *
+ * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
+ * for their async templates to be resolved.
+ *
+ *
+ * ### Transclusion
+ *
+ * Transclusion is the process of extracting a collection of DOM element from one part of the DOM and
+ * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
+ * scope from where they were taken.
+ *
+ * Transclusion is used (often with {@link ngTransclude}) to insert the
+ * original contents of a directive's element into a specified place in the template of the directive.
+ * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
+ * content has access to the properties on the scope from which it was taken, even if the directive
+ * has isolated scope.
+ * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
+ *
+ * This makes it possible for the widget to have private state for its template, while the transcluded
+ * content has access to its originating scope.
+ *
+ *
+ * **Note:** When testing an element transclude directive you must not place the directive at the root of the
+ * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
+ * Testing Transclusion Directives}.
+ *
+ *
+ * #### Transclusion Functions
+ *
+ * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
+ * function** to the directive's `link` function and `controller`. This transclusion function is a special
+ * **linking function** that will return the compiled contents linked to a new transclusion scope.
+ *
+ *
+ * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
+ * ngTransclude will deal with it for us.
+ *
+ *
+ * If you want to manually control the insertion and removal of the transcluded content in your directive
+ * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
+ * object that contains the compiled DOM, which is linked to the correct transclusion scope.
+ *
+ * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
+ * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
+ * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
+ *
+ *
+ * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
+ * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
+ *
+ *
+ * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
+ * attach function**:
+ *
+ * ```js
+ * var transcludedContent, transclusionScope;
+ *
+ * $transclude(function(clone, scope) {
+ * element.append(clone);
+ * transcludedContent = clone;
+ * transclusionScope = scope;
+ * });
+ * ```
+ *
+ * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
+ * associated transclusion scope:
+ *
+ * ```js
+ * transcludedContent.remove();
+ * transclusionScope.$destroy();
+ * ```
+ *
+ *
+ * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
+ * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
+ * then you are also responsible for calling `$destroy` on the transclusion scope.
+ *
+ *
+ * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
+ * automatically destroy their transluded clones as necessary so you do not need to worry about this if
+ * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
+ *
+ *
+ * #### Transclusion Scopes
+ *
+ * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
+ * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
+ * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
+ * was taken.
+ *
+ * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
+ * like this:
+ *
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ *
+ * The `$parent` scope hierarchy will look like this:
+ *
+ * ```
+ * - $rootScope
+ * - isolate
+ * - transclusion
+ * ```
+ *
+ * but the scopes will inherit prototypically from different scopes to their `$parent`.
+ *
+ * ```
+ * - $rootScope
+ * - transclusion
+ * - isolate
+ * ```
+ *
*
- *
* ### Attributes
*
* The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
@@ -5522,7 +6681,7 @@ function $TemplateCacheProvider() {
* }
* ```
*
- * Below is an example using `$compileProvider`.
+ * ## Example
*
*
* **Note**: Typically directives are registered with `module.directive`. The example below is
@@ -5564,8 +6723,8 @@ function $TemplateCacheProvider() {
}]);
-
-
+
+
@@ -5585,21 +6744,41 @@ function $TemplateCacheProvider() {
*
*
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
- * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives.
+ * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
+ *
+ *
+ * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
+ * e.g. will not use the right outer scope. Please pass the transclude function as a
+ * `parentBoundTranscludeFn` to the link function instead.
+ *
+ *
* @param {number} maxPriority only apply directives lower than given priority (Only effects the
* root element(s), not their children)
- * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template
+ * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
* (a DOM element/tree) to a scope. Where:
*
* * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
* * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
* `template` and call the `cloneAttachFn` function allowing the caller to attach the
* cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
- * called as: `cloneAttachFn(clonedElement, scope)` where:
+ * called as: `cloneAttachFn(clonedElement, scope)` where:
*
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
* * `scope` - is the current scope with which the linking function is working with.
*
+ * * `options` - An optional object hash with linking options. If `options` is provided, then the following
+ * keys may be used to control linking behavior:
+ *
+ * * `parentBoundTranscludeFn` - the transclude function made available to
+ * directives; if given, it will be passed through to the link functions of
+ * directives found in `element` during compilation.
+ * * `transcludeControllers` - an object hash with keys that map controller names
+ * to controller instances; if given, it will make the controllers
+ * available to directives.
+ * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
+ * the cloned elements; only needed for transcludes that are allowed to contain non html
+ * elements (e.g. SVG elements). See also the directive.controller property.
+ *
* Calling the linking function returns the element of the template. It is either the original
* element passed in, or the clone of the element if the `cloneAttachFn` is provided.
*
@@ -5638,7 +6817,6 @@ var $compileMinErr = minErr('$compile');
/**
* @ngdoc provider
* @name $compileProvider
- * @kind function
*
* @description
*/
@@ -5646,14 +6824,93 @@ $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
function $CompileProvider($provide, $$sanitizeUriProvider) {
var hasDirectives = {},
Suffix = 'Directive',
- COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,
- CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/;
+ COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
+ CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
+ ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
+ REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
// The assumption is that future DOM event attribute names will begin with
// 'on' and be composed of only English letters.
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
+ function parseIsolateBindings(scope, directiveName, isController) {
+ var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
+
+ var bindings = {};
+
+ forEach(scope, function(definition, scopeName) {
+ var match = definition.match(LOCAL_REGEXP);
+
+ if (!match) {
+ throw $compileMinErr('iscp',
+ "Invalid {3} for directive '{0}'." +
+ " Definition: {... {1}: '{2}' ...}",
+ directiveName, scopeName, definition,
+ (isController ? "controller bindings definition" :
+ "isolate scope definition"));
+ }
+
+ bindings[scopeName] = {
+ mode: match[1][0],
+ collection: match[2] === '*',
+ optional: match[3] === '?',
+ attrName: match[4] || scopeName
+ };
+ });
+
+ return bindings;
+ }
+
+ function parseDirectiveBindings(directive, directiveName) {
+ var bindings = {
+ isolateScope: null,
+ bindToController: null
+ };
+ if (isObject(directive.scope)) {
+ if (directive.bindToController === true) {
+ bindings.bindToController = parseIsolateBindings(directive.scope,
+ directiveName, true);
+ bindings.isolateScope = {};
+ } else {
+ bindings.isolateScope = parseIsolateBindings(directive.scope,
+ directiveName, false);
+ }
+ }
+ if (isObject(directive.bindToController)) {
+ bindings.bindToController =
+ parseIsolateBindings(directive.bindToController, directiveName, true);
+ }
+ if (isObject(bindings.bindToController)) {
+ var controller = directive.controller;
+ var controllerAs = directive.controllerAs;
+ if (!controller) {
+ // There is no controller, there may or may not be a controllerAs property
+ throw $compileMinErr('noctrl',
+ "Cannot bind to controller without directive '{0}'s controller.",
+ directiveName);
+ } else if (!identifierForController(controller, controllerAs)) {
+ // There is a controller, but no identifier or controllerAs property
+ throw $compileMinErr('noident',
+ "Cannot bind to controller without identifier for directive '{0}'.",
+ directiveName);
+ }
+ }
+ return bindings;
+ }
+
+ function assertValidDirectiveName(name) {
+ var letter = name.charAt(0);
+ if (!letter || letter !== lowercase(letter)) {
+ throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
+ }
+ if (name !== name.trim()) {
+ throw $compileMinErr('baddir',
+ "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
+ name);
+ }
+ }
+
/**
* @ngdoc method
* @name $compileProvider#directive
@@ -5672,6 +6929,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
this.directive = function registerDirective(name, directiveFactory) {
assertNotHasOwnProperty(name, 'directive');
if (isString(name)) {
+ assertValidDirectiveName(name);
assertArg(directiveFactory, 'directiveFactory');
if (!hasDirectives.hasOwnProperty(name)) {
hasDirectives[name] = [];
@@ -5690,7 +6948,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
directive.index = index;
directive.name = directive.name || name;
directive.require = directive.require || (directive.controller && directive.name);
- directive.restrict = directive.restrict || 'A';
+ directive.restrict = directive.restrict || 'EA';
+ var bindings = directive.$$bindings =
+ parseDirectiveBindings(directive, directive.name);
+ if (isObject(bindings.isolateScope)) {
+ directive.$$isolateBindings = bindings.isolateScope;
+ }
+ directive.$$moduleName = directiveFactory.$$moduleName;
directives.push(directive);
} catch (e) {
$exceptionHandler(e);
@@ -5716,7 +6980,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
* urls during a[href] sanitization.
*
- * The sanitization is a security measure aimed at prevent XSS attacks via html links.
+ * The sanitization is a security measure aimed at preventing XSS attacks via html links.
*
* Any url about to be assigned to a[href] via data-binding is first normalized and turned into
* an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
@@ -5766,18 +7030,75 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
};
+ /**
+ * @ngdoc method
+ * @name $compileProvider#debugInfoEnabled
+ *
+ * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
+ * current debugInfoEnabled state
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
+ *
+ * @kind function
+ *
+ * @description
+ * Call this method to enable/disable various debug runtime information in the compiler such as adding
+ * binding information and a reference to the current scope on to DOM elements.
+ * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
+ * * `ng-binding` CSS class
+ * * `$binding` data property containing an array of the binding expressions
+ *
+ * You may want to disable this in production for a significant performance boost. See
+ * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
+ *
+ * The default value is true.
+ */
+ var debugInfoEnabled = true;
+ this.debugInfoEnabled = function(enabled) {
+ if (isDefined(enabled)) {
+ debugInfoEnabled = enabled;
+ return this;
+ }
+ return debugInfoEnabled;
+ };
+
this.$get = [
- '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
+ '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
'$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
- function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
+ function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
$controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
- var Attributes = function(element, attr) {
+ var Attributes = function(element, attributesToCopy) {
+ if (attributesToCopy) {
+ var keys = Object.keys(attributesToCopy);
+ var i, l, key;
+
+ for (i = 0, l = keys.length; i < l; i++) {
+ key = keys[i];
+ this[key] = attributesToCopy[key];
+ }
+ } else {
+ this.$attr = {};
+ }
+
this.$$element = element;
- this.$attr = attr || {};
};
Attributes.prototype = {
+ /**
+ * @ngdoc method
+ * @name $compile.directive.Attributes#$normalize
+ * @kind function
+ *
+ * @description
+ * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
+ * `data-`) to its normalized, camelCase form.
+ *
+ * Also there is special case for Moz prefix starting with upper case letter.
+ *
+ * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
+ *
+ * @param {string} name Name to normalize
+ */
$normalize: directiveNormalize,
@@ -5792,8 +7113,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
*
* @param {string} classVal The className value that will be added to the element
*/
- $addClass : function(classVal) {
- if(classVal && classVal.length > 0) {
+ $addClass: function(classVal) {
+ if (classVal && classVal.length > 0) {
$animate.addClass(this.$$element, classVal);
}
},
@@ -5809,8 +7130,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
*
* @param {string} classVal The className value that will be removed from the element
*/
- $removeClass : function(classVal) {
- if(classVal && classVal.length > 0) {
+ $removeClass: function(classVal) {
+ if (classVal && classVal.length > 0) {
$animate.removeClass(this.$$element, classVal);
}
},
@@ -5827,16 +7148,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* @param {string} newClasses The current CSS className value
* @param {string} oldClasses The former CSS className value
*/
- $updateClass : function(newClasses, oldClasses) {
+ $updateClass: function(newClasses, oldClasses) {
var toAdd = tokenDifference(newClasses, oldClasses);
- var toRemove = tokenDifference(oldClasses, newClasses);
-
- if(toAdd.length === 0) {
- $animate.removeClass(this.$$element, toRemove);
- } else if(toRemove.length === 0) {
+ if (toAdd && toAdd.length) {
$animate.addClass(this.$$element, toAdd);
- } else {
- $animate.setClass(this.$$element, toAdd, toRemove);
+ }
+
+ var toRemove = tokenDifference(oldClasses, newClasses);
+ if (toRemove && toRemove.length) {
+ $animate.removeClass(this.$$element, toRemove);
}
},
@@ -5854,13 +7174,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
//is set through this function since it may cause $updateClass to
//become unstable.
- var booleanKey = getBooleanAttrName(this.$$element[0], key),
- normalizedVal,
+ var node = this.$$element[0],
+ booleanKey = getBooleanAttrName(node, key),
+ aliasedKey = getAliasedAttrName(node, key),
+ observer = key,
nodeName;
if (booleanKey) {
this.$$element.prop(key, value);
attrName = booleanKey;
+ } else if (aliasedKey) {
+ this[aliasedKey] = value;
+ observer = aliasedKey;
}
this[key] = value;
@@ -5877,10 +7202,44 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
nodeName = nodeName_(this.$$element);
- // sanitize a[href] and img[src] values
- if ((nodeName === 'A' && key === 'href') ||
- (nodeName === 'IMG' && key === 'src')) {
+ if ((nodeName === 'a' && key === 'href') ||
+ (nodeName === 'img' && key === 'src')) {
+ // sanitize a[href] and img[src] values
this[key] = value = $$sanitizeUri(value, key === 'src');
+ } else if (nodeName === 'img' && key === 'srcset') {
+ // sanitize img[srcset] values
+ var result = "";
+
+ // first check if there are spaces because it's not the same pattern
+ var trimmedSrcset = trim(value);
+ // ( 999x ,| 999w ,| ,|, )
+ var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
+ var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
+
+ // split srcset into tuple of uri and descriptor except for the last item
+ var rawUris = trimmedSrcset.split(pattern);
+
+ // for each tuples
+ var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
+ for (var i = 0; i < nbrUrisWith2parts; i++) {
+ var innerIdx = i * 2;
+ // sanitize the uri
+ result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
+ // add the descriptor
+ result += (" " + trim(rawUris[innerIdx + 1]));
+ }
+
+ // split the last item into uri and descriptor
+ var lastTuple = trim(rawUris[i * 2]).split(/\s/);
+
+ // sanitize the last uri
+ result += $$sanitizeUri(trim(lastTuple[0]), true);
+
+ // and add the last descriptor if any
+ if (lastTuple.length === 2) {
+ result += (" " + trim(lastTuple[1]));
+ }
+ this[key] = value = result;
}
if (writeAttr !== false) {
@@ -5893,7 +7252,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// fire observers
var $$observers = this.$$observers;
- $$observers && forEach($$observers[key], function(fn) {
+ $$observers && forEach($$observers[observer], function(fn) {
try {
fn(value);
} catch (e) {
@@ -5918,25 +7277,39 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* @param {string} key Normalized key. (ie ngAttribute) .
* @param {function(interpolatedValue)} fn Function that will be called whenever
the interpolated value of the attribute changes.
- * See the {@link guide/directive#Attributes Directives} guide for more info.
- * @returns {function()} the `fn` parameter.
+ * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
+ * @returns {function()} Returns a deregistration function for this observer.
*/
$observe: function(key, fn) {
var attrs = this,
- $$observers = (attrs.$$observers || (attrs.$$observers = {})),
+ $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
listeners = ($$observers[key] || ($$observers[key] = []));
listeners.push(fn);
$rootScope.$evalAsync(function() {
- if (!listeners.$$inter) {
+ if (!listeners.$$inter && attrs.hasOwnProperty(key)) {
// no one registered attribute interpolation function, so lets call it manually
fn(attrs[key]);
}
});
- return fn;
+
+ return function() {
+ arrayRemove(listeners, fn);
+ };
}
};
+
+ function safeAddClass($element, className) {
+ try {
+ $element.addClass(className);
+ } catch (e) {
+ // ignore, since it means that we are trying to set class on
+ // SVG element, where class name is read-only.
+ }
+ }
+
+
var startSymbol = $interpolate.startSymbol(),
endSymbol = $interpolate.endSymbol(),
denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
@@ -5946,6 +7319,30 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
},
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
+ compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
+ var bindings = $element.data('$binding') || [];
+
+ if (isArray(binding)) {
+ bindings = bindings.concat(binding);
+ } else {
+ bindings.push(binding);
+ }
+
+ $element.data('$binding', bindings);
+ } : noop;
+
+ compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
+ safeAddClass($element, 'ng-binding');
+ } : noop;
+
+ compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
+ var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
+ $element.data(dataName, scope);
+ } : noop;
+
+ compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
+ safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
+ } : noop;
return compile;
@@ -5960,48 +7357,74 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
// We can not compile top level text elements since text nodes can be merged and we will
// not be able to attach scope data to them, so we will wrap them in
- forEach($compileNodes, function(node, index){
- if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
- $compileNodes[index] = node = jqLite(node).wrap('').parent()[0];
+ forEach($compileNodes, function(node, index) {
+ if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
+ $compileNodes[index] = jqLite(node).wrap('').parent()[0];
}
});
var compositeLinkFn =
compileNodes($compileNodes, transcludeFn, $compileNodes,
maxPriority, ignoreDirective, previousCompileContext);
- safeAddClass($compileNodes, 'ng-scope');
- return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){
+ compile.$$addScopeClass($compileNodes);
+ var namespace = null;
+ return function publicLinkFn(scope, cloneConnectFn, options) {
assertArg(scope, 'scope');
- // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
- // and sometimes changes the structure of the DOM.
- var $linkNode = cloneConnectFn
- ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
- : $compileNodes;
- forEach(transcludeControllers, function(instance, name) {
- $linkNode.data('$' + name + 'Controller', instance);
- });
+ options = options || {};
+ var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
+ transcludeControllers = options.transcludeControllers,
+ futureParentElement = options.futureParentElement;
- // Attach scope only to non-text nodes.
- for(var i = 0, ii = $linkNode.length; i').append($compileNodes).html())
+ );
+ } else if (cloneConnectFn) {
+ // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
+ // and sometimes changes the structure of the DOM.
+ $linkNode = JQLitePrototype.clone.call($compileNodes);
+ } else {
+ $linkNode = $compileNodes;
+ }
+
+ if (transcludeControllers) {
+ for (var controllerName in transcludeControllers) {
+ $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
}
}
+ compile.$$addScopeInfo($linkNode, scope);
+
if (cloneConnectFn) cloneConnectFn($linkNode, scope);
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
return $linkNode;
};
}
- function safeAddClass($element, className) {
- try {
- $element.addClass(className);
- } catch(e) {
- // ignore, since it means that we are trying to set class on
- // SVG element, where class name is read-only.
+ function detectNamespaceForChildElements(parentElement) {
+ // TODO: Make this detect MathML as well...
+ var node = parentElement && parentElement[0];
+ if (!node) {
+ return 'html';
+ } else {
+ return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
}
}
@@ -6023,7 +7446,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
previousCompileContext) {
var linkFns = [],
- attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound;
+ attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
for (var i = 0; i < nodeList.length; i++) {
attrs = new Attributes();
@@ -6038,7 +7461,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
: null;
if (nodeLinkFn && nodeLinkFn.scope) {
- safeAddClass(attrs.$$element, 'ng-scope');
+ compile.$$addScopeClass(attrs.$$element);
}
childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
@@ -6050,8 +7473,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
(nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
&& nodeLinkFn.transclude) : transcludeFn);
- linkFns.push(nodeLinkFn, childLinkFn);
- linkFnFound = linkFnFound || nodeLinkFn || childLinkFn;
+ if (nodeLinkFn || childLinkFn) {
+ linkFns.push(i, nodeLinkFn, childLinkFn);
+ linkFnFound = true;
+ nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
+ }
+
//use the previous context only for the first element in the virtual group
previousCompileContext = null;
}
@@ -6060,30 +7487,46 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return linkFnFound ? compositeLinkFn : null;
function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
- var nodeLinkFn, childLinkFn, node, childScope, i, ii, n, childBoundTranscludeFn;
+ var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
+ var stableNodeList;
- // copy nodeList so that linking doesn't break due to live list updates.
- var nodeListLength = nodeList.length,
- stableNodeList = new Array(nodeListLength);
- for (i = 0; i < nodeListLength; i++) {
- stableNodeList[i] = nodeList[i];
+
+ if (nodeLinkFnFound) {
+ // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
+ // offsets don't get screwed up
+ var nodeListLength = nodeList.length;
+ stableNodeList = new Array(nodeListLength);
+
+ // create a sparse array by only copying the elements which have a linkFn
+ for (i = 0; i < linkFns.length; i+=3) {
+ idx = linkFns[i];
+ stableNodeList[idx] = nodeList[idx];
+ }
+ } else {
+ stableNodeList = nodeList;
}
- for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
- node = stableNodeList[n];
+ for (i = 0, ii = linkFns.length; i < ii;) {
+ node = stableNodeList[linkFns[i++]];
nodeLinkFn = linkFns[i++];
childLinkFn = linkFns[i++];
if (nodeLinkFn) {
if (nodeLinkFn.scope) {
childScope = scope.$new();
- jqLite.data(node, '$scope', childScope);
+ compile.$$addScopeInfo(jqLite(node), childScope);
+ var destroyBindings = nodeLinkFn.$$destroyBindings;
+ if (destroyBindings) {
+ nodeLinkFn.$$destroyBindings = null;
+ childScope.$on('$destroyed', destroyBindings);
+ }
} else {
childScope = scope;
}
- if ( nodeLinkFn.transcludeOnThisElement ) {
- childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
+ if (nodeLinkFn.transcludeOnThisElement) {
+ childBoundTranscludeFn = createBoundTranscludeFn(
+ scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
} else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
childBoundTranscludeFn = parentBoundTranscludeFn;
@@ -6095,7 +7538,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
childBoundTranscludeFn = null;
}
- nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
+ nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn,
+ nodeLinkFn);
} else if (childLinkFn) {
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
@@ -6106,20 +7550,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
- var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) {
- var scopeCreated = false;
+ var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
if (!transcludedScope) {
- transcludedScope = scope.$new();
+ transcludedScope = scope.$new(false, containingScope);
transcludedScope.$$transcluded = true;
- scopeCreated = true;
}
- var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn);
- if (scopeCreated) {
- clone.on('$destroy', function() { transcludedScope.$destroy(); });
- }
- return clone;
+ return transcludeFn(transcludedScope, cloneFn, {
+ parentBoundTranscludeFn: previousBoundTranscludeFn,
+ transcludeControllers: controllers,
+ futureParentElement: futureParentElement
+ });
};
return boundTranscludeFn;
@@ -6141,11 +7583,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
match,
className;
- switch(nodeType) {
- case 1: /* Element */
+ switch (nodeType) {
+ case NODE_TYPE_ELEMENT: /* Element */
// use the node name:
addDirective(directives,
- directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective);
+ directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
// iterate over the attributes
for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
@@ -6154,39 +7596,46 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var attrEndName = false;
attr = nAttrs[j];
- if (!msie || msie >= 8 || attr.specified) {
- name = attr.name;
- value = trim(attr.value);
+ name = attr.name;
+ value = trim(attr.value);
- // support ngAttr attribute binding
- ngAttrName = directiveNormalize(name);
- if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
- name = snake_case(ngAttrName.substr(6), '-');
- }
+ // support ngAttr attribute binding
+ ngAttrName = directiveNormalize(name);
+ if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
+ name = name.replace(PREFIX_REGEXP, '')
+ .substr(8).replace(/_(.)/g, function(match, letter) {
+ return letter.toUpperCase();
+ });
+ }
- var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
+ var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
+ if (directiveIsMultiElement(directiveNName)) {
if (ngAttrName === directiveNName + 'Start') {
attrStartName = name;
attrEndName = name.substr(0, name.length - 5) + 'end';
name = name.substr(0, name.length - 6);
}
-
- nName = directiveNormalize(name.toLowerCase());
- attrsMap[nName] = name;
- if (isNgAttr || !attrs.hasOwnProperty(nName)) {
- attrs[nName] = value;
- if (getBooleanAttrName(node, nName)) {
- attrs[nName] = true; // presence means true
- }
- }
- addAttrInterpolateDirective(node, directives, value, nName);
- addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
- attrEndName);
}
+
+ nName = directiveNormalize(name.toLowerCase());
+ attrsMap[nName] = name;
+ if (isNgAttr || !attrs.hasOwnProperty(nName)) {
+ attrs[nName] = value;
+ if (getBooleanAttrName(node, nName)) {
+ attrs[nName] = true; // presence means true
+ }
+ }
+ addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
+ addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
+ attrEndName);
}
// use class as directive
className = node.className;
+ if (isObject(className)) {
+ // Maybe SVGAnimatedString
+ className = className.animVal;
+ }
if (isString(className) && className !== '') {
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
nName = directiveNormalize(match[2]);
@@ -6197,10 +7646,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
}
break;
- case 3: /* Text Node */
+ case NODE_TYPE_TEXT: /* Text Node */
+ if (msie === 11) {
+ // Workaround for #11781
+ while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) {
+ node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
+ node.parentNode.removeChild(node.nextSibling);
+ }
+ }
addTextInterpolateDirective(directives, node.nodeValue);
break;
- case 8: /* Comment */
+ case NODE_TYPE_COMMENT: /* Comment */
try {
match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
if (match) {
@@ -6233,14 +7689,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var nodes = [];
var depth = 0;
if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
- var startNode = node;
do {
if (!node) {
throw $compileMinErr('uterdir',
"Unterminated attribute, found '{0}' but no matching '{1}' found.",
attrStart, attrEnd);
}
- if (node.nodeType == 1 /** Element **/) {
+ if (node.nodeType == NODE_TYPE_ELEMENT) {
if (node.hasAttribute(attrStart)) depth++;
if (node.hasAttribute(attrEnd)) depth--;
}
@@ -6298,7 +7753,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
previousCompileContext = previousCompileContext || {};
var terminalPriority = -Number.MAX_VALUE,
- newScopeDirective,
+ newScopeDirective = previousCompileContext.newScopeDirective,
controllerDirectives = previousCompileContext.controllerDirectives,
newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
templateDirective = previousCompileContext.templateDirective,
@@ -6316,7 +7771,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
directiveValue;
// executes all directives on the current element
- for(var i = 0, ii = directives.length; i < ii; i++) {
+ for (var i = 0, ii = directives.length; i < ii; i++) {
directive = directives[i];
var attrStart = directive.$$start;
var attrEnd = directive.$$end;
@@ -6332,24 +7787,32 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
if (directiveValue = directive.scope) {
- newScopeDirective = newScopeDirective || directive;
// skip the check for directives with async templates, we'll check the derived sync
// directive when the template arrives
if (!directive.templateUrl) {
- assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
- $compileNode);
if (isObject(directiveValue)) {
+ // This directive is trying to add an isolated scope.
+ // Check that there is no scope of any kind already
+ assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
+ directive, $compileNode);
newIsolateScopeDirective = directive;
+ } else {
+ // This directive is trying to add a child scope.
+ // Check that there is no isolated scope already
+ assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
+ $compileNode);
}
}
+
+ newScopeDirective = newScopeDirective || directive;
}
directiveName = directive.name;
if (!directive.templateUrl && directive.controller) {
directiveValue = directive.controller;
- controllerDirectives = controllerDirectives || {};
+ controllerDirectives = controllerDirectives || createMap();
assertNoDuplicate("'" + directiveName + "' controller",
controllerDirectives[directiveName], directive, $compileNode);
controllerDirectives[directiveName] = directive;
@@ -6410,11 +7873,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (jqLiteIsTextNode(directiveValue)) {
$template = [];
} else {
- $template = jqLite(trim(directiveValue));
+ $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
}
compileNode = $template[0];
- if ($template.length != 1 || compileNode.nodeType !== 1) {
+ if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
throw $compileMinErr('tplrt',
"Template for directive '{0}' must have exactly one root element. {1}",
directiveName, '');
@@ -6456,6 +7919,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
controllerDirectives: controllerDirectives,
+ newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
newIsolateScopeDirective: newIsolateScopeDirective,
templateDirective: templateDirective,
nonTlbTranscludeDirective: nonTlbTranscludeDirective
@@ -6516,176 +7980,159 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function getControllers(directiveName, require, $element, elementControllers) {
- var value, retrievalMethod = 'data', optional = false;
- if (isString(require)) {
- while((value = require.charAt(0)) == '^' || value == '?') {
- require = require.substr(1);
- if (value == '^') {
- retrievalMethod = 'inheritedData';
- }
- optional = optional || value == '?';
- }
- value = null;
+ var value;
- if (elementControllers && retrievalMethod === 'data') {
- value = elementControllers[require];
+ if (isString(require)) {
+ var match = require.match(REQUIRE_PREFIX_REGEXP);
+ var name = require.substring(match[0].length);
+ var inheritType = match[1] || match[3];
+ var optional = match[2] === '?';
+
+ //If only parents then start at the parent element
+ if (inheritType === '^^') {
+ $element = $element.parent();
+ //Otherwise attempt getting the controller from elementControllers in case
+ //the element is transcluded (and has no data) and to avoid .data if possible
+ } else {
+ value = elementControllers && elementControllers[name];
+ value = value && value.instance;
+ }
+
+ if (!value) {
+ var dataName = '$' + name + 'Controller';
+ value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
}
- value = value || $element[retrievalMethod]('$' + require + 'Controller');
if (!value && !optional) {
throw $compileMinErr('ctreq',
"Controller '{0}', required by directive '{1}', can't be found!",
- require, directiveName);
+ name, directiveName);
}
- return value;
} else if (isArray(require)) {
value = [];
- forEach(require, function(require) {
- value.push(getControllers(directiveName, require, $element, elementControllers));
- });
+ for (var i = 0, ii = require.length; i < ii; i++) {
+ value[i] = getControllers(directiveName, require[i], $element, elementControllers);
+ }
}
- return value;
+
+ return value || null;
}
+ function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
+ var elementControllers = createMap();
+ for (var controllerKey in controllerDirectives) {
+ var directive = controllerDirectives[controllerKey];
+ var locals = {
+ $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
+ $element: $element,
+ $attrs: attrs,
+ $transclude: transcludeFn
+ };
- function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
- var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn;
-
- attrs = (compileNode === linkNode)
- ? templateAttrs
- : shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
- $element = attrs.$$element;
-
- if (newIsolateScopeDirective) {
- var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
-
- isolateScope = scope.$new(true);
-
- if (templateDirective && (templateDirective === newIsolateScopeDirective ||
- templateDirective === newIsolateScopeDirective.$$originalDirective)) {
- $element.data('$isolateScope', isolateScope);
- } else {
- $element.data('$isolateScopeNoTemplate', isolateScope);
+ var controller = directive.controller;
+ if (controller == '@') {
+ controller = attrs[directive.name];
}
+ var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
-
- safeAddClass($element, 'ng-isolate-scope');
-
- forEach(newIsolateScopeDirective.scope, function(definition, scopeName) {
- var match = definition.match(LOCAL_REGEXP) || [],
- attrName = match[3] || scopeName,
- optional = (match[2] == '?'),
- mode = match[1], // @, =, or &
- lastValue,
- parentGet, parentSet, compare;
-
- isolateScope.$$isolateBindings[scopeName] = mode + attrName;
-
- switch (mode) {
-
- case '@':
- attrs.$observe(attrName, function(value) {
- isolateScope[scopeName] = value;
- });
- attrs.$$observers[attrName].$$scope = scope;
- if( attrs[attrName] ) {
- // If the attribute has been provided then we trigger an interpolation to ensure
- // the value is there for use in the link fn
- isolateScope[scopeName] = $interpolate(attrs[attrName])(scope);
- }
- break;
-
- case '=':
- if (optional && !attrs[attrName]) {
- return;
- }
- parentGet = $parse(attrs[attrName]);
- if (parentGet.literal) {
- compare = equals;
- } else {
- compare = function(a,b) { return a === b || (a !== a && b !== b); };
- }
- parentSet = parentGet.assign || function() {
- // reset the change, or we will throw this exception on every $digest
- lastValue = isolateScope[scopeName] = parentGet(scope);
- throw $compileMinErr('nonassign',
- "Expression '{0}' used with directive '{1}' is non-assignable!",
- attrs[attrName], newIsolateScopeDirective.name);
- };
- lastValue = isolateScope[scopeName] = parentGet(scope);
- isolateScope.$watch(function parentValueWatch() {
- var parentValue = parentGet(scope);
- if (!compare(parentValue, isolateScope[scopeName])) {
- // we are out of sync and need to copy
- if (!compare(parentValue, lastValue)) {
- // parent changed and it has precedence
- isolateScope[scopeName] = parentValue;
- } else {
- // if the parent can be assigned then do so
- parentSet(scope, parentValue = isolateScope[scopeName]);
- }
- }
- return lastValue = parentValue;
- }, null, parentGet.literal);
- break;
-
- case '&':
- parentGet = $parse(attrs[attrName]);
- isolateScope[scopeName] = function(locals) {
- return parentGet(scope, locals);
- };
- break;
-
- default:
- throw $compileMinErr('iscp',
- "Invalid isolate scope definition for directive '{0}'." +
- " Definition: {... {1}: '{2}' ...}",
- newIsolateScopeDirective.name, scopeName, definition);
- }
- });
+ // For directives with element transclusion the element is a comment,
+ // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
+ // clean up (http://bugs.jquery.com/ticket/8335).
+ // Instead, we save the controllers for the element in a local hash and attach to .data
+ // later, once we have the actual element.
+ elementControllers[directive.name] = controllerInstance;
+ if (!hasElementTranscludeDirective) {
+ $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
+ }
}
- transcludeFn = boundTranscludeFn && controllersBoundTransclude;
+ return elementControllers;
+ }
+
+ function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn,
+ thisLinkFn) {
+ var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element,
+ attrs;
+
+ if (compileNode === linkNode) {
+ attrs = templateAttrs;
+ $element = templateAttrs.$$element;
+ } else {
+ $element = jqLite(linkNode);
+ attrs = new Attributes($element, templateAttrs);
+ }
+
+ if (newIsolateScopeDirective) {
+ isolateScope = scope.$new(true);
+ }
+
+ if (boundTranscludeFn) {
+ // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
+ // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
+ transcludeFn = controllersBoundTransclude;
+ transcludeFn.$$boundTransclude = boundTranscludeFn;
+ }
+
if (controllerDirectives) {
- forEach(controllerDirectives, function(directive) {
- var locals = {
- $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
- $element: $element,
- $attrs: attrs,
- $transclude: transcludeFn
- }, controllerInstance;
+ elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
+ }
- controller = directive.controller;
- if (controller == '@') {
- controller = attrs[directive.name];
- }
+ if (newIsolateScopeDirective) {
+ // Initialize isolate scope bindings for new isolate scope directive.
+ compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
+ templateDirective === newIsolateScopeDirective.$$originalDirective)));
+ compile.$$addScopeClass($element, true);
+ isolateScope.$$isolateBindings =
+ newIsolateScopeDirective.$$isolateBindings;
+ initializeDirectiveBindings(scope, attrs, isolateScope,
+ isolateScope.$$isolateBindings,
+ newIsolateScopeDirective, isolateScope);
+ }
+ if (elementControllers) {
+ // Initialize bindToController bindings for new/isolate scopes
+ var scopeDirective = newIsolateScopeDirective || newScopeDirective;
+ var bindings;
+ var controllerForBindings;
+ if (scopeDirective && elementControllers[scopeDirective.name]) {
+ bindings = scopeDirective.$$bindings.bindToController;
+ controller = elementControllers[scopeDirective.name];
- controllerInstance = $controller(controller, locals);
- // For directives with element transclusion the element is a comment,
- // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
- // clean up (http://bugs.jquery.com/ticket/8335).
- // Instead, we save the controllers for the element in a local hash and attach to .data
- // later, once we have the actual element.
- elementControllers[directive.name] = controllerInstance;
- if (!hasElementTranscludeDirective) {
- $element.data('$' + directive.name + 'Controller', controllerInstance);
+ if (controller && controller.identifier && bindings) {
+ controllerForBindings = controller;
+ thisLinkFn.$$destroyBindings =
+ initializeDirectiveBindings(scope, attrs, controller.instance,
+ bindings, scopeDirective);
}
+ }
+ for (i in elementControllers) {
+ controller = elementControllers[i];
+ var controllerResult = controller();
- if (directive.controllerAs) {
- locals.$scope[directive.controllerAs] = controllerInstance;
+ if (controllerResult !== controller.instance) {
+ // If the controller constructor has a return value, overwrite the instance
+ // from setupControllers and update the element data
+ controller.instance = controllerResult;
+ $element.data('$' + i + 'Controller', controllerResult);
+ if (controller === controllerForBindings) {
+ // Remove and re-install bindToController bindings
+ thisLinkFn.$$destroyBindings();
+ thisLinkFn.$$destroyBindings =
+ initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective);
+ }
}
- });
+ }
}
// PRELINKING
- for(i = 0, ii = preLinkFns.length; i < ii; i++) {
- try {
- linkFn = preLinkFns[i];
- linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
- linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn);
- } catch (e) {
- $exceptionHandler(e, startingTag($element));
- }
+ for (i = 0, ii = preLinkFns.length; i < ii; i++) {
+ linkFn = preLinkFns[i];
+ invokeLinkFn(linkFn,
+ linkFn.isolateScope ? isolateScope : scope,
+ $element,
+ attrs,
+ linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
+ transcludeFn
+ );
}
// RECURSION
@@ -6698,22 +8145,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
// POSTLINKING
- for(i = postLinkFns.length - 1; i >= 0; i--) {
- try {
- linkFn = postLinkFns[i];
- linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
- linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn);
- } catch (e) {
- $exceptionHandler(e, startingTag($element));
- }
+ for (i = postLinkFns.length - 1; i >= 0; i--) {
+ linkFn = postLinkFns[i];
+ invokeLinkFn(linkFn,
+ linkFn.isolateScope ? isolateScope : scope,
+ $element,
+ attrs,
+ linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
+ transcludeFn
+ );
}
// This is the function that is injected as `$transclude`.
- function controllersBoundTransclude(scope, cloneAttachFn) {
+ // Note: all arguments are optional!
+ function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
var transcludeControllers;
- // no scope passed
- if (arguments.length < 2) {
+ // No scope passed in:
+ if (!isScope(scope)) {
+ futureParentElement = cloneAttachFn;
cloneAttachFn = scope;
scope = undefined;
}
@@ -6721,8 +8171,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (hasElementTranscludeDirective) {
transcludeControllers = elementControllers;
}
-
- return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers);
+ if (!futureParentElement) {
+ futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
+ }
+ return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
}
}
}
@@ -6753,11 +8205,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (name === ignoreDirective) return null;
var match = null;
if (hasDirectives.hasOwnProperty(name)) {
- for(var directive, directives = $injector.get(name + Suffix),
- i = 0, ii = directives.length; i directive.priority) &&
+ if ((maxPriority === undefined || maxPriority > directive.priority) &&
directive.restrict.indexOf(location) != -1) {
if (startAttrName) {
directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
@@ -6765,13 +8217,34 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
tDirectives.push(directive);
match = directive;
}
- } catch(e) { $exceptionHandler(e); }
+ } catch (e) { $exceptionHandler(e); }
}
}
return match;
}
+ /**
+ * looks up the directive and returns true if it is a multi-element directive,
+ * and therefore requires DOM nodes between -start and -end markers to be grouped
+ * together.
+ *
+ * @param {string} name name of the directive to look up.
+ * @returns true if directive was registered as multi-element.
+ */
+ function directiveIsMultiElement(name) {
+ if (hasDirectives.hasOwnProperty(name)) {
+ for (var directive, directives = $injector.get(name + Suffix),
+ i = 0, ii = directives.length; i < ii; i++) {
+ directive = directives[i];
+ if (directive.multiElement) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* When the element is replaced with HTML template then the new attributes
* on the template need to be merged with the existing attributes in the DOM.
@@ -6821,18 +8294,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
afterTemplateChildLinkFn,
beforeTemplateCompileNode = $compileNode[0],
origAsyncDirective = directives.shift(),
- // The fact that we have to copy and patch the directive seems wrong!
- derivedSyncDirective = extend({}, origAsyncDirective, {
+ derivedSyncDirective = inherit(origAsyncDirective, {
templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
}),
templateUrl = (isFunction(origAsyncDirective.templateUrl))
? origAsyncDirective.templateUrl($compileNode, tAttrs)
- : origAsyncDirective.templateUrl;
+ : origAsyncDirective.templateUrl,
+ templateNamespace = origAsyncDirective.templateNamespace;
$compileNode.empty();
- $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}).
- success(function(content) {
+ $templateRequest(templateUrl)
+ .then(function(content) {
var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
content = denormalizeTemplate(content);
@@ -6841,11 +8314,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (jqLiteIsTextNode(content)) {
$template = [];
} else {
- $template = jqLite(trim(content));
+ $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
}
compileNode = $template[0];
- if ($template.length != 1 || compileNode.nodeType !== 1) {
+ if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
throw $compileMinErr('tplrt',
"Template for directive '{0}' must have exactly one root element. {1}",
origAsyncDirective.name, templateUrl);
@@ -6877,13 +8350,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
});
afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
- while(linkQueue.length) {
+ while (linkQueue.length) {
var scope = linkQueue.shift(),
beforeTemplateLinkNode = linkQueue.shift(),
linkRootElement = linkQueue.shift(),
boundTranscludeFn = linkQueue.shift(),
linkNode = $compileNode[0];
+ if (scope.$$destroyed) continue;
+
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
var oldClasses = beforeTemplateLinkNode.className;
@@ -6892,7 +8367,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// it was cloned therefore we have to clone as well.
linkNode = jqLiteClone(compileNode);
}
-
replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
// Copy in CSS classes from original node
@@ -6904,26 +8378,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
childBoundTranscludeFn = boundTranscludeFn;
}
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
- childBoundTranscludeFn);
+ childBoundTranscludeFn, afterTemplateNodeLinkFn);
}
linkQueue = null;
- }).
- error(function(response, code, headers, config) {
- throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url);
});
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
var childBoundTranscludeFn = boundTranscludeFn;
+ if (scope.$$destroyed) return;
if (linkQueue) {
- linkQueue.push(scope);
- linkQueue.push(node);
- linkQueue.push(rootElement);
- linkQueue.push(childBoundTranscludeFn);
+ linkQueue.push(scope,
+ node,
+ rootElement,
+ childBoundTranscludeFn);
} else {
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
}
- afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
+ afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn,
+ afterTemplateNodeLinkFn);
}
};
}
@@ -6939,40 +8412,61 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return a.index - b.index;
}
-
function assertNoDuplicate(what, previousDirective, directive, element) {
+
+ function wrapModuleNameIfDefined(moduleName) {
+ return moduleName ?
+ (' (module: ' + moduleName + ')') :
+ '';
+ }
+
if (previousDirective) {
- throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}',
- previousDirective.name, directive.name, what, startingTag(element));
+ throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
+ previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
+ directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
}
}
- function addTextInterpolateDirective(directives, text) {
- var interpolateFn = $interpolate(text, true);
- if (interpolateFn) {
- directives.push({
- priority: 0,
- compile: function textInterpolateCompileFn(templateNode) {
- // when transcluding a template that has bindings in the root
- // then we don't have a parent and should do this in the linkFn
- var parent = templateNode.parent(), hasCompileParent = parent.length;
- if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding');
+ function addTextInterpolateDirective(directives, text) {
+ var interpolateFn = $interpolate(text, true);
+ if (interpolateFn) {
+ directives.push({
+ priority: 0,
+ compile: function textInterpolateCompileFn(templateNode) {
+ var templateNodeParent = templateNode.parent(),
+ hasCompileParent = !!templateNodeParent.length;
- return function textInterpolateLinkFn(scope, node) {
- var parent = node.parent(),
- bindings = parent.data('$binding') || [];
- bindings.push(interpolateFn);
- parent.data('$binding', bindings);
- if (!hasCompileParent) safeAddClass(parent, 'ng-binding');
- scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
- node[0].nodeValue = value;
- });
- };
- }
- });
- }
+ // When transcluding a template that has bindings in the root
+ // we don't have a parent and thus need to add the class during linking fn.
+ if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
+
+ return function textInterpolateLinkFn(scope, node) {
+ var parent = node.parent();
+ if (!hasCompileParent) compile.$$addBindingClass(parent);
+ compile.$$addBindingInfo(parent, interpolateFn.expressions);
+ scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
+ node[0].nodeValue = value;
+ });
+ };
+ }
+ });
}
+ }
+
+
+ function wrapTemplate(type, template) {
+ type = lowercase(type || 'html');
+ switch (type) {
+ case 'svg':
+ case 'math':
+ var wrapper = document.createElement('div');
+ wrapper.innerHTML = '<' + type + '>' + template + '' + type + '>';
+ return wrapper.childNodes[0].childNodes;
+ default:
+ return template;
+ }
+ }
function getTrustedContext(node, attrNormalizedName) {
@@ -6982,22 +8476,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var tag = nodeName_(node);
// maction[xlink:href] can source SVG. It's not limited to .
if (attrNormalizedName == "xlinkHref" ||
- (tag == "FORM" && attrNormalizedName == "action") ||
- (tag != "IMG" && (attrNormalizedName == "src" ||
+ (tag == "form" && attrNormalizedName == "action") ||
+ (tag != "img" && (attrNormalizedName == "src" ||
attrNormalizedName == "ngSrc"))) {
return $sce.RESOURCE_URL;
}
}
- function addAttrInterpolateDirective(node, directives, value, name) {
- var interpolateFn = $interpolate(value, true);
+ function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
+ var trustedContext = getTrustedContext(node, name);
+ allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
+
+ var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
// no interpolation found -> ignore
if (!interpolateFn) return;
- if (name === "multiple" && nodeName_(node) === "SELECT") {
+ if (name === "multiple" && nodeName_(node) === "select") {
throw $compileMinErr("selmulti",
"Binding to the 'multiple' attribute is not supported. Element: {0}",
startingTag(node));
@@ -7016,17 +8513,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
"ng- versions (such as ng-click instead of onclick) instead.");
}
- // we need to interpolate again, in case the attribute value has been updated
- // (e.g. by another directive's compile function)
- interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name));
+ // If the attribute has changed since last $interpolate()ed
+ var newValue = attr[name];
+ if (newValue !== value) {
+ // we need to interpolate again since the attribute value has been updated
+ // (e.g. by another directive's compile function)
+ // ensure unset/empty values make interpolateFn falsy
+ interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
+ value = newValue;
+ }
// if attribute was updated so that there is no interpolation going on we don't want to
// register any observers
if (!interpolateFn) return;
- // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the
- // actual attr value
+ // initialize attr object so that it's ready in case we need the value for isolate
+ // scope initialization, otherwise the value would not be available from isolate
+ // directive's linking fn during linking phase
attr[name] = interpolateFn(scope);
+
($$observers[name] || ($$observers[name] = [])).$$inter = true;
(attr.$$observers && attr.$$observers[name].$$scope || scope).
$watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
@@ -7036,7 +8541,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
//skip animations when the first digest occurs (when
//both the new and the old values are the same) since
//the CSS classes are the non-interpolated values
- if(name === 'class' && newValue != oldValue) {
+ if (name === 'class' && newValue != oldValue) {
attr.$updateClass(newValue, oldValue);
} else {
attr.$set(name, newValue);
@@ -7066,7 +8571,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
i, ii;
if ($rootElement) {
- for(i = 0, ii = $rootElement.length; i < ii; i++) {
+ for (i = 0, ii = $rootElement.length; i < ii; i++) {
if ($rootElement[i] == firstElementToRemove) {
$rootElement[i++] = newNode;
for (var j = i, j2 = j + removeCount - 1,
@@ -7079,6 +8584,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
}
$rootElement.length -= removeCount - 1;
+
+ // If the replaced element is also the jQuery .context then replace it
+ // .context is a deprecated jQuery api, so we should set it only when jQuery set it
+ // http://api.jquery.com/context/
+ if ($rootElement.context === firstElementToRemove) {
+ $rootElement.context = newNode;
+ }
break;
}
}
@@ -7087,9 +8599,35 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (parent) {
parent.replaceChild(newNode, firstElementToRemove);
}
+
+ // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
var fragment = document.createDocumentFragment();
fragment.appendChild(firstElementToRemove);
- newNode[jqLite.expando] = firstElementToRemove[jqLite.expando];
+
+ if (jqLite.hasData(firstElementToRemove)) {
+ // Copy over user data (that includes Angular's $scope etc.). Don't copy private
+ // data here because there's no public interface in jQuery to do that and copying over
+ // event listeners (which is the main use of private data) wouldn't work anyway.
+ jqLite(newNode).data(jqLite(firstElementToRemove).data());
+
+ // Remove data of the replaced element. We cannot just call .remove()
+ // on the element it since that would deallocate scope that is needed
+ // for the new node. Instead, remove the data "manually".
+ if (!jQuery) {
+ delete jqLite.cache[firstElementToRemove[jqLite.expando]];
+ } else {
+ // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
+ // the replaced element. The cleanData version monkey-patched by Angular would cause
+ // the scope to be trashed and we do need the very same scope to work with the new
+ // element. However, we cannot just cache the non-patched version and use it here as
+ // that would break if another library patches the method after Angular does (one
+ // example is jQuery UI). Instead, set a flag indicating scope destroying should be
+ // skipped this one time.
+ skipDestroyOnNextJQueryCleanData = true;
+ jQuery.cleanData([firstElementToRemove]);
+ }
+ }
+
for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
var element = elementsToRemove[k];
jqLite(element).remove(); // must do this way to clean up expando
@@ -7105,19 +8643,125 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function cloneAndAnnotateFn(fn, annotation) {
return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
}
+
+
+ function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
+ try {
+ linkFn(scope, $element, attrs, controllers, transcludeFn);
+ } catch (e) {
+ $exceptionHandler(e, startingTag($element));
+ }
+ }
+
+
+ // Set up $watches for isolate scope and controller bindings. This process
+ // only occurs for isolate scopes and new scopes with controllerAs.
+ function initializeDirectiveBindings(scope, attrs, destination, bindings,
+ directive, newScope) {
+ var onNewScopeDestroyed;
+ forEach(bindings, function(definition, scopeName) {
+ var attrName = definition.attrName,
+ optional = definition.optional,
+ mode = definition.mode, // @, =, or &
+ lastValue,
+ parentGet, parentSet, compare;
+
+ if (!hasOwnProperty.call(attrs, attrName)) {
+ // In the case of user defined a binding with the same name as a method in Object.prototype but didn't set
+ // the corresponding attribute. We need to make sure subsequent code won't access to the prototype function
+ attrs[attrName] = undefined;
+ }
+
+ switch (mode) {
+
+ case '@':
+ if (!attrs[attrName] && !optional) {
+ destination[scopeName] = undefined;
+ }
+
+ attrs.$observe(attrName, function(value) {
+ destination[scopeName] = value;
+ });
+ attrs.$$observers[attrName].$$scope = scope;
+ if (attrs[attrName]) {
+ // If the attribute has been provided then we trigger an interpolation to ensure
+ // the value is there for use in the link fn
+ destination[scopeName] = $interpolate(attrs[attrName])(scope);
+ }
+ break;
+
+ case '=':
+ if (optional && !attrs[attrName]) {
+ return;
+ }
+ parentGet = $parse(attrs[attrName]);
+
+ if (parentGet.literal) {
+ compare = equals;
+ } else {
+ compare = function(a, b) { return a === b || (a !== a && b !== b); };
+ }
+ parentSet = parentGet.assign || function() {
+ // reset the change, or we will throw this exception on every $digest
+ lastValue = destination[scopeName] = parentGet(scope);
+ throw $compileMinErr('nonassign',
+ "Expression '{0}' used with directive '{1}' is non-assignable!",
+ attrs[attrName], directive.name);
+ };
+ lastValue = destination[scopeName] = parentGet(scope);
+ var parentValueWatch = function parentValueWatch(parentValue) {
+ if (!compare(parentValue, destination[scopeName])) {
+ // we are out of sync and need to copy
+ if (!compare(parentValue, lastValue)) {
+ // parent changed and it has precedence
+ destination[scopeName] = parentValue;
+ } else {
+ // if the parent can be assigned then do so
+ parentSet(scope, parentValue = destination[scopeName]);
+ }
+ }
+ return lastValue = parentValue;
+ };
+ parentValueWatch.$stateful = true;
+ var unwatch;
+ if (definition.collection) {
+ unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
+ } else {
+ unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
+ }
+ onNewScopeDestroyed = (onNewScopeDestroyed || []);
+ onNewScopeDestroyed.push(unwatch);
+ break;
+
+ case '&':
+ parentGet = $parse(attrs[attrName]);
+
+ // Don't assign noop to destination if expression is not valid
+ if (parentGet === noop && optional) break;
+
+ destination[scopeName] = function(locals) {
+ return parentGet(scope, locals);
+ };
+ break;
+ }
+ });
+ var destroyBindings = onNewScopeDestroyed ? function destroyBindings() {
+ for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) {
+ onNewScopeDestroyed[i]();
+ }
+ } : noop;
+ if (newScope && destroyBindings !== noop) {
+ newScope.$on('$destroy', destroyBindings);
+ return noop;
+ }
+ return destroyBindings;
+ }
}];
}
-var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
+var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
/**
* Converts all accepted directives format into proper directive name.
- * All of these will become 'myDirective':
- * my:Directive
- * my-directive
- * x-my-directive
- * data-my:directive
- *
- * Also there is special case for Moz prefix starting with upper case letter.
* @param name Name to normalize
*/
function directiveNormalize(name) {
@@ -7174,7 +8818,7 @@ function nodesetLinkingFn(
/* NodeList */ nodeList,
/* Element */ rootElement,
/* function(Function) */ boundTranscludeFn
-){}
+) {}
function directiveLinkingFn(
/* nodesetLinkingFn */ nodesetLinkingFn,
@@ -7182,7 +8826,7 @@ function directiveLinkingFn(
/* Node */ node,
/* Element */ rootElement,
/* function(Function) */ boundTranscludeFn
-){}
+) {}
function tokenDifference(str1, str2) {
var values = '',
@@ -7190,16 +8834,46 @@ function tokenDifference(str1, str2) {
tokens2 = str2.split(/\s+/);
outer:
- for(var i = 0; i < tokens1.length; i++) {
+ for (var i = 0; i < tokens1.length; i++) {
var token = tokens1[i];
- for(var j = 0; j < tokens2.length; j++) {
- if(token == tokens2[j]) continue outer;
+ for (var j = 0; j < tokens2.length; j++) {
+ if (token == tokens2[j]) continue outer;
}
values += (values.length > 0 ? ' ' : '') + token;
}
return values;
}
+function removeComments(jqNodes) {
+ jqNodes = jqLite(jqNodes);
+ var i = jqNodes.length;
+
+ if (i <= 1) {
+ return jqNodes;
+ }
+
+ while (i--) {
+ var node = jqNodes[i];
+ if (node.nodeType === NODE_TYPE_COMMENT) {
+ splice.call(jqNodes, i, 1);
+ }
+ }
+ return jqNodes;
+}
+
+var $controllerMinErr = minErr('$controller');
+
+
+var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
+function identifierForController(controller, ident) {
+ if (ident && isString(ident)) return ident;
+ if (isString(controller)) {
+ var match = CNTRL_REG.exec(controller);
+ if (match) return match[3];
+ }
+}
+
+
/**
* @ngdoc provider
* @name $controllerProvider
@@ -7212,8 +8886,7 @@ function tokenDifference(str1, str2) {
*/
function $ControllerProvider() {
var controllers = {},
- CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
-
+ globals = false;
/**
* @ngdoc method
@@ -7232,6 +8905,15 @@ function $ControllerProvider() {
}
};
+ /**
+ * @ngdoc method
+ * @name $controllerProvider#allowGlobals
+ * @description If called, allows `$controller` to find controller constructors on `window`
+ */
+ this.allowGlobals = function() {
+ globals = true;
+ };
+
this.$get = ['$injector', '$window', function($injector, $window) {
@@ -7246,7 +8928,12 @@ function $ControllerProvider() {
*
* * check if a controller with given name is registered via `$controllerProvider`
* * check if evaluating the string on the current scope returns a constructor
- * * check `window[constructor]` on the global `window` object
+ * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
+ * `window` object (not recommended)
+ *
+ * The string can use the `controller as property` syntax, where the controller instance is published
+ * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
+ * to work correctly.
*
* @param {Object} locals Injection locals for Controller.
* @return {Object} Instance of given controller.
@@ -7257,34 +8944,91 @@ function $ControllerProvider() {
* It's just a simple call to {@link auto.$injector $injector}, but extracted into
* a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
*/
- return function(expression, locals) {
+ return function(expression, locals, later, ident) {
+ // PRIVATE API:
+ // param `later` --- indicates that the controller's constructor is invoked at a later time.
+ // If true, $controller will allocate the object with the correct
+ // prototype chain, but will not invoke the controller until a returned
+ // callback is invoked.
+ // param `ident` --- An optional label which overrides the label parsed from the controller
+ // expression, if any.
var instance, match, constructor, identifier;
+ later = later === true;
+ if (ident && isString(ident)) {
+ identifier = ident;
+ }
- if(isString(expression)) {
- match = expression.match(CNTRL_REG),
+ if (isString(expression)) {
+ match = expression.match(CNTRL_REG);
+ if (!match) {
+ throw $controllerMinErr('ctrlfmt',
+ "Badly formed controller string '{0}'. " +
+ "Must match `__name__ as __id__` or `__name__`.", expression);
+ }
constructor = match[1],
- identifier = match[3];
+ identifier = identifier || match[3];
expression = controllers.hasOwnProperty(constructor)
? controllers[constructor]
- : getter(locals.$scope, constructor, true) || getter($window, constructor, true);
+ : getter(locals.$scope, constructor, true) ||
+ (globals ? getter($window, constructor, true) : undefined);
assertArgFn(expression, constructor, true);
}
- instance = $injector.instantiate(expression, locals);
+ if (later) {
+ // Instantiate controller later:
+ // This machinery is used to create an instance of the object before calling the
+ // controller's constructor itself.
+ //
+ // This allows properties to be added to the controller before the constructor is
+ // invoked. Primarily, this is used for isolate scope bindings in $compile.
+ //
+ // This feature is not intended for use by applications, and is thus not documented
+ // publicly.
+ // Object creation: http://jsperf.com/create-constructor/2
+ var controllerPrototype = (isArray(expression) ?
+ expression[expression.length - 1] : expression).prototype;
+ instance = Object.create(controllerPrototype || null);
- if (identifier) {
- if (!(locals && typeof locals.$scope === 'object')) {
- throw minErr('$controller')('noscp',
- "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
- constructor || expression.name, identifier);
+ if (identifier) {
+ addIdentifier(locals, identifier, instance, constructor || expression.name);
}
- locals.$scope[identifier] = instance;
+ var instantiate;
+ return instantiate = extend(function() {
+ var result = $injector.invoke(expression, instance, locals, constructor);
+ if (result !== instance && (isObject(result) || isFunction(result))) {
+ instance = result;
+ if (identifier) {
+ // If result changed, re-assign controllerAs value to scope.
+ addIdentifier(locals, identifier, instance, constructor || expression.name);
+ }
+ }
+ return instance;
+ }, {
+ instance: instance,
+ identifier: identifier
+ });
+ }
+
+ instance = $injector.instantiate(expression, locals, constructor);
+
+ if (identifier) {
+ addIdentifier(locals, identifier, instance, constructor || expression.name);
}
return instance;
};
+
+ function addIdentifier(locals, identifier, instance, name) {
+ if (!(locals && isObject(locals.$scope))) {
+ throw minErr('$controller')('noscp',
+ "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
+ name, identifier);
+ }
+
+ locals.$scope[identifier] = instance;
+ }
}];
}
@@ -7313,8 +9057,8 @@ function $ControllerProvider() {
*/
-function $DocumentProvider(){
- this.$get = ['$window', function(window){
+function $DocumentProvider() {
+ this.$get = ['$window', function(window) {
return jqLite(window.document);
}];
}
@@ -7335,8 +9079,8 @@ function $DocumentProvider(){
* ## Example:
*
* ```js
- * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () {
- * return function (exception, cause) {
+ * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
+ * return function(exception, cause) {
* exception.message += ' (caused by "' + cause + '")';
* throw exception;
* };
@@ -7346,6 +9090,14 @@ function $DocumentProvider(){
* This example will override the normal action of `$exceptionHandler`, to make angular
* exceptions fail hard when they happen, instead of just logging to the console.
*
+ *
+ * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
+ * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
+ * (unless executed during a digest).
+ *
+ * If you wish, you can manually delegate exceptions, e.g.
+ * `try { ... } catch(e) { $exceptionHandler(e); }`
+ *
* @param {Error} exception Exception associated with the error.
* @param {string=} cause optional information about the context in which
* the error was thrown.
@@ -7359,6 +9111,153 @@ function $ExceptionHandlerProvider() {
}];
}
+var APPLICATION_JSON = 'application/json';
+var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
+var JSON_START = /^\[|^\{(?!\{)/;
+var JSON_ENDS = {
+ '[': /]$/,
+ '{': /}$/
+};
+var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
+
+function serializeValue(v) {
+ if (isObject(v)) {
+ return isDate(v) ? v.toISOString() : toJson(v);
+ }
+ return v;
+}
+
+
+function $HttpParamSerializerProvider() {
+ /**
+ * @ngdoc service
+ * @name $httpParamSerializer
+ * @description
+ *
+ * Default {@link $http `$http`} params serializer that converts objects to strings
+ * according to the following rules:
+ *
+ * * `{'foo': 'bar'}` results in `foo=bar`
+ * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
+ * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
+ * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
+ *
+ * Note that serializer will sort the request parameters alphabetically.
+ * */
+
+ this.$get = function() {
+ return function ngParamSerializer(params) {
+ if (!params) return '';
+ var parts = [];
+ forEachSorted(params, function(value, key) {
+ if (value === null || isUndefined(value)) return;
+ if (isArray(value)) {
+ forEach(value, function(v, k) {
+ parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
+ });
+ } else {
+ parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
+ }
+ });
+
+ return parts.join('&');
+ };
+ };
+}
+
+function $HttpParamSerializerJQLikeProvider() {
+ /**
+ * @ngdoc service
+ * @name $httpParamSerializerJQLike
+ * @description
+ *
+ * Alternative {@link $http `$http`} params serializer that follows
+ * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
+ * The serializer will also sort the params alphabetically.
+ *
+ * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
+ *
+ * ```js
+ * $http({
+ * url: myUrl,
+ * method: 'GET',
+ * params: myParams,
+ * paramSerializer: '$httpParamSerializerJQLike'
+ * });
+ * ```
+ *
+ * It is also possible to set it as the default `paramSerializer` in the
+ * {@link $httpProvider#defaults `$httpProvider`}.
+ *
+ * Additionally, you can inject the serializer and use it explicitly, for example to serialize
+ * form data for submission:
+ *
+ * ```js
+ * .controller(function($http, $httpParamSerializerJQLike) {
+ * //...
+ *
+ * $http({
+ * url: myUrl,
+ * method: 'POST',
+ * data: $httpParamSerializerJQLike(myData),
+ * headers: {
+ * 'Content-Type': 'application/x-www-form-urlencoded'
+ * }
+ * });
+ *
+ * });
+ * ```
+ *
+ * */
+ this.$get = function() {
+ return function jQueryLikeParamSerializer(params) {
+ if (!params) return '';
+ var parts = [];
+ serialize(params, '', true);
+ return parts.join('&');
+
+ function serialize(toSerialize, prefix, topLevel) {
+ if (toSerialize === null || isUndefined(toSerialize)) return;
+ if (isArray(toSerialize)) {
+ forEach(toSerialize, function(value) {
+ serialize(value, prefix + '[]');
+ });
+ } else if (isObject(toSerialize) && !isDate(toSerialize)) {
+ forEachSorted(toSerialize, function(value, key) {
+ serialize(value, prefix +
+ (topLevel ? '' : '[') +
+ key +
+ (topLevel ? '' : ']'));
+ });
+ } else {
+ parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
+ }
+ }
+ };
+ };
+}
+
+function defaultHttpResponseTransform(data, headers) {
+ if (isString(data)) {
+ // Strip json vulnerability protection prefix and trim whitespace
+ var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
+
+ if (tempData) {
+ var contentType = headers('Content-Type');
+ if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
+ data = fromJson(tempData);
+ }
+ }
+ }
+
+ return data;
+}
+
+function isJsonLike(str) {
+ var jsonStart = str.match(JSON_START);
+ return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
+}
+
/**
* Parse headers into key value object
*
@@ -7366,19 +9265,24 @@ function $ExceptionHandlerProvider() {
* @returns {Object} Parsed headers as key value object
*/
function parseHeaders(headers) {
- var parsed = {}, key, val, i;
-
- if (!headers) return parsed;
-
- forEach(headers.split('\n'), function(line) {
- i = line.indexOf(':');
- key = lowercase(trim(line.substr(0, i)));
- val = trim(line.substr(i + 1));
+ var parsed = createMap(), i;
+ function fillInParsed(key, val) {
if (key) {
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
}
- });
+ }
+
+ if (isString(headers)) {
+ forEach(headers.split('\n'), function(line) {
+ i = line.indexOf(':');
+ fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
+ });
+ } else if (isObject(headers)) {
+ forEach(headers, function(headerVal, headerKey) {
+ fillInParsed(lowercase(headerKey), trim(headerVal));
+ });
+ }
return parsed;
}
@@ -7397,13 +9301,17 @@ function parseHeaders(headers) {
* - if called with no arguments returns an object containing all headers.
*/
function headersGetter(headers) {
- var headersObj = isObject(headers) ? headers : undefined;
+ var headersObj;
return function(name) {
if (!headersObj) headersObj = parseHeaders(headers);
if (name) {
- return headersObj[lowercase(name)] || null;
+ var value = headersObj[lowercase(name)];
+ if (value === void 0) {
+ value = null;
+ }
+ return value;
}
return headersObj;
@@ -7417,16 +9325,18 @@ function headersGetter(headers) {
* This function is used for both request and response transforming
*
* @param {*} data Data to transform.
- * @param {function(string=)} headers Http headers getter fn.
+ * @param {function(string=)} headers HTTP headers getter fn.
+ * @param {number} status HTTP status code of the response.
* @param {(Function|Array.)} fns Function or an array of functions.
* @returns {*} Transformed data.
*/
-function transformData(data, headers, fns) {
- if (isFunction(fns))
- return fns(data, headers);
+function transformData(data, headers, status, fns) {
+ if (isFunction(fns)) {
+ return fns(data, headers, status);
+ }
forEach(fns, function(fn) {
- data = fn(data, headers);
+ data = fn(data, headers, status);
});
return data;
@@ -7445,11 +9355,6 @@ function isSuccess(status) {
* Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
* */
function $HttpProvider() {
- var JSON_START = /^\s*(\[|\{[^\{])/,
- JSON_END = /[\}\]]\s*$/,
- PROTECTION_PREFIX = /^\)\]\}',?\n/,
- CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'};
-
/**
* @ngdoc property
* @name $httpProvider#defaults
@@ -7457,6 +9362,11 @@ function $HttpProvider() {
*
* Object containing default values for all {@link ng.$http $http} requests.
*
+ * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
+ * that will provide the cache for all requests who set their `cache` property to `true`.
+ * If you set the `defaults.cache = false` then only requests that specify their own custom
+ * cache object will be cached. See {@link $http#caching $http Caching} for more information.
+ *
* - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
* Defaults value is `'XSRF-TOKEN'`.
*
@@ -7470,22 +9380,21 @@ function $HttpProvider() {
* - **`defaults.headers.post`**
* - **`defaults.headers.put`**
* - **`defaults.headers.patch`**
+ *
+ *
+ * - **`defaults.paramSerializer`** - `{string|function(Object):string}` - A function
+ * used to the prepare string representation of request parameters (specified as an object).
+ * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
+ * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
+ *
**/
var defaults = this.defaults = {
// transform incoming response data
- transformResponse: [function(data) {
- if (isString(data)) {
- // strip json vulnerability protection prefix
- data = data.replace(PROTECTION_PREFIX, '');
- if (JSON_START.test(data) && JSON_END.test(data))
- data = fromJson(data);
- }
- return data;
- }],
+ transformResponse: [defaultHttpResponseTransform],
// transform outgoing request data
transformRequest: [function(d) {
- return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d;
+ return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
}],
// default headers
@@ -7499,26 +9408,65 @@ function $HttpProvider() {
},
xsrfCookieName: 'XSRF-TOKEN',
- xsrfHeaderName: 'X-XSRF-TOKEN'
+ xsrfHeaderName: 'X-XSRF-TOKEN',
+
+ paramSerializer: '$httpParamSerializer'
+ };
+
+ var useApplyAsync = false;
+ /**
+ * @ngdoc method
+ * @name $httpProvider#useApplyAsync
+ * @description
+ *
+ * Configure $http service to combine processing of multiple http responses received at around
+ * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
+ * significant performance improvement for bigger applications that make many HTTP requests
+ * concurrently (common during application bootstrap).
+ *
+ * Defaults to false. If no value is specified, returns the current configured value.
+ *
+ * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
+ * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
+ * to load and share the same digest cycle.
+ *
+ * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
+ * otherwise, returns the current configured value.
+ **/
+ this.useApplyAsync = function(value) {
+ if (isDefined(value)) {
+ useApplyAsync = !!value;
+ return this;
+ }
+ return useApplyAsync;
};
/**
- * Are ordered by request, i.e. they are applied in the same order as the
+ * @ngdoc property
+ * @name $httpProvider#interceptors
+ * @description
+ *
+ * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
+ * pre-processing of request or postprocessing of responses.
+ *
+ * These service factories are ordered by request, i.e. they are applied in the same order as the
* array, on request, but reverse order, on response.
- */
+ *
+ * {@link ng.$http#interceptors Interceptors detailed info}
+ **/
var interceptorFactories = this.interceptors = [];
- /**
- * For historical reasons, response interceptors are ordered by the order in which
- * they are applied to the response. (This is the opposite of interceptorFactories)
- */
- var responseInterceptorFactories = this.responseInterceptors = [];
-
- this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
- function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
+ this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
+ function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
var defaultCache = $cacheFactory('$http');
+ /**
+ * Make sure that default param serializer is exposed as a function
+ */
+ defaults.paramSerializer = isString(defaults.paramSerializer) ?
+ $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
+
/**
* Interceptors stored in reverse order. Inner interceptors before outer interceptors.
* The reversal is needed so that we can build up the interception chain around the
@@ -7531,27 +9479,6 @@ function $HttpProvider() {
? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
});
- forEach(responseInterceptorFactories, function(interceptorFactory, index) {
- var responseFn = isString(interceptorFactory)
- ? $injector.get(interceptorFactory)
- : $injector.invoke(interceptorFactory);
-
- /**
- * Response interceptors go before "around" interceptors (no real reason, just
- * had to pick one.) But they are already reversed, so we can't use unshift, hence
- * the splice.
- */
- reversedInterceptors.splice(index, 0, {
- response: function(response) {
- return responseFn($q.when(response));
- },
- responseError: function(response) {
- return responseFn($q.reject(response));
- }
- });
- });
-
-
/**
* @ngdoc service
* @kind function
@@ -7578,13 +9505,14 @@ function $HttpProvider() {
* it is important to familiarize yourself with these APIs and the guarantees they provide.
*
*
- * # General usage
+ * ## General usage
* The `$http` service is a function which takes a single argument — a configuration object —
* that is used to generate an HTTP request and returns a {@link ng.$q promise}
* with two $http specific methods: `success` and `error`.
*
* ```js
- * $http({method: 'GET', url: '/someUrl'}).
+ * // Simple GET request example :
+ * $http.get('/someUrl').
* success(function(data, status, headers, config) {
* // this callback will be called asynchronously
* // when the response is available
@@ -7595,6 +9523,20 @@ function $HttpProvider() {
* });
* ```
*
+ * ```js
+ * // Simple POST request example (passing data) :
+ * $http.post('/someUrl', {msg:'hello word!'}).
+ * success(function(data, status, headers, config) {
+ * // this callback will be called asynchronously
+ * // when the response is available
+ * }).
+ * error(function(data, status, headers, config) {
+ * // called asynchronously if an error occurs
+ * // or server returns response with an error status.
+ * });
+ * ```
+ *
+ *
* Since the returned value of calling the $http function is a `promise`, you can also use
* the `then` method to register callbacks, and these callbacks will receive a single argument –
* an object representing the response. See the API signature and type info below for more
@@ -7605,7 +9547,7 @@ function $HttpProvider() {
* XMLHttpRequest will transparently follow it, meaning that the error callback will not be
* called for such responses.
*
- * # Writing Unit Tests that use $http
+ * ## Writing Unit Tests that use $http
* When unit testing (using {@link ngMock ngMock}), it is necessary to call
* {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
* request using trained responses.
@@ -7616,7 +9558,7 @@ function $HttpProvider() {
* $httpBackend.flush();
* ```
*
- * # Shortcut methods
+ * ## Shortcut methods
*
* Shortcut methods are also available. All shortcut methods require passing in the URL, and
* request data must be passed in for POST/PUT requests.
@@ -7637,7 +9579,7 @@ function $HttpProvider() {
* - {@link ng.$http#patch $http.patch}
*
*
- * # Setting HTTP Headers
+ * ## Setting HTTP Headers
*
* The $http service will automatically add certain HTTP headers to all requests. These defaults
* can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
@@ -7653,7 +9595,7 @@ function $HttpProvider() {
* To add or overwrite these defaults, simply add or remove a property from these configuration
* objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
* with the lowercased HTTP method name as the key, e.g.
- * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }.
+ * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
*
* The defaults can also be set at runtime via the `$http.defaults` object in the same
* fashion. For example:
@@ -7667,37 +9609,85 @@ function $HttpProvider() {
* In addition, you can supply a `headers` property in the config object passed when
* calling `$http(config)`, which overrides the defaults without changing them globally.
*
+ * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
+ * Use the `headers` property, setting the desired header to `undefined`. For example:
*
- * # Transforming Requests and Responses
+ * ```js
+ * var req = {
+ * method: 'POST',
+ * url: 'http://example.com',
+ * headers: {
+ * 'Content-Type': undefined
+ * },
+ * data: { test: 'test' }
+ * }
*
- * Both requests and responses can be transformed using transform functions. By default, Angular
- * applies these transformations:
+ * $http(req).success(function(){...}).error(function(){...});
+ * ```
*
- * Request transformations:
+ * ## Transforming Requests and Responses
+ *
+ * Both requests and responses can be transformed using transformation functions: `transformRequest`
+ * and `transformResponse`. These properties can be a single function that returns
+ * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
+ * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
+ *
+ * ### Default Transformations
+ *
+ * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
+ * `defaults.transformResponse` properties. If a request does not provide its own transformations
+ * then these will be applied.
+ *
+ * You can augment or replace the default transformations by modifying these properties by adding to or
+ * replacing the array.
+ *
+ * Angular provides the following default transformations:
+ *
+ * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`):
*
* - If the `data` property of the request configuration object contains an object, serialize it
* into JSON format.
*
- * Response transformations:
+ * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`):
*
* - If XSRF prefix is detected, strip it (see Security Considerations section below).
* - If JSON response is detected, deserialize it using a JSON parser.
*
- * To globally augment or override the default transforms, modify the
- * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse`
- * properties. These properties are by default an array of transform functions, which allows you
- * to `push` or `unshift` a new transformation function into the transformation chain. You can
- * also decide to completely override any default transformations by assigning your
- * transformation functions to these properties directly without the array wrapper. These defaults
- * are again available on the $http factory at run-time, which may be useful if you have run-time
- * services you wish to be involved in your transformations.
*
- * Similarly, to locally override the request/response transforms, augment the
- * `transformRequest` and/or `transformResponse` properties of the configuration object passed
+ * ### Overriding the Default Transformations Per Request
+ *
+ * If you wish override the request/response transformations only for a single request then provide
+ * `transformRequest` and/or `transformResponse` properties on the configuration object passed
* into `$http`.
*
+ * Note that if you provide these properties on the config object the default transformations will be
+ * overwritten. If you wish to augment the default transformations then you must include them in your
+ * local transformation array.
*
- * # Caching
+ * The following code demonstrates adding a new response transformation to be run after the default response
+ * transformations have been run.
+ *
+ * ```js
+ * function appendTransform(defaults, transform) {
+ *
+ * // We can't guarantee that the default transformation is an array
+ * defaults = angular.isArray(defaults) ? defaults : [defaults];
+ *
+ * // Append the new transformation to the defaults
+ * return defaults.concat(transform);
+ * }
+ *
+ * $http({
+ * url: '...',
+ * method: 'GET',
+ * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
+ * return doTransform(value);
+ * })
+ * });
+ * ```
+ *
+ *
+ * ## Caching
*
* To enable caching, set the request configuration `cache` property to `true` (to use default
* cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}).
@@ -7714,13 +9704,13 @@ function $HttpProvider() {
*
* You can change the default cache to a new object (built with
* {@link ng.$cacheFactory `$cacheFactory`}) by updating the
- * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set
+ * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set
* their `cache` property to `true` will now use this cache object.
*
* If you set the default cache to `false` then only requests that specify their own custom
* cache object will be cached.
*
- * # Interceptors
+ * ## Interceptors
*
* Before you start creating interceptors, be sure to understand the
* {@link ng.$q $q and deferred/promise APIs}.
@@ -7805,52 +9795,7 @@ function $HttpProvider() {
* });
* ```
*
- * # Response interceptors (DEPRECATED)
- *
- * Before you start creating interceptors, be sure to understand the
- * {@link ng.$q $q and deferred/promise APIs}.
- *
- * For purposes of global error handling, authentication or any kind of synchronous or
- * asynchronous preprocessing of received responses, it is desirable to be able to intercept
- * responses for http requests before they are handed over to the application code that
- * initiated these requests. The response interceptors leverage the {@link ng.$q
- * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing.
- *
- * The interceptors are service factories that are registered with the $httpProvider by
- * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and
- * injected with dependencies (if specified) and returns the interceptor — a function that
- * takes a {@link ng.$q promise} and returns the original or a new promise.
- *
- * ```js
- * // register the interceptor as a service
- * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
- * return function(promise) {
- * return promise.then(function(response) {
- * // do something on success
- * return response;
- * }, function(response) {
- * // do something on error
- * if (canRecover(response)) {
- * return responseOrNewPromise
- * }
- * return $q.reject(response);
- * });
- * }
- * });
- *
- * $httpProvider.responseInterceptors.push('myHttpInterceptor');
- *
- *
- * // register the interceptor via an anonymous factory
- * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
- * return function(promise) {
- * // same as above
- * }
- * });
- * ```
- *
- *
- * # Security Considerations
+ * ## Security Considerations
*
* When designing web applications, consider security threats from:
*
@@ -7861,7 +9806,7 @@ function $HttpProvider() {
* pre-configured with strategies that address these issues, but for this to work backend server
* cooperation is required.
*
- * ## JSON Vulnerability Protection
+ * ### JSON Vulnerability Protection
*
* A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
* allows third party website to turn your JSON resource URL into
@@ -7883,7 +9828,7 @@ function $HttpProvider() {
* Angular will strip the prefix, before processing the JSON.
*
*
- * ## Cross Site Request Forgery (XSRF) Protection
+ * ### Cross Site Request Forgery (XSRF) Protection
*
* [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which
* an unauthorized site can gain your user's private data. Angular provides a mechanism
@@ -7906,29 +9851,42 @@ function $HttpProvider() {
* properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
* or the per-request config object.
*
+ * In order to prevent collisions in environments where multiple Angular apps share the
+ * same domain or subdomain, we recommend that each application uses unique cookie name.
+ *
*
* @param {object} config Object describing the request to be made and how it should be
* processed. The object has following properties:
*
* - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
* - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
- * - **params** – `{Object.}` – Map of strings or objects which will be turned
- * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be
- * JSONified.
+ * - **params** – `{Object.}` – Map of strings or objects which will be serialized
+ * with the `paramSerializer` and appended as GET parameters.
* - **data** – `{string|Object}` – Data to be sent as the request message data.
* - **headers** – `{Object}` – Map of strings or functions which return strings representing
* HTTP headers to send to the server. If the return value of a function is null, the
- * header will not be sent.
+ * header will not be sent. Functions accept a config object as an argument.
* - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
* - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
* - **transformRequest** –
* `{function(data, headersGetter)|Array.}` –
* transform function or an array of such functions. The transform function takes the http
* request body and headers and returns its transformed (typically serialized) version.
+ * See {@link ng.$http#overriding-the-default-transformations-per-request
+ * Overriding the Default Transformations}
* - **transformResponse** –
- * `{function(data, headersGetter)|Array.}` –
+ * `{function(data, headersGetter, status)|Array.}` –
* transform function or an array of such functions. The transform function takes the http
- * response body and headers and returns its transformed (typically deserialized) version.
+ * response body, headers and status and returns its transformed (typically deserialized) version.
+ * See {@link ng.$http#overriding-the-default-transformations-per-request
+ * Overriding the Default TransformationjqLiks}
+ * - **paramSerializer** - `{string|function(Object):string}` - A function used to
+ * prepare the string representation of request parameters (specified as an object).
+ * If specified as string, it is interpreted as function registered with the
+ * {@link $injector $injector}, which means you can create your own serializer
+ * by registering it as a {@link auto.$provide#service service}.
+ * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
+ * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
* - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
* GET request, otherwise if a cache instance built with
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
@@ -7939,7 +9897,7 @@ function $HttpProvider() {
* XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
* for more information.
* - **responseType** - `{string}` - see
- * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
+ * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
*
* @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
* standard `then` method and two http specific methods: `success` and `error`. The `then`
@@ -7964,11 +9922,11 @@ function $HttpProvider() {