From 3cc0a372e1ed54d0186dbbe9fdba830f2c485901 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Fri, 19 Dec 2014 12:00:32 +0100 Subject: [PATCH] Update to angular 1.3.7 --- src/lib/angular/angular-animate.js | 245 ++-- src/lib/angular/angular-mocks.js | 54 +- src/lib/angular/angular-route.js | 29 +- src/lib/angular/angular.js | 1787 ++++++++++++++++------------ 4 files changed, 1225 insertions(+), 890 deletions(-) diff --git a/src/lib/angular/angular-animate.js b/src/lib/angular/angular-animate.js index 74605f8..7d09d78 100644 --- a/src/lib/angular/angular-animate.js +++ b/src/lib/angular/angular-animate.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.3.2 + * @license AngularJS v1.3.7 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ @@ -19,7 +19,7 @@ * # 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: + * 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. * @@ -161,8 +161,8 @@ * ### Structural transition animations * * Structural transitions (such as enter, leave and move) will always apply a `0s none` transition - * value to force the browser into rendering the styles defined in the setup (.ng-enter, .ng-leave - * or .ng-move) class. This means that any active transition animations operating on the element + * value to force the browser into rendering the styles defined in the setup (`.ng-enter`, `.ng-leave` + * or `.ng-move`) class. This means that any active transition animations operating on the element * will be cut off to make way for the enter, leave or move animation. * * ### Class-based transition animations @@ -245,7 +245,7 @@ * You then configure `$animate` to enforce this prefix: * * ```js - * $animateProvider.classNamePrefix(/animate-/); + * $animateProvider.classNameFilter(/animate-/); * ``` * * @@ -479,11 +479,12 @@ angular.module('ngAnimate', ['ng']) function isMatchingElement(elm1, elm2) { return extractElementNode(elm1) == extractElementNode(elm2); } - + var $$jqLite; $provide.decorator('$animate', - ['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest', - function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest) { + ['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest', '$$jqLite', + function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest, $$$jqLite) { + $$jqLite = $$$jqLite; $rootElement.data(NG_ANIMATE_STATE, rootAnimateState); // Wait until all directive and route-related templates are downloaded and @@ -877,22 +878,22 @@ angular.module('ngAnimate', ['ng']) * * Below is a breakdown of each step that occurs during the `animate` animation: * - * | Animation Step | What the element class attribute looks like | - * |-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------| - * | 1. $animate.animate(...) is called | class="my-animation" | - * | 2. $animate waits for the next digest to start the animation | class="my-animation ng-animate" | - * | 3. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | - * | 4. the className class value is added to the element | class="my-animation ng-animate className" | - * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate className" | - * | 6. $animate blocks all CSS transitions on the element to ensure the .className class styling is applied right away| class="my-animation ng-animate className" | - * | 7. $animate applies the provided collection of `from` CSS styles to the element | class="my-animation ng-animate className" | - * | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate className" | - * | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate className" | - * | 10. the className-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate className className-active" | - * | 11. $animate applies the collection of `to` CSS styles to the element which are then handled by the transition | class="my-animation ng-animate className className-active" | - * | 12. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate className className-active" | - * | 13. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | - * | 14. The returned promise is resolved. | class="my-animation" | + * | Animation Step | What the element class attribute looks like | + * |-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| + * | 1. `$animate.animate(...)` is called | `class="my-animation"` | + * | 2. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` | + * | 3. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` | + * | 4. the `className` class value is added to the element | `class="my-animation ng-animate className"` | + * | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate className"` | + * | 6. `$animate` blocks all CSS transitions on the element to ensure the `.className` class styling is applied right away| `class="my-animation ng-animate className"` | + * | 7. `$animate` applies the provided collection of `from` CSS styles to the element | `class="my-animation ng-animate className"` | + * | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate className"` | + * | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate className"` | + * | 10. the `className-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate className className-active"` | + * | 11. `$animate` applies the collection of `to` CSS styles to the element which are then handled by the transition | `class="my-animation ng-animate className className-active"` | + * | 12. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate className className-active"` | + * | 13. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` | + * | 14. The returned promise is resolved. | `class="my-animation"` | * * @param {DOMElement} element the element that will be the focus of the enter animation * @param {object} from a collection of CSS styles that will be applied to the element at the start of the animation @@ -923,21 +924,21 @@ angular.module('ngAnimate', ['ng']) * * 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 waits for the next digest to start the animation | class="my-animation ng-animate" | - * | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | - * | 5. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" | - * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" | - * | 7. $animate blocks all CSS transitions on the element to ensure the .ng-enter class styling is applied right away | class="my-animation ng-animate ng-enter" | - * | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-enter" | - * | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-enter" | - * | 10. the .ng-enter-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-enter ng-enter-active" | - * | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-enter ng-enter-active" | - * | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | - * | 13. The returned promise is resolved. | class="my-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` waits for the next digest to start the animation | `class="my-animation ng-animate"` | + * | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` | + * | 5. the `.ng-enter` class is added to the element | `class="my-animation ng-animate ng-enter"` | + * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-enter"` | + * | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-enter` class styling is applied right away | `class="my-animation ng-animate ng-enter"` | + * | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-enter"` | + * | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-enter"` | + * | 10. the `.ng-enter-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-enter ng-enter-active"` | + * | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-enter ng-enter-active"` | + * | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` | + * | 13. The returned promise is resolved. | `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 @@ -969,21 +970,21 @@ angular.module('ngAnimate', ['ng']) * * 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 the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | - * | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" | - * | 4. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" | - * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" | - * | 6. $animate blocks all CSS transitions on the element to ensure the .ng-leave class styling is applied right away | class="my-animation ng-animate ng-leave” | - * | 7. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-leave" | - * | 8. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-leave” | - * | 9. the .ng-leave-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-leave ng-leave-active" | - * | 10. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-leave ng-leave-active" | - * | 11. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | - * | 12. The element is removed from the DOM | ... | - * | 13. The returned promise is resolved. | ... | + * | Animation Step | What the element class attribute looks like | + * |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------| + * | 1. `$animate.leave(...)` is called | `class="my-animation"` | + * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` | + * | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` | + * | 4. the `.ng-leave` class is added to the element | `class="my-animation ng-animate ng-leave"` | + * | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-leave"` | + * | 6. `$animate` blocks all CSS transitions on the element to ensure the `.ng-leave` class styling is applied right away | `class="my-animation ng-animate ng-leave"` | + * | 7. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-leave"` | + * | 8. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-leave"` | + * | 9. the `.ng-leave-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-leave ng-leave-active"` | + * | 10. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-leave ng-leave-active"` | + * | 11. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` | + * | 12. The element is removed from the DOM | ... | + * | 13. The returned promise is resolved. | ... | * * @param {DOMElement} element the element that will be the focus of the leave animation * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation @@ -1014,21 +1015,21 @@ angular.module('ngAnimate', ['ng']) * * 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 waits for the next digest to start the animation | class="my-animation ng-animate" | - * | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | - * | 5. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" | - * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" | - * | 7. $animate blocks all CSS transitions on the element to ensure the .ng-move class styling is applied right away | class="my-animation ng-animate ng-move” | - * | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-move" | - * | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-move” | - * | 10. the .ng-move-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-move ng-move-active" | - * | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-move ng-move-active" | - * | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | - * | 13. The returned promise is resolved. | class="my-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` waits for the next digest to start the animation | `class="my-animation ng-animate"` | + * | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` | + * | 5. the `.ng-move` class is added to the element | `class="my-animation ng-animate ng-move"` | + * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-move"` | + * | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-move` class styling is applied right away | `class="my-animation ng-animate ng-move"` | + * | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-move"` | + * | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-move"` | + * | 10. the `.ng-move-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-move ng-move-active"` | + * | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-move ng-move-active"` | + * | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` | + * | 13. The returned promise is resolved. | `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 @@ -1062,18 +1063,18 @@ angular.module('ngAnimate', ['ng']) * * 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 the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | - * | 3. the .super-add class is added to the element | class="my-animation ng-animate super-add" | - * | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate super-add" | - * | 5. the .super and .super-add-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate super super-add super-add-active" | - * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-add" | - * | 7. $animate waits for the animation to complete (via events and timeout) | 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 returned promise is resolved. | class="my-animation super" | + * | Animation Step | What the element class attribute looks like | + * |--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| + * | 1. `$animate.addClass(element, 'super')` is called | `class="my-animation"` | + * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` | + * | 3. the `.super-add` class is added to the element | `class="my-animation ng-animate super-add"` | + * | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate super-add"` | + * | 5. the `.super` and `.super-add-active` classes are added (this triggers the CSS transition/animation) | `class="my-animation ng-animate super super-add super-add-active"` | + * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super super-add super-add-active"` | + * | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate 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 returned promise is resolved. | `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 @@ -1096,17 +1097,17 @@ angular.module('ngAnimate', ['ng']) * * 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 the JavaScript-defined animations detected on the element | class="my-animation super ng-animate" | - * | 3. the .super-remove class is added to the element | class="my-animation super ng-animate super-remove" | - * | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation super ng-animate super-remove" | - * | 5. the .super-remove-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate super-remove super-remove-active" | - * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" | - * | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate super-remove super-remove-active" | - * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | - * | 9. The returned promise is resolved. | class="my-animation" | + * | Animation Step | What the element class attribute looks like | + * |----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| + * | 1. `$animate.removeClass(element, 'super')` is called | `class="my-animation super"` | + * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation super ng-animate"` | + * | 3. the `.super-remove` class is added to the element | `class="my-animation super ng-animate super-remove"` | + * | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation super ng-animate super-remove"` | + * | 5. the `.super-remove-active` classes are added and `.super` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate super-remove super-remove-active"` | + * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super-remove super-remove-active"` | + * | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate super-remove super-remove-active"` | + * | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` | + * | 9. The returned promise is resolved. | `class="my-animation"` | * * * @param {DOMElement} element the element that will be animated @@ -1124,19 +1125,19 @@ angular.module('ngAnimate', ['ng']) * @name $animate#setClass * * @description Adds and/or removes the given CSS classes to and from the element. - * Once complete, the done() callback will be fired (if provided). + * Once complete, the `done()` callback will be fired (if provided). * - * | Animation Step | What the element class attribute looks like | - * |--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------| - * | 1. $animate.removeClass(element, ‘on’, ‘off’) is called | class="my-animation super off” | - * | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation super ng-animate off” | - * | 3. the .on-add and .off-remove classes are added to the element | class="my-animation ng-animate on-add off-remove off” | - * | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate on-add off-remove off” | - * | 5. the .on, .on-add-active and .off-remove-active classes are added and .off is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active” | - * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" | - * | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" | - * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation on" | - * | 9. The returned promise is resolved. | class="my-animation on" | + * | Animation Step | What the element class attribute looks like | + * |----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------| + * | 1. `$animate.setClass(element, 'on', 'off')` is called | `class="my-animation off"` | + * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate off"` | + * | 3. the `.on-add` and `.off-remove` classes are added to the element | `class="my-animation ng-animate on-add off-remove off"` | + * | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate on-add off-remove off"` | + * | 5. the `.on`, `.on-add-active` and `.off-remove-active` classes are added and `.off` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` | + * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` | + * | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` | + * | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation on"` | + * | 9. The returned promise is resolved. | `class="my-animation on"` | * * @param {DOMElement} element the element which will have its CSS classes changed * removed from it @@ -1274,7 +1275,7 @@ angular.module('ngAnimate', ['ng']) 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 + 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, options, doneCallback) { @@ -1386,10 +1387,10 @@ angular.module('ngAnimate', ['ng']) //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); + $$jqLite.addClass(element, NG_ANIMATE_CLASS_NAME); if (options && options.tempClasses) { forEach(options.tempClasses, function(className) { - element.addClass(className); + $$jqLite.addClass(element, className); }); } @@ -1467,7 +1468,7 @@ angular.module('ngAnimate', ['ng']) closeAnimation.hasBeenRun = true; if (options && options.tempClasses) { forEach(options.tempClasses, function(className) { - element.removeClass(className); + $$jqLite.removeClass(element, className); }); } @@ -1529,7 +1530,7 @@ angular.module('ngAnimate', ['ng']) } if (removeAnimations || !data.totalActive) { - element.removeClass(NG_ANIMATE_CLASS_NAME); + $$jqLite.removeClass(element, NG_ANIMATE_CLASS_NAME); element.removeData(NG_ANIMATE_STATE); } } @@ -1770,14 +1771,14 @@ angular.module('ngAnimate', ['ng']) var staggerCacheKey = cacheKey + ' ' + staggerClassName; var applyClasses = !lookupCache[staggerCacheKey]; - applyClasses && element.addClass(staggerClassName); + applyClasses && $$jqLite.addClass(element, staggerClassName); stagger = getElementAnimationDetails(element, staggerCacheKey); - applyClasses && element.removeClass(staggerClassName); + applyClasses && $$jqLite.removeClass(element, staggerClassName); } - element.addClass(className); + $$jqLite.addClass(element, className); var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {}; var timings = getElementAnimationDetails(element, eventCacheKey); @@ -1785,7 +1786,7 @@ angular.module('ngAnimate', ['ng']) var animationDuration = timings.animationDuration; if (structural && transitionDuration === 0 && animationDuration === 0) { - element.removeClass(className); + $$jqLite.removeClass(element, className); return false; } @@ -1857,7 +1858,7 @@ angular.module('ngAnimate', ['ng']) } if (!staggerTime) { - element.addClass(activeClassName); + $$jqLite.addClass(element, activeClassName); if (elementData.blockTransition) { blockTransitions(node, false); } @@ -1867,7 +1868,7 @@ angular.module('ngAnimate', ['ng']) var timings = getElementAnimationDetails(element, eventCacheKey); var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration); if (maxDuration === 0) { - element.removeClass(activeClassName); + $$jqLite.removeClass(element, activeClassName); animateClose(element, className); activeAnimationComplete(); return; @@ -1889,7 +1890,7 @@ angular.module('ngAnimate', ['ng']) //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') || ''; - if (oldStyle.charAt(oldStyle.length-1) !== ';') { + if (oldStyle.charAt(oldStyle.length - 1) !== ';') { oldStyle += ';'; } node.setAttribute('style', oldStyle + ' ' + style); @@ -1902,7 +1903,7 @@ angular.module('ngAnimate', ['ng']) var staggerTimeout; if (staggerTime > 0) { - element.addClass(pendingClassName); + $$jqLite.addClass(element, pendingClassName); staggerTimeout = $timeout(function() { staggerTimeout = null; @@ -1913,8 +1914,8 @@ angular.module('ngAnimate', ['ng']) blockAnimations(node, false); } - element.addClass(activeClassName); - element.removeClass(pendingClassName); + $$jqLite.addClass(element, activeClassName); + $$jqLite.removeClass(element, pendingClassName); if (styles) { if (timings.transitionDuration === 0) { @@ -1941,8 +1942,8 @@ angular.module('ngAnimate', ['ng']) // timeout done method. function onEnd() { element.off(css3AnimationEvents, onAnimationProgress); - element.removeClass(activeClassName); - element.removeClass(pendingClassName); + $$jqLite.removeClass(element, activeClassName); + $$jqLite.removeClass(element, pendingClassName); if (staggerTimeout) { $timeout.cancel(staggerTimeout); } @@ -2030,7 +2031,7 @@ angular.module('ngAnimate', ['ng']) } function animateClose(element, className) { - element.removeClass(className); + $$jqLite.removeClass(element, className); var data = element.data(NG_ANIMATE_CSS_DATA_KEY); if (data) { if (data.running) { diff --git a/src/lib/angular/angular-mocks.js b/src/lib/angular/angular-mocks.js index 25b8653..3430bb7 100644 --- a/src/lib/angular/angular-mocks.js +++ b/src/lib/angular/angular-mocks.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.3.2 + * @license AngularJS v1.3.7 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ @@ -117,7 +117,7 @@ angular.mock.$Browser = function() { self.defer.now += delay; } else { if (self.deferredFns.length) { - self.defer.now = self.deferredFns[self.deferredFns.length-1].time; + self.defer.now = self.deferredFns[self.deferredFns.length - 1].time; } else { throw new Error('No deferred tasks to be flushed'); } @@ -428,7 +428,7 @@ angular.mock.$LogProvider = function() { }); }); if (errors.length) { - errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or "+ + errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or " + "an expected log message was not checked and removed:"); errors.push(''); throw new Error(errors.join('\n---------\n')); @@ -461,17 +461,17 @@ angular.mock.$LogProvider = function() { * @returns {promise} A promise which will be notified on each iteration. */ angular.mock.$IntervalProvider = function() { - this.$get = ['$rootScope', '$q', - function($rootScope, $q) { + this.$get = ['$browser', '$rootScope', '$q', '$$q', + function($browser, $rootScope, $q, $$q) { var repeatFns = [], nextRepeatId = 0, now = 0; var $interval = function(fn, delay, count, invokeApply) { - var deferred = $q.defer(), - promise = deferred.promise, - iteration = 0, - skipApply = (angular.isDefined(invokeApply) && !invokeApply); + var iteration = 0, + skipApply = (angular.isDefined(invokeApply) && !invokeApply), + deferred = (skipApply ? $$q : $q).defer(), + promise = deferred.promise; count = (angular.isDefined(count)) ? count : 0; promise.then(null, null, fn); @@ -494,7 +494,11 @@ angular.mock.$IntervalProvider = function() { } } - if (!skipApply) $rootScope.$apply(); + if (skipApply) { + $browser.defer.flush(); + } else { + $rootScope.$apply(); + } } repeatFns.push({ @@ -581,10 +585,10 @@ function jsonStringToDate(string) { tzMin = int(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.setUTCHours(int(match[4] || 0) - tzHour, + int(match[5] || 0) - tzMin, + int(match[6] || 0), + int(match[7] || 0)); return date; } return string; @@ -663,7 +667,7 @@ angular.mock.TzDate = function(offset, 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() { @@ -815,7 +819,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng']) animate.queue.push({ event: method, element: arguments[0], - options: arguments[arguments.length-1], + options: arguments[arguments.length - 1], args: arguments }); return $delegate[method].apply($delegate, arguments); @@ -1115,7 +1119,7 @@ angular.mock.dump = function(object) { ``` */ angular.mock.$HttpBackendProvider = function() { - this.$get = ['$rootScope', createHttpBackendMock]; + this.$get = ['$rootScope', '$timeout', createHttpBackendMock]; }; /** @@ -1132,7 +1136,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 = [], @@ -1145,7 +1149,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { return function() { return angular.isNumber(status) ? [status, data, headers, statusText] - : [200, status, data]; + : [200, status, data, headers]; }; } @@ -1162,7 +1166,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; @@ -1770,7 +1776,7 @@ angular.mock.$RAFDecorator = ['$delegate', function($delegate) { } var length = queue.length; - for (var i=0;i=0) + if (index >= 0) array.splice(index, 1); return value; } @@ -916,7 +905,7 @@ function equals(o1, o2) { if (isArray(o1)) { if (!isArray(o2)) return false; if ((length = o1.length) == o2.length) { - for (key=0; key= 0) return '<>'; + + seen.push(val); + } + return val; + }); +} + +function toDebugString(obj) { + if (typeof obj === 'function') { + return obj.toString().replace(/ \{[\s\S]*$/, ''); + } else if (typeof obj === 'undefined') { + return 'undefined'; + } else if (typeof obj !== 'string') { + return serializeObject(obj); + } + return obj; +} + /* global angularModule: true, version: true, @@ -2070,7 +2096,8 @@ function setupModuleLoader(window) { $TimeoutProvider, $$RAFProvider, $$AsyncCallbackProvider, - $WindowProvider + $WindowProvider, + $$jqLiteProvider */ @@ -2089,11 +2116,11 @@ function setupModuleLoader(window) { * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.3.2', // all of these placeholder strings will be replaced by grunt's + full: '1.3.7', // all of these placeholder strings will be replaced by grunt's major: 1, // package task minor: 3, - dot: 2, - codeName: 'cardiovasculatory-magnification' + dot: 7, + codeName: 'leaky-obstruction' }; @@ -2223,7 +2250,8 @@ function publishExternalAPI(angular) { $timeout: $TimeoutProvider, $window: $WindowProvider, $$rAF: $$RAFProvider, - $$asyncCallback: $$AsyncCallbackProvider + $$asyncCallback: $$AsyncCallbackProvider, + $$jqLite: $$jqLiteProvider }); } ]); @@ -2316,10 +2344,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. * @@ -2826,7 +2856,7 @@ forEach({ } } else { return (element[name] || - (element.attributes.getNamedItem(name)|| noop).specified) + (element.attributes.getNamedItem(name) || noop).specified) ? lowercasedName : undefined; } @@ -3231,6 +3261,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: @@ -3314,9 +3365,10 @@ HashMap.prototype = { * 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. + * {@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 @@ -3462,8 +3514,10 @@ function annotate(fn, strictDi, name) { * ## 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. @@ -3480,6 +3534,7 @@ function annotate(fn, strictDi, name) { * 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. */ @@ -3548,6 +3603,8 @@ function annotate(fn, strictDi, name) { * 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. * @@ -3600,6 +3657,8 @@ function annotate(fn, strictDi, name) { * @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. */ @@ -3926,14 +3985,17 @@ function createInjector(modulesToLoad, strictDi) { } }, 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, undefined, servicename); + createInternalInjector(instanceCache, function(serviceName, caller) { + var provider = providerInjector.get(serviceName + providerSuffix, caller); + return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); })); @@ -3968,7 +4030,7 @@ function createInjector(modulesToLoad, strictDi) { function enforceReturnValue(name, factory) { return function enforcedReturnValue() { - var result = instanceInjector.invoke(factory, this, undefined, name); + var result = instanceInjector.invoke(factory, this); if (isUndefined(result)) { throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); } @@ -4063,7 +4125,7 @@ function createInjector(modulesToLoad, strictDi) { 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}', @@ -4074,7 +4136,7 @@ function createInjector(modulesToLoad, strictDi) { 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]; @@ -4106,7 +4168,7 @@ function createInjector(modulesToLoad, strictDi) { args.push( locals && locals.hasOwnProperty(key) ? locals[key] - : getService(key) + : getService(key, serviceName) ); } if (isArray(fn)) { @@ -4119,14 +4181,11 @@ function createInjector(modulesToLoad, strictDi) { } function instantiate(Type, locals, serviceName) { - var Constructor = function() {}, - instance, returnedValue; - // 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, serviceName); + // Object creation: http://jsperf.com/create-constructor/2 + var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype); + var returnedValue = invoke(Type, instance, locals, serviceName); return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; } @@ -4162,7 +4221,7 @@ function $AnchorScrollProvider() { * @name $anchorScrollProvider#disableAutoScrolling * * @description - * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically will detect changes to + * 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. * @@ -4811,8 +4870,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) { @@ -4854,6 +4912,11 @@ function Browser(window, document, $log, $sniffer) { } } + function getHash(url) { + var index = url.indexOf('#'); + return index === -1 ? '' : url.substr(index + 1); + } + /** * @private * Note: this method is used only by scenario runner @@ -4963,7 +5026,7 @@ function Browser(window, document, $log, $sniffer) { // 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; + return self; } var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); lastBrowserUrl = url; @@ -4983,8 +5046,10 @@ function Browser(window, document, $log, $sniffer) { } if (replace) { location.replace(url); - } else { + } else if (!sameBase) { location.href = url; + } else { + location.hash = getHash(url); } } return self; @@ -5162,8 +5227,8 @@ function Browser(window, document, $log, $sniffer) { // - 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 ("+ + $log.warn("Cookie '" + name + + "' possibly not set or overflowed because it was too large (" + cookieLength + " > 4096 bytes)!"); } } @@ -5763,7 +5828,7 @@ function $TemplateCacheProvider() { * #### `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 recomended that this feature be used on directives + * 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}). * @@ -5828,7 +5893,7 @@ function $TemplateCacheProvider() { * * * #### `bindToController` - * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController` will + * 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. * @@ -6451,7 +6516,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` @@ -6518,7 +6583,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * * `ng-binding` CSS class * * `$binding` data property containing an array of the binding expressions * - * You may want to use this in production for a significant performance boost. See + * 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. @@ -6555,6 +6620,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }; 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, @@ -6677,16 +6757,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // for each tuples var nbrUrisWith2parts = Math.floor(rawUris.length / 2); - for (var i=0; i directive.priority) && @@ -7722,7 +7805,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function directiveIsMultiElement(name) { if (hasDirectives.hasOwnProperty(name)) { for (var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i'+template+''; + wrapper.innerHTML = '<' + type + '>' + template + ''; return wrapper.childNodes[0].childNodes; default: return template; @@ -7965,7 +8048,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { - var interpolateFn = $interpolate(value, true); + 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; @@ -7990,16 +8076,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { "ng- versions (such as ng-click instead of onclick) instead."); } - // If the attribute was removed, then we are done - if (!attr[name]) { - return; + // 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; } - // 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), - ALL_OR_NOTHING_ATTRS[name] || allOrNothing); - // if attribute was updated so that there is no interpolation going on we don't want to // register any observers if (!interpolateFn) return; @@ -8133,13 +8219,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { 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) { @@ -8298,6 +8377,10 @@ function $ControllerProvider() { * * 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. * @@ -8343,10 +8426,10 @@ function $ControllerProvider() { // // This feature is not intended for use by applications, and is thus not documented // publicly. - var Constructor = function() {}; - Constructor.prototype = (isArray(expression) ? + // Object creation: http://jsperf.com/create-constructor/2 + var controllerPrototype = (isArray(expression) ? expression[expression.length - 1] : expression).prototype; - instance = new Constructor(); + instance = Object.create(controllerPrototype); if (identifier) { addIdentifier(locals, identifier, instance, constructor || expression.name); @@ -8463,23 +8546,34 @@ function $ExceptionHandlerProvider() { var APPLICATION_JSON = 'application/json'; var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; -var JSON_START = /^\s*(\[|\{[^\{])/; -var JSON_END = /[\}\]]\s*$/; +var JSON_START = /^\[|^\{(?!\{)/; +var JSON_ENDS = { + '[': /]$/, + '{': /}$/ +}; var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; function defaultHttpResponseTransform(data, headers) { if (isString(data)) { - // strip json vulnerability protection prefix - data = data.replace(JSON_PROTECTION_PREFIX, ''); - var contentType = headers('Content-Type'); - if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) || - (JSON_START.test(data) && JSON_END.test(data))) { - data = fromJson(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 * @@ -8487,7 +8581,7 @@ function defaultHttpResponseTransform(data, headers) { * @returns {Object} Parsed headers as key value object */ function parseHeaders(headers) { - var parsed = {}, key, val, i; + var parsed = createMap(), key, val, i; if (!headers) return parsed; @@ -8524,7 +8618,11 @@ function headersGetter(headers) { 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; @@ -8538,16 +8636,17 @@ 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) { +function transformData(data, headers, status, fns) { if (isFunction(fns)) - return fns(data, headers); + return fns(data, headers, status); forEach(fns, function(fn) { - data = fn(data, headers); + data = fn(data, headers, status); }); return data; @@ -8573,6 +8672,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 `default.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'`. * @@ -8586,6 +8690,7 @@ function $HttpProvider() { * - **`defaults.headers.post`** * - **`defaults.headers.put`** * - **`defaults.headers.patch`** + * **/ var defaults = this.defaults = { // transform incoming response data @@ -8593,7 +8698,7 @@ function $HttpProvider() { // 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 @@ -8800,12 +8905,27 @@ 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: + * + * ```js + * var req = { + * method: 'POST', + * url: 'http://example.com', + * headers: { + * 'Content-Type': undefined + * }, + * data: { test: 'test' }, + * } + * + * $http(req).success(function(){...}).error(function(){...}); + * ``` * * ## 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)`) or an array of such transformation functions, + * 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 @@ -9046,12 +9166,14 @@ function $HttpProvider() { * `{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 #overriding-the-default-transformations-per-request Overriding the Default Transformations} + * 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. - * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations} + * 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 Transformations} * - **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 @@ -9172,20 +9294,23 @@ function $HttpProvider() { */ function $http(requestConfig) { - var config = { + + if (!angular.isObject(requestConfig)) { + throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); + } + + var config = extend({ method: 'get', transformRequest: defaults.transformRequest, transformResponse: defaults.transformResponse - }; - var headers = mergeHeaders(requestConfig); + }, requestConfig); - extend(config, requestConfig); - config.headers = headers; + config.headers = mergeHeaders(requestConfig); config.method = uppercase(config.method); var serverRequest = function(config) { - headers = config.headers; - var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); + var headers = config.headers; + var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); // strip content-type if data is undefined if (isUndefined(reqData)) { @@ -9201,7 +9326,7 @@ function $HttpProvider() { } // send request - return sendReq(config, reqData, headers).then(transformResponse, transformResponse); + return sendReq(config, reqData).then(transformResponse, transformResponse); }; var chain = [serverRequest, undefined]; @@ -9246,13 +9371,30 @@ function $HttpProvider() { if (!response.data) { resp.data = response.data; } else { - resp.data = transformData(response.data, response.headers, config.transformResponse); + resp.data = transformData(response.data, response.headers, response.status, config.transformResponse); } return (isSuccess(response.status)) ? resp : $q.reject(resp); } + function executeHeaderFns(headers) { + var headerContent, processedHeaders = {}; + + forEach(headers, function(headerFn, header) { + if (isFunction(headerFn)) { + headerContent = headerFn(); + if (headerContent != null) { + processedHeaders[header] = headerContent; + } + } else { + processedHeaders[header] = headerFn; + } + }); + + return processedHeaders; + } + function mergeHeaders(config) { var defHeaders = defaults.headers, reqHeaders = extend({}, config.headers), @@ -9275,23 +9417,7 @@ function $HttpProvider() { } // execute if header value is a function for merged headers - execHeaders(reqHeaders); - return reqHeaders; - - function execHeaders(headers) { - var headerContent; - - forEach(headers, function(headerFn, header) { - if (isFunction(headerFn)) { - headerContent = headerFn(); - if (headerContent != null) { - headers[header] = headerContent; - } else { - delete headers[header]; - } - } - }); - } + return executeHeaderFns(reqHeaders); } } @@ -9434,11 +9560,12 @@ function $HttpProvider() { * !!! ACCESSES CLOSURE VARS: * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests */ - function sendReq(config, reqData, reqHeaders) { + function sendReq(config, reqData) { var deferred = $q.defer(), promise = deferred.promise, cache, cachedResp, + reqHeaders = config.headers, url = buildUrl(config.url, config.params); $http.pendingRequests.push(config); @@ -9457,8 +9584,7 @@ function $HttpProvider() { if (isDefined(cachedResp)) { if (isPromiseLike(cachedResp)) { // cached request has already been sent, but there is no response yet - cachedResp.then(removePendingReq, removePendingReq); - return cachedResp; + cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); } else { // serving from cache if (isArray(cachedResp)) { @@ -9536,6 +9662,9 @@ function $HttpProvider() { }); } + function resolvePromiseWithResult(result) { + resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); + } function removePendingReq() { var idx = $http.pendingRequests.indexOf(config); @@ -9697,7 +9826,9 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc function completeRequest(callback, status, response, headersString, statusText) { // cancel timeout and subsequent timeout promise resolution - timeoutId && $browserDefer.cancel(timeoutId); + if (timeoutId !== undefined) { + $browserDefer.cancel(timeoutId); + } jsonpDone = xhr = null; callback(status, response, headersString, statusText); @@ -10043,7 +10174,8 @@ function $InterpolateProvider() { function parseStringifyInterceptor(value) { try { - return stringify(getValue(value)); + value = getValue(value); + return allOrNothing && !isDefined(value) ? value : stringify(value); } catch (err) { var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); @@ -10144,33 +10276,33 @@ function $IntervalProvider() { * // Don't start a new fight if we are already fighting * if ( angular.isDefined(stop) ) return; * - * stop = $interval(function() { - * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { - * $scope.blood_1 = $scope.blood_1 - 3; - * $scope.blood_2 = $scope.blood_2 - 4; - * } else { - * $scope.stopFight(); + * stop = $interval(function() { + * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { + * $scope.blood_1 = $scope.blood_1 - 3; + * $scope.blood_2 = $scope.blood_2 - 4; + * } else { + * $scope.stopFight(); + * } + * }, 100); + * }; + * + * $scope.stopFight = function() { + * if (angular.isDefined(stop)) { + * $interval.cancel(stop); + * stop = undefined; * } - * }, 100); - * }; + * }; * - * $scope.stopFight = function() { - * if (angular.isDefined(stop)) { - * $interval.cancel(stop); - * stop = undefined; - * } - * }; + * $scope.resetFight = function() { + * $scope.blood_1 = 100; + * $scope.blood_2 = 120; + * }; * - * $scope.resetFight = function() { - * $scope.blood_1 = 100; - * $scope.blood_2 = 120; - * }; - * - * $scope.$on('$destroy', function() { - * // Make sure that the interval is destroyed too - * $scope.stopFight(); - * }); - * }]) + * $scope.$on('$destroy', function() { + * // Make sure that the interval is destroyed too + * $scope.stopFight(); + * }); + * }]) * // Register the 'myCurrentTime' directive factory method. * // We inject $interval and dateFilter service since the factory method is DI. * .directive('myCurrentTime', ['$interval', 'dateFilter', @@ -10367,8 +10499,8 @@ function encodePath(path) { return segments.join('/'); } -function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { - var parsedUrl = urlResolve(absoluteUrl, appBase); +function parseAbsoluteUrl(absoluteUrl, locationObj) { + var parsedUrl = urlResolve(absoluteUrl); locationObj.$$protocol = parsedUrl.protocol; locationObj.$$host = parsedUrl.hostname; @@ -10376,12 +10508,12 @@ function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { } -function parseAppUrl(relativeUrl, locationObj, appBase) { +function parseAppUrl(relativeUrl, locationObj) { var prefixed = (relativeUrl.charAt(0) !== '/'); if (prefixed) { relativeUrl = '/' + relativeUrl; } - var match = urlResolve(relativeUrl, appBase); + var match = urlResolve(relativeUrl); locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname); locationObj.$$search = parseKeyValue(match.search); @@ -10413,6 +10545,10 @@ function stripHash(url) { return index == -1 ? url : url.substr(0, index); } +function trimEmptyHash(url) { + return url.replace(/(#.+)|#$/, '$1'); +} + function stripFile(url) { return url.substr(0, stripHash(url).lastIndexOf('/') + 1); @@ -10436,7 +10572,7 @@ function LocationHtml5Url(appBase, basePrefix) { this.$$html5 = true; basePrefix = basePrefix || ''; var appBaseNoFile = stripFile(appBase); - parseAbsoluteUrl(appBase, this, appBase); + parseAbsoluteUrl(appBase, this); /** @@ -10451,7 +10587,7 @@ function LocationHtml5Url(appBase, basePrefix) { appBaseNoFile); } - parseAppUrl(pathUrl, this, appBase); + parseAppUrl(pathUrl, this); if (!this.$$path) { this.$$path = '/'; @@ -10514,7 +10650,7 @@ function LocationHtml5Url(appBase, basePrefix) { function LocationHashbangUrl(appBase, hashPrefix) { var appBaseNoFile = stripFile(appBase); - parseAbsoluteUrl(appBase, this, appBase); + parseAbsoluteUrl(appBase, this); /** @@ -10524,17 +10660,26 @@ function LocationHashbangUrl(appBase, hashPrefix) { */ this.$$parse = function(url) { var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); - var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' - ? beginsWith(hashPrefix, withoutBaseUrl) - : (this.$$html5) - ? withoutBaseUrl - : ''; + var withoutHashUrl; - if (!isString(withoutHashUrl)) { - throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, - hashPrefix); + if (withoutBaseUrl.charAt(0) === '#') { + + // The rest of the url starts with a hash so we have + // got either a hashbang path or a plain hash fragment + withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); + if (isUndefined(withoutHashUrl)) { + // There was no hashbang prefix so we just have a hash fragment + withoutHashUrl = withoutBaseUrl; + } + + } else { + // There was no hashbang path nor hash fragment: + // If we are in HTML5 mode we use what is left as the path; + // Otherwise we ignore what is left + withoutHashUrl = this.$$html5 ? withoutBaseUrl : ''; } - parseAppUrl(withoutHashUrl, this, appBase); + + parseAppUrl(withoutHashUrl, this); this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); @@ -10672,6 +10817,13 @@ var locationPrototype = { * Return full url representation with all segments encoded according to rules specified in * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var absUrl = $location.absUrl(); + * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" + * ``` + * * @return {string} full url */ absUrl: locationGetter('$$absUrl'), @@ -10687,6 +10839,13 @@ var locationPrototype = { * * Change path, search and hash, when called with parameter and return `$location`. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var url = $location.url(); + * // => "/some/path?foo=bar&baz=xoxo" + * ``` + * * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) * @return {string} url */ @@ -10695,8 +10854,8 @@ var locationPrototype = { return this.$$url; var match = PATH_MATCH.exec(url); - if (match[1]) this.path(decodeURIComponent(match[1])); - if (match[2] || match[1]) this.search(match[3] || ''); + if (match[1] || url === '') this.path(decodeURIComponent(match[1])); + if (match[2] || match[1] || url === '') this.search(match[3] || ''); this.hash(match[5] || ''); return this; @@ -10711,6 +10870,13 @@ var locationPrototype = { * * Return protocol of current url. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var protocol = $location.protocol(); + * // => "http" + * ``` + * * @return {string} protocol of current url */ protocol: locationGetter('$$protocol'), @@ -10724,6 +10890,13 @@ var locationPrototype = { * * Return host of current url. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var host = $location.host(); + * // => "example.com" + * ``` + * * @return {string} host of current url. */ host: locationGetter('$$host'), @@ -10737,6 +10910,13 @@ var locationPrototype = { * * Return port of current url. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var port = $location.port(); + * // => 80 + * ``` + * * @return {Number} port */ port: locationGetter('$$port'), @@ -10755,6 +10935,13 @@ var locationPrototype = { * Note: Path should always begin with forward slash (/), this method will add the forward slash * if it is missing. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var path = $location.path(); + * // => "/some/path" + * ``` + * * @param {(string|number)=} path New path * @return {string} path */ @@ -10780,10 +10967,9 @@ var locationPrototype = { * var searchObject = $location.search(); * // => {foo: 'bar', baz: 'xoxo'} * - * * // set foo to 'yipee' * $location.search('foo', 'yipee'); - * // => $location + * // $location.search() => {foo: 'yipee', baz: 'xoxo'} * ``` * * @param {string|Object.|Object.>} search New search params - string or @@ -10853,6 +11039,13 @@ var locationPrototype = { * * Change hash fragment when called with parameter and return `$location`. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue + * var hash = $location.hash(); + * // => "hashValue" + * ``` + * * @param {(string|number)=} hash New hash fragment * @return {string} hash */ @@ -11072,8 +11265,8 @@ function $LocationProvider() { * @param {string=} oldState History state object that was before it was changed. */ - this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', - function($rootScope, $browser, $sniffer, $rootElement) { + this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', + function($rootScope, $browser, $sniffer, $rootElement, $window) { var $location, LocationMode, baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' @@ -11155,7 +11348,7 @@ function $LocationProvider() { if ($location.absUrl() != $browser.url()) { $rootScope.$apply(); // hack to work around FF6 bug 684208 when scenario runner clicks on links - window.angular['ff-684208-preventDefault'] = true; + $window.angular['ff-684208-preventDefault'] = true; } } } @@ -11174,11 +11367,19 @@ function $LocationProvider() { $rootScope.$evalAsync(function() { var oldUrl = $location.absUrl(); var oldState = $location.$$state; + var defaultPrevented; $location.$$parse(newUrl); $location.$$state = newState; - if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, - newState, oldState).defaultPrevented) { + + defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, + newState, oldState).defaultPrevented; + + // if the location was changed by a `$locationChangeStart` handler then stop + // processing this location change + if ($location.absUrl() !== newUrl) return; + + if (defaultPrevented) { $location.$$parse(oldUrl); $location.$$state = oldState; setBrowserUrlWithFallback(oldUrl, false, oldState); @@ -11192,23 +11393,31 @@ function $LocationProvider() { // update browser $rootScope.$watch(function $locationWatch() { - var oldUrl = $browser.url(); + var oldUrl = trimEmptyHash($browser.url()); + var newUrl = trimEmptyHash($location.absUrl()); var oldState = $browser.state(); var currentReplace = $location.$$replace; - var urlOrStateChanged = oldUrl !== $location.absUrl() || + var urlOrStateChanged = oldUrl !== newUrl || ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); if (initializing || urlOrStateChanged) { initializing = false; $rootScope.$evalAsync(function() { - if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl, - $location.$$state, oldState).defaultPrevented) { + var newUrl = $location.absUrl(); + var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, + $location.$$state, oldState).defaultPrevented; + + // if the location was changed by a `$locationChangeStart` handler then stop + // processing this location change + if ($location.absUrl() !== newUrl) return; + + if (defaultPrevented) { $location.$$parse(oldUrl); $location.$$state = oldState; } else { if (urlOrStateChanged) { - setBrowserUrlWithFallback($location.absUrl(), currentReplace, + setBrowserUrlWithFallback(newUrl, currentReplace, oldState === $location.$$state ? null : $location.$$state); } afterLocationChange(oldUrl, oldState); @@ -11398,7 +11607,7 @@ var $parseMinErr = minErr('$parse'); // Sandboxing Angular Expressions // ------------------------------ // Angular expressions are generally considered safe because these expressions only have direct -// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by +// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by // obtaining a reference to native JS functions such as the Function constructor. // // As an example, consider the following Angular expression: @@ -11407,7 +11616,7 @@ var $parseMinErr = minErr('$parse'); // // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits // against the expression language, but not to prevent exploits that were enabled by exposing -// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good +// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good // practice and therefore we are not even trying to protect against interaction with an object // explicitly exposed in this way. // @@ -11415,6 +11624,8 @@ var $parseMinErr = minErr('$parse'); // window or some DOM object that has a reference to window is published onto a Scope. // Similarly we prevent invocations of function known to be dangerous, as well as assignments to // native objects. +// +// See https://docs.angularjs.org/guide/security function ensureSafeMemberName(name, fullExpression) { @@ -11423,7 +11634,7 @@ function ensureSafeMemberName(name, fullExpression) { || name === "__proto__") { throw $parseMinErr('isecfld', 'Attempting to access a disallowed field in Angular expressions! ' - +'Expression: {0}', fullExpression); + + 'Expression: {0}', fullExpression); } return name; } @@ -11500,24 +11711,24 @@ var OPERATORS = extend(createMap(), { } return a; } - return isDefined(b)?b:undefined;}, + return isDefined(b) ? b : undefined;}, '-':function(self, locals, a, b) { a=a(self, locals); b=b(self, locals); - return (isDefined(a)?a:0)-(isDefined(b)?b:0); + return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0); }, - '*':function(self, locals, a, b) {return a(self, locals)*b(self, locals);}, - '/':function(self, locals, a, b) {return a(self, locals)/b(self, locals);}, - '%':function(self, locals, a, b) {return a(self, locals)%b(self, locals);}, - '===':function(self, locals, a, b) {return a(self, locals)===b(self, locals);}, - '!==':function(self, locals, a, b) {return a(self, locals)!==b(self, locals);}, - '==':function(self, locals, a, b) {return a(self, locals)==b(self, locals);}, - '!=':function(self, locals, a, b) {return a(self, locals)!=b(self, locals);}, - '<':function(self, locals, a, b) {return a(self, locals)':function(self, locals, a, b) {return a(self, locals)>b(self, locals);}, - '<=':function(self, locals, a, b) {return a(self, locals)<=b(self, locals);}, - '>=':function(self, locals, a, b) {return a(self, locals)>=b(self, locals);}, - '&&':function(self, locals, a, b) {return a(self, locals)&&b(self, locals);}, - '||':function(self, locals, a, b) {return a(self, locals)||b(self, locals);}, + '*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);}, + '/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);}, + '%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);}, + '===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);}, + '!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);}, + '==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);}, + '!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);}, + '<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);}, + '>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);}, + '<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);}, + '>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);}, + '&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);}, + '||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);}, '!':function(self, locals, a) {return !a(self, locals);}, //Tokenized as operators but parsed as assignment/filters @@ -11543,44 +11754,31 @@ Lexer.prototype = { lex: function(text) { this.text = text; this.index = 0; - this.ch = undefined; this.tokens = []; while (this.index < this.text.length) { - this.ch = this.text.charAt(this.index); - if (this.is('"\'')) { - this.readString(this.ch); - } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { + var ch = this.text.charAt(this.index); + if (ch === '"' || ch === "'") { + this.readString(ch); + } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { this.readNumber(); - } else if (this.isIdent(this.ch)) { + } else if (this.isIdent(ch)) { this.readIdent(); - } else if (this.is('(){}[].,;:?')) { - this.tokens.push({ - index: this.index, - text: this.ch - }); + } else if (this.is(ch, '(){}[].,;:?')) { + this.tokens.push({index: this.index, text: ch}); this.index++; - } else if (this.isWhitespace(this.ch)) { + } else if (this.isWhitespace(ch)) { this.index++; } else { - var ch2 = this.ch + this.peek(); + var ch2 = ch + this.peek(); var ch3 = ch2 + this.peek(2); - var fn = OPERATORS[this.ch]; - var fn2 = OPERATORS[ch2]; - var fn3 = OPERATORS[ch3]; - if (fn3) { - this.tokens.push({index: this.index, text: ch3, fn: fn3}); - this.index += 3; - } else if (fn2) { - this.tokens.push({index: this.index, text: ch2, fn: fn2}); - this.index += 2; - } else if (fn) { - this.tokens.push({ - index: this.index, - text: this.ch, - fn: fn - }); - this.index += 1; + var op1 = OPERATORS[ch]; + var op2 = OPERATORS[ch2]; + var op3 = OPERATORS[ch3]; + if (op1 || op2 || op3) { + var token = op3 ? ch3 : (op2 ? ch2 : ch); + this.tokens.push({index: this.index, text: token, operator: true}); + this.index += token.length; } else { this.throwError('Unexpected next character ', this.index, this.index + 1); } @@ -11589,8 +11787,8 @@ Lexer.prototype = { return this.tokens; }, - is: function(chars) { - return chars.indexOf(this.ch) !== -1; + is: function(ch, chars) { + return chars.indexOf(ch) !== -1; }, peek: function(i) { @@ -11599,7 +11797,7 @@ Lexer.prototype = { }, isNumber: function(ch) { - return ('0' <= ch && ch <= '9'); + return ('0' <= ch && ch <= '9') && typeof ch === "string"; }, isWhitespace: function(ch) { @@ -11652,79 +11850,28 @@ Lexer.prototype = { } this.index++; } - number = 1 * number; this.tokens.push({ index: start, text: number, constant: true, - fn: function() { return number; } + value: Number(number) }); }, readIdent: function() { - var expression = this.text; - - var ident = ''; var start = this.index; - - var lastDot, peekIndex, methodName, ch; - while (this.index < this.text.length) { - ch = this.text.charAt(this.index); - if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { - if (ch === '.') lastDot = this.index; - ident += ch; - } else { + var ch = this.text.charAt(this.index); + if (!(this.isIdent(ch) || this.isNumber(ch))) { break; } this.index++; } - - //check if the identifier ends with . and if so move back one char - if (lastDot && ident[ident.length - 1] === '.') { - this.index--; - ident = ident.slice(0, -1); - lastDot = ident.lastIndexOf('.'); - if (lastDot === -1) { - lastDot = undefined; - } - } - - //check if this is not a method invocation and if it is back out to last dot - if (lastDot) { - peekIndex = this.index; - while (peekIndex < this.text.length) { - ch = this.text.charAt(peekIndex); - if (ch === '(') { - methodName = ident.substr(lastDot - start + 1); - ident = ident.substr(0, lastDot - start); - this.index = peekIndex; - break; - } - if (this.isWhitespace(ch)) { - peekIndex++; - } else { - break; - } - } - } - this.tokens.push({ index: start, - text: ident, - fn: CONSTANTS[ident] || getterFn(ident, this.options, expression) + text: this.text.slice(start, this.index), + identifier: true }); - - if (methodName) { - this.tokens.push({ - index: lastDot, - text: '.' - }); - this.tokens.push({ - index: lastDot + 1, - text: methodName - }); - } }, readString: function(quote) { @@ -11755,9 +11902,8 @@ Lexer.prototype = { this.tokens.push({ index: start, text: rawString, - string: string, constant: true, - fn: function() { return string; } + value: string }); return; } else { @@ -11818,16 +11964,14 @@ Parser.prototype = { primary = this.arrayDeclaration(); } else if (this.expect('{')) { primary = this.object(); + } else if (this.peek().identifier && this.peek().text in CONSTANTS) { + primary = CONSTANTS[this.consume().text]; + } else if (this.peek().identifier) { + primary = this.identifier(); + } else if (this.peek().constant) { + primary = this.constant(); } else { - var token = this.expect(); - primary = token.fn; - if (!primary) { - this.throwError('not a primary expression', token); - } - if (token.constant) { - primary.constant = true; - primary.literal = true; - } + this.throwError('not a primary expression', this.peek()); } var next, context; @@ -11861,8 +12005,11 @@ Parser.prototype = { }, peek: function(e1, e2, e3, e4) { - if (this.tokens.length > 0) { - var token = this.tokens[0]; + return this.peekAhead(0, e1, e2, e3, e4); + }, + peekAhead: function(i, e1, e2, e3, e4) { + if (this.tokens.length > i) { + var token = this.tokens[i]; var t = token.text; if (t === e1 || t === e2 || t === e3 || t === e4 || (!e1 && !e2 && !e3 && !e4)) { @@ -11882,12 +12029,19 @@ Parser.prototype = { }, consume: function(e1) { - if (!this.expect(e1)) { + if (this.tokens.length === 0) { + throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); + } + + var token = this.expect(e1); + if (!token) { this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); } + return token; }, - unaryFn: function(fn, right) { + unaryFn: function(op, right) { + var fn = OPERATORS[op]; return extend(function $parseUnaryFn(self, locals) { return fn(self, locals, right); }, { @@ -11896,7 +12050,8 @@ Parser.prototype = { }); }, - binaryFn: function(left, fn, right, isBranching) { + binaryFn: function(left, op, right, isBranching) { + var fn = OPERATORS[op]; return extend(function $parseBinaryFn(self, locals) { return fn(self, locals, left, right); }, { @@ -11905,6 +12060,28 @@ Parser.prototype = { }); }, + identifier: function() { + var id = this.consume().text; + + //Continue reading each `.identifier` unless it is a method invocation + while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) { + id += this.consume().text + this.consume().text; + } + + return getterFn(id, this.options, this.text); + }, + + constant: function() { + var value = this.consume().value; + + return extend(function $parseConstant() { + return value; + }, { + constant: true, + literal: true + }); + }, + statements: function() { var statements = []; while (true) { @@ -11936,8 +12113,7 @@ Parser.prototype = { }, filter: function(inputFn) { - var token = this.expect(); - var fn = this.$filter(token.text); + var fn = this.$filter(this.consume().text); var argsFn; var args; @@ -12000,7 +12176,7 @@ Parser.prototype = { var token; if ((token = this.expect('?'))) { middle = this.assignment(); - if ((token = this.expect(':'))) { + if (this.consume(':')) { var right = this.assignment(); return extend(function $parseTernary(self, locals) { @@ -12008,9 +12184,6 @@ Parser.prototype = { }, { constant: left.constant && middle.constant && right.constant }); - - } else { - this.throwError('expected :', token); } } @@ -12021,7 +12194,7 @@ Parser.prototype = { var left = this.logicalAND(); var token; while ((token = this.expect('||'))) { - left = this.binaryFn(left, token.fn, this.logicalAND(), true); + left = this.binaryFn(left, token.text, this.logicalAND(), true); } return left; }, @@ -12029,8 +12202,8 @@ Parser.prototype = { logicalAND: function() { var left = this.equality(); var token; - if ((token = this.expect('&&'))) { - left = this.binaryFn(left, token.fn, this.logicalAND(), true); + while ((token = this.expect('&&'))) { + left = this.binaryFn(left, token.text, this.equality(), true); } return left; }, @@ -12038,8 +12211,8 @@ Parser.prototype = { equality: function() { var left = this.relational(); var token; - if ((token = this.expect('==','!=','===','!=='))) { - left = this.binaryFn(left, token.fn, this.equality()); + while ((token = this.expect('==','!=','===','!=='))) { + left = this.binaryFn(left, token.text, this.relational()); } return left; }, @@ -12047,8 +12220,8 @@ Parser.prototype = { relational: function() { var left = this.additive(); var token; - if ((token = this.expect('<', '>', '<=', '>='))) { - left = this.binaryFn(left, token.fn, this.relational()); + while ((token = this.expect('<', '>', '<=', '>='))) { + left = this.binaryFn(left, token.text, this.additive()); } return left; }, @@ -12057,7 +12230,7 @@ Parser.prototype = { var left = this.multiplicative(); var token; while ((token = this.expect('+','-'))) { - left = this.binaryFn(left, token.fn, this.multiplicative()); + left = this.binaryFn(left, token.text, this.multiplicative()); } return left; }, @@ -12066,7 +12239,7 @@ Parser.prototype = { var left = this.unary(); var token; while ((token = this.expect('*','/','%'))) { - left = this.binaryFn(left, token.fn, this.unary()); + left = this.binaryFn(left, token.text, this.unary()); } return left; }, @@ -12076,26 +12249,25 @@ Parser.prototype = { if (this.expect('+')) { return this.primary(); } else if ((token = this.expect('-'))) { - return this.binaryFn(Parser.ZERO, token.fn, this.unary()); + return this.binaryFn(Parser.ZERO, token.text, this.unary()); } else if ((token = this.expect('!'))) { - return this.unaryFn(token.fn, this.unary()); + return this.unaryFn(token.text, this.unary()); } else { return this.primary(); } }, fieldAccess: function(object) { - var expression = this.text; - var field = this.expect().text; - var getter = getterFn(field, this.options, expression); + var getter = this.identifier(); return extend(function $parseFieldAccess(scope, locals, self) { - return getter(self || object(scope, locals)); + var o = self || object(scope, locals); + return (o == null) ? undefined : getter(o); }, { assign: function(scope, value, locals) { var o = object(scope, locals); if (!o) object.assign(scope, o = {}); - return setter(o, field, value, expression); + return getter.assign(o, value); } }); }, @@ -12140,7 +12312,7 @@ Parser.prototype = { var args = argsFn.length ? [] : null; return function $parseFunctionCall(scope, locals) { - var context = contextGetter ? contextGetter(scope, locals) : scope; + var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope; var fn = fnGetter(scope, locals, context) || noop; if (args) { @@ -12153,13 +12325,13 @@ Parser.prototype = { ensureSafeObject(context, expressionText); ensureSafeFunction(fn, expressionText); - // IE stupidity! (IE doesn't have apply for some native functions) + // IE doesn't have apply for some native functions var v = fn.apply ? fn.apply(context, args) : fn(args[0], args[1], args[2], args[3], args[4]); return ensureSafeObject(v, expressionText); - }; + }; }, // This is used with json array declaration @@ -12171,8 +12343,7 @@ Parser.prototype = { // Support trailing commas per ES5.1. break; } - var elementFn = this.expression(); - elementFns.push(elementFn); + elementFns.push(this.expression()); } while (this.expect(',')); } this.consume(']'); @@ -12198,11 +12369,16 @@ Parser.prototype = { // Support trailing commas per ES5.1. break; } - var token = this.expect(); - keys.push(token.string || token.text); + var token = this.consume(); + if (token.constant) { + keys.push(token.value); + } else if (token.identifier) { + keys.push(token.text); + } else { + this.throwError("invalid key", token); + } this.consume(':'); - var value = this.expression(); - valueFns.push(value); + valueFns.push(this.expression()); } while (this.expect(',')); } this.consume('}'); @@ -12644,13 +12820,21 @@ function $ParseProvider() { function addInterceptor(parsedExpression, interceptorFn) { if (!interceptorFn) return parsedExpression; + var watchDelegate = parsedExpression.$$watchDelegate; - var fn = function interceptedExpression(scope, locals) { + var regularWatch = + watchDelegate !== oneTimeLiteralWatchDelegate && + watchDelegate !== oneTimeWatchDelegate; + + var fn = regularWatch ? function regularInterceptedExpression(scope, locals) { + var value = parsedExpression(scope, locals); + return interceptorFn(value, scope, locals); + } : function oneTimeInterceptedExpression(scope, locals) { var value = parsedExpression(scope, locals); var result = interceptorFn(value, scope, locals); // we only return the interceptor's result if the // initial value is defined (for bind-once) - return isDefined(value) || interceptorFn.$stateful ? result : value; + return isDefined(value) ? result : value; }; // Propagate $$watchDelegates other then inputsWatchDelegate @@ -12675,7 +12859,11 @@ function $ParseProvider() { * @requires $rootScope * * @description - * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). + * A service that helps you run functions asynchronously, and use their return values (or exceptions) + * when they are done processing. + * + * This is an implementation of promises/deferred objects inspired by + * [Kris Kowal's Q](https://github.com/kriskowal/q). * * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred * implementations, and the other which resembles ES6 promises to some degree. @@ -12811,16 +12999,12 @@ function $ParseProvider() { * * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` * - * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, + * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise, * but to do so without modifying the final value. This is useful to release resources or do some * clean-up that needs to be done whether the promise was rejected or resolved. See the [full * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for * more information. * - * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as - * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to - * make your code IE8 and Android 2.x compatible. - * * # Chaining promises * * Because calling the `then` method of a promise returns a new derived promise, it is easily @@ -13241,12 +13425,10 @@ function qFactory(nextTick, exceptionHandler) { function $$RAFProvider() { //rAF this.$get = ['$window', '$timeout', function($window, $timeout) { var requestAnimationFrame = $window.requestAnimationFrame || - $window.webkitRequestAnimationFrame || - $window.mozRequestAnimationFrame; + $window.webkitRequestAnimationFrame; var cancelAnimationFrame = $window.cancelAnimationFrame || $window.webkitCancelAnimationFrame || - $window.mozCancelAnimationFrame || $window.webkitCancelRequestAnimationFrame; var rafSupported = !!requestAnimationFrame; @@ -13375,7 +13557,6 @@ function $RootScopeProvider() { var child = parent.$new(); parent.salutation = "Hello"; - child.name = "World"; expect(child.salutation).toEqual('Hello'); child.salutation = "Welcome"; @@ -14013,7 +14194,7 @@ function $RootScopeProvider() { while (asyncQueue.length) { try { asyncTask = asyncQueue.shift(); - asyncTask.scope.$eval(asyncTask.expression); + asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); } catch (e) { $exceptionHandler(e); } @@ -14043,11 +14224,11 @@ function $RootScopeProvider() { if (ttl < 5) { logIdx = 4 - ttl; if (!watchLog[logIdx]) watchLog[logIdx] = []; - logMsg = (isFunction(watch.exp)) - ? 'fn: ' + (watch.exp.name || watch.exp.toString()) - : watch.exp; - logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); - watchLog[logIdx].push(logMsg); + watchLog[logIdx].push({ + msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, + newVal: value, + oldVal: last + }); } } else if (watch === lastDirtyWatch) { // If the most recently dirty watcher is now clean, short circuit since the remaining watchers @@ -14080,7 +14261,7 @@ function $RootScopeProvider() { throw $rootScopeMinErr('infdig', '{0} $digest() iterations reached. Aborting!\n' + 'Watchers fired in the last 5 iterations: {1}', - TTL, toJson(watchLog)); + TTL, watchLog); } } while (dirty || asyncQueue.length); @@ -14228,8 +14409,9 @@ function $RootScopeProvider() { * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. * + * @param {(object)=} locals Local variables object, useful for overriding values in scope. */ - $evalAsync: function(expr) { + $evalAsync: function(expr, locals) { // if we are outside of an $digest loop and this is the first time we are scheduling async // task also schedule async auto-flush if (!$rootScope.$$phase && !asyncQueue.length) { @@ -14240,7 +14422,7 @@ function $RootScopeProvider() { }); } - asyncQueue.push({scope: this, expression: expr}); + asyncQueue.push({scope: this, expression: expr, locals: locals}); }, $$postDigest: function(fn) { @@ -14431,7 +14613,7 @@ function $RootScopeProvider() { do { namedListeners = scope.$$listeners[name] || empty; event.currentScope = scope; - for (i=0, length=namedListeners.length; i -1; - }; - } - } - - var search = function(obj, text) { - if (typeof text === 'string' && text.charAt(0) === '!') { - return !search(obj, text.substr(1)); - } - switch (typeof obj) { - case 'boolean': - case 'number': - case 'string': - return comparator(obj, text); - case 'object': - switch (typeof text) { - case 'object': - return comparator(obj, text); - default: - for (var objKey in obj) { - if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { - return true; - } - } - break; - } - return false; - case 'array': - for (var i = 0; i < obj.length; i++) { - if (search(obj[i], text)) { - return true; - } - } - return false; - default: - return false; - } - }; switch (typeof expression) { + case 'function': + predicateFn = expression; + break; case 'boolean': case 'number': case 'string': - // Set up expression object and fall through - expression = {$:expression}; - // jshint -W086 + matchAgainstAnyProp = true; + //jshint -W086 case 'object': - // jshint +W086 - for (var key in expression) { - (function(path) { - if (typeof expression[path] === 'undefined') return; - predicates.push(function(value) { - return search(path == '$' ? value : (value && value[path]), expression[path]); - }); - })(key); - } - break; - case 'function': - predicates.push(expression); + //jshint +W086 + predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); break; default: return array; } - var filtered = []; - for (var j = 0; j < array.length; j++) { - var value = array[j]; - if (predicates.check(value, j)) { - filtered.push(value); - } - } - return filtered; + + return array.filter(predicateFn); }; } +// Helper functions for `filterFilter` +function createPredicateFn(expression, comparator, matchAgainstAnyProp) { + var predicateFn; + + if (comparator === true) { + comparator = equals; + } else if (!isFunction(comparator)) { + comparator = function(actual, expected) { + if (isObject(actual) || isObject(expected)) { + // Prevent an object to be considered equal to a string like `'[object'` + return false; + } + + actual = lowercase('' + actual); + expected = lowercase('' + expected); + return actual.indexOf(expected) !== -1; + }; + } + + predicateFn = function(item) { + return deepCompare(item, expression, comparator, matchAgainstAnyProp); + }; + + return predicateFn; +} + +function deepCompare(actual, expected, comparator, matchAgainstAnyProp) { + var actualType = typeof actual; + var expectedType = typeof expected; + + if ((expectedType === 'string') && (expected.charAt(0) === '!')) { + return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); + } else if (actualType === 'array') { + // In case `actual` is an array, consider it a match + // if ANY of it's items matches `expected` + return actual.some(function(item) { + return deepCompare(item, expected, comparator, matchAgainstAnyProp); + }); + } + + switch (actualType) { + case 'object': + var key; + if (matchAgainstAnyProp) { + for (key in actual) { + if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) { + return true; + } + } + return false; + } else if (expectedType === 'object') { + for (key in expected) { + var expectedVal = expected[key]; + if (isFunction(expectedVal)) { + continue; + } + + var keyIsDollar = key === '$'; + var actualVal = keyIsDollar ? actual : actual[key]; + if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) { + return false; + } + } + return true; + } else { + return comparator(actual, expected); + } + break; + case 'function': + return false; + default: + return comparator(actual, expected); + } +} + /** * @ngdoc filter * @name currency @@ -16595,7 +16769,7 @@ function filterFilter() { * * @param {number} amount Input to filter. * @param {string=} symbol Currency symbol or identifier to be displayed. - * @param {number=} fractionSize Number of decimal places to round the amount to. + * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale * @returns {string} Formatted number. * * @@ -16645,8 +16819,7 @@ function currencyFilter($locale) { } if (isUndefined(fractionSize)) { - // TODO: read the default value from the locale file - fractionSize = 2; + fractionSize = formats.PATTERNS[1].maxFrac; } // if null or undefined pass it through @@ -16735,7 +16908,6 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { if (numStr.indexOf('e') !== -1) { var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); if (match && match[2] == '-' && match[3] > fractionSize + 1) { - numStr = '0'; number = 0; } else { formatedText = numStr; @@ -16756,10 +16928,6 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); - if (number === 0) { - isNegative = false; - } - var fraction = ('' + number).split(DECIMAL_SEP); var whole = fraction[0]; fraction = fraction[1] || ''; @@ -16771,7 +16939,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { if (whole.length >= (lgroup + group)) { pos = whole.length - lgroup; for (i = 0; i < pos; i++) { - if ((pos - i)%group === 0 && i !== 0) { + if ((pos - i) % group === 0 && i !== 0) { formatedText += groupSep; } formatedText += whole.charAt(i); @@ -16779,7 +16947,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { } for (i = pos; i < whole.length; i++) { - if ((whole.length - i)%lgroup === 0 && i !== 0) { + if ((whole.length - i) % lgroup === 0 && i !== 0) { formatedText += groupSep; } formatedText += whole.charAt(i); @@ -16792,15 +16960,19 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); } else { - - if (fractionSize > 0 && number > -1 && number < 1) { + if (fractionSize > 0 && number < 1) { formatedText = number.toFixed(fractionSize); + number = parseFloat(formatedText); } } - parts.push(isNegative ? pattern.negPre : pattern.posPre); - parts.push(formatedText); - parts.push(isNegative ? pattern.negSuf : pattern.posSuf); + if (number === 0) { + isNegative = false; + } + + parts.push(isNegative ? pattern.negPre : pattern.posPre, + formatedText, + isNegative ? pattern.negSuf : pattern.posSuf); return parts.join(''); } @@ -16942,8 +17114,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) * * `'a'`: AM/PM marker * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) - * * `'ww'`: ISO-8601 week of year (00-53) - * * `'w'`: ISO-8601 week of year (0-53) + * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year + * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year * * `format` string can also be one of the following predefined * {@link guide/i18n localizable formats}: @@ -17019,10 +17191,10 @@ function dateFilter($locale) { tzMin = int(match[9] + match[11]); } dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); - var h = int(match[4]||0) - tzHour; - var m = int(match[5]||0) - tzMin; - var s = int(match[6]||0); - var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); + var h = int(match[4] || 0) - tzHour; + var m = int(match[5] || 0) - tzMin; + var s = int(match[6] || 0); + var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); timeSetter.call(date, h, m, s, ms); return date; } @@ -17087,25 +17259,31 @@ function dateFilter($locale) { * the binding is automatically converted to JSON. * * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. + * @param {number=} spacing The number of spaces to use per indentation, defaults to 2. * @returns {string} JSON string. * * * @example -
{{ {'name':'value'} | json }}
+
{{ {'name':'value'} | json }}
+
{{ {'name':'value'} | json:4 }}
it('should jsonify filtered objects', function() { - expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); + expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); + expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); });
* */ function jsonFilter() { - return function(object) { - return toJson(object, true); + return function(object, spacing) { + if (isUndefined(spacing)) { + spacing = 2; + } + return toJson(object, spacing); }; } @@ -17254,7 +17432,7 @@ function limitToFilter() { n = input.length; } - for (; i= minVal; + return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; }; attr.$observe('min', function(val) { minVal = parseObservedDateValue(val); @@ -19585,18 +19803,18 @@ function createDateInputType(type, regexp, parseDate, format) { if (isDefined(attr.max) || attr.ngMax) { var maxVal; ctrl.$validators.max = function(value) { - return ctrl.$isEmpty(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; + return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; }; attr.$observe('max', function(val) { maxVal = parseObservedDateValue(val); ctrl.$validate(); }); } - // Override the standard $isEmpty to detect invalid dates as well - ctrl.$isEmpty = function(value) { + + function isValidDate(value) { // Invalid Date: getTime() returns NaN - return !value || (value.getTime && value.getTime() !== value.getTime()); - }; + return value && !(value.getTime && value.getTime() !== value.getTime()); + } function parseObservedDateValue(val) { return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined; @@ -19680,7 +19898,8 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { stringBasedInputType(ctrl); ctrl.$$parserName = 'url'; - ctrl.$validators.url = function(value) { + ctrl.$validators.url = function(modelValue, viewValue) { + var value = modelValue || viewValue; return ctrl.$isEmpty(value) || URL_REGEXP.test(value); }; } @@ -19692,7 +19911,8 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { stringBasedInputType(ctrl); ctrl.$$parserName = 'email'; - ctrl.$validators.email = function(value) { + ctrl.$validators.email = function(modelValue, viewValue) { + var value = modelValue || viewValue; return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); }; } @@ -19746,9 +19966,11 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt element[0].checked = ctrl.$viewValue; }; - // Override the standard `$isEmpty` because an empty checkbox is never equal to the trueValue + // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` + // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert + // it to a boolean. ctrl.$isEmpty = function(value) { - return value !== trueValue; + return value === false; }; ctrl.$formatters.push(function(value) { @@ -19780,7 +20002,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any + * length. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. @@ -19812,7 +20035,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any + * length. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. @@ -19941,12 +20165,17 @@ var VALID_CLASS = 'ng-valid', * @property {string} $viewValue Actual string value in the view. * @property {*} $modelValue The value in the model that the control is bound to. * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever - the control reads value from the DOM. The functions are called in array order, each passing the value - through to the next. The last return value is forwarded to the $validators collection. - Used to sanitize / convert the value. - Returning undefined from a parser means a parse error occurred. No $validators will - run and the 'ngModel' will not be updated until the parse error is resolved. The parse error is stored - in 'ngModel.$error.parse'. + the control reads value from the DOM. The functions are called in array order, each passing + its return value through to the next. The last return value is forwarded to the + {@link ngModel.NgModelController#$validators `$validators`} collection. + +Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue +`$viewValue`}. + +Returning `undefined` from a parser means a parse error occurred. In that case, +no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` +will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} +is set to `true`. The parse error is stored in `ngModel.$error.parse`. * * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever @@ -20023,13 +20252,18 @@ var VALID_CLASS = 'ng-valid', * * @description * - * `NgModelController` provides API for the `ng-model` directive. The controller contains - * services for data-binding, validation, CSS updates, and value formatting and parsing. It - * purposefully does not contain any logic which deals with DOM rendering or listening to - * DOM events. Such DOM related logic should be provided by other directives which make use of - * `NgModelController` for data-binding. + * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. + * The controller contains services for data-binding, validation, CSS updates, and value formatting + * and parsing. It purposefully does not contain any logic which deals with DOM rendering or + * listening to DOM events. + * Such DOM related logic should be provided by other directives which make use of + * `NgModelController` for data-binding to control elements. + * Angular provides this DOM logic for most {@link input `input`} elements. + * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example + * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. * - * ## Custom Control Example + * @example + * ### Custom Control Example * This example shows how to use `NgModelController` with a custom control to achieve * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) * collaborate together to achieve the desired result. @@ -20071,7 +20305,7 @@ var VALID_CLASS = 'ng-valid', // Listen for change events to enable binding element.on('blur keyup change', function() { - scope.$apply(read); + scope.$evalAsync(read); }); read(); // initialize @@ -20126,6 +20360,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { this.$viewValue = Number.NaN; this.$modelValue = Number.NaN; + this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. this.$validators = {}; this.$asyncValidators = {}; this.$parsers = []; @@ -20144,32 +20379,33 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ var parsedNgModel = $parse($attr.ngModel), + parsedNgModelAssign = parsedNgModel.assign, + ngModelGet = parsedNgModel, + ngModelSet = parsedNgModelAssign, pendingDebounce = null, ctrl = this; - var ngModelGet = function ngModelGet() { - var modelValue = parsedNgModel($scope); - if (ctrl.$options && ctrl.$options.getterSetter && isFunction(modelValue)) { - modelValue = modelValue(); - } - return modelValue; - }; - - var ngModelSet = function ngModelSet(newValue) { - var getterSetter; - if (ctrl.$options && ctrl.$options.getterSetter && - isFunction(getterSetter = parsedNgModel($scope))) { - - getterSetter(ctrl.$modelValue); - } else { - parsedNgModel.assign($scope, ctrl.$modelValue); - } - }; - this.$$setOptions = function(options) { ctrl.$options = options; + if (options && options.getterSetter) { + var invokeModelGetter = $parse($attr.ngModel + '()'), + invokeModelSetter = $parse($attr.ngModel + '($$$p)'); - if (!parsedNgModel.assign && (!options || !options.getterSetter)) { + ngModelGet = function($scope) { + var modelValue = parsedNgModel($scope); + if (isFunction(modelValue)) { + modelValue = invokeModelGetter($scope); + } + return modelValue; + }; + ngModelSet = function($scope, newValue) { + if (isFunction(parsedNgModel($scope))) { + invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); + } else { + parsedNgModelAssign($scope, ctrl.$modelValue); + } + }; + } else if (!parsedNgModel.assign) { throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", $attr.ngModel, startingTag($element)); } @@ -20202,17 +20438,18 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * @name ngModel.NgModelController#$isEmpty * * @description - * This is called when we need to determine if the value of the input is empty. + * This is called when we need to determine if the value of an input is empty. * * For instance, the required directive does this to work out if the input has data or not. + * * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. * * You can override this for input directives whose concept of being empty is different to the * default. The `checkboxInputType` directive does this because in its case a value of `false` * implies empty. * - * @param {*} value Model value to check. - * @returns {boolean} True if `value` is empty. + * @param {*} value The value of the input to check for emptiness. + * @returns {boolean} True if `value` is "empty". */ this.$isEmpty = function(value) { return isUndefined(value) || value === '' || value === null || value !== value; @@ -20263,9 +20500,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * @description * Sets the control to its pristine state. * - * This method can be called to remove the 'ng-dirty' class and set the control to its pristine - * state (ng-pristine class). A model is considered to be pristine when the model has not been changed - * from when first compiled within then form. + * This method can be called to remove the `ng-dirty` class and set the control to its pristine + * state (`ng-pristine` class). A model is considered to be pristine when the control + * has not been changed from when first compiled. */ this.$setPristine = function() { ctrl.$dirty = false; @@ -20274,6 +20511,25 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ $animate.addClass($element, PRISTINE_CLASS); }; + /** + * @ngdoc method + * @name ngModel.NgModelController#$setDirty + * + * @description + * Sets the control to its dirty state. + * + * This method can be called to remove the `ng-pristine` class and set the control to its dirty + * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed + * from when first compiled. + */ + this.$setDirty = function() { + ctrl.$dirty = true; + ctrl.$pristine = false; + $animate.removeClass($element, PRISTINE_CLASS); + $animate.addClass($element, DIRTY_CLASS); + parentForm.$setDirty(); + }; + /** * @ngdoc method * @name ngModel.NgModelController#$setUntouched @@ -20281,8 +20537,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * @description * Sets the control to its untouched state. * - * This method can be called to remove the 'ng-touched' class and set the control to its - * untouched state (ng-untouched class). Upon compilation, a model is set as untouched + * This method can be called to remove the `ng-touched` class and set the control to its + * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched * by default, however this function can be used to restore that state if the model has * already been touched by the user. */ @@ -20299,10 +20555,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * @description * Sets the control to its touched state. * - * This method can be called to remove the 'ng-untouched' class and set the control to its - * touched state (ng-touched class). A model is considered to be touched when the user has - * first interacted (focussed) on the model input element and then shifted focus away (blurred) - * from the input element. + * This method can be called to remove the `ng-untouched` class and set the control to its + * touched state (`ng-touched` class). A model is considered to be touched when the user has + * first focused the control element and then shifted focus away from the control (blur event). */ this.$setTouched = function() { ctrl.$touched = true; @@ -20380,14 +20635,51 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * @name ngModel.NgModelController#$validate * * @description - * Runs each of the registered validators (first synchronous validators and then asynchronous validators). + * Runs each of the registered validators (first synchronous validators and then + * asynchronous validators). + * If the validity changes to invalid, the model will be set to `undefined`, + * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. + * If the validity changes to valid, it will set the model to the last available valid + * modelValue, i.e. either the last parsed value or the last value set from the scope. */ this.$validate = function() { // ignore $validate before model is initialized if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { return; } - this.$$parseAndValidate(); + + var viewValue = ctrl.$$lastCommittedViewValue; + // Note: we use the $$rawModelValue as $modelValue might have been + // set to undefined during a view -> model update that found validation + // errors. We can't parse the view here, since that could change + // the model although neither viewValue nor the model on the scope changed + var modelValue = ctrl.$$rawModelValue; + + // Check if the there's a parse error, so we don't unset it accidentially + var parserName = ctrl.$$parserName || 'parse'; + var parserValid = ctrl.$error[parserName] ? false : undefined; + + var prevValid = ctrl.$valid; + var prevModelValue = ctrl.$modelValue; + + var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; + + ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) { + // If there was no change in validity, don't update the model + // This prevents changing an invalid modelValue to undefined + if (!allowInvalid && prevValid !== allValid) { + // Note: Don't check ctrl.$valid here, as we could have + // external validators (e.g. calculated on the server), + // that just call $setValidity and need the model value + // to calculate their validity. + ctrl.$modelValue = allValid ? modelValue : undefined; + + if (ctrl.$modelValue !== prevModelValue) { + ctrl.$$writeModelToScope(); + } + } + }); + }; this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) { @@ -20506,11 +20798,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ // change to dirty if (ctrl.$pristine) { - ctrl.$dirty = true; - ctrl.$pristine = false; - $animate.removeClass($element, PRISTINE_CLASS); - $animate.addClass($element, DIRTY_CLASS); - parentForm.$setDirty(); + this.$setDirty(); } this.$$parseAndValidate(); }; @@ -20531,15 +20819,20 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ } if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { // ctrl.$modelValue has not been touched yet... - ctrl.$modelValue = ngModelGet(); + ctrl.$modelValue = ngModelGet($scope); } var prevModelValue = ctrl.$modelValue; var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; + ctrl.$$rawModelValue = modelValue; + if (allowInvalid) { ctrl.$modelValue = modelValue; writeToModelIfNeeded(); } - ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) { + + // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. + // This can happen if e.g. $setViewValue is called from inside a parser + ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { if (!allowInvalid) { // Note: Don't check ctrl.$valid here, as we could have // external validators (e.g. calculated on the server), @@ -20558,7 +20851,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ }; this.$$writeModelToScope = function() { - ngModelSet(ctrl.$modelValue); + ngModelSet($scope, ctrl.$modelValue); forEach(ctrl.$viewChangeListeners, function(listener) { try { listener(); @@ -20654,12 +20947,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ // ng-change executes in apply phase // 4. view should be changed back to 'a' $scope.$watch(function ngModelWatch() { - var modelValue = ngModelGet(); + var modelValue = ngModelGet($scope); // if scope model value and ngModel value are out of sync // TODO(perf): why not move this to the action fn? if (modelValue !== ctrl.$modelValue) { - ctrl.$modelValue = modelValue; + ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; var formatters = ctrl.$formatters, idx = formatters.length; @@ -20844,7 +21137,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * */ -var ngModelDirective = function() { +var ngModelDirective = ['$rootScope', function($rootScope) { return { restrict: 'A', require: ['ngModel', '^?form', '^?ngModelOptions'], @@ -20888,15 +21181,17 @@ var ngModelDirective = function() { element.on('blur', function(ev) { if (modelCtrl.$touched) return; - scope.$apply(function() { - modelCtrl.$setTouched(); - }); + if ($rootScope.$$phase) { + scope.$evalAsync(modelCtrl.$setTouched); + } else { + scope.$apply(modelCtrl.$setTouched); + } }); } }; } }; -}; +}]; /** @@ -20985,8 +21280,8 @@ var requiredDirective = function() { if (!ctrl) return; attr.required = true; // force truthy in case we are on non input element - ctrl.$validators.required = function(value) { - return !attr.required || !ctrl.$isEmpty(value); + ctrl.$validators.required = function(modelValue, viewValue) { + return !attr.required || !ctrl.$isEmpty(viewValue); }; attr.$observe('required', function() { @@ -21007,7 +21302,7 @@ var patternDirective = function() { var regexp, patternExp = attr.ngPattern || attr.pattern; attr.$observe('pattern', function(regex) { if (isString(regex) && regex.length > 0) { - regex = new RegExp(regex); + regex = new RegExp('^' + regex + '$'); } if (regex && !regex.test) { @@ -21035,13 +21330,14 @@ var maxlengthDirective = function() { link: function(scope, elm, attr, ctrl) { if (!ctrl) return; - var maxlength = 0; + var maxlength = -1; attr.$observe('maxlength', function(value) { - maxlength = int(value) || 0; + var intVal = int(value); + maxlength = isNaN(intVal) ? -1 : intVal; ctrl.$validate(); }); ctrl.$validators.maxlength = function(modelValue, viewValue) { - return ctrl.$isEmpty(modelValue) || viewValue.length <= maxlength; + return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength); }; } }; @@ -21060,7 +21356,7 @@ var minlengthDirective = function() { ctrl.$validate(); }); ctrl.$validators.minlength = function(modelValue, viewValue) { - return ctrl.$isEmpty(modelValue) || viewValue.length >= minlength; + return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; }; } }; @@ -21298,7 +21594,7 @@ var ngValueDirective = function() { * `ngModelOptions` has an effect on the element it's declared on and its descendants. * * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: - * - `updateOn`: string specifying which event should be the input bound to. You can set several + * - `updateOn`: string specifying which event should the input be bound to. You can set several * events using an space delimited list. There is a special event called `default` that * matches the default events belonging of the control. * - `debounce`: integer value which contains the debounce model update value in milliseconds. A @@ -21688,12 +21984,11 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate * @name ngBindHtml * * @description - * Creates a binding that will innerHTML the result of evaluating the `expression` into the current - * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link - * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` - * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in - * core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to - * include "angular-sanitize.js" in your application. + * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, + * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. + * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link + * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} + * in your module's dependencies, you need to include "angular-sanitize.js" in your application. * * You may also bypass sanitization for values you know are safe. To do so, bind to * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example @@ -23757,7 +24052,9 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); */ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { - var BRACE = /{}/g; + var BRACE = /{}/g, + IS_WHEN = /^when(Minus)?(.+)$/; + return { restrict: 'EA', link: function(scope, element, attr) { @@ -23768,34 +24065,44 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp whensExpFns = {}, startSymbol = $interpolate.startSymbol(), endSymbol = $interpolate.endSymbol(), - isWhen = /^when(Minus)?(.+)$/; + braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, + watchRemover = angular.noop, + lastCount; forEach(attr, function(expression, attributeName) { - if (isWhen.test(attributeName)) { - whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = - element.attr(attr.$attr[attributeName]); + var tmpMatch = IS_WHEN.exec(attributeName); + if (tmpMatch) { + var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); + whens[whenKey] = element.attr(attr.$attr[attributeName]); } }); forEach(whens, function(expression, key) { - whensExpFns[key] = - $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + - offset + endSymbol)); + whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); + }); - scope.$watch(function ngPluralizeWatch() { - var value = parseFloat(scope.$eval(numberExp)); + scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { + var count = parseFloat(newVal); + var countIsNaN = isNaN(count); - if (!isNaN(value)) { - //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, - //check it against pluralization rules in $locale service - if (!(value in whens)) value = $locale.pluralCat(value - offset); - return whensExpFns[value](scope); - } else { - return ''; + if (!countIsNaN && !(count in whens)) { + // If an explicit number rule such as 1, 2, 3... is defined, just use it. + // Otherwise, check it against pluralization rules in $locale service. + count = $locale.pluralCat(count - offset); + } + + // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. + // In JS `NaN !== NaN`, so we have to exlicitly check. + if ((count !== lastCount) && !(countIsNaN && isNaN(lastCount))) { + watchRemover(); + watchRemover = scope.$watch(whensExpFns[count], updateElementText); + lastCount = count; } - }, function ngPluralizeWatchAction(newVal) { - element.text(newVal); }); + + function updateElementText(newText) { + element.text(newText || ''); + } } }; }]; @@ -24057,7 +24364,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { var aliasAs = match[3]; var trackByExp = match[4]; - match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); + match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); if (!match) { throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", @@ -24166,7 +24473,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { }); throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", - expression, trackById, toJson(value)); + expression, trackById, value); } else { // new never before seen block nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; @@ -24277,17 +24584,17 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; * * ### Overriding `.ng-hide` * - * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change + * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` * class in CSS: * * ```css * .ng-hide { * /* this is just another form of hiding an element */ - * display:block!important; - * position:absolute; - * top:-9999px; - * left:-9999px; + * display: block!important; + * position: absolute; + * top: -9999px; + * left: -9999px; * } * ``` * @@ -24307,13 +24614,13 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; * .my-element.ng-hide-add, .my-element.ng-hide-remove { * /* this is required as of 1.3x to properly * apply all styling in a show/hide animation */ - * transition:0s linear all; + * transition: 0s linear all; * } * * .my-element.ng-hide-add-active, * .my-element.ng-hide-remove-active { * /* the transition is defined in the active class */ - * transition:1s linear all; + * transition: 1s linear all; * } * * .my-element.ng-hide-add { ... } @@ -24355,29 +24662,29 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; .animate-show { - line-height:20px; - opacity:1; - padding:10px; - border:1px solid black; - background:white; + line-height: 20px; + opacity: 1; + padding: 10px; + border: 1px solid black; + background: white; } .animate-show.ng-hide-add.ng-hide-add-active, .animate-show.ng-hide-remove.ng-hide-remove-active { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; + -webkit-transition: all linear 0.5s; + transition: all linear 0.5s; } .animate-show.ng-hide { - line-height:0; - opacity:0; - padding:0 10px; + line-height: 0; + opacity: 0; + padding: 0 10px; } .check-element { - padding:10px; - border:1px solid black; - background:white; + padding: 10px; + border: 1px solid black; + background: white; } @@ -24451,17 +24758,17 @@ var ngShowDirective = ['$animate', function($animate) { * * ### Overriding `.ng-hide` * - * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change + * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` * class in CSS: * * ```css * .ng-hide { * /* this is just another form of hiding an element */ - * display:block!important; - * position:absolute; - * top:-9999px; - * left:-9999px; + * display: block!important; + * position: absolute; + * top: -9999px; + * left: -9999px; * } * ``` * @@ -24478,7 +24785,7 @@ var ngShowDirective = ['$animate', function($animate) { * //a working example can be found at the bottom of this page * // * .my-element.ng-hide-add, .my-element.ng-hide-remove { - * transition:0.5s linear all; + * transition: 0.5s linear all; * } * * .my-element.ng-hide-add { ... } @@ -24520,25 +24827,25 @@ var ngShowDirective = ['$animate', function($animate) { .animate-hide { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - line-height:20px; - opacity:1; - padding:10px; - border:1px solid black; - background:white; + -webkit-transition: all linear 0.5s; + transition: all linear 0.5s; + line-height: 20px; + opacity: 1; + padding: 10px; + border: 1px solid black; + background: white; } .animate-hide.ng-hide { - line-height:0; - opacity:0; - padding:0 10px; + line-height: 0; + opacity: 0; + padding: 0 10px; } .check-element { - padding:10px; - border:1px solid black; - background:white; + padding: 10px; + border: 1px solid black; + background: white; } @@ -24867,7 +25174,7 @@ var ngSwitchDefaultDirective = ngDirective({ }]);
-
+

{{text}}
@@ -24945,7 +25252,6 @@ var scriptDirective = ['$templateCache', function($templateCache) { compile: function(element, attr) { if (attr.type == 'text/ng-template') { var templateUrl = attr.id, - // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent text = element[0].text; $templateCache.put(templateUrl, text); @@ -24972,7 +25278,7 @@ var ngOptionsMinErr = minErr('ngOptions'); * In many cases, `ngRepeat` can be used on `