diff --git a/resources/assets/scripts/base/angular.js b/resources/assets/scripts/base/angular.js index 140682e4..a924ef52 100644 --- a/resources/assets/scripts/base/angular.js +++ b/resources/assets/scripts/base/angular.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.2.0rc1 + * @license AngularJS v1.2.0 * (c) 2010-2012 Google, Inc. http://angularjs.org * License: MIT */ @@ -35,10 +35,21 @@ function minErr(module) { return function () { - var prefix = '[' + (module ? module + ':' : '') + arguments[0] + '] ', + var code = arguments[0], + prefix = '[' + (module ? module + ':' : '') + code + '] ', template = arguments[1], templateArgs = arguments, - message; + stringify = function (obj) { + if (isFunction(obj)) { + return obj.toString().replace(/ \{[\s\S]*$/, ''); + } else if (isUndefined(obj)) { + return 'undefined'; + } else if (!isString(obj)) { + return JSON.stringify(obj); + } + return obj; + }, + message, i; message = prefix + template.replace(/\{\d+\}/g, function (match) { var index = +match.slice(1, -1), arg; @@ -57,21 +68,102 @@ function minErr(module) { return match; }); + message = message + '\nhttp://errors.angularjs.org/' + version.full + '/' + + (module ? module + '/' : '') + code; + for (i = 2; i < arguments.length; i++) { + message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + + encodeURIComponent(stringify(arguments[i])); + } + return new Error(message); }; } -//////////////////////////////////// +/* We need to tell jshint what variables are being exported */ +/* global + -angular, + -msie, + -jqLite, + -jQuery, + -slice, + -push, + -toString, + -ngMinErr, + -_angular, + -angularModule, + -nodeName_, + -uid, -/** - * hasOwnProperty may be overwritten by a property of the same name, or entirely - * absent from an object that does not inherit Object.prototype; this copy is - * used instead - */ -var hasOwnPropertyFn = Object.prototype.hasOwnProperty; -var hasOwnPropertyLocal = function(obj, key) { - return hasOwnPropertyFn.call(obj, key); -}; + -lowercase, + -uppercase, + -manualLowercase, + -manualUppercase, + -nodeName_, + -isArrayLike, + -forEach, + -sortedKeys, + -forEachSorted, + -reverseParams, + -nextUid, + -setHashKey, + -extend, + -int, + -inherit, + -noop, + -identity, + -valueFn, + -isUndefined, + -isDefined, + -isObject, + -isString, + -isNumber, + -isDate, + -isArray, + -isFunction, + -isRegExp, + -isWindow, + -isScope, + -isFile, + -isBoolean, + -trim, + -isElement, + -makeMap, + -map, + -size, + -includes, + -indexOf, + -arrayRemove, + -isLeafNode, + -copy, + -shallowCopy, + -equals, + -csp, + -concat, + -sliceArgs, + -bind, + -toJsonReplacer, + -toJson, + -fromJson, + -toBoolean, + -startingTag, + -tryDecodeURIComponent, + -parseKeyValue, + -toKeyValue, + -encodeUriSegment, + -encodeUriQuery, + -angularInit, + -bootstrap, + -snake_case, + -bindJQuery, + -assertArg, + -assertArgFn, + -assertNotHasOwnProperty, + -getter, + -getBlockElements + +*/ + +//////////////////////////////////// /** * @ngdoc function @@ -98,11 +190,13 @@ var uppercase = function(string){return isString(string) ? string.toUpperCase() var manualLowercase = function(s) { + /* jshint bitwise: false */ return isString(s) ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) : s; }; var manualUppercase = function(s) { + /* jshint bitwise: false */ return isString(s) ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) : s; @@ -119,7 +213,7 @@ if ('i' !== 'I'.toLowerCase()) { var /** holds major version number for IE or NaN for real browsers */ - msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]), + msie, jqLite, // delay binding since jQuery could be loaded after us. jQuery, // delay binding slice = [].slice, @@ -135,25 +229,35 @@ var /** holds major version number for IE or NaN for real browsers */ nodeName_, uid = ['0', '0', '0']; +/** + * IE 11 changed the format of the UserAgent string. + * See http://msdn.microsoft.com/en-us/library/ms537503.aspx + */ +msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); +if (isNaN(msie)) { + msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); +} + + /** * @private * @param {*} obj - * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...) + * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, + * String ...) */ function isArrayLike(obj) { if (obj == null || isWindow(obj)) { return false; } - + var length = obj.length; if (obj.nodeType === 1 && length) { return true; } - return isArray(obj) || !isFunction(obj) && ( - length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj - ); + return isString(obj) || isArray(obj) || length === 0 || + typeof length === 'number' && length > 0 && (length - 1) in obj; } /** @@ -233,7 +337,7 @@ function forEachSorted(obj, iterator, context) { * @returns {function(*, string)} */ function reverseParams(iteratorFn) { - return function(value, key) { iteratorFn(key, value) }; + return function(value, key) { iteratorFn(key, value); }; } /** @@ -592,17 +696,17 @@ function map(obj, iterator, context) { * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. */ function size(obj, ownPropsOnly) { - var size = 0, key; + var count = 0, key; if (isArray(obj) || isString(obj)) { return obj.length; } else if (isObject(obj)){ for (key in obj) if (!ownPropsOnly || obj.hasOwnProperty(key)) - size++; + count++; } - return size; + return count; } @@ -649,20 +753,55 @@ function isLeafNode (node) { * * If no destination is supplied, a copy of the object or array is created. * * If a destination is provided, all of its elements (for array) or properties (for objects) * are deleted and then all elements/properties from the source are copied to it. - * * If `source` is not an object or array, `source` is returned. - * - * Note: this function is used to augment the Object type in Angular expressions. See - * {@link ng.$filter} for more information about Angular arrays. + * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. + * * If `source` is identical to 'destination' an exception will be thrown. * * @param {*} source The source that will be used to make a copy. * Can be any type, including primitives, `null`, and `undefined`. * @param {(Object|Array)=} destination Destination into which the source is copied. If * provided, must be of the same type as `source`. * @returns {*} The copy or updated `destination`, if `destination` was specified. + * + * @example + + +
+
+ Name:
+ E-mail:
+ Gender: male + female
+ + +
+
form = {{user | json}}
+
master = {{master | json}}
+
+ + +
+
*/ function copy(source, destination){ if (isWindow(source) || isScope(source)) { - throw ngMinErr('cpws', "Can't copy! Making copies of Window or Scope instances is not supported."); + throw ngMinErr('cpws', + "Can't copy! Making copies of Window or Scope instances is not supported."); } if (!destination) { @@ -679,7 +818,8 @@ function copy(source, destination){ } } } else { - if (source === destination) throw ngMinErr('cpi', "Can't copy! Source and destination are identical."); + if (source === destination) throw ngMinErr('cpi', + "Can't copy! Source and destination are identical."); if (isArray(source)) { destination.length = 0; for ( var i = 0; i < source.length; i++) { @@ -706,6 +846,8 @@ function shallowCopy(src, dst) { dst = dst || {}; for(var key in src) { + // shallowCopy is only ever called by $compile nodeLinkFn, which has control over src + // so we don't need to worry hasOwnProperty here if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') { dst[key] = src[key]; } @@ -721,14 +863,15 @@ function shallowCopy(src, dst) { * @function * * @description - * Determines if two objects or two values are equivalent. Supports value types, regular expressions, arrays and - * objects. + * Determines if two objects or two values are equivalent. Supports value types, regular + * expressions, arrays and objects. * * Two objects or values are considered equivalent if at least one of the following is true: * * * Both objects or values pass `===` comparison. - * * Both objects or values are of the same type and all of their properties pass `===` comparison. - * * Both values are NaN. (In JavasScript, NaN == NaN => false. But we consider two NaN as equal) + * * Both objects or values are of the same type and all of their properties are equal by + * comparing them with `angular.equals`. + * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) * * Both values represent the same regular expression (In JavasScript, * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual * representation matches). @@ -783,6 +926,13 @@ function equals(o1, o2) { } +function csp() { + return (document.securityPolicy && document.securityPolicy.isActive) || + (document.querySelector && + !!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]'))); +} + + function concat(array1, array2, index) { return array1.concat(slice.call(array2, index)); } @@ -792,6 +942,7 @@ function sliceArgs(args, startIndex) { } +/* jshint -W101 */ /** * @ngdoc function * @name angular.bind @@ -800,13 +951,15 @@ function sliceArgs(args, startIndex) { * @description * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for * `fn`). You can supply optional `args` that are prebound to the function. This feature is also - * known as [function currying](http://en.wikipedia.org/wiki/Currying). + * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as + * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). * * @param {Object} self Context which `fn` should be evaluated in. * @param {function()} fn Function to be bound. * @param {...*} args Optional arguments to be prebound to the `fn` function call. * @returns {function()} Function that wraps the `fn` with all the specified bindings. */ +/* jshint +W101 */ function bind(self, fn) { var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; if (isFunction(fn) && !(fn instanceof RegExp)) { @@ -831,7 +984,7 @@ function bind(self, fn) { function toJsonReplacer(key, value) { var val = value; - if (/^\$+/.test(key)) { + if (typeof key === 'string' && key.charAt(0) === '$') { val = undefined; } else if (isWindow(value)) { val = '$WINDOW'; @@ -966,10 +1119,12 @@ function toKeyValue(obj) { forEach(obj, function(value, key) { if (isArray(value)) { forEach(value, function(arrayValue) { - parts.push(encodeUriQuery(key, true) + (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); + parts.push(encodeUriQuery(key, true) + + (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); }); } else { - parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true))); + parts.push(encodeUriQuery(key, true) + + (value === true ? '' : '=' + encodeUriQuery(value, true))); } }); return parts.length ? parts.join('&') : ''; @@ -1031,11 +1186,11 @@ function encodeUriQuery(val, pctEncodeSpaces) { * designates the root of the application and is typically placed * at the root of the page. * - * The first ngApp found in the document will be auto-bootstrapped. To use multiple applications in an - * HTML document you must manually bootstrap them using {@link angular.bootstrap}. + * The first ngApp found in the document will be auto-bootstrapped. To use multiple applications in + * an HTML document you must manually bootstrap them using {@link angular.bootstrap}. * Applications cannot be nested. * - * In the example below if the `ngApp` directive would not be placed + * In the example below if the `ngApp` directive were not placed * on the `html` element then the document would not be compiled * and the `{{ 1+2 }}` would not be resolved to `3`. * @@ -1104,7 +1259,10 @@ function angularInit(element, bootstrap) { * They must use {@link api/ng.directive:ngApp ngApp}. * * @param {Element} element DOM element which is the root of angular application. - * @param {Array=} modules an array of module declarations. See: {@link angular.module modules} + * @param {Array=} modules an array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * See: {@link angular.module modules} * @returns {AUTO.$injector} Returns the newly created injector for this app. */ function bootstrap(element, modules) { @@ -1128,7 +1286,6 @@ function bootstrap(element, modules) { element.data('$injector', injector); compile(element)(scope); }); - animate.enabled(true); }] ); return injector; @@ -1165,14 +1322,16 @@ function bindJQuery() { jqLite = jQuery; extend(jQuery.fn, { scope: JQLitePrototype.scope, + isolateScope: JQLitePrototype.isolateScope, controller: JQLitePrototype.controller, injector: JQLitePrototype.injector, inheritedData: JQLitePrototype.inheritedData }); - // Method signature: JQLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) - JQLitePatchJQueryRemove('remove', true, true, false); - JQLitePatchJQueryRemove('empty', false, false, false); - JQLitePatchJQueryRemove('html', false, false, true); + // Method signature: + // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) + jqLitePatchJQueryRemove('remove', true, true, false); + jqLitePatchJQueryRemove('empty', false, false, false); + jqLitePatchJQueryRemove('html', false, false, true); } else { jqLite = JQLite; } @@ -1199,6 +1358,17 @@ function assertArgFn(arg, name, acceptArrayAnnotation) { return arg; } +/** + * throw error if the name given is hasOwnProperty + * @param {String} name the name to test + * @param {String} context the context in which the name is used, such as module or directive + */ +function assertNotHasOwnProperty(name, context) { + if (name === 'hasOwnProperty') { + throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); + } +} + /** * Return the value accessible from the object by path. Any undefined traversals are ignored * @param {Object} obj starting object @@ -1226,6 +1396,28 @@ function getter(obj, path, bindFnToScope) { return obj; } +/** + * Return the siblings between `startNode` and `endNode`, inclusive + * @param {Object} object with `startNode` and `endNode` properties + * @returns jQlite object containing the elements + */ +function getBlockElements(block) { + if (block.startNode === block.endNode) { + return jqLite(block.startNode); + } + + var element = block.startNode; + var elements = [element]; + + do { + element = element.nextSibling; + if (!element) break; + elements.push(element); + } while (element !== block.endNode); + + return jqLite(elements); +} + /** * @ngdoc interface * @name angular.Module @@ -1236,6 +1428,8 @@ function getter(obj, path, bindFnToScope) { function setupModuleLoader(window) { + var $injectorMinErr = minErr('$injector'); + function ensure(obj, name, factory) { return obj[name] || (obj[name] = factory()); } @@ -1249,10 +1443,14 @@ function setupModuleLoader(window) { * @name angular.module * @description * - * The `angular.module` is a global place for creating and registering Angular modules. All - * modules (angular core or 3rd party) that should be available to an application must be + * The `angular.module` is a global place for creating, registering and retrieving Angular + * modules. + * All modules (angular core or 3rd party) that should be available to an application must be * registered using this mechanism. * + * When passed two or more arguments, a new module is created. If passed only one argument, an + * existing module (the name passed as the first argument to `module`) is retrieved. + * * * # Module * @@ -1284,21 +1482,22 @@ function setupModuleLoader(window) { * {@link angular.bootstrap} to simplify this process for you. * * @param {!string} name The name of the module to create or retrieve. - * @param {Array.=} requires If specified then new module is being created. If unspecified then the - * the module is being retrieved for further configuration. + * @param {Array.=} requires If specified then new module is being created. If + * unspecified then the the module is being retrieved for further configuration. * @param {Function} configFn Optional configuration function for the module. Same as - * {@link angular.Module#config Module#config()}. + * {@link angular.Module#methods_config Module#config()}. * @returns {module} new module with the {@link angular.Module} api. */ return function module(name, requires, configFn) { + assertNotHasOwnProperty(name, 'module'); if (requires && modules.hasOwnProperty(name)) { modules[name] = null; } return ensure(modules, name, function() { if (!requires) { - throw minErr('$injector')('nomod', "Module '{0}' is not available! You either misspelled the module name " + - "or forgot to load it. If registering a module ensure that you specify the dependencies as the second " + - "argument.", name); + throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + + "the module name or forgot to load it. If registering a module ensure that you " + + "specify the dependencies as the second argument.", name); } /** @type {!Array.>} */ @@ -1321,7 +1520,8 @@ function setupModuleLoader(window) { * @propertyOf angular.Module * @returns {Array.} List of module names which must be loaded before this module. * @description - * Holds the list of modules which the injector will load before the current module is loaded. + * Holds the list of modules which the injector will load before the current module is + * loaded. */ requires: requires, @@ -1340,7 +1540,8 @@ function setupModuleLoader(window) { * @name angular.Module#provider * @methodOf angular.Module * @param {string} name service name - * @param {Function} providerType Construction function for creating new instance of the service. + * @param {Function} providerType Construction function for creating new instance of the + * service. * @description * See {@link AUTO.$provide#provider $provide.provider()}. */ @@ -1396,14 +1597,15 @@ function setupModuleLoader(window) { * @name angular.Module#animation * @methodOf angular.Module * @param {string} name animation name - * @param {Function} animationFactory Factory function for creating new instance of an animation. + * @param {Function} animationFactory Factory function for creating new instance of an + * animation. * @description * - * **NOTE**: animations are take effect only if the **ngAnimate** module is loaded. + * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. * * - * Defines an animation hook that can be later used with {@link ngAnimate.$animate $animate} service and - * directives that use this service. + * Defines an animation hook that can be later used with + * {@link ngAnimate.$animate $animate} service and directives that use this service. * *
            * module.animation('.animation-name', function($inject1, $inject2) {
@@ -1439,7 +1641,8 @@ function setupModuleLoader(window) {
            * @ngdoc method
            * @name angular.Module#controller
            * @methodOf angular.Module
-           * @param {string} name Controller name.
+           * @param {string|Object} name Controller name, or an object map of controllers where the
+           *    keys are the names and the values are the constructors.
            * @param {Function} constructor Controller constructor function.
            * @description
            * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
@@ -1450,11 +1653,12 @@ function setupModuleLoader(window) {
            * @ngdoc method
            * @name angular.Module#directive
            * @methodOf angular.Module
-           * @param {string} name directive name
+           * @param {string|Object} name Directive name, or an object map of directives where the
+           *    keys are the names and the values are the factories.
            * @param {Function} directiveFactory Factory function for creating new instance of
            * directives.
            * @description
-           * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
+           * See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}.
            */
           directive: invokeLater('$compileProvider', 'directive'),
 
@@ -1501,7 +1705,7 @@ function setupModuleLoader(window) {
           return function() {
             invokeQueue[insertMethod || 'push']([provider, method, arguments]);
             return moduleInstance;
-          }
+          };
         }
       });
     };
@@ -1509,6 +1713,80 @@ function setupModuleLoader(window) {
 
 }
 
+/* global
+    angularModule: true,
+    version: true,
+
+    $LocaleProvider,
+    $CompileProvider,
+
+    htmlAnchorDirective,
+    inputDirective,
+    inputDirective,
+    formDirective,
+    scriptDirective,
+    selectDirective,
+    styleDirective,
+    optionDirective,
+    ngBindDirective,
+    ngBindHtmlDirective,
+    ngBindTemplateDirective,
+    ngClassDirective,
+    ngClassEvenDirective,
+    ngClassOddDirective,
+    ngCspDirective,
+    ngCloakDirective,
+    ngControllerDirective,
+    ngFormDirective,
+    ngHideDirective,
+    ngIfDirective,
+    ngIncludeDirective,
+    ngInitDirective,
+    ngNonBindableDirective,
+    ngPluralizeDirective,
+    ngRepeatDirective,
+    ngShowDirective,
+    ngStyleDirective,
+    ngSwitchDirective,
+    ngSwitchWhenDirective,
+    ngSwitchDefaultDirective,
+    ngOptionsDirective,
+    ngTranscludeDirective,
+    ngModelDirective,
+    ngListDirective,
+    ngChangeDirective,
+    requiredDirective,
+    requiredDirective,
+    ngValueDirective,
+    ngAttributeAliasDirectives,
+    ngEventDirectives,
+
+    $AnchorScrollProvider,
+    $AnimateProvider,
+    $BrowserProvider,
+    $CacheFactoryProvider,
+    $ControllerProvider,
+    $DocumentProvider,
+    $ExceptionHandlerProvider,
+    $FilterProvider,
+    $InterpolateProvider,
+    $IntervalProvider,
+    $HttpProvider,
+    $HttpBackendProvider,
+    $LocationProvider,
+    $LogProvider,
+    $ParseProvider,
+    $RootScopeProvider,
+    $QProvider,
+    $SceProvider,
+    $SceDelegateProvider,
+    $SnifferProvider,
+    $TemplateCacheProvider,
+    $TimeoutProvider,
+    $WindowProvider
+*/
+
+
 /**
  * @ngdoc property
  * @name angular.version
@@ -1516,18 +1794,18 @@ function setupModuleLoader(window) {
  * An object that contains information about the current AngularJS version. This object has the
  * following properties:
  *
- * - `full` – `{string}` – Full version string, such as "0.9.18".
- * - `major` – `{number}` – Major version number, such as "0".
- * - `minor` – `{number}` – Minor version number, such as "9".
- * - `dot` – `{number}` – Dot version number, such as "18".
- * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
+ * - `full` – `{string}` – Full version string, such as "0.9.18".
+ * - `major` – `{number}` – Major version number, such as "0".
+ * - `minor` – `{number}` – Minor version number, such as "9".
+ * - `dot` – `{number}` – Dot version number, such as "18".
+ * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
  */
 var version = {
-  full: '1.2.0rc1',    // all of these placeholder strings will be replaced by grunt's
+  full: '1.2.0',    // all of these placeholder strings will be replaced by grunt's
   major: 1,    // package task
-  minor: 2,
+  minor: "NG_VERSION_MINOR",
   dot: 0,
-  codeName: 'spooky-giraffe'
+  codeName: 'timely-delivery'
 };
 
 
@@ -1553,12 +1831,13 @@ function publishExternalAPI(angular){
     'isNumber': isNumber,
     'isElement': isElement,
     'isArray': isArray,
-    '$$minErr': minErr,
     'version': version,
     'isDate': isDate,
     'lowercase': lowercase,
     'uppercase': uppercase,
-    'callbacks': {counter: 0}
+    'callbacks': {counter: 0},
+    '$$minErr': minErr,
+    '$$csp': csp
   });
 
   angularModule = setupModuleLoader(window);
@@ -1586,7 +1865,6 @@ function publishExternalAPI(angular){
             ngClass: ngClassDirective,
             ngClassEven: ngClassEvenDirective,
             ngClassOdd: ngClassOddDirective,
-            ngCsp: ngCspDirective,
             ngCloak: ngCloakDirective,
             ngController: ngControllerDirective,
             ngForm: ngFormDirective,
@@ -1623,6 +1901,7 @@ function publishExternalAPI(angular){
         $exceptionHandler: $ExceptionHandlerProvider,
         $filter: $FilterProvider,
         $interpolate: $InterpolateProvider,
+        $interval: $IntervalProvider,
         $http: $HttpProvider,
         $httpBackend: $HttpBackendProvider,
         $location: $LocationProvider,
@@ -1635,13 +1914,20 @@ function publishExternalAPI(angular){
         $sniffer: $SnifferProvider,
         $templateCache: $TemplateCacheProvider,
         $timeout: $TimeoutProvider,
-        $window: $WindowProvider,
-        $$urlUtils: $$UrlUtilsProvider
+        $window: $WindowProvider
       });
     }
   ]);
 }
 
+/* global
+
+  -JQLitePrototype,
+  -addEventListenerFn,
+  -removeEventListenerFn,
+  -BOOLEAN_ATTR
+*/
+
 //////////////////////////////////
 //JQLite
 //////////////////////////////////
@@ -1653,64 +1939,64 @@ function publishExternalAPI(angular){
  *
  * @description
  * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
- * `angular.element` can be either an alias for [jQuery](http://api.jquery.com/jQuery/) function, if
- * jQuery is available, or a function that wraps the element or string in Angular's jQuery lite
- * implementation (commonly referred to as jqLite).
  *
- * Real jQuery always takes precedence over jqLite, provided it was loaded before `DOMContentLoaded`
- * event fired.
+ * If jQuery is available, `angular.element` is an alias for the
+ * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
+ * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
  *
- * jqLite is a tiny, API-compatible subset of jQuery that allows
- * Angular to manipulate the DOM. jqLite implements only the most commonly needed functionality
- * within a very small footprint, so only a subset of the jQuery API - methods, arguments and
- * invocation styles - are supported.
+ * 
jqLite is a tiny, API-compatible subset of jQuery that allows + * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most + * commonly needed functionality with the goal of having a very small footprint.
* - * Note: All element references in Angular are always wrapped with jQuery or jqLite; they are never - * raw DOM references. + * To use jQuery, simply load it before `DOMContentLoaded` event fired. + * + *
**Note:** all element references in Angular are always wrapped with jQuery or + * jqLite; they are never raw DOM references.
* * ## Angular's jqLite - * Angular's lite version of jQuery provides only the following jQuery methods: + * jqLite provides only the following jQuery methods: * - * - [addClass()](http://api.jquery.com/addClass/) - * - [after()](http://api.jquery.com/after/) - * - [append()](http://api.jquery.com/append/) - * - [attr()](http://api.jquery.com/attr/) - * - [bind()](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData - * - [children()](http://api.jquery.com/children/) - Does not support selectors - * - [clone()](http://api.jquery.com/clone/) - * - [contents()](http://api.jquery.com/contents/) - * - [css()](http://api.jquery.com/css/) - * - [data()](http://api.jquery.com/data/) - * - [eq()](http://api.jquery.com/eq/) - * - [find()](http://api.jquery.com/find/) - Limited to lookups by tag name - * - [hasClass()](http://api.jquery.com/hasClass/) - * - [html()](http://api.jquery.com/html/) - * - [next()](http://api.jquery.com/next/) - Does not support selectors - * - [on()](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData - * - [off()](http://api.jquery.com/off/) - Does not support namespaces or selectors - * - [parent()](http://api.jquery.com/parent/) - Does not support selectors - * - [prepend()](http://api.jquery.com/prepend/) - * - [prop()](http://api.jquery.com/prop/) - * - [ready()](http://api.jquery.com/ready/) - * - [remove()](http://api.jquery.com/remove/) - * - [removeAttr()](http://api.jquery.com/removeAttr/) - * - [removeClass()](http://api.jquery.com/removeClass/) - * - [removeData()](http://api.jquery.com/removeData/) - * - [replaceWith()](http://api.jquery.com/replaceWith/) - * - [text()](http://api.jquery.com/text/) - * - [toggleClass()](http://api.jquery.com/toggleClass/) - * - [triggerHandler()](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. - * - [unbind()](http://api.jquery.com/off/) - Does not support namespaces - * - [val()](http://api.jquery.com/val/) - * - [wrap()](http://api.jquery.com/wrap/) + * - [`addClass()`](http://api.jquery.com/addClass/) + * - [`after()`](http://api.jquery.com/after/) + * - [`append()`](http://api.jquery.com/append/) + * - [`attr()`](http://api.jquery.com/attr/) + * - [`bind()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData + * - [`children()`](http://api.jquery.com/children/) - Does not support selectors + * - [`clone()`](http://api.jquery.com/clone/) + * - [`contents()`](http://api.jquery.com/contents/) + * - [`css()`](http://api.jquery.com/css/) + * - [`data()`](http://api.jquery.com/data/) + * - [`eq()`](http://api.jquery.com/eq/) + * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name + * - [`hasClass()`](http://api.jquery.com/hasClass/) + * - [`html()`](http://api.jquery.com/html/) + * - [`next()`](http://api.jquery.com/next/) - Does not support selectors + * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData + * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors + * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors + * - [`prepend()`](http://api.jquery.com/prepend/) + * - [`prop()`](http://api.jquery.com/prop/) + * - [`ready()`](http://api.jquery.com/ready/) + * - [`remove()`](http://api.jquery.com/remove/) + * - [`removeAttr()`](http://api.jquery.com/removeAttr/) + * - [`removeClass()`](http://api.jquery.com/removeClass/) + * - [`removeData()`](http://api.jquery.com/removeData/) + * - [`replaceWith()`](http://api.jquery.com/replaceWith/) + * - [`text()`](http://api.jquery.com/text/) + * - [`toggleClass()`](http://api.jquery.com/toggleClass/) + * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. + * - [`unbind()`](http://api.jquery.com/off/) - Does not support namespaces + * - [`val()`](http://api.jquery.com/val/) + * - [`wrap()`](http://api.jquery.com/wrap/) * * ## jQuery/jqLite Extras * Angular also provides the following additional methods and events to both jQuery and jqLite: * * ### Events * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event - * on all DOM nodes being removed. This can be used to clean up and 3rd party bindings to the DOM + * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM * element before it is removed. + * * ### Methods * - `controller(name)` - retrieves the controller of the current element or its parent. By default * retrieves controller associated with the `ngController` directive. If `name` is provided as @@ -1719,6 +2005,9 @@ function publishExternalAPI(angular){ * - `injector()` - retrieves the injector of the current element or its parent. * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current * element or its parent. + * - `isolateScope()` - retrieves an isolate {@link api/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. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top * parent element is reached. * @@ -1764,13 +2053,14 @@ function camelCase(name) { // ///////////////////////////////////////////// -function JQLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { +function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { var originalJqFn = jQuery.fn[name]; originalJqFn = originalJqFn.$original || originalJqFn; removePatch.$original = originalJqFn; jQuery.fn[name] = removePatch; function removePatch(param) { + // jshint -W040 var list = filterElems && param ? [this.filter(param)] : [this], fireEvent = dispatchThis, set, setIndex, setLength, @@ -1816,30 +2106,30 @@ function JQLite(element) { // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx div.innerHTML = '
 
' + element; // IE insanity to make NoScope elements work! div.removeChild(div.firstChild); // remove the superfluous div - JQLiteAddNodes(this, div.childNodes); + jqLiteAddNodes(this, div.childNodes); var fragment = jqLite(document.createDocumentFragment()); fragment.append(this); // detach the elements from the temporary DOM div. } else { - JQLiteAddNodes(this, element); + jqLiteAddNodes(this, element); } } -function JQLiteClone(element) { +function jqLiteClone(element) { return element.cloneNode(true); } -function JQLiteDealoc(element){ - JQLiteRemoveData(element); +function jqLiteDealoc(element){ + jqLiteRemoveData(element); for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { - JQLiteDealoc(children[i]); + jqLiteDealoc(children[i]); } } -function JQLiteOff(element, type, fn, unsupported) { +function jqLiteOff(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); - var events = JQLiteExpandoStore(element, 'events'), - handle = JQLiteExpandoStore(element, 'handle'); + var events = jqLiteExpandoStore(element, 'events'), + handle = jqLiteExpandoStore(element, 'handle'); if (!handle) return; //no listeners registered @@ -1860,7 +2150,7 @@ function JQLiteOff(element, type, fn, unsupported) { } } -function JQLiteRemoveData(element, name) { +function jqLiteRemoveData(element, name) { var expandoId = element[jqName], expandoStore = jqCache[expandoId]; @@ -1872,14 +2162,14 @@ function JQLiteRemoveData(element, name) { if (expandoStore.handle) { expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); - JQLiteOff(element); + jqLiteOff(element); } delete jqCache[expandoId]; element[jqName] = undefined; // ie does not allow deletion of attributes on elements. } } -function JQLiteExpandoStore(element, key, value) { +function jqLiteExpandoStore(element, key, value) { var expandoId = element[jqName], expandoStore = jqCache[expandoId || -1]; @@ -1894,14 +2184,14 @@ function JQLiteExpandoStore(element, key, value) { } } -function JQLiteData(element, key, value) { - var data = JQLiteExpandoStore(element, 'data'), +function jqLiteData(element, key, value) { + var data = jqLiteExpandoStore(element, 'data'), isSetter = isDefined(value), keyDefined = !isSetter && isDefined(key), isSimpleGetter = keyDefined && !isObject(key); if (!data && !isSimpleGetter) { - JQLiteExpandoStore(element, 'data', data = {}); + jqLiteExpandoStore(element, 'data', data = {}); } if (isSetter) { @@ -1920,34 +2210,41 @@ function JQLiteData(element, key, value) { } } -function JQLiteHasClass(element, selector) { - return ((" " + element.className + " ").replace(/[\n\t]/g, " "). +function jqLiteHasClass(element, selector) { + if (!element.getAttribute) return false; + return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). indexOf( " " + selector + " " ) > -1); } -function JQLiteRemoveClass(element, cssClasses) { - if (cssClasses) { +function jqLiteRemoveClass(element, cssClasses) { + if (cssClasses && element.setAttribute) { forEach(cssClasses.split(' '), function(cssClass) { - element.className = trim( - (" " + element.className + " ") + element.setAttribute('class', trim( + (" " + (element.getAttribute('class') || '') + " ") .replace(/[\n\t]/g, " ") - .replace(" " + trim(cssClass) + " ", " ") + .replace(" " + trim(cssClass) + " ", " ")) ); }); } } -function JQLiteAddClass(element, cssClasses) { - if (cssClasses) { +function jqLiteAddClass(element, cssClasses) { + if (cssClasses && element.setAttribute) { + var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') + .replace(/[\n\t]/g, " "); + forEach(cssClasses.split(' '), function(cssClass) { - if (!JQLiteHasClass(element, cssClass)) { - element.className = trim(element.className + ' ' + trim(cssClass)); + cssClass = trim(cssClass); + if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { + existingClasses += cssClass + ' '; } }); + + element.setAttribute('class', trim(existingClasses)); } } -function JQLiteAddNodes(root, elements) { +function jqLiteAddNodes(root, elements) { if (elements) { elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) ? elements @@ -1958,11 +2255,11 @@ function JQLiteAddNodes(root, elements) { } } -function JQLiteController(element, name) { - return JQLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); +function jqLiteController(element, name) { + return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); } -function JQLiteInheritedData(element, name, value) { +function jqLiteInheritedData(element, name, value) { element = jqLite(element); // if element is the document object work with the html element instead @@ -1970,9 +2267,13 @@ function JQLiteInheritedData(element, name, value) { if(element[0].nodeType == 9) { element = element.find('html'); } + var names = isArray(name) ? name : [name]; while (element.length) { - if ((value = element.data(name)) !== undefined) return value; + + for (var i = 0, ii = names.length; i < ii; i++) { + if ((value = element.data(names[i])) !== undefined) return value; + } element = element.parent(); } } @@ -1996,7 +2297,9 @@ var JQLitePrototype = JQLite.prototype = { } else { this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 // we can not use jqLite since we are not done loading and jQuery could be loaded later. + // jshint -W064 JQLite(window).on('load', trigger); // fallback to window.onload for others + // jshint +W064 } }, toString: function() { @@ -2038,24 +2341,30 @@ function getBooleanAttrName(element, name) { } forEach({ - data: JQLiteData, - inheritedData: JQLiteInheritedData, + data: jqLiteData, + inheritedData: jqLiteInheritedData, scope: function(element) { - return JQLiteInheritedData(element, '$scope'); + // Can't use jqLiteData here directly so we stay compatible with jQuery! + return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); }, - controller: JQLiteController , + isolateScope: function(element) { + // Can't use jqLiteData here directly so we stay compatible with jQuery! + return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); + }, + + controller: jqLiteController , injector: function(element) { - return JQLiteInheritedData(element, '$injector'); + return jqLiteInheritedData(element, '$injector'); }, removeAttr: function(element,name) { element.removeAttribute(name); }, - hasClass: JQLiteHasClass, + hasClass: jqLiteHasClass, css: function(element, name, value) { name = camelCase(name); @@ -2131,7 +2440,7 @@ forEach({ return getText; function getText(element, value) { - var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType] + var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; if (isUndefined(value)) { return textProp ? element[textProp] : ''; } @@ -2160,7 +2469,7 @@ forEach({ return element.innerHTML; } for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { - JQLiteDealoc(childNodes[i]); + jqLiteDealoc(childNodes[i]); } element.innerHTML = value; } @@ -2171,14 +2480,14 @@ forEach({ JQLite.prototype[name] = function(arg1, arg2) { var i, key; - // JQLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it + // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it // in a way that survives minification. - if (((fn.length == 2 && (fn !== JQLiteHasClass && fn !== JQLiteController)) ? arg1 : arg2) === undefined) { + if (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined) { if (isObject(arg1)) { // we are a write, but the object properties are the key/values for(i=0; i < this.length; i++) { - if (fn === JQLiteData) { + if (fn === jqLiteData) { // data() takes the whole object in jQuery fn(this[i], arg1); } else { @@ -2193,7 +2502,7 @@ forEach({ // we are a read, so read the first child. var value = fn.$dv; // Only if we have $dv do we iterate over all, otherwise it is just the first element. - var jj = value == undefined ? Math.min(this.length, 1) : this.length; + var jj = (value === undefined) ? Math.min(this.length, 1) : this.length; for (var j = 0; j < jj; j++) { var nodeValue = fn(this[j], arg1, arg2); value = value ? value + nodeValue : nodeValue; @@ -2239,7 +2548,7 @@ function createEventHandler(element, events) { } event.isDefaultPrevented = function() { - return event.defaultPrevented || event.returnValue == false; + return event.defaultPrevented || event.returnValue === false; }; forEach(events[type || event.type], function(fn) { @@ -2270,18 +2579,18 @@ function createEventHandler(element, events) { // selector. ////////////////////////////////////////// forEach({ - removeData: JQLiteRemoveData, + removeData: jqLiteRemoveData, - dealoc: JQLiteDealoc, + dealoc: jqLiteDealoc, on: function onFn(element, type, fn, unsupported){ if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); - var events = JQLiteExpandoStore(element, 'events'), - handle = JQLiteExpandoStore(element, 'handle'); + var events = jqLiteExpandoStore(element, 'events'), + handle = jqLiteExpandoStore(element, 'handle'); - if (!events) JQLiteExpandoStore(element, 'events', events = {}); - if (!handle) JQLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); + if (!events) jqLiteExpandoStore(element, 'events', events = {}); + if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); forEach(type.split(' '), function(type){ var eventFns = events[type]; @@ -2290,6 +2599,7 @@ forEach({ if (type == 'mouseenter' || type == 'mouseleave') { var contains = document.body.contains || document.body.compareDocumentPosition ? function( a, b ) { + // jshint bitwise: false var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( @@ -2329,17 +2639,17 @@ forEach({ addEventListenerFn(element, type, handle); events[type] = []; } - eventFns = events[type] + eventFns = events[type]; } eventFns.push(fn); }); }, - off: JQLiteOff, + off: jqLiteOff, replaceWith: function(element, replaceNode) { var index, parent = element.parentNode; - JQLiteDealoc(element); + jqLiteDealoc(element); forEach(new JQLite(replaceNode), function(node){ if (index) { parent.insertBefore(node, index.nextSibling); @@ -2390,7 +2700,7 @@ forEach({ }, remove: function(element) { - JQLiteDealoc(element); + jqLiteDealoc(element); var parent = element.parentNode; if (parent) parent.removeChild(element); }, @@ -2403,14 +2713,14 @@ forEach({ }); }, - addClass: JQLiteAddClass, - removeClass: JQLiteRemoveClass, + addClass: jqLiteAddClass, + removeClass: jqLiteRemoveClass, toggleClass: function(element, selector, condition) { if (isUndefined(condition)) { - condition = !JQLiteHasClass(element, selector); + condition = !jqLiteHasClass(element, selector); } - (condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector); + (condition ? jqLiteAddClass : jqLiteRemoveClass)(element, selector); }, parent: function(element) { @@ -2435,17 +2745,20 @@ forEach({ return element.getElementsByTagName(selector); }, - clone: JQLiteClone, + clone: jqLiteClone, triggerHandler: function(element, eventName, eventData) { - var eventFns = (JQLiteExpandoStore(element, 'events') || {})[eventName]; - eventData = eventData || { + var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; + + eventData = eventData || []; + + var event = [{ preventDefault: noop, stopPropagation: noop - }; + }]; forEach(eventFns, function(fn) { - fn.call(element, eventData); + fn.apply(element, event.concat(eventData)); }); } }, function(fn, name){ @@ -2455,17 +2768,17 @@ forEach({ JQLite.prototype[name] = function(arg1, arg2, arg3) { var value; for(var i=0; i < this.length; i++) { - if (value == undefined) { + if (isUndefined(value)) { value = fn(this[i], arg1, arg2, arg3); - if (value !== undefined) { + if (isDefined(value)) { // any function which returns a value needs to be wrapped value = jqLite(value); } } else { - JQLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); + jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); } } - return value == undefined ? this : value; + return isDefined(value) ? value : this; }; // bind legacy bind/unbind to on/off @@ -2590,13 +2903,15 @@ function annotate(fn) { if (typeof fn == 'function') { if (!($inject = fn.$inject)) { $inject = []; - fnText = fn.toString().replace(STRIP_COMMENTS, ''); - argDecl = fnText.match(FN_ARGS); - forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ - arg.replace(FN_ARG, function(all, underscore, name){ - $inject.push(name); + if (fn.length) { + fnText = fn.toString().replace(STRIP_COMMENTS, ''); + argDecl = fnText.match(FN_ARGS); + forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ + arg.replace(FN_ARG, function(all, underscore, name){ + $inject.push(name); + }); }); - }); + } fn.$inject = $inject; } } else if (isArray(fn)) { @@ -2652,9 +2967,9 @@ function annotate(fn) { * * ## Inference * - * In JavaScript calling `toString()` on a function returns the function definition. The definition can then be - * parsed and the function arguments can be extracted. *NOTE:* This does not work with minification, and obfuscation - * tools since these tools change the argument names. + * 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. * * ## `$inject` Annotation * By adding a `$inject` property onto a function the injection parameters can be specified. @@ -2683,10 +2998,11 @@ function annotate(fn) { * @description * Invoke the method and supply the method arguments from the `$injector`. * - * @param {!function} fn The function to invoke. The function arguments come form the function annotation. + * @param {!function} fn The function to invoke. Function parameters are injected according to the + * {@link guide/di $inject Annotation} rules. * @param {Object=} self The `this` for the invoked method. - * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before - * the `$injector` is consulted. + * @param {Object=} locals Optional object. If preset then any argument names are read from this + * object first, before the `$injector` is consulted. * @returns {*} the value returned by the invoked `fn` function. */ @@ -2707,12 +3023,13 @@ function annotate(fn) { * @name AUTO.$injector#instantiate * @methodOf AUTO.$injector * @description - * Create a new instance of JS type. The method takes a constructor function invokes the new operator and supplies - * all of the arguments to the constructor function as specified by the constructor annotation. + * Create a new instance of JS type. The method takes a constructor function invokes the new + * operator and supplies all of the arguments to the constructor function as specified by the + * constructor annotation. * * @param {function} Type Annotated constructor function. - * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before - * the `$injector` is consulted. + * @param {Object=} locals Optional object. If preset then any argument names are read from this + * object first, before the `$injector` is consulted. * @returns {Object} new instance of `Type`. */ @@ -2722,14 +3039,16 @@ function annotate(fn) { * @methodOf AUTO.$injector * * @description - * Returns an array of service names which the function is requesting for injection. This API is used by the injector - * to determine which services need to be injected into the function when the function is invoked. There are three - * ways in which the function can be annotated with the needed dependencies. + * Returns an array of service names which the function is requesting for injection. This API is + * used by the injector to determine which services need to be injected into the function when the + * function is invoked. There are three ways in which the function can be annotated with the needed + * dependencies. * * # Argument names * - * The simplest form is to extract the dependencies from the arguments of the function. This is done by converting - * the function into a string using `toString()` method and extracting the argument names. + * The simplest form is to extract the dependencies from the arguments of the function. This is done + * by converting the function into a string using `toString()` method and extracting the argument + * names. *
  *   // Given
  *   function MyController($scope, $route) {
@@ -2740,13 +3059,13 @@ function annotate(fn) {
  *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
  * 
* - * This method does not work with code minification / obfuscation. For this reason the following annotation strategies - * are supported. + * This method does not work with code minification / obfuscation. For this reason the following + * annotation strategies are supported. * * # The `$inject` property * - * If a function has an `$inject` property and its value is an array of strings, then the strings represent names of - * services to be injected into the function. + * If a function has an `$inject` property and its value is an array of strings, then the strings + * represent names of services to be injected into the function. *
  *   // Given
  *   var MyController = function(obfuscatedScope, obfuscatedRoute) {
@@ -2761,9 +3080,9 @@ function annotate(fn) {
  *
  * # The array notation
  *
- * It is often desirable to inline Injected functions and that's when setting the `$inject` property is very
- * inconvenient. In these situations using the array notation to specify the dependencies in a way that survives
- * minification is a better choice:
+ * It is often desirable to inline Injected functions and that's when setting the `$inject` property
+ * is very inconvenient. In these situations using the array notation to specify the dependencies in
+ * a way that survives minification is a better choice:
  *
  * 
  *   // We wish to write this (not minification / obfuscation safe)
@@ -2789,8 +3108,8 @@ function annotate(fn) {
  *    ).toEqual(['$compile', '$rootScope']);
  * 
* - * @param {function|Array.} fn Function for which dependent service names need to be retrieved as described - * above. + * @param {function|Array.} fn Function for which dependent service names need to + * be retrieved as described above. * * @returns {Array.} The names of the services which the function requires. */ @@ -2804,46 +3123,38 @@ function annotate(fn) { * * @description * - * Use `$provide` to register new providers with the `$injector`. The providers are the factories for the instance. - * The providers share the same name as the instance they create with `Provider` suffixed to them. + * The {@link AUTO.$provide $provide} service has a number of methods for registering components + * with the {@link AUTO.$injector $injector}. Many of these functions are also exposed on + * {@link angular.Module}. * - * A provider is an object with a `$get()` method. The injector calls the `$get` method to create a new instance of - * a service. The Provider can have additional methods which would allow for configuration of the provider. + * An Angular **service** is a singleton object created by a **service factory**. These **service + * factories** are functions which, in turn, are created by a **service provider**. + * The **service providers** are constructor functions. When instantiated they must contain a + * property called `$get`, which holds the **service factory** function. * - *
- *   function GreetProvider() {
- *     var salutation = 'Hello';
+ * When you request a service, the {@link AUTO.$injector $injector} is responsible for finding the
+ * correct **service provider**, instantiating it and then calling its `$get` **service factory**
+ * function to get the instance of the **service**.
  *
- *     this.salutation = function(text) {
- *       salutation = text;
- *     };
+ * Often services have no configuration options and there is no need to add methods to the service
+ * provider.  The provider will be no more than a constructor function with a `$get` property. For
+ * these cases the {@link AUTO.$provide $provide} service has additional helper methods to register
+ * services without specifying a provider.
  *
- *     this.$get = function() {
- *       return function (name) {
- *         return salutation + ' ' + name + '!';
- *       };
- *     };
- *   }
+ * * {@link AUTO.$provide#methods_provider provider(provider)} - registers a **service provider** with the
+ *     {@link AUTO.$injector $injector}
+ * * {@link AUTO.$provide#methods_constant constant(obj)} - registers a value/object that can be accessed by
+ *     providers and services.
+ * * {@link AUTO.$provide#methods_value value(obj)} - registers a value/object that can only be accessed by
+ *     services, not providers.
+ * * {@link AUTO.$provide#methods_factory factory(fn)} - registers a service **factory function**, `fn`,
+ *     that will be wrapped in a **service provider** object, whose `$get` property will contain the
+ *     given factory function.
+ * * {@link AUTO.$provide#methods_service service(class)} - registers a **constructor function**, `class` that
+ *     that will be wrapped in a **service provider** object, whose `$get` property will instantiate
+ *      a new object using the given constructor function.
  *
- *   describe('Greeter', function(){
- *
- *     beforeEach(module(function($provide) {
- *       $provide.provider('greet', GreetProvider);
- *     }));
- *
- *     it('should greet', inject(function(greet) {
- *       expect(greet('angular')).toEqual('Hello angular!');
- *     }));
- *
- *     it('should allow configuration of salutation', function() {
- *       module(function(greetProvider) {
- *         greetProvider.salutation('Ahoj');
- *       });
- *       inject(function(greet) {
- *         expect(greet('angular')).toEqual('Ahoj angular!');
- *       });
- *     });
- * 
+ * See the individual methods for more information and examples. */ /** @@ -2852,17 +3163,97 @@ function annotate(fn) { * @methodOf AUTO.$provide * @description * - * Register a provider for a service. The providers can be retrieved and can have additional configuration methods. + * Register a **provider function** with the {@link AUTO.$injector $injector}. Provider functions + * are constructor functions, whose instances are responsible for "providing" a factory for a + * service. * - * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provider'` key. + * Service provider names start with the name of the service they provide followed by `Provider`. + * For example, the {@link ng.$log $log} service has a provider called + * {@link ng.$logProvider $logProvider}. + * + * Service provider objects can have additional methods which allow configuration of the provider + * and its service. Importantly, you can configure what kind of service is created by the `$get` + * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a + * method {@link ng.$logProvider#debugEnabled debugEnabled} + * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the + * console or not. + * + * @param {string} name The name of the instance. NOTE: the provider will be available under `name + + 'Provider'` key. * @param {(Object|function())} provider If the provider is: * * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using - * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created. + * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be + * created. * - `Constructor`: a new instance of the provider will be created using - * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`. + * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as + * `object`. * * @returns {Object} registered provider instance + + * @example + * + * The following example shows how to create a simple event tracking service and register it using + * {@link AUTO.$provide#methods_provider $provide.provider()}. + * + *
+ *  // Define the eventTracker provider
+ *  function EventTrackerProvider() {
+ *    var trackingUrl = '/track';
+ *
+ *    // A provider method for configuring where the tracked events should been saved
+ *    this.setTrackingUrl = function(url) {
+ *      trackingUrl = url;
+ *    };
+ *
+ *    // The service factory function
+ *    this.$get = ['$http', function($http) {
+ *      var trackedEvents = {};
+ *      return {
+ *        // Call this to track an event
+ *        event: function(event) {
+ *          var count = trackedEvents[event] || 0;
+ *          count += 1;
+ *          trackedEvents[event] = count;
+ *          return count;
+ *        },
+ *        // Call this to save the tracked events to the trackingUrl
+ *        save: function() {
+ *          $http.post(trackingUrl, trackedEvents);
+ *        }
+ *      };
+ *    }];
+ *  }
+ *
+ *  describe('eventTracker', function() {
+ *    var postSpy;
+ *
+ *    beforeEach(module(function($provide) {
+ *      // Register the eventTracker provider
+ *      $provide.provider('eventTracker', EventTrackerProvider);
+ *    }));
+ *
+ *    beforeEach(module(function(eventTrackerProvider) {
+ *      // Configure eventTracker provider
+ *      eventTrackerProvider.setTrackingUrl('/custom-track');
+ *    }));
+ *
+ *    it('tracks events', inject(function(eventTracker) {
+ *      expect(eventTracker.event('login')).toEqual(1);
+ *      expect(eventTracker.event('login')).toEqual(2);
+ *    }));
+ *
+ *    it('saves to the tracking url', inject(function(eventTracker, $http) {
+ *      postSpy = spyOn($http, 'post');
+ *      eventTracker.event('login');
+ *      eventTracker.save();
+ *      expect(postSpy).toHaveBeenCalled();
+ *      expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
+ *      expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
+ *      expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
+ *    }));
+ *  });
+ * 
*/ /** @@ -2871,12 +3262,32 @@ function annotate(fn) { * @methodOf AUTO.$provide * @description * - * A short hand for configuring services if only `$get` method is required. + * Register a **service factory**, which will be called to return the service instance. + * This is short for registering a service where its provider consists of only a `$get` property, + * which is the given service factory function. + * You should use {@link AUTO.$provide#factory $provide.factory(getFn)} if you do not need to + * configure your service in a provider. * * @param {string} name The name of the instance. - * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand for - * `$provide.provider(name, {$get: $getFn})`. + * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand + * for `$provide.provider(name, {$get: $getFn})`. * @returns {Object} registered provider instance + * + * @example + * Here is an example of registering a service + *
+ *   $provide.factory('ping', ['$http', function($http) {
+ *     return function ping() {
+ *       return $http.send('/ping');
+ *     };
+ *   }]);
+ * 
+ * You would then inject and use this service like this: + *
+ *   someModule.controller('Ctrl', ['ping', function(ping) {
+ *     ping();
+ *   }]);
+ * 
*/ @@ -2886,11 +3297,35 @@ function annotate(fn) { * @methodOf AUTO.$provide * @description * - * A short hand for registering service of given class. + * Register a **service constructor**, which will be invoked with `new` to create the service + * instance. + * This is short for registering a service where its provider's `$get` property is the service + * constructor function that will be used to instantiate the service instance. + * + * You should use {@link AUTO.$provide#methods_service $provide.service(class)} if you define your service + * as a type/class. This is common when using {@link http://coffeescript.org CoffeeScript}. * * @param {string} name The name of the instance. * @param {Function} constructor A class (constructor function) that will be instantiated. * @returns {Object} registered provider instance + * + * @example + * Here is an example of registering a service using + * {@link AUTO.$provide#methods_service $provide.service(class)} that is defined as a CoffeeScript class. + *
+ *   class Ping
+ *     constructor: (@$http)->
+ *     send: ()=>
+ *       @$http.get('/ping')
+ *
+ *   $provide.service('ping', ['$http', Ping])
+ * 
+ * You would then inject and use this service like this: + *
+ *   someModule.controller 'Ctrl', ['ping', (ping)->
+ *     ping.send()
+ *   ]
+ * 
*/ @@ -2900,11 +3335,31 @@ function annotate(fn) { * @methodOf AUTO.$provide * @description * - * A short hand for configuring services if the `$get` method is a constant. + * Register a **value service** with the {@link AUTO.$injector $injector}, such as a string, a + * number, an array, an object or a function. This is short for registering a service where its + * provider's `$get` property is a factory function that takes no arguments and returns the **value + * service**. + * + * Value services are similar to constant services, except that they cannot be injected into a + * module configuration function (see {@link angular.Module#config}) but they can be overridden by + * an Angular + * {@link AUTO.$provide#decorator decorator}. * * @param {string} name The name of the instance. * @param {*} value The value. * @returns {Object} registered provider instance + * + * @example + * Here are some examples of creating value services. + *
+ *   $provide.constant('ADMIN_USER', 'admin');
+ *
+ *   $provide.constant('RoleLookup', { admin: 0, writer: 1, reader: 2 });
+ *
+ *   $provide.constant('halfOf', function(value) {
+ *     return value / 2;
+ *   });
+ * 
*/ @@ -2914,13 +3369,26 @@ function annotate(fn) { * @methodOf AUTO.$provide * @description * - * A constant value, but unlike {@link AUTO.$provide#value value} it can be injected - * into configuration function (other modules) and it is not interceptable by - * {@link AUTO.$provide#decorator decorator}. + * Register a **constant service**, such as a string, a number, an array, an object or a function, + * with the {@link AUTO.$injector $injector}. Unlike {@link AUTO.$provide#value value} it can be + * injected into a module configuration function (see {@link angular.Module#config}) and it cannot + * be overridden by an Angular {@link AUTO.$provide#decorator decorator}. * * @param {string} name The name of the constant. * @param {*} value The constant value. * @returns {Object} registered instance + * + * @example + * Here a some examples of creating constants: + *
+ *   $provide.constant('SHARD_HEIGHT', 306);
+ *
+ *   $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
+ *
+ *   $provide.constant('double', function(value) {
+ *     return value * 2;
+ *   });
+ * 
*/ @@ -2930,17 +3398,29 @@ function annotate(fn) { * @methodOf AUTO.$provide * @description * - * Decoration of service, allows the decorator to intercept the service instance creation. The - * returned instance may be the original instance, or a new instance which delegates to the - * original instance. + * Register a **service decorator** with the {@link AUTO.$injector $injector}. A service decorator + * intercepts the creation of a service, allowing it to override or modify the behaviour of the + * service. The object returned by the decorator may be the original service, or a new service + * object which replaces or wraps and delegates to the original service. * * @param {string} name The name of the service to decorate. * @param {function()} decorator This function will be invoked when the service needs to be - * instantiated. The function is called using the {@link AUTO.$injector#invoke - * injector.invoke} method and is therefore fully injectable. Local injection arguments: + * instantiated and should return the decorated service instance. The function is called using + * the {@link AUTO.$injector#invoke injector.invoke} method and is therefore fully injectable. + * Local injection arguments: * * * `$delegate` - The original service instance, which can be monkey patched, configured, * decorated or delegated to. + * + * @example + * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting + * calls to {@link ng.$log#error $log.warn()}. + *
+ *   $provider.decorator('$log', ['$delegate', function($delegate) {
+ *     $delegate.warn = $delegate.error;
+ *     return $delegate;
+ *   }]);
+ * 
*/ @@ -2986,10 +3466,11 @@ function createInjector(modulesToLoad) { } else { return delegate(key, value); } - } + }; } function provider(name, provider_) { + assertNotHasOwnProperty(name, 'service'); if (isFunction(provider_) || isArray(provider_)) { provider_ = providerInjector.instantiate(provider_); } @@ -3007,9 +3488,10 @@ function createInjector(modulesToLoad) { }]); } - function value(name, value) { return factory(name, valueFn(value)); } + function value(name, val) { return factory(name, valueFn(val)); } function constant(name, value) { + assertNotHasOwnProperty(name, 'constant'); providerCache[name] = value; instanceCache[name] = value; } @@ -3028,17 +3510,17 @@ function createInjector(modulesToLoad) { // Module Loading //////////////////////////////////// function loadModules(modulesToLoad){ - var runBlocks = []; + var runBlocks = [], moduleFn, invokeQueue, i, ii; forEach(modulesToLoad, function(module) { if (loadedModules.get(module)) return; loadedModules.put(module, true); try { if (isString(module)) { - var moduleFn = angularModule(module); + moduleFn = angularModule(module); runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); - for(var invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { + for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { var invokeArgs = invokeQueue[i], provider = providerInjector.get(invokeArgs[0]); @@ -3056,12 +3538,15 @@ function createInjector(modulesToLoad) { module = module[module.length - 1]; } if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { - // Safari & FF's stack traces don't contain error.message content unlike those of Chrome and IE + // Safari & FF's stack traces don't contain error.message content + // unlike those of Chrome and IE // So if stack doesn't contain message, we create a new string that contains both. // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. + /* jshint -W022 */ e = e.message + '\n' + e.stack; } - throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", module, e.stack || e.message || e); + throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", + module, e.stack || e.message || e); } }); return runBlocks; @@ -3099,7 +3584,8 @@ function createInjector(modulesToLoad) { for(i = 0, length = $inject.length; i < length; i++) { key = $inject[i]; if (typeof key !== 'string') { - throw $injectorMinErr('itkn', 'Incorrect injection token! Expected service name as string, got {0}', key); + throw $injectorMinErr('itkn', + 'Incorrect injection token! Expected service name as string, got {0}', key); } args.push( locals && locals.hasOwnProperty(key) @@ -3124,8 +3610,10 @@ function createInjector(modulesToLoad) { case 6: return fn(args[0], args[1], args[2], args[3], args[4], args[5]); case 7: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); case 8: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); - case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); - case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); + case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], + args[8]); + case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], + args[8], args[9]); default: return fn.apply(self, args); } } @@ -3140,7 +3628,7 @@ function createInjector(modulesToLoad) { instance = new Constructor(); returnedValue = invoke(Type, instance, locals); - return isObject(returnedValue) ? returnedValue : instance; + return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; } return { @@ -3167,8 +3655,41 @@ function createInjector(modulesToLoad) { * according to rules specified in * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}. * - * It also watches the `$location.hash()` and scroll whenever it changes to match any anchor. + * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. + * + * @example + + +
+ Go to bottom + You're at the bottom! +
+
+ + function ScrollCtrl($scope, $location, $anchorScroll) { + $scope.gotoBottom = function (){ + // set the location.hash to the id of + // the element you wish to scroll to. + $location.hash('bottom'); + + // call $anchorScroll() + $anchorScroll(); + } + } + + + #scrollArea { + height: 350px; + overflow: auto; + } + + #bottom { + display: block; + margin-top: 2000px; + } + +
*/ function $AnchorScrollProvider() { @@ -3229,7 +3750,8 @@ var $animateMinErr = minErr('$animate'); * @name ng.$animateProvider * * @description - * Default implementation of $animate that doesn't perform any animations, instead just synchronously performs DOM + * Default implementation of $animate that doesn't perform any animations, instead just + * synchronously performs DOM * updates and calls done() callbacks. * * In order to enable animations the ngAnimate module has to be loaded. @@ -3238,6 +3760,7 @@ var $animateMinErr = minErr('$animate'); */ var $AnimateProvider = ['$provide', function($provide) { + this.$$selectors = {}; @@ -3247,12 +3770,14 @@ var $AnimateProvider = ['$provide', function($provide) { * @methodOf ng.$animateProvider * * @description - * Registers a new injectable animation factory function. The factory function produces the animation object which - * contains callback functions for each event that is expected to be animated. + * Registers a new injectable animation factory function. The factory function produces the + * animation object which contains callback functions for each event that is expected to be + * animated. * - * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` must be called once the - * element animation is complete. If a function is returned then the animation service will use this function to - * cancel the animation whenever a cancel event is triggered. + * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` + * must be called once the element animation is complete. If a function is returned then the + * animation service will use this function to cancel the animation whenever a cancel event is + * triggered. * * *
@@ -3268,7 +3793,8 @@ var $AnimateProvider = ['$provide', function($provide) {
    *
* * @param {string} name The name of the animation. - * @param {function} factory The factory function that will be executed to return the animation object. + * @param {function} factory The factory function that will be executed to return the animation + * object. */ this.register = function(name, factory) { var key = name + '-animation'; @@ -3281,36 +3807,39 @@ var $AnimateProvider = ['$provide', function($provide) { this.$get = ['$timeout', function($timeout) { /** + * * @ngdoc object * @name ng.$animate + * @description The $animate service provides rudimentary DOM manipulation functions to + * insert, remove and move elements within the DOM, as well as adding and removing classes. + * This service is the core service used by the ngAnimate $animator service which provides + * high-level animation hooks for CSS and JavaScript. * - * @description - * The $animate service provides rudimentary DOM manipulation functions to insert, remove, move elements within - * the DOM as well as adding and removing classes. This service is the core service used by the ngAnimate $animator - * service which provides high-level animation hooks for CSS and JavaScript. + * $animate is available in the AngularJS core, however, the ngAnimate module must be included + * to enable full out animation support. Otherwise, $animate will only perform simple DOM + * manipulation operations. * - * $animate is available in the AngularJS core, however, the ngAnimate module must be included to enable full out - * animation support. Otherwise, $animate will only perform simple DOM manipulation operations. - * - * To learn more about enabling animation support, click here to visit the {@link ngAnimate ngAnimate module page} - * as well as the {@link ngAnimate.$animate ngAnimate $animate service page}. + * To learn more about enabling animation support, click here to visit the {@link ngAnimate + * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service + * page}. */ return { /** + * * @ngdoc function * @name ng.$animate#enter * @methodOf ng.$animate * @function - * - * @description - * Inserts the element into the DOM either after the `after` element or within the `parent` element. Once complete, - * the done() callback will be fired (if provided). - * + * @description Inserts the element into the DOM either after the `after` element or within + * the `parent` element. Once complete, the done() callback will be fired (if provided). * @param {jQuery/jqLite element} element the element which will be inserted into the DOM - * @param {jQuery/jqLite element} parent the parent element which will append the element as a child (if the after element is not present) - * @param {jQuery/jqLite element} after the sibling element which will append the element after itself - * @param {function=} done callback function that will be called after the element has been inserted into the DOM + * @param {jQuery/jqLite element} parent the parent element which will append the element as + * a child (if the after element is not present) + * @param {jQuery/jqLite element} after the sibling element which will append the element + * after itself + * @param {function=} done callback function that will be called after the element has been + * inserted into the DOM */ enter : function(element, parent, after, done) { var afterNode = after && after[after.length - 1]; @@ -3320,40 +3849,44 @@ var $AnimateProvider = ['$provide', function($provide) { forEach(element, function(node) { parentNode.insertBefore(node, afterNextSibling); }); - $timeout(done || noop, 0, false); + done && $timeout(done, 0, false); }, /** + * * @ngdoc function * @name ng.$animate#leave * @methodOf ng.$animate * @function - * - * @description - * Removes the element from the DOM. Once complete, the done() callback will be fired (if provided). - * + * @description Removes the element from the DOM. Once complete, the done() callback will be + * fired (if provided). * @param {jQuery/jqLite element} element the element which will be removed from the DOM - * @param {function=} done callback function that will be called after the element has been removed from the DOM + * @param {function=} done callback function that will be called after the element has been + * removed from the DOM */ leave : function(element, done) { element.remove(); - $timeout(done || noop, 0, false); + done && $timeout(done, 0, false); }, /** + * * @ngdoc function * @name ng.$animate#move * @methodOf ng.$animate * @function + * @description Moves the position of the provided element within the DOM to be placed + * either after the `after` element or inside of the `parent` element. Once complete, the + * done() callback will be fired (if provided). * - * @description - * Moves the position of the provided element within the DOM to be placed either after the `after` element or inside of the `parent` element. - * Once complete, the done() callback will be fired (if provided). - * - * @param {jQuery/jqLite element} element the element which will be moved around within the DOM - * @param {jQuery/jqLite element} parent the parent element where the element will be inserted into (if the after element is not present) - * @param {jQuery/jqLite element} after the sibling element where the element will be positioned next to - * @param {function=} done the callback function (if provided) that will be fired after the element has been moved to it's new position + * @param {jQuery/jqLite element} element the element which will be moved around within the + * DOM + * @param {jQuery/jqLite element} parent the parent element where the element will be + * inserted into (if the after element is not present) + * @param {jQuery/jqLite element} after the sibling element where the element will be + * positioned next to + * @param {function=} done the callback function (if provided) that will be fired after the + * element has been moved to its new position */ move : function(element, parent, after, done) { // Do not remove element before insert. Removing will cause data associated with the @@ -3362,45 +3895,51 @@ var $AnimateProvider = ['$provide', function($provide) { }, /** + * * @ngdoc function * @name ng.$animate#addClass * @methodOf ng.$animate * @function - * - * @description - * Adds the provided className CSS class value to the provided element. Once complete, the done() callback will be fired (if provided). - * - * @param {jQuery/jqLite element} element the element which will have the className value added to it + * @description Adds the provided className CSS class value to the provided element. Once + * complete, the done() callback will be fired (if provided). + * @param {jQuery/jqLite element} element the element which will have the className value + * added to it * @param {string} className the CSS class which will be added to the element - * @param {function=} done the callback function (if provided) that will be fired after the className value has been added to the element + * @param {function=} done the callback function (if provided) that will be fired after the + * className value has been added to the element */ addClass : function(element, className, done) { className = isString(className) ? className : isArray(className) ? className.join(' ') : ''; - element.addClass(className); - $timeout(done || noop, 0, false); + forEach(element, function (element) { + jqLiteAddClass(element, className); + }); + done && $timeout(done, 0, false); }, /** + * * @ngdoc function * @name ng.$animate#removeClass * @methodOf ng.$animate * @function - * - * @description - * Removes the provided className CSS class value from the provided element. Once complete, the done() callback will be fired (if provided). - * - * @param {jQuery/jqLite element} element the element which will have the className value removed from it + * @description Removes the provided className CSS class value from the provided element. + * Once complete, the done() callback will be fired (if provided). + * @param {jQuery/jqLite element} element the element which will have the className value + * removed from it * @param {string} className the CSS class which will be removed from the element - * @param {function=} done the callback function (if provided) that will be fired after the className value has been removed from the element + * @param {function=} done the callback function (if provided) that will be fired after the + * className value has been removed from the element */ removeClass : function(element, className, done) { className = isString(className) ? className : isArray(className) ? className.join(' ') : ''; - element.removeClass(className); - $timeout(done || noop, 0, false); + forEach(element, function (element) { + jqLiteRemoveClass(element, className); + }); + done && $timeout(done, 0, false); }, enabled : noop @@ -3533,7 +4072,7 @@ function Browser(window, document, $log, $sniffer) { var lastBrowserUrl = location.href, baseElement = document.find('base'), - replacedUrl = null; + newLocation = null; /** * @name ng.$browser#url @@ -3556,6 +4095,9 @@ function Browser(window, document, $log, $sniffer) { * @param {boolean=} replace Should new url replace current history record ? */ self.url = function(url, replace) { + // Android Browser BFCache causes location reference to become stale. + if (location !== window.location) location = window.location; + // setter if (url) { if (lastBrowserUrl == url) return; @@ -3568,21 +4110,20 @@ function Browser(window, document, $log, $sniffer) { baseElement.attr('href', baseElement.attr('href')); } } else { + newLocation = url; if (replace) { location.replace(url); - replacedUrl = url; } else { location.href = url; - replacedUrl = null; } } return self; // getter } else { - // - the replacedUrl is a workaround for an IE8-9 issue with location.replace method that doesn't update - // location.href synchronously + // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href + // methods not updating location.href synchronously. // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 - return replacedUrl || location.href.replace(/%27/g,"'"); + return newLocation || location.href.replace(/%27/g,"'"); } }; @@ -3590,6 +4131,7 @@ function Browser(window, document, $log, $sniffer) { urlChangeInit = false; function fireUrlChange() { + newLocation = null; if (lastBrowserUrl == self.url()) return; lastBrowserUrl = self.url(); @@ -3646,10 +4188,14 @@ function Browser(window, document, $log, $sniffer) { ////////////////////////////////////////////////////////////// /** + * @name ng.$browser#baseHref + * @methodOf ng.$browser + * + * @description * Returns current * (always relative - without domain) * - * @returns {string=} + * @returns {string=} current */ self.baseHref = function() { var href = baseElement.attr('href'); @@ -3675,30 +4221,35 @@ function Browser(window, document, $log, $sniffer) { * It is not meant to be used directly, use the $cookie service instead. * * The return values vary depending on the arguments that the method was called with as follows: - *
    - *
  • cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it
  • - *
  • cookies(name, value) -> set name to value, if value is undefined delete the cookie
  • - *
  • cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)
  • - *
+ * + * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify + * it + * - cookies(name, value) -> set name to value, if value is undefined delete the cookie + * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that + * way) * * @returns {Object} Hash of all cookies (if called without any parameter) */ self.cookies = function(name, value) { + /* global escape: false, unescape: false */ var cookieLength, cookieArray, cookie, i, index; if (name) { if (value === undefined) { - rawDocument.cookie = escape(name) + "=;path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; + rawDocument.cookie = escape(name) + "=;path=" + cookiePath + + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; } else { if (isString(value)) { - cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1; + cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + + ';path=' + cookiePath).length + 1; // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: // - 300 cookies // - 20 cookies per unique domain // - 4096 bytes per cookie if (cookieLength > 4096) { - $log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+ + $log.warn("Cookie '"+ name + + "' possibly not set or overflowed because it was too large ("+ cookieLength + " > 4096 bytes)!"); } } @@ -3713,7 +4264,7 @@ function Browser(window, document, $log, $sniffer) { cookie = cookieArray[i]; index = cookie.indexOf('='); if (index > 0) { //ignore nameless cookies - var name = unescape(cookie.substring(0, index)); + name = unescape(cookie.substring(0, index)); // the first value that is seen for a cookie is the most // specific one. values for the same cookie name that // follow are for less specific paths. @@ -3763,7 +4314,8 @@ function Browser(window, document, $log, $sniffer) { * Cancels a deferred task identified with `deferId`. * * @param {*} deferId Token returned by the `$browser.defer` function. - * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully canceled. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. */ self.defer.cancel = function(deferId) { if (pendingDeferIds[deferId]) { @@ -3790,34 +4342,36 @@ function $BrowserProvider(){ * * @description * Factory that constructs cache objects and gives access to them. - * + * *
- * 
+ *
  *  var cache = $cacheFactory('cacheId');
  *  expect($cacheFactory.get('cacheId')).toBe(cache);
  *  expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
  *
  *  cache.put("key", "value");
  *  cache.put("another key", "another value");
- * 
- *  expect(cache.info()).toEqual({id: 'cacheId', size: 2}); // Since we've specified no options on creation
- * 
+ *
+ *  // We've specified no options on creation
+ *  expect(cache.info()).toEqual({id: 'cacheId', size: 2});
+ *
  * 
* * * @param {string} cacheId Name or id of the newly created cache. * @param {object=} options Options object that specifies the cache behavior. Properties: * - * - `{number=}` `capacity` — turns the cache into LRU cache. + * - `{number=}` `capacity` — turns the cache into LRU cache. * * @returns {object} Newly created cache object with the following set of methods: * - * - `{object}` `info()` — Returns id, size, and options of cache. - * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns it. - * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. - * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. - * - `{void}` `removeAll()` — Removes all cached values. - * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. + * - `{object}` `info()` — Returns id, size, and options of cache. + * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns + * it. + * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. + * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. + * - `{void}` `removeAll()` — Removes all cached values. + * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. * */ function $CacheFactoryProvider() { @@ -3980,10 +4534,10 @@ function $CacheFactoryProvider() { * @name ng.$templateCache * * @description - * The first time a template is used, it is loaded in the template cache for quick retrieval. You can - * load templates directly into the cache in a `script` tag, or by consuming the `$templateCache` - * service directly. - * + * The first time a template is used, it is loaded in the template cache for quick retrieval. You + * can load templates directly into the cache in a `script` tag, or by consuming the + * `$templateCache` service directly. + * * Adding via the `script` tag: *
  * 
@@ -3995,29 +4549,29 @@ function $CacheFactoryProvider() {
  *   ...
  * 
  * 
- * - * **Note:** the `script` tag containing the template does not need to be included in the `head` of the document, but - * it must be below the `ng-app` definition. - * + * + * **Note:** the `script` tag containing the template does not need to be included in the `head` of + * the document, but it must be below the `ng-app` definition. + * * Adding via the $templateCache service: - * + * *
  * var myApp = angular.module('myApp', []);
  * myApp.run(function($templateCache) {
  *   $templateCache.put('templateId.html', 'This is the content of the template');
  * });
  * 
- * + * * To retrieve the template later, simply use it in your HTML: *
  * 
*
- * + * * or get it via Javascript: *
  * $templateCache.get('templateId.html')
  * 
- * + * * See {@link ng.$cacheFactory $cacheFactory}. * */ @@ -4052,21 +4606,355 @@ function $TemplateCacheProvider() { * * @description * Compiles a piece of HTML string or DOM into a template and produces a template function, which - * can then be used to link {@link ng.$rootScope.Scope scope} and the template together. + * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. * - * The compilation is a process of walking the DOM tree and trying to match DOM elements to - * {@link ng.$compileProvider#directive directives}. For each match it - * executes corresponding template function and collects the - * instance functions into a single template function which is then returned. + * The compilation is a process of walking the DOM tree and matching DOM elements to + * {@link ng.$compileProvider#methods_directive directives}. * - * The template function can then be used once to produce the view or as it is the case with - * {@link ng.directive:ngRepeat repeater} many-times, in which - * case each call results in a view that is a DOM clone of the original template. + *
+ * **Note:** This document is an in-depth reference of all directive options. + * For a gentle introduction to directives with examples of common use cases, + * see the {@link guide/directive directive guide}. + *
+ * + * ## Comprehensive Directive API + * + * There are many different options for a directive. + * + * The difference resides in the return value of the factory function. + * You can either return a "Directive Definition Object" (see below) that defines the directive properties, + * or just the `postLink` function (all other properties will have the default values). + * + *
+ * **Best Practice:** It's recommended to use the "directive definition object" form. + *
+ * + * Here's an example directive declared with a Directive Definition Object: + * + *
+ *   var myModule = angular.module(...);
+ *
+ *   myModule.directive('directiveName', function factory(injectables) {
+ *     var directiveDefinitionObject = {
+ *       priority: 0,
+ *       template: '
', // or // function(tElement, tAttrs) { ... }, + * // or + * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, + * replace: false, + * transclude: false, + * restrict: 'A', + * scope: false, + * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, + * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], + * compile: function compile(tElement, tAttrs, transclude) { + * return { + * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, + * post: function postLink(scope, iElement, iAttrs, controller) { ... } + * } + * // or + * // return function postLink( ... ) { ... } + * }, + * // or + * // link: { + * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, + * // post: function postLink(scope, iElement, iAttrs, controller) { ... } + * // } + * // or + * // link: function postLink( ... ) { ... } + * }; + * return directiveDefinitionObject; + * }); + *
+ * + *
+ * **Note:** Any unspecified options will use the default value. You can see the default values below. + *
+ * + * Therefore the above can be simplified as: + * + *
+ *   var myModule = angular.module(...);
+ *
+ *   myModule.directive('directiveName', function factory(injectables) {
+ *     var directiveDefinitionObject = {
+ *       link: function postLink(scope, iElement, iAttrs) { ... }
+ *     };
+ *     return directiveDefinitionObject;
+ *     // or
+ *     // return function postLink(scope, iElement, iAttrs) { ... }
+ *   });
+ * 
+ * + * + * + * ### Directive Definition Object + * + * The directive definition object provides instructions to the {@link api/ng.$compile + * compiler}. The attributes are: + * + * #### `priority` + * When there are multiple directives defined on a single DOM element, sometimes it + * is necessary to specify the order in which the directives are applied. The `priority` is used + * to sort the directives before their `compile` functions get called. Priority is defined as a + * number. Directives with greater numerical `priority` are compiled first. The order of directives with + * the same priority is undefined. The default priority is `0`. + * + * #### `terminal` + * If set to true then the current `priority` will be the last set of directives + * which will execute (any directives at the current priority will still execute + * as the order of execution on same `priority` is undefined). + * + * #### `scope` + * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the + * same element request a new scope, only one new scope is created. The new scope rule does not + * apply for the root of the template since the root of the template always gets a new scope. + * + * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from + * normal scope in that it does not prototypically inherit from the parent scope. This is useful + * when creating reusable components, which should not accidentally read or modify data in the + * parent scope. + * + * The 'isolate' scope takes an object hash which defines a set of local scope properties + * derived from the parent scope. These local properties are useful for aliasing values for + * templates. Locals definition is a hash of local scope property to its source: + * + * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is + * always a string since DOM attributes are strings. If no `attr` name is specified then the + * attribute name is assumed to be the same as the local name. + * Given `` and widget definition + * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect + * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the + * `localName` property on the widget scope. The `name` is read from the parent scope (not + * component scope). + * + * * `=` or `=attr` - set up bi-directional binding between a local scope property and the + * parent scope property of name defined via the value of the `attr` attribute. If no `attr` + * name is specified then the attribute name is assumed to be the same as the local name. + * Given `` and widget definition of + * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the + * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected + * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent + * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You + * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. + * + * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. + * If no `attr` name is specified then the attribute name is assumed to be the same as the + * local name. Given `` and widget definition of + * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to + * a function wrapper for the `count = count + value` expression. Often it's desirable to + * pass data from the isolated scope via an expression and to the parent scope, this can be + * done by passing a map of local variable names and values into the expression wrapper fn. + * For example, if the expression is `increment(amount)` then we can specify the amount value + * by calling the `localFn` as `localFn({amount: 22})`. + * + * + * + * #### `controller` + * Controller constructor function. The controller is instantiated before the + * pre-linking phase and it is shared with other directives (see + * `require` attribute). This allows the directives to communicate with each other and augment + * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: + * + * * `$scope` - Current scope associated with the element + * * `$element` - Current element + * * `$attrs` - Current attributes object for the element + * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: + * `function(cloneLinkingFn)`. + * + * + * #### `require` + * Require another directive and inject its controller as the fourth argument to the linking function. The + * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the + * injected argument will be an array in corresponding order. If no such directive can be + * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: + * + * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. + * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. + * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. + * * `?^` - Attempt to locate the required controller by searching the element's parentsor pass `null` to the + * `link` fn if not found. + * + * + * #### `controllerAs` + * Controller alias at the directive scope. An alias for the controller so it + * can be referenced at the directive template. The directive needs to define a scope for this + * configuration to be used. Useful in the case when directive is used as component. + * + * + * #### `restrict` + * String of subset of `EACM` which restricts the directive to a specific directive + * declaration style. If omitted, the default (attributes only) is used. + * + * * `E` - Element name: `` + * * `A` - Attribute (default): `
` + * * `C` - Class: `
` + * * `M` - Comment: `` + * + * + * #### `template` + * replace the current element with the contents of the HTML. The replacement process + * migrates all of the attributes / classes from the old element to the new one. See the + * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive + * Directives Guide} for an example. + * + * You can specify `template` as a string representing the template or as a function which takes + * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and + * returns a string value representing the template. + * + * + * #### `templateUrl` + * Same as `template` but the template is loaded from the specified URL. Because + * the template loading is asynchronous the compilation/linking is suspended until the template + * is loaded. + * + * You can specify `templateUrl` as a string representing the URL or as a function which takes two + * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns + * a string value representing the url. In either case, the template URL is passed through {@link + * api/ng.$sce#methods_getTrustedResourceUrl $sce.getTrustedResourceUrl}. + * + * + * #### `replace` + * specify where the template should be inserted. Defaults to `false`. + * + * * `true` - the template will replace the current element. + * * `false` - the template will replace the contents of the current element. + * + * + * #### `transclude` + * compile the content of the element and make it available to the directive. + * Typically used with {@link api/ng.directive:ngTransclude + * ngTransclude}. The advantage of transclusion is that the linking function receives a + * transclusion function which is pre-bound to the correct scope. In a typical setup the widget + * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` + * scope. This makes it possible for the widget to have private state, and the transclusion to + * be bound to the parent (pre-`isolate`) scope. + * + * * `true` - transclude the content of the directive. + * * `'element'` - transclude the whole element including any directives defined at lower priority. + * + * + * #### `compile` + * + *
+ *   function compile(tElement, tAttrs, transclude) { ... }
+ * 
+ * + * The compile function deals with transforming the template DOM. Since most directives do not do + * template transformation, it is not used often. Examples that require compile functions are + * directives that transform template DOM, such as {@link + * api/ng.directive:ngRepeat ngRepeat}, or load the contents + * asynchronously, such as {@link api/ngRoute.directive:ngView ngView}. The + * compile function takes the following arguments. + * + * * `tElement` - template element - The element where the directive has been declared. It is + * safe to do template transformation on the element and child elements only. + * + * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared + * between all directive compile functions. + * + * * `transclude` - A transclude linking function: `function(scope, cloneLinkingFn)`. + * + *
+ * **Note:** The template instance and the link instance may be different objects if the template has + * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that + * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration + * should be done in a linking function rather than in a compile function. + *
+ * + * A compile function can have a return value which can be either a function or an object. + * + * * returning a (post-link) function - is equivalent to registering the linking function via the + * `link` property of the config object when the compile function is empty. + * + * * returning an object with function(s) registered via `pre` and `post` properties - allows you to + * control when a linking function should be called during the linking phase. See info about + * pre-linking and post-linking functions below. + * + * + * #### `link` + * This property is used only if the `compile` property is not defined. + * + *
+ *   function link(scope, iElement, iAttrs, controller) { ... }
+ * 
+ * + * The link function is responsible for registering DOM listeners as well as updating the DOM. It is + * executed after the template has been cloned. This is where most of the directive logic will be + * put. + * + * * `scope` - {@link api/ng.$rootScope.Scope Scope} - The scope to be used by the + * directive for registering {@link api/ng.$rootScope.Scope#methods_$watch watches}. + * + * * `iElement` - instance element - The element where the directive is to be used. It is safe to + * manipulate the children of the element only in `postLink` function since the children have + * already been linked. + * + * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared + * between all directive linking functions. + * + * * `controller` - a controller instance - A controller instance if at least one directive on the + * element defines a controller. The controller is shared among all the directives, which allows + * the directives to use the controllers as a communication channel. + * + * + * + * #### Pre-linking function + * + * Executed before the child elements are linked. Not safe to do DOM transformation since the + * compiler linking function will fail to locate the correct elements for linking. + * + * #### Post-linking function + * + * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. + * + * + * ### Attributes + * + * The {@link api/ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the + * `link()` or `compile()` functions. It has a variety of uses. + * + * accessing *Normalized attribute names:* + * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. + * the attributes object allows for normalized access to + * the attributes. + * + * * *Directive inter-communication:* All directives share the same instance of the attributes + * object which allows the directives to use the attributes object as inter directive + * communication. + * + * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object + * allowing other directives to read the interpolated value. + * + * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes + * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also + * the only way to easily get the actual value because during the linking phase the interpolation + * hasn't been evaluated yet and so the value is at this time set to `undefined`. + * + *
+ * function linkingFn(scope, elm, attrs, ctrl) {
+ *   // get the attribute value
+ *   console.log(attrs.ngModel);
+ *
+ *   // change the attribute
+ *   attrs.$set('ngModel', 'new value');
+ *
+ *   // observe changes to interpolated attribute
+ *   attrs.$observe('ngModel', function(value) {
+ *     console.log('ngModel has changed value to ' + value);
+ *   });
+ * }
+ * 
+ * + * Below is an example using `$compileProvider`. + * + *
+ * **Note**: Typically directives are registered with `module.directive`. The example below is + * to illustrate how `$compile` works. + *
* -
- //label// -
-
-
+ + + +
+ //demo.label// +
+
+ + it('should interpolate binding with custom symbols', function() { + expect(binding('demo.label')).toBe('This binding is brought you by // interpolation symbols.'); + }); + +
*/ function $InterpolateProvider() { var startSymbol = '{{'; @@ -6858,11 +7942,11 @@ function $InterpolateProvider() { * embedded expression in order to return an interpolation function. Strings with no * embedded expression will return null for the interpolation function. * @param {string=} trustedContext when provided, the returned function passes the interpolated - * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, + * result through {@link ng.$sce#methods_getTrusted $sce.getTrusted(interpolatedResult, * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that * provides Strict Contextual Escaping for details. - * @returns {function(context)} an interpolation function which is used to compute the interpolated - * string. The function has these parameters: + * @returns {function(context)} an interpolation function which is used to compute the + * interpolated string. The function has these parameters: * * * `context`: an object against which any expressions embedded in the strings are evaluated * against. @@ -6900,12 +7984,12 @@ function $InterpolateProvider() { length = 1; } - // Concatenating expressions makes it hard to reason about whether some combination of concatenated - // values are unsafe to use and could easily lead to XSS. By requiring that a single - // expression be used for iframe[src], object[src], etc., we ensure that the value that's used - // is assigned or constructed by some JS code somewhere that is more testable or make it - // obvious that you bound the value to some user controlled value. This helps reduce the load - // when auditing for XSS issues. + // Concatenating expressions makes it hard to reason about whether some combination of + // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a + // single expression be used for iframe[src], object[src], etc., we ensure that the value + // that's used is assigned or constructed by some JS code somewhere that is more testable or + // make it obvious that you bound the value to some user controlled value. This helps reduce + // the load when auditing for XSS issues. if (trustedContext && parts.length > 1) { throw $interpolateMinErr('noconcat', "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + @@ -6925,7 +8009,7 @@ function $InterpolateProvider() { } else { part = $sce.valueOf(part); } - if (part == null || part == undefined) { + if (part === null || isUndefined(part)) { part = ''; } else if (typeof part != 'string') { part = toJson(part); @@ -6936,7 +8020,8 @@ function $InterpolateProvider() { return concat.join(''); } catch(err) { - var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); + var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, + err.toString()); $exceptionHandler(newErr); } }; @@ -6961,7 +8046,7 @@ function $InterpolateProvider() { */ $interpolate.startSymbol = function() { return startSymbol; - } + }; /** @@ -6978,12 +8063,100 @@ function $InterpolateProvider() { */ $interpolate.endSymbol = function() { return endSymbol; - } + }; return $interpolate; }]; } +function $IntervalProvider() { + this.$get = ['$rootScope', '$window', '$q', + function($rootScope, $window, $q) { + var intervals = {}; + + + /** + * @ngdoc function + * @name ng.$interval + * + * @description + * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` + * milliseconds. + * + * The return value of registering an interval function is a promise. This promise will be + * notified upon each tick of the interval, and will be resolved after `count` iterations, or + * run indefinitely if `count` is not defined. The value of the notification will be the + * number of iterations that have run. + * To cancel an interval, call `$interval.cancel(promise)`. + * + * In tests you can use {@link ngMock.$interval#methods_flush `$interval.flush(millis)`} to + * move forward by `millis` milliseconds and trigger any functions scheduled to run in that + * time. + * + * @param {function()} fn A function that should be called repeatedly. + * @param {number} delay Number of milliseconds between each function call. + * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat + * indefinitely. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block. + * @returns {promise} A promise which will be notified on each iteration. + */ + function interval(fn, delay, count, invokeApply) { + var setInterval = $window.setInterval, + clearInterval = $window.clearInterval, + deferred = $q.defer(), + promise = deferred.promise, + iteration = 0, + skipApply = (isDefined(invokeApply) && !invokeApply); + + count = isDefined(count) ? count : 0, + + promise.then(null, null, fn); + + promise.$$intervalId = setInterval(function tick() { + deferred.notify(iteration++); + + if (count > 0 && iteration >= count) { + deferred.resolve(iteration); + clearInterval(promise.$$intervalId); + delete intervals[promise.$$intervalId]; + } + + if (!skipApply) $rootScope.$apply(); + + }, delay); + + intervals[promise.$$intervalId] = deferred; + + return promise; + } + + + /** + * @ngdoc function + * @name ng.$interval#cancel + * @methodOf ng.$interval + * + * @description + * Cancels a task associated with the `promise`. + * + * @param {number} promise Promise returned by the `$interval` function. + * @returns {boolean} Returns `true` if the task was successfully canceled. + */ + interval.cancel = function(promise) { + if (promise && promise.$$intervalId in intervals) { + intervals[promise.$$intervalId].reject('canceled'); + clearInterval(promise.$$intervalId); + delete intervals[promise.$$intervalId]; + return true; + } + return false; + }; + + return interval; + }]; +} + /** * @ngdoc object * @name ng.$locale @@ -6992,7 +8165,7 @@ function $InterpolateProvider() { * $locale service provides localization rules for various Angular components. As of right now the * only public api is: * - * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) + * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) */ function $LocaleProvider(){ this.$get = function() { @@ -7029,8 +8202,9 @@ function $LocaleProvider(){ }, DATETIME_FORMATS: { - MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December' - .split(','), + MONTH: + 'January,February,March,April,May,June,July,August,September,October,November,December' + .split(','), SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), @@ -7055,8 +8229,7 @@ function $LocaleProvider(){ }; } -var SERVER_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/, - PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, +var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; var $locationMinErr = minErr('$location'); @@ -7078,39 +8251,44 @@ function encodePath(path) { return segments.join('/'); } -function matchUrl(url, obj) { - var match = SERVER_MATCH.exec(url); +function parseAbsoluteUrl(absoluteUrl, locationObj) { + var parsedUrl = urlResolve(absoluteUrl); - obj.$$protocol = match[1]; - obj.$$host = match[3]; - obj.$$port = int(match[5]) || DEFAULT_PORTS[match[1]] || null; + locationObj.$$protocol = parsedUrl.protocol; + locationObj.$$host = parsedUrl.hostname; + locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; } -function matchAppUrl(url, obj) { - var match = PATH_MATCH.exec(url); - obj.$$path = decodeURIComponent(match[1]); - obj.$$search = parseKeyValue(match[3]); - obj.$$hash = decodeURIComponent(match[5] || ''); +function parseAppUrl(relativeUrl, locationObj) { + var prefixed = (relativeUrl.charAt(0) !== '/'); + if (prefixed) { + relativeUrl = '/' + relativeUrl; + } + var match = urlResolve(relativeUrl); + locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? + match.pathname.substring(1) : match.pathname); + locationObj.$$search = parseKeyValue(match.search); + locationObj.$$hash = decodeURIComponent(match.hash); // make sure path starts with '/'; - if (obj.$$path && obj.$$path.charAt(0) != '/') obj.$$path = '/' + obj.$$path; + if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { + locationObj.$$path = '/' + locationObj.$$path; + } } -function composeProtocolHostPort(protocol, host, port) { - return protocol + '://' + host + (port == DEFAULT_PORTS[protocol] ? '' : ':' + port); -} - /** * * @param {string} begin * @param {string} whole - * @param {string} otherwise - * @returns {string} returns text from whole after begin or otherwise if it does not begin with expected string. + * @returns {string} returns text from whole after begin or undefined if it does not begin with + * expected string. */ -function beginsWith(begin, whole, otherwise) { - return whole.indexOf(begin) == 0 ? whole.substr(begin.length) : otherwise; +function beginsWith(begin, whole) { + if (whole.indexOf(begin) === 0) { + return whole.substr(begin.length); + } } @@ -7142,20 +8320,23 @@ function LocationHtml5Url(appBase, basePrefix) { this.$$html5 = true; basePrefix = basePrefix || ''; var appBaseNoFile = stripFile(appBase); + parseAbsoluteUrl(appBase, this); + + /** * Parse given html5 (regular) url string into properties * @param {string} newAbsoluteUrl HTML5 url * @private */ this.$$parse = function(url) { - var parsed = {} - matchUrl(url, parsed); var pathUrl = beginsWith(appBaseNoFile, url); if (!isString(pathUrl)) { - throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, appBaseNoFile); + throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, + appBaseNoFile); } - matchAppUrl(pathUrl, parsed); - extend(this, parsed); + + parseAppUrl(pathUrl, this); + if (!this.$$path) { this.$$path = '/'; } @@ -7190,7 +8371,7 @@ function LocationHtml5Url(appBase, basePrefix) { } else if (appBaseNoFile == url + '/') { return appBaseNoFile; } - } + }; } @@ -7206,7 +8387,7 @@ function LocationHtml5Url(appBase, basePrefix) { function LocationHashbangUrl(appBase, hashPrefix) { var appBaseNoFile = stripFile(appBase); - matchUrl(appBase, this); + parseAbsoluteUrl(appBase, this); /** @@ -7223,9 +8404,10 @@ function LocationHashbangUrl(appBase, hashPrefix) { : ''; if (!isString(withoutHashUrl)) { - throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, hashPrefix); + throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, + hashPrefix); } - matchAppUrl(withoutHashUrl, this); + parseAppUrl(withoutHashUrl, this); this.$$compose(); }; @@ -7245,7 +8427,7 @@ function LocationHashbangUrl(appBase, hashPrefix) { if(stripHash(appBase) == stripHash(url)) { return url; } - } + }; } @@ -7274,7 +8456,7 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) { } else if ( appBaseNoFile === url + '/') { return appBaseNoFile; } - } + }; } @@ -7413,10 +8595,13 @@ LocationHashbangInHtml5Url.prototype = * * Change search part when called with parameter and return `$location`. * - * @param {string|Object.|Object.>} search New search params - string or hash object. Hash object - * may contain an array of values, which will be decoded as duplicates in the url. - * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a - * single search parameter. If the value is `null`, the parameter will be deleted. + * @param {string|Object.|Object.>} search New search params - string or + * hash object. Hash object may contain an array of values, which will be decoded as duplicates in + * the url. + * + * @param {(string|Array)=} paramValue If `search` is a string, then `paramValue` will override only a + * single search parameter. If `paramValue` is an array, it will set the parameter as a + * comma-separated value. If `paramValue` is `null`, the parameter will be deleted. * * @return {string} search */ @@ -7430,11 +8615,12 @@ LocationHashbangInHtml5Url.prototype = } else if (isObject(search)) { this.$$search = search; } else { - throw $locationMinErr('isrcharg', 'The first argument of the `$location#search()` call must be a string or an object.'); + throw $locationMinErr('isrcharg', + 'The first argument of the `$location#search()` call must be a string or an object.'); } break; default: - if (paramValue == undefined || paramValue == null) { + if (isUndefined(paramValue) || paramValue === null) { delete this.$$search[search]; } else { this.$$search[search] = paramValue; @@ -7558,7 +8744,7 @@ function $LocationProvider(){ * @name ng.$locationProvider#html5Mode * @methodOf ng.$locationProvider * @description - * @param {string=} mode Use HTML5 strategy if available. + * @param {boolean=} mode Use HTML5 strategy if available. * @returns {*} current value if used as getter or itself (chaining) if used as setter */ this.html5Mode = function(mode) { @@ -7570,6 +8756,35 @@ function $LocationProvider(){ } }; + /** + * @ngdoc event + * @name ng.$location#$locationChangeStart + * @eventOf ng.$location + * @eventType broadcast on root scope + * @description + * Broadcasted before a URL will change. This change can be prevented by calling + * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more + * details about event object. Upon successful change + * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. + * + * @param {Object} angularEvent Synthetic event object. + * @param {string} newUrl New URL + * @param {string=} oldUrl URL that was before it was changed. + */ + + /** + * @ngdoc event + * @name ng.$location#$locationChangeSuccess + * @eventOf ng.$location + * @eventType broadcast on root scope + * @description + * Broadcasted after a URL was changed. + * + * @param {Object} angularEvent Synthetic event object. + * @param {string} newUrl New URL + * @param {string=} oldUrl URL that was before it was changed. + */ + this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', function( $rootScope, $browser, $sniffer, $rootElement) { var $location, @@ -7626,7 +8841,8 @@ function $LocationProvider(){ // update $location when $browser url changes $browser.onUrlChange(function(newUrl) { if ($location.absUrl() != newUrl) { - if ($rootScope.$broadcast('$locationChangeStart', newUrl, $location.absUrl()).defaultPrevented) { + if ($rootScope.$broadcast('$locationChangeStart', newUrl, + $location.absUrl()).defaultPrevented) { $browser.url($location.absUrl()); return; } @@ -7677,11 +8893,14 @@ function $LocationProvider(){ * @requires $window * * @description - * Simple service for logging. Default implementation writes the message + * Simple service for logging. Default implementation safely writes the message * into the browser's console (if present). * * The main purpose of this service is to simplify debugging and troubleshooting. * + * The default is not to log `debug` messages. You can use + * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. + * * @example @@ -7713,7 +8932,7 @@ function $LocationProvider(){ function $LogProvider(){ var debug = true, self = this; - + /** * @ngdoc property * @name ng.$logProvider#debugEnabled @@ -7723,14 +8942,14 @@ function $LogProvider(){ * @returns {*} current value if used as getter or itself (chaining) if used as setter */ this.debugEnabled = function(flag) { - if (isDefined(flag)) { - debug = flag; - return this; - } else { - return debug; - } + if (isDefined(flag)) { + debug = flag; + return this; + } else { + return debug; + } }; - + this.$get = ['$window', function($window){ return { /** @@ -7772,23 +8991,23 @@ function $LogProvider(){ * Write an error message */ error: consoleLog('error'), - + /** * @ngdoc method * @name ng.$log#debug * @methodOf ng.$log - * + * * @description * Write a debug message */ debug: (function () { - var fn = consoleLog('debug'); - - return function() { - if (debug) { - fn.apply(self, arguments); - } - } + var fn = consoleLog('debug'); + + return function() { + if (debug) { + fn.apply(self, arguments); + } + }; }()) }; @@ -7822,62 +9041,90 @@ function $LogProvider(){ // we are IE which either doesn't have window.console => this is noop and we do nothing, // or we are IE where console.log doesn't have apply so we log at least first 2 args return function(arg1, arg2) { - logFn(arg1, arg2); - } + logFn(arg1, arg2 == null ? '' : arg2); + }; } }]; } var $parseMinErr = minErr('$parse'); +var promiseWarningCache = {}; +var promiseWarning; // 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 obtaining a reference to native JS -// functions such as the Function constructor. +// 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 +// obtaining a reference to native JS functions such as the Function constructor, the global Window +// or Document object. In addition, many powerful functions for use by JavaScript code are +// published on scope that shouldn't be available from within an Angular expression. // // As an example, consider the following Angular expression: // // {}.toString.constructor(alert("evil JS code")) // -// We want to prevent this type of access. For the sake of performance, during the lexing phase we disallow any "dotted" -// access to any member named "constructor". +// We want to prevent this type of access. For the sake of performance, during the lexing phase we +// disallow any "dotted" access to any member named "constructor" or to any member whose name begins +// or ends with an underscore. The latter allows one to exclude the private / JavaScript only API +// available on the scope and controllers from the context of an Angular expression. // -// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor while evaluating -// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor while evaluating -// the expression, which is a stronger but more expensive test. Since reflective calls are expensive anyway, this is not -// such a big deal compared to static dereferencing. +// For reflective calls (a[b]), we check that the value of the lookup is not the Function +// constructor, Window or DOM node while evaluating the expression, which is a stronger but more +// expensive test. Since reflective calls are expensive anyway, this is not such a big deal compared +// to static dereferencing. // -// 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 practice and therefore we are not even trying to protect -// against interaction with an object explicitly exposed in this way. +// 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 +// practice and therefore we are not even trying to protect against interaction with an object +// explicitly exposed in this way. // -// A developer could foil the name check by aliasing the Function constructor under a different name on the scope. +// A developer could foil the name check by aliasing the Function constructor under a different +// name on the scope. // -// In general, it is not possible to access a Window object from an angular expression unless a window or some DOM -// object that has a reference to window is published onto a Scope. +// In general, it is not possible to access a Window object from an angular expression unless a +// window or some DOM object that has a reference to window is published onto a Scope. -function ensureSafeMemberName(name, fullExpression) { - if (name === "constructor") { +function ensureSafeMemberName(name, fullExpression, allowConstructor) { + if (typeof name !== 'string' && toString.apply(name) !== "[object String]") { + return name; + } + if (name === "constructor" && !allowConstructor) { throw $parseMinErr('isecfld', - 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', fullExpression); + 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } + if (name.charAt(0) === '_' || name.charAt(name.length-1) === '_') { + throw $parseMinErr('isecprv', + 'Referencing private fields in Angular expressions is disallowed! Expression: {0}', + fullExpression); } return name; -}; +} function ensureSafeObject(obj, fullExpression) { // nifty check if obj is Function that is fast and works across iframes and other contexts if (obj && obj.constructor === obj) { throw $parseMinErr('isecfn', - 'Referencing Function in Angular expressions is disallowed! Expression: {0}', fullExpression); + 'Referencing Function in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (// isWindow(obj) + obj && obj.document && obj.location && obj.alert && obj.setInterval) { + throw $parseMinErr('isecwindow', + 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (// isElement(obj) + obj && (obj.nodeName || (obj.on && obj.find))) { + throw $parseMinErr('isecdom', + 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', + fullExpression); } else { return obj; } } - var OPERATORS = { + /* jshint bitwise : false */ 'null':function(){return null;}, 'true':function(){return true;}, 'false':function(){return false;}, @@ -7891,7 +9138,10 @@ var OPERATORS = { return a; } 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);}, + '-':function(self, locals, a,b){ + a=a(self, locals); b=b(self, locals); + 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);}, @@ -7912,158 +9162,196 @@ var OPERATORS = { '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, '!':function(self, locals, a){return !a(self, locals);} }; +/* jshint bitwise: true */ var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; -function lex(text, csp){ - var tokens = [], - token, - index = 0, - json = [], - ch, - lastCh = ':'; // can start regexp - while (index < text.length) { - ch = text.charAt(index); - if (is('"\'')) { - readString(ch); - } else if (isNumber(ch) || is('.') && isNumber(peek())) { - readNumber(); - } else if (isIdent(ch)) { - readIdent(); - // identifiers can only be if the preceding char was a { or , - if (was('{,') && json[0]=='{' && - (token=tokens[tokens.length-1])) { - token.json = token.text.indexOf('.') == -1; - } - } else if (is('(){}[].,;:?')) { - tokens.push({ - index:index, - text:ch, - json:(was(':[,') && is('{[')) || is('}]:,') - }); - if (is('{[')) json.unshift(ch); - if (is('}]')) json.shift(); - index++; - } else if (isWhitespace(ch)) { - index++; - continue; - } else { - var ch2 = ch + peek(), - ch3 = ch2 + peek(2), - fn = OPERATORS[ch], - fn2 = OPERATORS[ch2], - fn3 = OPERATORS[ch3]; - if (fn3) { - tokens.push({index:index, text:ch3, fn:fn3}); - index += 3; - } else if (fn2) { - tokens.push({index:index, text:ch2, fn:fn2}); - index += 2; - } else if (fn) { - tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')}); - index += 1; +///////////////////////////////////////// + + +/** + * @constructor + */ +var Lexer = function (options) { + this.options = options; +}; + +Lexer.prototype = { + constructor: Lexer, + + lex: function (text) { + this.text = text; + + this.index = 0; + this.ch = undefined; + this.lastCh = ':'; // can start regexp + + this.tokens = []; + + var token; + var json = []; + + 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())) { + this.readNumber(); + } else if (this.isIdent(this.ch)) { + this.readIdent(); + // identifiers can only be if the preceding char was a { or , + if (this.was('{,') && json[0] === '{' && + (token = this.tokens[this.tokens.length - 1])) { + token.json = token.text.indexOf('.') === -1; + } + } else if (this.is('(){}[].,;:?')) { + this.tokens.push({ + index: this.index, + text: this.ch, + json: (this.was(':[,') && this.is('{[')) || this.is('}]:,') + }); + if (this.is('{[')) json.unshift(this.ch); + if (this.is('}]')) json.shift(); + this.index++; + } else if (this.isWhitespace(this.ch)) { + this.index++; + continue; } else { - throwError("Unexpected next character ", index, index+1); + var ch2 = this.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, + json: (this.was('[,:') && this.is('+-')) + }); + this.index += 1; + } else { + this.throwError('Unexpected next character ', this.index, this.index + 1); + } } + this.lastCh = this.ch; } - lastCh = ch; - } - return tokens; + return this.tokens; + }, - function is(chars) { - return chars.indexOf(ch) != -1; - } + is: function(chars) { + return chars.indexOf(this.ch) !== -1; + }, - function was(chars) { - return chars.indexOf(lastCh) != -1; - } + was: function(chars) { + return chars.indexOf(this.lastCh) !== -1; + }, - function peek(i) { + peek: function(i) { var num = i || 1; - return index + num < text.length ? text.charAt(index + num) : false; - } - function isNumber(ch) { - return '0' <= ch && ch <= '9'; - } - function isWhitespace(ch) { - return ch == ' ' || ch == '\r' || ch == '\t' || - ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0 - } - function isIdent(ch) { - return 'a' <= ch && ch <= 'z' || - 'A' <= ch && ch <= 'Z' || - '_' == ch || ch == '$'; - } - function isExpOperator(ch) { - return ch == '-' || ch == '+' || isNumber(ch); - } + return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; + }, - function throwError(error, start, end) { - end = end || index; - var colStr = (isDefined(start) ? - "s " + start + "-" + index + " [" + text.substring(start, end) + "]" - : " " + end); - throw $parseMinErr('lexerr', "Lexer Error: {0} at column{1} in expression [{2}].", - error, colStr, text); - } + isNumber: function(ch) { + return ('0' <= ch && ch <= '9'); + }, - function readNumber() { - var number = ""; - var start = index; - while (index < text.length) { - var ch = lowercase(text.charAt(index)); - if (ch == '.' || isNumber(ch)) { + isWhitespace: function(ch) { + // IE treats non-breaking space as \u00A0 + return (ch === ' ' || ch === '\r' || ch === '\t' || + ch === '\n' || ch === '\v' || ch === '\u00A0'); + }, + + isIdent: function(ch) { + return ('a' <= ch && ch <= 'z' || + 'A' <= ch && ch <= 'Z' || + '_' === ch || ch === '$'); + }, + + isExpOperator: function(ch) { + return (ch === '-' || ch === '+' || this.isNumber(ch)); + }, + + throwError: function(error, start, end) { + end = end || this.index; + var colStr = (isDefined(start) + ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' + : ' ' + end); + throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', + error, colStr, this.text); + }, + + readNumber: function() { + var number = ''; + var start = this.index; + while (this.index < this.text.length) { + var ch = lowercase(this.text.charAt(this.index)); + if (ch == '.' || this.isNumber(ch)) { number += ch; } else { - var peekCh = peek(); - if (ch == 'e' && isExpOperator(peekCh)) { + var peekCh = this.peek(); + if (ch == 'e' && this.isExpOperator(peekCh)) { number += ch; - } else if (isExpOperator(ch) && - peekCh && isNumber(peekCh) && + } else if (this.isExpOperator(ch) && + peekCh && this.isNumber(peekCh) && number.charAt(number.length - 1) == 'e') { number += ch; - } else if (isExpOperator(ch) && - (!peekCh || !isNumber(peekCh)) && + } else if (this.isExpOperator(ch) && + (!peekCh || !this.isNumber(peekCh)) && number.charAt(number.length - 1) == 'e') { - throwError('Invalid exponent'); + this.throwError('Invalid exponent'); } else { break; } } - index++; + this.index++; } number = 1 * number; - tokens.push({index:start, text:number, json:true, - fn:function() {return number;}}); - } - function readIdent() { - var ident = "", - start = index, - lastDot, peekIndex, methodName, ch; + this.tokens.push({ + index: start, + text: number, + json: true, + fn: function() { return number; } + }); + }, - while (index < text.length) { - ch = text.charAt(index); - if (ch == '.' || isIdent(ch) || isNumber(ch)) { - if (ch == '.') lastDot = index; + readIdent: function() { + var parser = this; + + 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 { break; } - index++; + this.index++; } //check if this is not a method invocation and if it is back out to last dot if (lastDot) { - peekIndex = index; - while(peekIndex < text.length) { - ch = text.charAt(peekIndex); - if (ch == '(') { + 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); - index = peekIndex; + this.index = peekIndex; break; } - if(isWhitespace(ch)) { + if (this.isWhitespace(ch)) { peekIndex++; } else { break; @@ -8073,54 +9361,56 @@ function lex(text, csp){ var token = { - index:start, - text:ident + index: start, + text: ident }; + // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn if (OPERATORS.hasOwnProperty(ident)) { - token.fn = token.json = OPERATORS[ident]; + token.fn = OPERATORS[ident]; + token.json = OPERATORS[ident]; } else { - var getter = getterFn(ident, csp, text); + var getter = getterFn(ident, this.options, this.text); token.fn = extend(function(self, locals) { return (getter(self, locals)); }, { assign: function(self, value) { - return setter(self, ident, value, text); + return setter(self, ident, value, parser.text, parser.options); } }); } - tokens.push(token); + this.tokens.push(token); if (methodName) { - tokens.push({ + this.tokens.push({ index:lastDot, text: '.', json: false }); - tokens.push({ + this.tokens.push({ index: lastDot + 1, text: methodName, json: false }); } - } + }, - function readString(quote) { - var start = index; - index++; - var string = ""; + readString: function(quote) { + var start = this.index; + this.index++; + var string = ''; var rawString = quote; var escape = false; - while (index < text.length) { - var ch = text.charAt(index); + while (this.index < this.text.length) { + var ch = this.text.charAt(this.index); rawString += ch; if (escape) { - if (ch == 'u') { - var hex = text.substring(index + 1, index + 5); + if (ch === 'u') { + var hex = this.text.substring(this.index + 1, this.index + 5); if (!hex.match(/[\da-f]{4}/i)) - throwError( "Invalid unicode escape [\\u" + hex + "]"); - index += 4; + this.throwError('Invalid unicode escape [\\u' + hex + ']'); + this.index += 4; string += String.fromCharCode(parseInt(hex, 16)); } else { var rep = ESCAPE[ch]; @@ -8131,172 +9421,227 @@ function lex(text, csp){ } } escape = false; - } else if (ch == '\\') { + } else if (ch === '\\') { escape = true; - } else if (ch == quote) { - index++; - tokens.push({ - index:start, - text:rawString, - string:string, - json:true, - fn:function() { return string; } + } else if (ch === quote) { + this.index++; + this.tokens.push({ + index: start, + text: rawString, + string: string, + json: true, + fn: function() { return string; } }); return; } else { string += ch; } - index++; + this.index++; } - throwError("Unterminated quote", start); + this.throwError('Unterminated quote', start); } -} +}; -///////////////////////////////////////// -function parser(text, json, $filter, csp){ - var ZERO = valueFn(0), - value, - tokens = lex(text, csp), - assignment = _assignment, - functionCall = _functionCall, - fieldAccess = _fieldAccess, - objectIndex = _objectIndex, - filterChain = _filterChain; +/** + * @constructor + */ +var Parser = function (lexer, $filter, options) { + this.lexer = lexer; + this.$filter = $filter; + this.options = options; +}; - if(json){ - // The extra level of aliasing is here, just in case the lexer misses something, so that - // we prevent any accidental execution in JSON. - assignment = logicalOR; - functionCall = - fieldAccess = - objectIndex = - filterChain = - function() { throwError("is not valid json", {text:text, index:0}); }; - value = primary(); - } else { - value = statements(); - } - if (tokens.length !== 0) { - throwError("is an unexpected token", tokens[0]); - } - value.literal = !!value.literal; - value.constant = !!value.constant; - return value; +Parser.ZERO = function () { return 0; }; - /////////////////////////////////// - function throwError(msg, token) { +Parser.prototype = { + constructor: Parser, + + parse: function (text, json) { + this.text = text; + + //TODO(i): strip all the obsolte json stuff from this file + this.json = json; + + this.tokens = this.lexer.lex(text); + + if (json) { + // The extra level of aliasing is here, just in case the lexer misses something, so that + // we prevent any accidental execution in JSON. + this.assignment = this.logicalOR; + + this.functionCall = + this.fieldAccess = + this.objectIndex = + this.filterChain = function() { + this.throwError('is not valid json', {text: text, index: 0}); + }; + } + + var value = json ? this.primary() : this.statements(); + + if (this.tokens.length !== 0) { + this.throwError('is an unexpected token', this.tokens[0]); + } + + value.literal = !!value.literal; + value.constant = !!value.constant; + + return value; + }, + + primary: function () { + var primary; + if (this.expect('(')) { + primary = this.filterChain(); + this.consume(')'); + } else if (this.expect('[')) { + primary = this.arrayDeclaration(); + } else if (this.expect('{')) { + primary = this.object(); + } else { + var token = this.expect(); + primary = token.fn; + if (!primary) { + this.throwError('not a primary expression', token); + } + if (token.json) { + primary.constant = true; + primary.literal = true; + } + } + + var next, context; + while ((next = this.expect('(', '[', '.'))) { + if (next.text === '(') { + primary = this.functionCall(primary, context); + context = null; + } else if (next.text === '[') { + context = primary; + primary = this.objectIndex(primary); + } else if (next.text === '.') { + context = primary; + primary = this.fieldAccess(primary); + } else { + this.throwError('IMPOSSIBLE'); + } + } + return primary; + }, + + throwError: function(msg, token) { throw $parseMinErr('syntax', - "Syntax Error: Token '{0}' {1} at column {2} of the expression [{3}] starting at [{4}].", - token.text, msg, (token.index + 1), text, text.substring(token.index)); - } + 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', + token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); + }, - function peekToken() { - if (tokens.length === 0) - throw $parseMinErr('ueoe', "Unexpected end of expression: {0}", text); - return tokens[0]; - } + peekToken: function() { + if (this.tokens.length === 0) + throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); + return this.tokens[0]; + }, - function peek(e1, e2, e3, e4) { - if (tokens.length > 0) { - var token = tokens[0]; + peek: function(e1, e2, e3, e4) { + if (this.tokens.length > 0) { + var token = this.tokens[0]; var t = token.text; - if (t==e1 || t==e2 || t==e3 || t==e4 || + if (t === e1 || t === e2 || t === e3 || t === e4 || (!e1 && !e2 && !e3 && !e4)) { return token; } } return false; - } + }, - function expect(e1, e2, e3, e4){ - var token = peek(e1, e2, e3, e4); + expect: function(e1, e2, e3, e4){ + var token = this.peek(e1, e2, e3, e4); if (token) { - if (json && !token.json) { - throwError("is not valid json", token); + if (this.json && !token.json) { + this.throwError('is not valid json', token); } - tokens.shift(); + this.tokens.shift(); return token; } return false; - } + }, - function consume(e1){ - if (!expect(e1)) { - throwError("is unexpected, expecting [" + e1 + "]", peek()); + consume: function(e1){ + if (!this.expect(e1)) { + this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); } - } + }, - function unaryFn(fn, right) { + unaryFn: function(fn, right) { return extend(function(self, locals) { return fn(self, locals, right); }, { constant:right.constant }); - } + }, - function ternaryFn(left, middle, right){ + ternaryFn: function(left, middle, right){ return extend(function(self, locals){ return left(self, locals) ? middle(self, locals) : right(self, locals); }, { constant: left.constant && middle.constant && right.constant }); - } + }, - function binaryFn(left, fn, right) { + binaryFn: function(left, fn, right) { return extend(function(self, locals) { return fn(self, locals, left, right); }, { constant:left.constant && right.constant }); - } + }, - function statements() { + statements: function() { var statements = []; - while(true) { - if (tokens.length > 0 && !peek('}', ')', ';', ']')) - statements.push(filterChain()); - if (!expect(';')) { + while (true) { + if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) + statements.push(this.filterChain()); + if (!this.expect(';')) { // optimize for the common case where there is only one statement. // TODO(size): maybe we should not support multiple statements? - return statements.length == 1 - ? statements[0] - : function(self, locals){ - var value; - for ( var i = 0; i < statements.length; i++) { - var statement = statements[i]; - if (statement) - value = statement(self, locals); - } - return value; - }; + return (statements.length === 1) + ? statements[0] + : function(self, locals) { + var value; + for (var i = 0; i < statements.length; i++) { + var statement = statements[i]; + if (statement) { + value = statement(self, locals); + } + } + return value; + }; } } - } + }, - function _filterChain() { - var left = expression(); + filterChain: function() { + var left = this.expression(); var token; - while(true) { - if ((token = expect('|'))) { - left = binaryFn(left, token.fn, filter()); + while (true) { + if ((token = this.expect('|'))) { + left = this.binaryFn(left, token.fn, this.filter()); } else { return left; } } - } + }, - function filter() { - var token = expect(); - var fn = $filter(token.text); + filter: function() { + var token = this.expect(); + var fn = this.$filter(token.text); var argsFn = []; - while(true) { - if ((token = expect(':'))) { - argsFn.push(expression()); + while (true) { + if ((token = this.expect(':'))) { + argsFn.push(this.expression()); } else { - var fnInvoke = function(self, locals, input){ + var fnInvoke = function(self, locals, input) { var args = [input]; - for ( var i = 0; i < argsFn.length; i++) { + for (var i = 0; i < argsFn.length; i++) { args.push(argsFn[i](self, locals)); } return fn.apply(self, args); @@ -8306,285 +9651,263 @@ function parser(text, json, $filter, csp){ }; } } - } + }, - function expression() { - return assignment(); - } + expression: function() { + return this.assignment(); + }, - function _assignment() { - var left = ternary(); + assignment: function() { + var left = this.ternary(); var right; var token; - if ((token = expect('='))) { + if ((token = this.expect('='))) { if (!left.assign) { - throwError("implies assignment but [" + - text.substring(0, token.index) + "] can not be assigned to", token); + this.throwError('implies assignment but [' + + this.text.substring(0, token.index) + '] can not be assigned to', token); } - right = ternary(); - return function(scope, locals){ + right = this.ternary(); + return function(scope, locals) { return left.assign(scope, right(scope, locals), locals); }; + } + return left; + }, + + ternary: function() { + var left = this.logicalOR(); + var middle; + var token; + if ((token = this.expect('?'))) { + middle = this.ternary(); + if ((token = this.expect(':'))) { + return this.ternaryFn(left, middle, this.ternary()); + } else { + this.throwError('expected :', token); + } } else { return left; } - } + }, - function ternary() { - var left = logicalOR(); - var middle; + logicalOR: function() { + var left = this.logicalAND(); var token; - if((token = expect('?'))){ - middle = ternary(); - if((token = expect(':'))){ - return ternaryFn(left, middle, ternary()); - } - else { - throwError('expected :', token); - } - } - else { - return left; - } - } - - function logicalOR() { - var left = logicalAND(); - var token; - while(true) { - if ((token = expect('||'))) { - left = binaryFn(left, token.fn, logicalAND()); + while (true) { + if ((token = this.expect('||'))) { + left = this.binaryFn(left, token.fn, this.logicalAND()); } else { return left; } } - } + }, - function logicalAND() { - var left = equality(); + logicalAND: function() { + var left = this.equality(); var token; - if ((token = expect('&&'))) { - left = binaryFn(left, token.fn, logicalAND()); + if ((token = this.expect('&&'))) { + left = this.binaryFn(left, token.fn, this.logicalAND()); } return left; - } + }, - function equality() { - var left = relational(); + equality: function() { + var left = this.relational(); var token; - if ((token = expect('==','!=','===','!=='))) { - left = binaryFn(left, token.fn, equality()); + if ((token = this.expect('==','!=','===','!=='))) { + left = this.binaryFn(left, token.fn, this.equality()); } return left; - } + }, - function relational() { - var left = additive(); + relational: function() { + var left = this.additive(); var token; - if ((token = expect('<', '>', '<=', '>='))) { - left = binaryFn(left, token.fn, relational()); + if ((token = this.expect('<', '>', '<=', '>='))) { + left = this.binaryFn(left, token.fn, this.relational()); } return left; - } + }, - function additive() { - var left = multiplicative(); + additive: function() { + var left = this.multiplicative(); var token; - while ((token = expect('+','-'))) { - left = binaryFn(left, token.fn, multiplicative()); + while ((token = this.expect('+','-'))) { + left = this.binaryFn(left, token.fn, this.multiplicative()); } return left; - } + }, - function multiplicative() { - var left = unary(); + multiplicative: function() { + var left = this.unary(); var token; - while ((token = expect('*','/','%'))) { - left = binaryFn(left, token.fn, unary()); + while ((token = this.expect('*','/','%'))) { + left = this.binaryFn(left, token.fn, this.unary()); } return left; - } + }, - function unary() { + unary: function() { var token; - if (expect('+')) { - return primary(); - } else if ((token = expect('-'))) { - return binaryFn(ZERO, token.fn, unary()); - } else if ((token = expect('!'))) { - return unaryFn(token.fn, unary()); + if (this.expect('+')) { + return this.primary(); + } else if ((token = this.expect('-'))) { + return this.binaryFn(Parser.ZERO, token.fn, this.unary()); + } else if ((token = this.expect('!'))) { + return this.unaryFn(token.fn, this.unary()); } else { - return primary(); + return this.primary(); } - } + }, + fieldAccess: function(object) { + var parser = this; + var field = this.expect().text; + var getter = getterFn(field, this.options, this.text); - function primary() { - var primary; - if (expect('(')) { - primary = filterChain(); - consume(')'); - } else if (expect('[')) { - primary = arrayDeclaration(); - } else if (expect('{')) { - primary = object(); - } else { - var token = expect(); - primary = token.fn; - if (!primary) { - throwError("not a primary expression", token); + return extend(function(scope, locals, self) { + return getter(self || object(scope, locals), locals); + }, { + assign: function(scope, value, locals) { + return setter(object(scope, locals), field, value, parser.text, parser.options); } - if (token.json) { - primary.constant = primary.literal = true; + }); + }, + + objectIndex: function(obj) { + var parser = this; + + var indexFn = this.expression(); + this.consume(']'); + + return extend(function(self, locals) { + var o = obj(self, locals), + // In the getter, we will not block looking up "constructor" by name in order to support user defined + // constructors. However, if value looked up is the Function constructor, we will still block it in the + // ensureSafeObject call right after we look up o[i] (a few lines below.) + i = ensureSafeMemberName(indexFn(self, locals), parser.text, true /* allowConstructor */), + v, p; + + if (!o) return undefined; + v = ensureSafeObject(o[i], parser.text); + if (v && v.then && parser.options.unwrapPromises) { + p = v; + if (!('$$v' in v)) { + p.$$v = undefined; + p.then(function(val) { p.$$v = val; }); + } + v = v.$$v; } - } - - var next, context; - while ((next = expect('(', '[', '.'))) { - if (next.text === '(') { - primary = functionCall(primary, context); - context = null; - } else if (next.text === '[') { - context = primary; - primary = objectIndex(primary); - } else if (next.text === '.') { - context = primary; - primary = fieldAccess(primary); - } else { - throwError("IMPOSSIBLE"); + return v; + }, { + assign: function(self, value, locals) { + var key = ensureSafeMemberName(indexFn(self, locals), parser.text); + // prevent overwriting of Function.constructor which would break ensureSafeObject check + var safe = ensureSafeObject(obj(self, locals), parser.text); + return safe[key] = value; } - } - return primary; - } + }); + }, - function _fieldAccess(object) { - var field = expect().text; - var getter = getterFn(field, csp, text); - return extend( - function(scope, locals, self) { - return getter(self || object(scope, locals), locals); - }, - { - assign:function(scope, value, locals) { - return setter(object(scope, locals), field, value, text); - } - } - ); - } - - function _objectIndex(obj) { - var indexFn = expression(); - consume(']'); - return extend( - function(self, locals){ - var o = obj(self, locals), - i = indexFn(self, locals), - v, p; - - if (!o) return undefined; - v = ensureSafeObject(o[i], text); - if (v && v.then) { - p = v; - if (!('$$v' in v)) { - p.$$v = undefined; - p.then(function(val) { p.$$v = val; }); - } - v = v.$$v; - } - return v; - }, { - assign:function(self, value, locals){ - var key = indexFn(self, locals); - // prevent overwriting of Function.constructor which would break ensureSafeObject check - return ensureSafeObject(obj(self, locals), text)[key] = value; - } - }); - } - - function _functionCall(fn, contextGetter) { + functionCall: function(fn, contextGetter) { var argsFn = []; - if (peekToken().text != ')') { + if (this.peekToken().text !== ')') { do { - argsFn.push(expression()); - } while (expect(',')); + argsFn.push(this.expression()); + } while (this.expect(',')); } - consume(')'); - return function(scope, locals){ - var args = [], - context = contextGetter ? contextGetter(scope, locals) : scope; + this.consume(')'); - for ( var i = 0; i < argsFn.length; i++) { + var parser = this; + + return function(scope, locals) { + var args = []; + var context = contextGetter ? contextGetter(scope, locals) : scope; + + for (var i = 0; i < argsFn.length; i++) { args.push(argsFn[i](scope, locals)); } var fnPtr = fn(scope, locals, context) || noop; - // IE stupidity! - return fnPtr.apply - ? fnPtr.apply(context, args) - : fnPtr(args[0], args[1], args[2], args[3], args[4]); + + ensureSafeObject(context, parser.text); + ensureSafeObject(fnPtr, parser.text); + + // IE stupidity! (IE doesn't have apply for some native functions) + var v = fnPtr.apply + ? fnPtr.apply(context, args) + : fnPtr(args[0], args[1], args[2], args[3], args[4]); + + return ensureSafeObject(v, parser.text); }; - } + }, // This is used with json array declaration - function arrayDeclaration () { + arrayDeclaration: function () { var elementFns = []; var allConstant = true; - if (peekToken().text != ']') { + if (this.peekToken().text !== ']') { do { - var elementFn = expression(); + var elementFn = this.expression(); elementFns.push(elementFn); if (!elementFn.constant) { allConstant = false; } - } while (expect(',')); + } while (this.expect(',')); } - consume(']'); - return extend(function(self, locals){ + this.consume(']'); + + return extend(function(self, locals) { var array = []; - for ( var i = 0; i < elementFns.length; i++) { + for (var i = 0; i < elementFns.length; i++) { array.push(elementFns[i](self, locals)); } return array; }, { - literal:true, - constant:allConstant + literal: true, + constant: allConstant }); - } + }, - function object () { + object: function () { var keyValues = []; var allConstant = true; - if (peekToken().text != '}') { + if (this.peekToken().text !== '}') { do { - var token = expect(), + var token = this.expect(), key = token.string || token.text; - consume(":"); - var value = expression(); - keyValues.push({key:key, value:value}); + this.consume(':'); + var value = this.expression(); + keyValues.push({key: key, value: value}); if (!value.constant) { allConstant = false; } - } while (expect(',')); + } while (this.expect(',')); } - consume('}'); - return extend(function(self, locals){ + this.consume('}'); + + return extend(function(self, locals) { var object = {}; - for ( var i = 0; i < keyValues.length; i++) { + for (var i = 0; i < keyValues.length; i++) { var keyValue = keyValues[i]; object[keyValue.key] = keyValue.value(self, locals); } return object; }, { - literal:true, - constant:allConstant + literal: true, + constant: allConstant }); } -} +}; + ////////////////////////////////////////////////// // Parser helper functions ////////////////////////////////////////////////// -function setter(obj, path, setValue, fullExp) { +function setter(obj, path, setValue, fullExp, options) { + //needed? + options = options || {}; + var element = path.split('.'), key; for (var i = 0; element.length > 1; i++) { key = ensureSafeMemberName(element.shift(), fullExp); @@ -8594,7 +9917,8 @@ function setter(obj, path, setValue, fullExp) { obj[key] = propertyObj; } obj = propertyObj; - if (obj.then) { + if (obj.then && options.unwrapPromises) { + promiseWarning(fullExp); if (!("$$v" in obj)) { (function(promise) { promise.then(function(val) { promise.$$v = val; }); } @@ -8618,76 +9942,106 @@ var getterFnCache = {}; * - http://jsperf.com/angularjs-parse-getter/4 * - http://jsperf.com/path-evaluation-simplified/7 */ -function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) { +function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { ensureSafeMemberName(key0, fullExp); ensureSafeMemberName(key1, fullExp); ensureSafeMemberName(key2, fullExp); ensureSafeMemberName(key3, fullExp); ensureSafeMemberName(key4, fullExp); - return function(scope, locals) { - var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, - promise; - if (pathVal === null || pathVal === undefined) return pathVal; + return !options.unwrapPromises + ? function cspSafeGetter(scope, locals) { + var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; - pathVal = pathVal[key0]; - if (pathVal && pathVal.then) { - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = val; }); - } - pathVal = pathVal.$$v; - } - if (!key1 || pathVal === null || pathVal === undefined) return pathVal; + if (pathVal === null || pathVal === undefined) return pathVal; + pathVal = pathVal[key0]; - pathVal = pathVal[key1]; - if (pathVal && pathVal.then) { - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = val; }); - } - pathVal = pathVal.$$v; - } - if (!key2 || pathVal === null || pathVal === undefined) return pathVal; + if (!key1 || pathVal === null || pathVal === undefined) return pathVal; + pathVal = pathVal[key1]; - pathVal = pathVal[key2]; - if (pathVal && pathVal.then) { - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = val; }); - } - pathVal = pathVal.$$v; - } - if (!key3 || pathVal === null || pathVal === undefined) return pathVal; + if (!key2 || pathVal === null || pathVal === undefined) return pathVal; + pathVal = pathVal[key2]; - pathVal = pathVal[key3]; - if (pathVal && pathVal.then) { - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = val; }); - } - pathVal = pathVal.$$v; - } - if (!key4 || pathVal === null || pathVal === undefined) return pathVal; + if (!key3 || pathVal === null || pathVal === undefined) return pathVal; + pathVal = pathVal[key3]; - pathVal = pathVal[key4]; - if (pathVal && pathVal.then) { - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = val; }); - } - pathVal = pathVal.$$v; - } - return pathVal; - }; + if (!key4 || pathVal === null || pathVal === undefined) return pathVal; + pathVal = pathVal[key4]; + + return pathVal; + } + : function cspSafePromiseEnabledGetter(scope, locals) { + var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, + promise; + + if (pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key0]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key1 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key1]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key2 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key2]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key3 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key3]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key4 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key4]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + return pathVal; + }; } -function getterFn(path, csp, fullExp) { +function getterFn(path, options, fullExp) { + // Check whether the cache has this getter already. + // We can use hasOwnProperty directly on the cache because we ensure, + // see below, that the cache never stores a path called 'hasOwnProperty' if (getterFnCache.hasOwnProperty(path)) { return getterFnCache[path]; } @@ -8696,21 +10050,23 @@ function getterFn(path, csp, fullExp) { pathKeysLength = pathKeys.length, fn; - if (csp) { - fn = (pathKeysLength < 6) - ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp) - : function(scope, locals) { - var i = 0, val; - do { - val = cspSafeGetterFn( - pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], fullExp - )(scope, locals); + if (options.csp) { + if (pathKeysLength < 6) { + fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, + options); + } else { + fn = function(scope, locals) { + var i = 0, val; + do { + val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], + pathKeys[i++], fullExp, options)(scope, locals); - locals = undefined; // clear after first iteration - scope = val; - } while (i < pathKeysLength); - return val; - } + locals = undefined; // clear after first iteration + scope = val; + } while (i < pathKeysLength); + return val; + }; + } } else { var code = 'var l, fn, p;\n'; forEach(pathKeys, function(key, index) { @@ -8722,21 +10078,35 @@ function getterFn(path, csp, fullExp) { ? 's' // but if we are first then we check locals first, and if so read it first : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + - 'if (s && s.then) {\n' + - ' if (!("$$v" in s)) {\n' + - ' p=s;\n' + - ' p.$$v = undefined;\n' + - ' p.then(function(v) {p.$$v=v;});\n' + - '}\n' + - ' s=s.$$v\n' + - '}\n'; + (options.unwrapPromises + ? 'if (s && s.then) {\n' + + ' pw("' + fullExp.replace(/\"/g, '\\"') + '");\n' + + ' if (!("$$v" in s)) {\n' + + ' p=s;\n' + + ' p.$$v = undefined;\n' + + ' p.then(function(v) {p.$$v=v;});\n' + + '}\n' + + ' s=s.$$v\n' + + '}\n' + : ''); }); code += 'return s;'; - fn = Function('s', 'k', code); // s=scope, k=locals - fn.toString = function() { return code; }; + + /* jshint -W054 */ + var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning + /* jshint +W054 */ + evaledFnGetter.toString = function() { return code; }; + fn = function(scope, locals) { + return evaledFnGetter(scope, locals, promiseWarning); + }; } - return getterFnCache[path] = fn; + // Only cache the value if it's not going to mess up the cache object + // This is more performant that using Object.prototype.hasOwnProperty.call + if (path !== 'hasOwnProperty') { + getterFnCache[path] = fn; + } + return fn; } /////////////////////////////////// @@ -8766,31 +10136,159 @@ function getterFn(path, csp, fullExp) { * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings + * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. * * The returned function also has the following properties: - * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript + * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript * literal. - * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript + * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript * constant literals. - * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be + * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be * set to a function to change its value on the given context. * */ + + +/** + * @ngdoc object + * @name ng.$parseProvider + * @function + * + * @description + * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} + * service. + */ function $ParseProvider() { var cache = {}; - this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { + + var $parseOptions = { + csp: false, + unwrapPromises: false, + logPromiseWarnings: true + }; + + + /** + * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. + * + * @ngdoc method + * @name ng.$parseProvider#unwrapPromises + * @methodOf ng.$parseProvider + * @description + * + * **This feature is deprecated, see deprecation notes below for more info** + * + * If set to true (default is false), $parse will unwrap promises automatically when a promise is + * found at any part of the expression. In other words, if set to true, the expression will always + * result in a non-promise value. + * + * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, + * the fulfillment value is used in place of the promise while evaluating the expression. + * + * **Deprecation notice** + * + * This is a feature that didn't prove to be wildly useful or popular, primarily because of the + * dichotomy between data access in templates (accessed as raw values) and controller code + * (accessed as promises). + * + * In most code we ended up resolving promises manually in controllers anyway and thus unifying + * the model access there. + * + * Other downsides of automatic promise unwrapping: + * + * - when building components it's often desirable to receive the raw promises + * - adds complexity and slows down expression evaluation + * - makes expression code pre-generation unattractive due to the amount of code that needs to be + * generated + * - makes IDE auto-completion and tool support hard + * + * **Warning Logs** + * + * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a + * promise (to reduce the noise, each expression is logged only once). To disable this logging use + * `$parseProvider.logPromiseWarnings(false)` api. + * + * + * @param {boolean=} value New value. + * @returns {boolean|self} Returns the current setting when used as getter and self if used as + * setter. + */ + this.unwrapPromises = function(value) { + if (isDefined(value)) { + $parseOptions.unwrapPromises = !!value; + return this; + } else { + return $parseOptions.unwrapPromises; + } + }; + + + /** + * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. + * + * @ngdoc method + * @name ng.$parseProvider#logPromiseWarnings + * @methodOf ng.$parseProvider + * @description + * + * Controls whether Angular should log a warning on any encounter of a promise in an expression. + * + * The default is set to `true`. + * + * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. + * + * @param {boolean=} value New value. + * @returns {boolean|self} Returns the current setting when used as getter and self if used as + * setter. + */ + this.logPromiseWarnings = function(value) { + if (isDefined(value)) { + $parseOptions.logPromiseWarnings = value; + return this; + } else { + return $parseOptions.logPromiseWarnings; + } + }; + + + this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { + $parseOptions.csp = $sniffer.csp; + + promiseWarning = function promiseWarningFn(fullExp) { + if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; + promiseWarningCache[fullExp] = true; + $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + + 'Automatic unwrapping of promises in Angular expressions is deprecated.'); + }; + return function(exp) { - switch(typeof exp) { + var parsedExpression; + + switch (typeof exp) { case 'string': - return cache.hasOwnProperty(exp) - ? cache[exp] - : cache[exp] = parser(exp, false, $filter, $sniffer.csp); + + if (cache.hasOwnProperty(exp)) { + return cache[exp]; + } + + var lexer = new Lexer($parseOptions); + var parser = new Parser(lexer, $filter, $parseOptions); + parsedExpression = parser.parse(exp, false); + + if (exp !== 'hasOwnProperty') { + // Only cache the value if it's not going to mess up the cache object + // This is more performant that using Object.prototype.hasOwnProperty.call + cache[exp] = parsedExpression; + } + + return parsedExpression; + case 'function': return exp; + default: return noop; } @@ -8824,6 +10322,8 @@ function $ParseProvider() { * // since this fn executes async in a future turn of the event loop, we need to wrap * // our code into an $apply call so that the model changes are properly observed. * scope.$apply(function() { + * deferred.notify('About to greet ' + name + '.'); + * * if (okToGreet(name)) { * deferred.resolve('Hello, ' + name + '!'); * } else { @@ -8840,12 +10340,14 @@ function $ParseProvider() { * alert('Success: ' + greeting); * }, function(reason) { * alert('Failed: ' + reason); + * }, function(update) { + * alert('Got notification: ' + update); * }); *
* * At first it might not be obvious why this extra complexity is worth the trouble. The payoff - * comes in the way of - * [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md). + * comes in the way of guarantees that promise and deferred APIs make, see + * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. * * Additionally the promise api allows for composition that is very hard to do with the * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. @@ -8858,18 +10360,21 @@ function $ParseProvider() { * A new instance of deferred is constructed by calling `$q.defer()`. * * The purpose of the deferred object is to expose the associated Promise instance as well as APIs - * that can be used for signaling the successful or unsuccessful completion of the task. + * that can be used for signaling the successful or unsuccessful completion, as well as the status + * of the task. * * **Methods** * - * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection + * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection * constructed via `$q.reject`, the promise will be rejected instead. - * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to + * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to * resolving it with a rejection constructed via `$q.reject`. + * - `notify(value)` - provides updates on the status of the promises execution. This may be called + * multiple times before the promise is either resolved or rejected. * * **Properties** * - * - promise – `{Promise}` – promise object associated with this deferred. + * - promise – `{Promise}` – promise object associated with this deferred. * * * # The Promise API @@ -8882,16 +10387,20 @@ function $ParseProvider() { * * **Methods** * - * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved - * or rejected, `then` calls one of the success or error callbacks asynchronously as soon as the result - * is available. The callbacks are called with a single argument: the result or rejection reason. + * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or + * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously + * as soon as the result is available. The callbacks are called with a single argument: the result + * or rejection reason. Additionally, the notify callback may be called zero or more times to + * provide a progress indication, before the promise is resolved or rejected. * * This method *returns a new promise* which is resolved or rejected via the return value of the - * `successCallback` or `errorCallback`. + * `successCallback`, `errorCallback`. It also notifies via the return value of the + * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback + * method. * - * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` + * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` * - * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, + * - `finally(callback)` – 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 @@ -8903,8 +10412,8 @@ function $ParseProvider() { * * # Chaining promises * - * Because calling the `then` method of a promise returns a new derived promise, it is easily possible - * to create a chain of promises: + * Because calling the `then` method of a promise returns a new derived promise, it is easily + * possible to create a chain of promises: * *
  *   promiseB = promiseA.then(function(result) {
@@ -8928,8 +10437,6 @@ function $ParseProvider() {
  * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
  *   mechanism in angular, which means faster propagation of resolution or rejection into your
  *   models and avoiding unnecessary browser repaints, which would result in flickering UI.
- * - $q promises are recognized by the templating engine in angular, which means that in templates
- *   you can treat promises attached to a scope as if they were the resulting values.
  * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
  *   all the important functionality needed for common async tasks.
  *
@@ -9039,7 +10546,7 @@ function qFactory(nextTick, exceptionHandler) {
 
           var wrappedCallback = function(value) {
             try {
-              result.resolve((callback || defaultCallback)(value));
+              result.resolve((isFunction(callback) ? callback : defaultCallback)(value));
             } catch(e) {
               result.reject(e);
               exceptionHandler(e);
@@ -9048,7 +10555,7 @@ function qFactory(nextTick, exceptionHandler) {
 
           var wrappedErrback = function(reason) {
             try {
-              result.resolve((errback || defaultErrback)(reason));
+              result.resolve((isFunction(errback) ? errback : defaultErrback)(reason));
             } catch(e) {
               result.reject(e);
               exceptionHandler(e);
@@ -9057,7 +10564,7 @@ function qFactory(nextTick, exceptionHandler) {
 
           var wrappedProgressback = function(progress) {
             try {
-              result.notify((progressback || defaultCallback)(progress));
+              result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress));
             } catch(e) {
               exceptionHandler(e);
             }
@@ -9095,7 +10602,7 @@ function qFactory(nextTick, exceptionHandler) {
             } catch(e) {
               return makePromise(e, false);
             }
-            if (callbackOutput && callbackOutput.then) {
+            if (callbackOutput && isFunction(callbackOutput.then)) {
               return callbackOutput.then(function() {
                 return makePromise(value, isResolved);
               }, function(error) {
@@ -9120,7 +10627,7 @@ function qFactory(nextTick, exceptionHandler) {
 
 
   var ref = function(value) {
-    if (value && value.then) return value;
+    if (value && isFunction(value.then)) return value;
     return {
       then: function(callback) {
         var result = defer();
@@ -9173,7 +10680,12 @@ function qFactory(nextTick, exceptionHandler) {
       then: function(callback, errback) {
         var result = defer();
         nextTick(function() {
-          result.resolve((errback || defaultErrback)(reason));
+          try {
+            result.resolve((isFunction(errback) ? errback : defaultErrback)(reason));
+          } catch(e) {
+            result.reject(e);
+            exceptionHandler(e);
+          }
         });
         return result.promise;
       }
@@ -9199,7 +10711,7 @@ function qFactory(nextTick, exceptionHandler) {
 
     var wrappedCallback = function(value) {
       try {
-        return (callback || defaultCallback)(value);
+        return (isFunction(callback) ? callback : defaultCallback)(value);
       } catch (e) {
         exceptionHandler(e);
         return reject(e);
@@ -9208,7 +10720,7 @@ function qFactory(nextTick, exceptionHandler) {
 
     var wrappedErrback = function(reason) {
       try {
-        return (errback || defaultErrback)(reason);
+        return (isFunction(errback) ? errback : defaultErrback)(reason);
       } catch (e) {
         exceptionHandler(e);
         return reject(e);
@@ -9217,7 +10729,7 @@ function qFactory(nextTick, exceptionHandler) {
 
     var wrappedProgressback = function(progress) {
       try {
-        return (progressback || defaultCallback)(progress);
+        return (isFunction(progressback) ? progressback : defaultCallback)(progress);
       } catch (e) {
         exceptionHandler(e);
       }
@@ -9262,9 +10774,9 @@ function qFactory(nextTick, exceptionHandler) {
    *
    * @param {Array.|Object.} promises An array or hash of promises.
    * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
-   *   each value corresponding to the promise at the same index/key in the `promises` array/hash. If any of
-   *   the promises is resolved with a rejection, this resulting promise will be resolved with the
-   *   same rejection.
+   *   each value corresponding to the promise at the same index/key in the `promises` array/hash.
+   *   If any of the promises is resolved with a rejection, this resulting promise will be rejected
+   *   with the same rejection value.
    */
   function all(promises) {
     var deferred = defer(),
@@ -9338,11 +10850,19 @@ function qFactory(nextTick, exceptionHandler) {
  * @methodOf ng.$rootScopeProvider
  * @description
  *
- * Sets the number of digest iterations the scope should attempt to execute before giving up and
+ * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
  * assuming that the model is unstable.
  *
  * The current default is 10 iterations.
  *
+ * In complex applications it's possible that the dependencies between `$watch`s will result in
+ * several digest iterations. However if an application needs more than the default 10 digest
+ * iterations for its model to stabilize then you should investigate what is causing the model to
+ * continuously change during the digest.
+ *
+ * Increasing the TTL could have performance implications, so you should not change it without
+ * proper justification.
+ *
  * @param {number} limit The number of digest iterations.
  */
 
@@ -9353,8 +10873,10 @@ function qFactory(nextTick, exceptionHandler) {
  * @description
  *
  * Every application has a single root {@link ng.$rootScope.Scope scope}.
- * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide
- * event processing life-cycle. See {@link guide/scope developer guide on scopes}.
+ * All other scopes are descendant scopes of the root scope. Scopes provide separation
+ * between the model and the view, via a mechanism for watching the model for changes.
+ * They also provide an event emission/broadcast and subscription facility. See the
+ * {@link guide/scope developer guide on scopes}.
  */
 function $RootScopeProvider(){
   var TTL = 10;
@@ -9367,8 +10889,8 @@ function $RootScopeProvider(){
     return TTL;
   };
 
-  this.$get = ['$injector', '$exceptionHandler', '$parse',
-      function( $injector,   $exceptionHandler,   $parse) {
+  this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
+      function( $injector,   $exceptionHandler,   $parse,   $browser) {
 
     /**
      * @ngdoc function
@@ -9377,7 +10899,7 @@ function $RootScopeProvider(){
      * @description
      * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
      * {@link AUTO.$injector $injector}. Child scopes are created using the
-     * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
+     * {@link ng.$rootScope.Scope#methods_$new $new()} method. (Most scopes are created automatically when
      * compiled HTML template is executed.)
      *
      * Here is a simple scope snippet to show how you can interact with the scope.
@@ -9401,11 +10923,12 @@ function $RootScopeProvider(){
      * 
* * - * @param {Object.=} providers Map of service factory which need to be provided - * for the current scope. Defaults to {@link ng}. + * @param {Object.=} providers Map of service factory which need to be + * provided for the current scope. Defaults to {@link ng}. * @param {Object.=} instanceCache Provides pre-instantiated services which should - * append/override services provided by `providers`. This is handy when unit-testing and having - * the need to override a default service. + * append/override services provided by `providers`. This is handy + * when unit-testing and having the need to override a default + * service. * @returns {Object} Newly created scope. * */ @@ -9417,6 +10940,7 @@ function $RootScopeProvider(){ this['this'] = this.$root = this; this.$$destroyed = false; this.$$asyncQueue = []; + this.$$postDigestQueue = []; this.$$listeners = {}; this.$$isolateBindings = {}; } @@ -9431,6 +10955,7 @@ function $RootScopeProvider(){ Scope.prototype = { + constructor: Scope, /** * @ngdoc function * @name ng.$rootScope.Scope#$new @@ -9441,16 +10966,16 @@ function $RootScopeProvider(){ * Creates a new child {@link ng.$rootScope.Scope scope}. * * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and - * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope - * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. + * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the + * scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. * - * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for - * the scope and its child scopes to be permanently detached from the parent and thus stop - * participating in model change detection and listener notification by invoking. + * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is + * desired for the scope and its child scopes to be permanently detached from the parent and + * thus stop participating in model change detection and listener notification by invoking. * - * @param {boolean} isolate if true then the scope does not prototypically inherit from the + * @param {boolean} isolate If true, then the scope does not prototypically inherit from the * parent scope. The scope is isolated, as it can not see parent scope properties. - * When creating widgets it is useful for the widget to not accidentally read parent + * When creating widgets, it is useful for the widget to not accidentally read parent * state. * * @returns {Object} The newly created child scope. @@ -9463,11 +10988,12 @@ function $RootScopeProvider(){ if (isolate) { child = new Scope(); child.$root = this.$root; - // ensure that there is just one async queue per $rootScope and it's children + // ensure that there is just one async queue per $rootScope and its children child.$$asyncQueue = this.$$asyncQueue; + child.$$postDigestQueue = this.$$postDigestQueue; } else { Child = function() {}; // should be anonymous; This is so that when the minifier munges - // the name it does not become random set of chars. These will then show up as class + // the name it does not become random set of chars. This will then show up as class // name in the debugger. Child.prototype = this; child = new Child(); @@ -9496,25 +11022,26 @@ function $RootScopeProvider(){ * @description * Registers a `listener` callback to be executed whenever the `watchExpression` changes. * - * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and - * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()} - * reruns when it detects changes the `watchExpression` can execute multiple times per + * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest + * $digest()} and should return the value that will be watched. (Since + * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the + * `watchExpression` can execute multiple times per * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) * - The `listener` is called only when the value from the current `watchExpression` and the * previous call to `watchExpression` are not equal (with the exception of the initial run, * see below). The inequality is determined according to - * {@link angular.equals} function. To save the value of the object for later comparison, the - * {@link angular.copy} function is used. It also means that watching complex options will - * have adverse memory and performance implications. - * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This - * is achieved by rerunning the watchers until no changes are detected. The rerun iteration - * limit is 10 to prevent an infinite loop deadlock. + * {@link angular.equals} function. To save the value of the object for later comparison, + * the {@link angular.copy} function is used. It also means that watching complex options + * will have adverse memory and performance implications. + * - The watch `listener` may change the model, which may trigger other `listener`s to fire. + * This is achieved by rerunning the watchers until no changes are detected. The rerun + * iteration limit is 10 to prevent an infinite loop deadlock. * * * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` - * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is - * detected, be prepared for multiple calls to your listener.) + * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a + * change is detected, be prepared for multiple calls to your listener.) * * After a watcher is registered with the scope, the `listener` fn is called asynchronously * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the @@ -9523,6 +11050,8 @@ function $RootScopeProvider(){ * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the * listener was called due to initialization. * + * The example below contains an illustration of using a function as your $watch listener + * * * # Example *
@@ -9532,7 +11061,9 @@ function $RootScopeProvider(){
            scope.counter = 0;
 
            expect(scope.counter).toEqual(0);
-           scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; });
+           scope.$watch('name', function(newValue, oldValue) {
+             scope.counter = scope.counter + 1;
+           });
            expect(scope.counter).toEqual(0);
 
            scope.$digest();
@@ -9542,13 +11073,43 @@ function $RootScopeProvider(){
            scope.name = 'adam';
            scope.$digest();
            expect(scope.counter).toEqual(1);
+
+
+
+           // Using a listener function
+           var food;
+           scope.foodCounter = 0;
+           expect(scope.foodCounter).toEqual(0);
+           scope.$watch(
+             // This is the listener function
+             function() { return food; },
+             // This is the change handler
+             function(newValue, oldValue) {
+               if ( newValue !== oldValue ) {
+                 // Only increment the counter if the value changed
+                 scope.foodCounter = scope.foodCounter + 1;
+               }
+             }
+           );
+           // No digest has been run so the counter will be zero
+           expect(scope.foodCounter).toEqual(0);
+
+           // Run the digest but since food has not changed cout will still be zero
+           scope.$digest();
+           expect(scope.foodCounter).toEqual(0);
+
+           // Update food and run digest.  Now the counter will increment
+           food = 'cheeseburger';
+           scope.$digest();
+           expect(scope.foodCounter).toEqual(1);
+
        * 
* * * * @param {(function()|string)} watchExpression Expression that is evaluated on each - * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a - * call to the `listener`. + * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers + * a call to the `listener`. * * - `string`: Evaluated as {@link guide/expression expression} * - `function(scope)`: called with current `scope` as a parameter. @@ -9556,7 +11117,8 @@ function $RootScopeProvider(){ * the `watchExpression` changes. * * - `string`: Evaluated as {@link guide/expression expression} - * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters. + * - `function(newValue, oldValue, scope)`: called with current and previous values as + * parameters. * * @param {boolean=} objectEquality Compare object for equality rather than for reference. * @returns {function()} Returns a deregistration function for this listener. @@ -9608,13 +11170,13 @@ function $RootScopeProvider(){ * * @description * Shallow watches the properties of an object and fires whenever any of the properties change - * (for arrays this implies watching the array items, for object maps this implies watching the properties). - * If a change is detected the `listener` callback is fired. + * (for arrays, this implies watching the array items; for object maps, this implies watching + * the properties). If a change is detected, the `listener` callback is fired. * - * - The `obj` collection is observed via standard $watch operation and is examined on every call to $digest() to - * see if any items have been added, removed, or moved. - * - The `listener` is called whenever anything within the `obj` has changed. Examples include adding new items - * into the object or array, removing and moving items around. + * - The `obj` collection is observed via standard $watch operation and is examined on every + * call to $digest() to see if any items have been added, removed, or moved. + * - The `listener` is called whenever anything within the `obj` has changed. Examples include + * adding, removing, and moving items belonging to an object or array. * * * # Example @@ -9640,19 +11202,19 @@ function $RootScopeProvider(){ *
* * - * @param {string|Function(scope)} obj Evaluated as {@link guide/expression expression}. The expression value - * should evaluate to an object or an array which is observed on each - * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the collection will trigger - * a call to the `listener`. + * @param {string|Function(scope)} obj Evaluated as {@link guide/expression expression}. The + * expression value should evaluate to an object or an array which is observed on each + * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the + * collection will trigger a call to the `listener`. * - * @param {function(newCollection, oldCollection, scope)} listener a callback function that is fired with both - * the `newCollection` and `oldCollection` as parameters. - * The `newCollection` object is the newly modified data obtained from the `obj` expression and the - * `oldCollection` object is a copy of the former collection data. + * @param {function(newCollection, oldCollection, scope)} listener a callback function that is + * fired with both the `newCollection` and `oldCollection` as parameters. + * The `newCollection` object is the newly modified data obtained from the `obj` expression + * and the `oldCollection` object is a copy of the former collection data. * The `scope` refers to the current scope. * - * @returns {function()} Returns a de-registration function for this listener. When the de-registration function is executed - * then the internal watch operation is terminated. + * @returns {function()} Returns a de-registration function for this listener. When the + * de-registration function is executed, the internal watch operation is terminated. */ $watchCollection: function(obj, listener) { var self = this; @@ -9747,24 +11309,24 @@ function $RootScopeProvider(){ * @function * * @description - * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children. - * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the - * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are - * firing. This means that it is possible to get into an infinite loop. This function will throw - * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10. + * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and + * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change + * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} + * until no more listeners are firing. This means that it is possible to get into an infinite + * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of + * iterations exceeds 10. * - * Usually you don't call `$digest()` directly in + * Usually, you don't call `$digest()` directly in * {@link ng.directive:ngController controllers} or in - * {@link ng.$compileProvider#directive directives}. - * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a - * {@link ng.$compileProvider#directive directives}) will force a `$digest()`. + * {@link ng.$compileProvider#methods_directive directives}. + * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within + * a {@link ng.$compileProvider#methods_directive directives}), which will force a `$digest()`. * * If you want to be notified whenever `$digest()` is called, - * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()} - * with no `listener`. + * you can register a `watchExpression` function with + * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. * - * You may have a need to call `$digest()` from within unit-tests, to simulate the scope - * life-cycle. + * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. * * # Example *
@@ -9792,11 +11354,12 @@ function $RootScopeProvider(){
         var watch, value, last,
             watchers,
             asyncQueue = this.$$asyncQueue,
+            postDigestQueue = this.$$postDigestQueue,
             length,
             dirty, ttl = TTL,
             next, current, target = this,
             watchLog = [],
-            logIdx, logMsg;
+            logIdx, logMsg, asyncTask;
 
         beginPhase('$digest');
 
@@ -9806,7 +11369,8 @@ function $RootScopeProvider(){
 
           while(asyncQueue.length) {
             try {
-              current.$eval(asyncQueue.shift());
+              asyncTask = asyncQueue.shift();
+              asyncTask.scope.$eval(asyncTask.expression);
             } catch (e) {
               $exceptionHandler(e);
             }
@@ -9858,12 +11422,21 @@ function $RootScopeProvider(){
           if(dirty && !(ttl--)) {
             clearPhase();
             throw $rootScopeMinErr('infdig',
-                '{0} $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: {1}',
+                '{0} $digest() iterations reached. Aborting!\n' +
+                'Watchers fired in the last 5 iterations: {1}',
                 TTL, toJson(watchLog));
           }
         } while (dirty || asyncQueue.length);
 
         clearPhase();
+
+        while(postDigestQueue.length) {
+          try {
+            postDigestQueue.shift()();
+          } catch (e) {
+            $exceptionHandler(e);
+          }
+        }
       },
 
 
@@ -9896,8 +11469,8 @@ function $RootScopeProvider(){
        * {@link ng.directive:ngRepeat ngRepeat} for managing the
        * unrolling of the loop.
        *
-       * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope.
-       * Application code can register a `$destroy` event handler that will give it chance to
+       * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
+       * Application code can register a `$destroy` event handler that will give it a chance to
        * perform any necessary cleanup.
        *
        * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
@@ -9929,8 +11502,9 @@ function $RootScopeProvider(){
        * @function
        *
        * @description
-       * Executes the `expression` on the current scope returning the result. Any exceptions in the
-       * expression are propagated (uncaught). This is useful when evaluating Angular expressions.
+       * Executes the `expression` on the current scope and returns the result. Any exceptions in
+       * the expression are propagated (uncaught). This is useful when evaluating Angular
+       * expressions.
        *
        * # Example
        * 
@@ -9947,6 +11521,7 @@ 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.
        * @returns {*} The result of evaluating the expression.
        */
       $eval: function(expr, locals) {
@@ -9962,23 +11537,43 @@ function $RootScopeProvider(){
        * @description
        * Executes the expression on the current scope at a later point in time.
        *
-       * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:
+       * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
+       * that:
        *
-       *   - it will execute in the current script execution context (before any DOM rendering).
+       *   - it will execute after the function that scheduled the evaluation (preferably before DOM
+       *     rendering).
        *   - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
        *     `expression` execution.
        *
        * Any exceptions from the execution of the expression are forwarded to the
        * {@link ng.$exceptionHandler $exceptionHandler} service.
        *
+       * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
+       * will be scheduled. However, it is encouraged to always call code that changes the model
+       * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
+       *
        * @param {(string|function())=} expression An angular expression to be executed.
        *
-       *    - `string`: execute using the rules as defined in  {@link guide/expression expression}.
+       *    - `string`: execute using the rules as defined in {@link guide/expression expression}.
        *    - `function(scope)`: execute the function with the current `scope` parameter.
        *
        */
       $evalAsync: function(expr) {
-        this.$$asyncQueue.push(expr);
+        // 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 && !$rootScope.$$asyncQueue.length) {
+          $browser.defer(function() {
+            if ($rootScope.$$asyncQueue.length) {
+              $rootScope.$digest();
+            }
+          });
+        }
+
+        this.$$asyncQueue.push({scope: this, expression: expr});
+      },
+
+      $$postDigest : function(fn) {
+        this.$$postDigestQueue.push(fn);
       },
 
       /**
@@ -9988,10 +11583,10 @@ function $RootScopeProvider(){
        * @function
        *
        * @description
-       * `$apply()` is used to execute an expression in angular from outside of the angular framework.
-       * (For example from browser DOM events, setTimeout, XHR or third party libraries).
-       * Because we are calling into the angular framework we need to perform proper scope life-cycle
-       * of {@link ng.$exceptionHandler exception handling},
+       * `$apply()` is used to execute an expression in angular from outside of the angular
+       * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
+       * Because we are calling into the angular framework we need to perform proper scope life
+       * cycle of {@link ng.$exceptionHandler exception handling},
        * {@link ng.$rootScope.Scope#$digest executing watches}.
        *
        * ## Life cycle
@@ -10016,8 +11611,8 @@ function $RootScopeProvider(){
        *    {@link ng.$rootScope.Scope#$eval $eval()} method.
        * 2. Any exceptions from the execution of the expression are forwarded to the
        *    {@link ng.$exceptionHandler $exceptionHandler} service.
-       * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression
-       *    was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
+       * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
+       *    expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
        *
        *
        * @param {(string|function())=} exp An angular expression to be executed.
@@ -10051,18 +11646,20 @@ function $RootScopeProvider(){
        * @function
        *
        * @description
-       * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
-       * event life cycle.
+       * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
+       * discussion of event life cycle.
        *
        * The event listener function format is: `function(event, args...)`. The `event` object
        * passed into the listener has the following attributes:
        *
-       *   - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
+       *   - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
+       *     `$broadcast`-ed.
        *   - `currentScope` - `{Scope}`: the current scope which is handling the event.
-       *   - `name` - `{string}`: Name of the event.
-       *   - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel further event
-       *     propagation (available only for events that were `$emit`-ed).
-       *   - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true.
+       *   - `name` - `{string}`: name of the event.
+       *   - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
+       *     further event propagation (available only for events that were `$emit`-ed).
+       *   - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
+       *     to true.
        *   - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
        *
        * @param {string} name Event name to listen on.
@@ -10093,16 +11690,17 @@ function $RootScopeProvider(){
        * registered {@link ng.$rootScope.Scope#$on} listeners.
        *
        * The event life cycle starts at the scope on which `$emit` was called. All
-       * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
-       * Afterwards, the event traverses upwards toward the root scope and calls all registered
-       * listeners along the way. The event will stop propagating if one of the listeners cancels it.
+       * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
+       * notified. Afterwards, the event traverses upwards toward the root scope and calls all
+       * registered listeners along the way. The event will stop propagating if one of the listeners
+       * cancels it.
        *
        * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
        * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
        *
        * @param {string} name Event name to emit.
        * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
-       * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
+       * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
        */
       $emit: function(name, args) {
         var empty = [],
@@ -10134,12 +11732,14 @@ function $RootScopeProvider(){
               continue;
             }
             try {
+              //allow all listeners attached to the current scope to run
               namedListeners[i].apply(null, listenerArgs);
-              if (stopPropagation) return event;
             } catch (e) {
               $exceptionHandler(e);
             }
           }
+          //if any listener on the current scope stops propagation, prevent bubbling
+          if (stopPropagation) return event;
           //traverse upwards
           scope = scope.$parent;
         } while (scope);
@@ -10159,9 +11759,9 @@ function $RootScopeProvider(){
        * registered {@link ng.$rootScope.Scope#$on} listeners.
        *
        * The event life cycle starts at the scope on which `$broadcast` was called. All
-       * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
-       * Afterwards, the event propagates to all direct and indirect scopes of the current scope and
-       * calls all registered listeners along the way. The event cannot be canceled.
+       * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
+       * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
+       * scope and calls all registered listeners along the way. The event cannot be canceled.
        *
        * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
        * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
@@ -10263,6 +11863,55 @@ var SCE_CONTEXTS = {
   JS: 'js'
 };
 
+// Helper functions follow.
+
+// Copied from:
+// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962
+// Prereq: s is a string.
+function escapeForRegexp(s) {
+  return s.replace(/([-()\[\]{}+?*.$\^|,:# -1) {
+      throw $sceMinErr('iwcard',
+          'Illegal sequence *** in string matcher.  String: {0}', matcher);
+    }
+    matcher = escapeForRegexp(matcher).
+                  replace('\\*\\*', '.*').
+                  replace('\\*', '[^:/.?&;]*');
+    return new RegExp('^' + matcher + '$');
+  } else if (isRegExp(matcher)) {
+    // The only other type of matcher allowed is a Regexp.
+    // Match entire URL / disallow partial matches.
+    // Flags are reset (i.e. no global, ignoreCase or multiline)
+    return new RegExp('^' + matcher.source + '$');
+  } else {
+    throw $sceMinErr('imatcher',
+        'Matchers may only be "self", string patterns or RegExp objects');
+  }
+}
+
+
+function adjustMatchers(matchers) {
+  var adjustedMatchers = [];
+  if (isDefined(matchers)) {
+    forEach(matchers, function(matcher) {
+      adjustedMatchers.push(adjustMatcher(matcher));
+    });
+  }
+  return adjustedMatchers;
+}
+
 
 /**
  * @ngdoc service
@@ -10286,9 +11935,9 @@ var SCE_CONTEXTS = {
  * can override it completely to change the behavior of `$sce`, the common case would
  * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
  * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
- * templates.  Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
+ * templates.  Refer {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist
  * $sceDelegateProvider.resourceUrlWhitelist} and {@link
- * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
+ * ng.$sceDelegateProvider#methods_resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
  */
 
 /**
@@ -10296,13 +11945,37 @@ var SCE_CONTEXTS = {
  * @name ng.$sceDelegateProvider
  * @description
  *
- * The $sceDelegateProvider provider allows developers to configure the {@link ng.$sceDelegate
+ * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
  * $sceDelegate} service.  This allows one to get/set the whitelists and blacklists used to ensure
- * that URLs used for sourcing Angular templates are safe.  Refer {@link
- * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and
- * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
+ * that the URLs used for sourcing Angular templates are safe.  Refer {@link
+ * ng.$sceDelegateProvider#methods_resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and
+ * {@link ng.$sceDelegateProvider#methods_resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
  *
- * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
+ * For the general details about this service in Angular, read the main page for {@link ng.$sce
+ * Strict Contextual Escaping (SCE)}.
+ *
+ * **Example**:  Consider the following case. 
+ *
+ * - your app is hosted at url `http://myapp.example.com/`
+ * - but some of your templates are hosted on other domains you control such as
+ *   `http://srv01.assets.example.com/`,  `http://srv02.assets.example.com/`, etc.
+ * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
+ *
+ * Here is what a secure configuration for this scenario might look like:
+ *
+ * 
+ *    angular.module('myApp', []).config(function($sceDelegateProvider) {
+ *      $sceDelegateProvider.resourceUrlWhitelist([
+ *        // Allow same origin resource loads.
+ *        'self',
+ *        // Allow loading from our assets domain.  Notice the difference between * and **.
+ *        'http://srv*.assets.example.com/**']);
+ *
+ *      // The blacklist overrides the whitelist so the open redirect here is blocked.
+ *      $sceDelegateProvider.resourceUrlBlacklist([
+ *        'http://myapp.example.com/clickThru**']);
+ *      });
+ * 
*/ function $SceDelegateProvider() { @@ -10319,28 +11992,25 @@ function $SceDelegateProvider() { * @function * * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value - * provided. This must be an array. + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. * - * Each element of this array must either be a regex or the special string `'self'`. + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. * - * When a regex is used, it is matched against the normalized / absolute URL of the resource - * being tested. - * - * The **special string** `'self'` can be used to match against all URLs of the same domain as the - * application document with the same protocol (allows sourcing https resources from http documents.) - * - * Please note that **an empty whitelist array will block all URLs**! + * Note: **an empty whitelist array will block all URLs**! * * @return {Array} the currently set whitelist array. * - * The **default value** when no whitelist has been explicitly set is `['self']`. + * The **default value** when no whitelist has been explicitly set is `['self']` allowing only + * same origin resource requests. * * @description * Sets/Gets the whitelist of trusted resource URLs. */ this.resourceUrlWhitelist = function (value) { if (arguments.length) { - resourceUrlWhitelist = value; + resourceUrlWhitelist = adjustMatchers(value); } return resourceUrlWhitelist; }; @@ -10352,24 +12022,22 @@ function $SceDelegateProvider() { * @function * * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value - * provided. This must be an array. + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. * - * Each element of this array must either be a regex or the special string `'self'` (see - * `resourceUrlWhitelist` for meaning - it's only really useful there.) + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. * - * When a regex is used, it is matched against the normalized / absolute URL of the resource - * being tested. - * - * The typical usage for the blacklist is to **block [open redirects](http://cwe.mitre.org/data/definitions/601.html)** - * served by your domain as these would otherwise be trusted but actually return content from the redirected - * domain. + * The typical usage for the blacklist is to **block + * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as + * these would otherwise be trusted but actually return content from the redirected domain. * * Finally, **the blacklist overrides the whitelist** and has the final say. * * @return {Array} the currently set blacklist array. * - * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there is - * no blacklist.) + * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there + * is no blacklist.) * * @description * Sets/Gets the blacklist of trusted resource URLs. @@ -10377,19 +12045,13 @@ function $SceDelegateProvider() { this.resourceUrlBlacklist = function (value) { if (arguments.length) { - resourceUrlBlacklist = value; + resourceUrlBlacklist = adjustMatchers(value); } return resourceUrlBlacklist; }; - // Helper functions for matching resource urls by policy. - function isCompatibleProtocol(documentProtocol, resourceProtocol) { - return ((documentProtocol === resourceProtocol) || - (documentProtocol === "http:" && resourceProtocol === "https:")); - } - - this.$get = ['$log', '$document', '$injector', '$$urlUtils', function( - $log, $document, $injector, $$urlUtils) { + this.$get = ['$log', '$document', '$injector', function( + $log, $document, $injector) { var htmlSanitizer = function htmlSanitizer(html) { throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); @@ -10402,14 +12064,15 @@ function $SceDelegateProvider() { function matchUrl(matcher, parsedUrl) { if (matcher === 'self') { - return $$urlUtils.isSameOrigin(parsedUrl); + return urlIsSameOrigin(parsedUrl); } else { - return !!parsedUrl.href.match(matcher); + // definitely a regex. See adjustMatchers() + return !!matcher.exec(parsedUrl.href); } } function isResourceUrlAllowedByPolicy(url) { - var parsedUrl = $$urlUtils.resolve(url.toString(), true); + var parsedUrl = urlResolve(url.toString()); var i, n, allowed = false; // Ensure that at least one item from the whitelist allows this url. for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { @@ -10430,21 +12093,21 @@ function $SceDelegateProvider() { return allowed; } - function generateHolderType(base) { + function generateHolderType(Base) { var holderType = function TrustedValueHolderType(trustedValue) { this.$$unwrapTrustedValue = function() { return trustedValue; }; }; - if (base) { - holderType.prototype = new base(); + if (Base) { + holderType.prototype = new Base(); } holderType.prototype.valueOf = function sceValueOf() { return this.$$unwrapTrustedValue(); - } + }; holderType.prototype.toString = function sceToString() { return this.$$unwrapTrustedValue().toString(); - } + }; return holderType; } @@ -10476,9 +12139,10 @@ function $SceDelegateProvider() { * where Angular expects a $sce.trustAs() return value. */ function trustAs(type, trustedValue) { - var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); - if (!constructor) { - throw $sceMinErr('icontext', 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', + var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + if (!Constructor) { + throw $sceMinErr('icontext', + 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', type, trustedValue); } if (trustedValue === null || trustedValue === undefined || trustedValue === '') { @@ -10491,7 +12155,7 @@ function $SceDelegateProvider() { 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', type); } - return new constructor(trustedValue); + return new Constructor(trustedValue); } /** @@ -10500,18 +12164,18 @@ function $SceDelegateProvider() { * @methodOf ng.$sceDelegate * * @description - * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs + * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#methods_trustAs * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link - * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. + * ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. * * If the passed parameter is not a value that had been returned by {@link - * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. + * ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}, returns it as-is. * - * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} + * @param {*} value The result of a prior {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`} * call or anything else. - * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns `value` - * unchanged. + * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#methods_trustAs + * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns + * `value` unchanged. */ function valueOf(maybeTrusted) { if (maybeTrusted instanceof trustedValueHolderBase) { @@ -10527,14 +12191,14 @@ function $SceDelegateProvider() { * @methodOf ng.$sceDelegate * * @description - * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and returns the - * originally supplied value if the queried context type is a supertype of the created type. If - * this condition isn't satisfied, throws an exception. + * Takes the result of a {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`} call and + * returns the originally supplied value if the queried context type is a supertype of the + * created type. If this condition isn't satisfied, throws an exception. * * @param {string} type The kind of context in which this value is to be used. - * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} call. - * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs + * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#methods_trustAs + * `$sceDelegate.trustAs`} call. + * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#methods_trustAs * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. */ function getTrusted(type, maybeTrusted) { @@ -10553,7 +12217,8 @@ function $SceDelegateProvider() { return maybeTrusted; } else { throw $sceMinErr('insecurl', - 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', maybeTrusted.toString()); + 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', + maybeTrusted.toString()); } } else if (type === SCE_CONTEXTS.HTML) { return htmlSanitizer(maybeTrusted); @@ -10580,6 +12245,8 @@ function $SceDelegateProvider() { * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. */ +/* jshint maxlen: false*/ + /** * @ngdoc service * @name ng.$sce @@ -10592,9 +12259,9 @@ function $SceDelegateProvider() { * # Strict Contextual Escaping * * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain - * contexts to result in a value that is marked as safe to use for that context One example of such - * a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer to these - * contexts as privileged or SCE contexts. + * contexts to result in a value that is marked as safe to use for that context. One example of + * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer + * to these contexts as privileged or SCE contexts. * * As of version 1.2, Angular ships with SCE enabled by default. * @@ -10636,20 +12303,20 @@ function $SceDelegateProvider() { * allowing only the files in a specific directory to do this. Ensuring that the internal API * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. * - * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} (and shorthand - * methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to obtain values that will be - * accepted by SCE / privileged contexts. + * In the case of AngularJS' SCE service, one uses {@link ng.$sce#methods_trustAs $sce.trustAs} + * (and shorthand methods such as {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}, etc.) to + * obtain values that will be accepted by SCE / privileged contexts. * * * ## How does it work? * - * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted + * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#methods_getTrusted * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link - * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the - * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. + * ng.$sce#methods_parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the + * {@link ng.$sce#methods_getTrusted $sce.getTrusted} behind the scenes on non-constant literals. * * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link - * ng.$sce#parseHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly + * ng.$sce#methods_parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly * simplified): * *
@@ -10668,10 +12335,10 @@ function $SceDelegateProvider() {
  * `templateUrl`'s specified by {@link guide/directive directives}.
  *
  * By default, Angular only loads templates from the same domain and protocol as the application
- * document.  This is done by calling {@link ng.$sce#getTrustedResourceUrl
+ * document.  This is done by calling {@link ng.$sce#methods_getTrustedResourceUrl
  * $sce.getTrustedResourceUrl} on the template URL.  To load templates from other domains and/or
- * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
- * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
+ * protocols, you may either either {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelist
+ * them} or {@link ng.$sce#methods_trustAsResourceUrl wrap it} into a trusted value.
  *
  * *Please note*:
  * The browser's
@@ -10691,88 +12358,137 @@ function $SceDelegateProvider() {
  * `
`) just works. * * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them - * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. + * through {@link ng.$sce#methods_getTrusted $sce.getTrusted}. SCE doesn't play a role here. * * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load * templates in `ng-include` from your application's domain without having to even know about SCE. * It blocks loading templates from other domains or loading templates over http from an https * served document. You can change these by setting your own custom {@link - * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link - * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. + * ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelists} and {@link + * ng.$sceDelegateProvider#methods_resourceUrlBlacklist blacklists} for matching such URLs. * * This significantly reduces the overhead. It is far easier to pay the small overhead and have an * application that's secure and can be audited to verify that with much more ease than bolting * security onto an application later. * - * ## What trusted context types are supported? + * + * ## What trusted context types are supported? * * | Context | Notes | - * |=====================|================| + * |---------------------|----------------| * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. | * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | * - * ## Show me an example. + * ## Format of items in {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#methods_resourceUrlBlacklist Blacklist}
* + * Each element in these arrays must be one of the following: * + * - **'self'** + * - The special **string**, `'self'`, can be used to match against all URLs of the **same + * domain** as the application document using the **same protocol**. + * - **String** (except the special value `'self'`) + * - The string is matched against the full *normalized / absolute URL* of the resource + * being tested (substring matches are not good enough.) + * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters + * match themselves. + * - `*`: matches zero or more occurances of any character other than one of the following 6 + * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use + * in a whitelist. + * - `**`: matches zero or more occurances of *any* character. As such, it's not + * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. + * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might + * not have been the intention.) It's usage at the very end of the path is ok. (e.g. + * http://foo.example.com/templates/**). + * - **RegExp** (*see caveat below*) + * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax + * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to + * accidentally introduce a bug when one updates a complex expression (imho, all regexes should + * have good test coverage.). For instance, the use of `.` in the regex is correct only in a + * small number of cases. A `.` character in the regex used when matching the scheme or a + * subdomain could be matched against a `:` or literal `.` that was likely not intended. It + * is highly recommended to use the string patterns and only fall back to regular expressions + * if they as a last resort. + * - The regular expression must be an instance of RegExp (i.e. not a string.) It is + * matched against the **entire** *normalized / absolute URL* of the resource being tested + * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags + * present on the RegExp (such as multiline, global, ignoreCase) are ignored. + * - If you are generating your Javascript from some other templating engine (not + * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), + * remember to escape your regular expression (and be aware that you might need more than + * one level of escaping depending on your templating engine and the way you interpolated + * the value.) Do make use of your platform's escaping mechanism as it might be good + * enough before coding your own. e.g. Ruby has + * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) + * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). + * Javascript lacks a similar built in function for escaping. Take a look at Google + * Closure library's [goog.string.regExpEscape(s)]( + * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). + * + * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. + * + * ## Show me an example using SCE. * * @example - - -
-

- User comments
- By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when $sanitize is available. If $sanitize isn't available, this results in an error instead of an exploit. -
-
- {{userComment.name}}: - -
-
+ + +
+

+ User comments
+ By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when + $sanitize is available. If $sanitize isn't available, this results in an error instead of an + exploit. +
+
+ {{userComment.name}}: + +
- +
+
- - var mySceApp = angular.module('mySceApp', ['ngSanitize']); + + var mySceApp = angular.module('mySceApp', ['ngSanitize']); - mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { - var self = this; - $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { - self.userComments = userComments; - }); - self.explicitlyTrustedHtml = $sce.trustAsHtml( + mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { + var self = this; + $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { + self.userComments = userComments; + }); + self.explicitlyTrustedHtml = $sce.trustAsHtml( + 'Hover over this text.'); + }); + + + +[ + { "name": "Alice", + "htmlComment": + "Is anyone reading this?" + }, + { "name": "Bob", + "htmlComment": "Yes! Am I the only other one?" + } +] + + + + describe('SCE doc demo', function() { + it('should sanitize untrusted values', function() { + expect(element('.htmlComment').html()).toBe('Is anyone reading this?'); + }); + it('should NOT sanitize explicitly trusted values', function() { + expect(element('#explicitlyTrustedHtml').html()).toBe( 'Hover over this text.'); }); - - - - [ - { "name": "Alice", - "htmlComment": "Is anyone reading this?" - }, - { "name": "Bob", - "htmlComment": "Yes! Am I the only other one?" - } - ] - - - - describe('SCE doc demo', function() { - it('should sanitize untrusted values', function() { - expect(element('.htmlComment').html()).toBe('Is anyone reading this?'); - }); - it('should NOT sanitize explicitly trusted values', function() { - expect(element('#explicitlyTrustedHtml').html()).toBe( - 'Hover over this text.'); - }); - }); - -
+ }); + + * * * @@ -10795,6 +12511,7 @@ function $SceDelegateProvider() { *
* */ +/* jshint maxlen: 100 */ function $SceProvider() { var enabled = true; @@ -10840,13 +12557,13 @@ function $SceProvider() { * This function should return the a value that is safe to use in the context specified by * contextEnum or throw and exception otherwise. * - * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be opaque - * or wrapped in some holder object. That happens to be an implementation detail. For instance, - * an implementation could maintain a registry of all trusted objects by context. In such a case, - * trustAs() would return the same object that was passed in. getTrusted() would return the same - * object passed in if it was found in the registry under a compatible context or throw an - * exception otherwise. An implementation might only wrap values some of the time based on - * some criteria. getTrusted() might return a value and not throw an exception for special + * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be + * opaque or wrapped in some holder object. That happens to be an implementation detail. For + * instance, an implementation could maintain a registry of all trusted objects by context. In + * such a case, trustAs() would return the same object that was passed in. getTrusted() would + * return the same object passed in if it was found in the registry under a compatible context or + * throw an exception otherwise. An implementation might only wrap values some of the time based + * on some criteria. getTrusted() might return a value and not throw an exception for special * constants or objects even if not wrapped. All such implementations fulfill this contract. * * @@ -10901,8 +12618,8 @@ function $SceProvider() { sce.valueOf = $sceDelegate.valueOf; if (!enabled) { - sce.trustAs = sce.getTrusted = function(type, value) { return value; }, - sce.valueOf = identity + sce.trustAs = sce.getTrusted = function(type, value) { return value; }; + sce.valueOf = identity; } /** @@ -10913,16 +12630,16 @@ function $SceProvider() { * @description * Converts Angular {@link guide/expression expression} into a function. This is like {@link * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it - * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, + * wraps the expression in a call to {@link ng.$sce#methods_getTrusted $sce.getTrusted(*type*, * *result*)} * * @param {string} type The kind of SCE context in which this result will be used. * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings + * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ sce.parseAs = function sceParseAs(type, expr) { @@ -10932,7 +12649,7 @@ function $SceProvider() { } else { return function sceParseAsTrusted(self, locals) { return sce.getTrusted(type, parsed(self, locals)); - } + }; } }; @@ -10942,11 +12659,12 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, returns an object - * that is trusted by angular for use in specified strict contextual escaping contexts (such as - * ng-html-bind-unsafe, ng-include, any src attribute interpolation, any dom event binding - * attribute interpolation such as for onclick, etc.) that uses the provided value. See * - * {@link ng.$sce $sce} for enabling strict contextual escaping. + * Delegates to {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. As such, + * returns an objectthat is trusted by angular for use in specified strict contextual + * escaping contexts (such as ng-html-bind-unsafe, ng-include, any src attribute + * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) + * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual + * escaping. * * @param {string} type The kind of context in which this value is safe for use. e.g. url, * resource_url, html, js and css. @@ -10961,13 +12679,14 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.trustAsHtml(value)` → {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} + * Shorthand method. `$sce.trustAsHtml(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.HTML, value)`} * * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedHtml * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) */ /** @@ -10976,13 +12695,14 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.trustAsUrl(value)` → {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} + * Shorthand method. `$sce.trustAsUrl(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.URL, value)`} * * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedUrl * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) */ /** @@ -10991,13 +12711,14 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.trustAsResourceUrl(value)` → {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} + * Shorthand method. `$sce.trustAsResourceUrl(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} * * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedResourceUrl * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the return - * value of {@link ng.$sce#trustAs $sce.trustAs}.) + * value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) */ /** @@ -11006,13 +12727,14 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.trustAsJs(value)` → {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} + * Shorthand method. `$sce.trustAsJs(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.JS, value)`} * * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedJs * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) */ /** @@ -11021,15 +12743,17 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, takes - * the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the originally supplied - * value if the queried context type is a supertype of the created type. If this condition - * isn't satisfied, throws an exception. + * Delegates to {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted`}. As such, + * takes the result of a {@link ng.$sce#methods_trustAs `$sce.trustAs`}() call and returns the + * originally supplied value if the queried context type is a supertype of the created type. + * If this condition isn't satisfied, throws an exception. * * @param {string} type The kind of context in which this value is to be used. - * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} call. - * @returns {*} The value the was originally provided to {@link ng.$sce#trustAs `$sce.trustAs`} if - * valid in this context. Otherwise, throws an exception. + * @param {*} maybeTrusted The result of a prior {@link ng.$sce#methods_trustAs `$sce.trustAs`} + * call. + * @returns {*} The value the was originally provided to + * {@link ng.$sce#methods_trustAs `$sce.trustAs`} if valid in this context. + * Otherwise, throws an exception. */ /** @@ -11038,7 +12762,8 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.getTrustedHtml(value)` → {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} + * Shorthand method. `$sce.getTrustedHtml(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` @@ -11050,7 +12775,8 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.getTrustedCss(value)` → {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} + * Shorthand method. `$sce.getTrustedCss(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` @@ -11062,7 +12788,8 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.getTrustedUrl(value)` → {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} + * Shorthand method. `$sce.getTrustedUrl(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` @@ -11074,7 +12801,8 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.getTrustedResourceUrl(value)` → {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} + * Shorthand method. `$sce.getTrustedResourceUrl(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} * * @param {*} value The value to pass to `$sceDelegate.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` @@ -11086,7 +12814,8 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.getTrustedJs(value)` → {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} + * Shorthand method. `$sce.getTrustedJs(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` @@ -11098,14 +12827,15 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.parseAsHtml(expression string)` → {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} + * Shorthand method. `$sce.parseAsHtml(expression string)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.HTML, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings + * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ @@ -11115,14 +12845,15 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.parseAsCss(value)` → {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} + * Shorthand method. `$sce.parseAsCss(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.CSS, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings + * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ @@ -11132,14 +12863,15 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.parseAsUrl(value)` → {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} + * Shorthand method. `$sce.parseAsUrl(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.URL, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings + * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ @@ -11149,14 +12881,15 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.parseAsResourceUrl(value)` → {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} + * Shorthand method. `$sce.parseAsResourceUrl(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.RESOURCE_URL, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings + * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ @@ -11166,14 +12899,15 @@ function $SceProvider() { * @methodOf ng.$sce * * @description - * Shorthand method. `$sce.parseAsJs(value)` → {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} + * Shorthand method. `$sce.parseAsJs(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.JS, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings + * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ @@ -11182,17 +12916,17 @@ function $SceProvider() { getTrusted = sce.getTrusted, trustAs = sce.trustAs; - angular.forEach(SCE_CONTEXTS, function (enumValue, name) { + forEach(SCE_CONTEXTS, function (enumValue, name) { var lName = lowercase(name); sce[camelCase("parse_as_" + lName)] = function (expr) { return parse(enumValue, expr); - } + }; sce[camelCase("get_trusted_" + lName)] = function (value) { return getTrusted(enumValue, value); - } + }; sce[camelCase("trust_as_" + lName)] = function (value) { return trustAs(enumValue, value); - } + }; }); return sce; @@ -11217,7 +12951,9 @@ function $SceProvider() { function $SnifferProvider() { this.$get = ['$window', '$document', function($window, $document) { var eventSupport = {}, - android = int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), + android = + int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), + boxee = /Boxee/i.test(($window.navigator || {}).userAgent), document = $document[0] || {}, vendorPrefix, vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, @@ -11234,12 +12970,17 @@ function $SnifferProvider() { break; } } + + if(!vendorPrefix) { + vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; + } + transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); - + if (android && (!transitions||!animations)) { - transitions = isString(document.body.style.webkitTransition); - animations = isString(document.body.style.webkitAnimation); + transitions = isString(document.body.style.webkitTransition); + animations = isString(document.body.style.webkitAnimation); } } @@ -11249,7 +12990,13 @@ function $SnifferProvider() { // so let's not use the history API at all. // http://code.google.com/p/android/issues/detail?id=17471 // https://github.com/angular/angular.js/issues/904 - history: !!($window.history && $window.history.pushState && !(android < 4)), + + // older webit browser (533.9) on Boxee box has exactly the same problem as Android has + // so let's not use the history API also + // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined + // jshint -W018 + history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), + // jshint +W018 hashchange: 'onhashchange' in $window && // IE8 compatible mode lies (!document.documentMode || document.documentMode > 7), @@ -11266,10 +13013,11 @@ function $SnifferProvider() { return eventSupport[event]; }, - csp: document.securityPolicy ? document.securityPolicy.isActive : false, + csp: csp(), vendorPrefix: vendorPrefix, transitions : transitions, - animations : animations + animations : animations, + msie : msie }; }]; } @@ -11301,15 +13049,103 @@ function $TimeoutProvider() { * @param {function()} fn A function, whose execution should be delayed. * @param {number=} [delay=0] Delay in milliseconds. * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise - * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block. * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this * promise will be resolved with is the return value of the `fn` function. + * + * @example + + + + +
+
+ Date format:
+ Current time is: +
+ Blood 1 : {{blood_1}} + Blood 2 : {{blood_2}} + + + +
+
+ +
+
*/ function timeout(fn, delay, invokeApply) { var deferred = $q.defer(), promise = deferred.promise, skipApply = (isDefined(invokeApply) && !invokeApply), - timeoutId, cleanup; + timeoutId; timeoutId = $browser.defer(function() { try { @@ -11318,17 +13154,15 @@ function $TimeoutProvider() { deferred.reject(e); $exceptionHandler(e); } + finally { + delete deferreds[promise.$$timeoutId]; + } if (!skipApply) $rootScope.$apply(); }, delay); - cleanup = function() { - delete deferreds[promise.$$timeoutId]; - }; - promise.$$timeoutId = timeoutId; deferreds[timeoutId] = deferred; - promise.then(cleanup, cleanup); return promise; } @@ -11350,6 +13184,7 @@ function $TimeoutProvider() { timeout.cancel = function(promise) { if (promise && promise.$$timeoutId in deferreds) { deferreds[promise.$$timeoutId].reject('canceled'); + delete deferreds[promise.$$timeoutId]; return $browser.defer.cancel(promise.$$timeoutId); } return false; @@ -11359,122 +13194,105 @@ function $TimeoutProvider() { }]; } -function $$UrlUtilsProvider() { - this.$get = [function() { - var urlParsingNode = document.createElement("a"), - // NOTE: The usage of window and document instead of $window and $document here is - // deliberate. This service depends on the specific behavior of anchor nodes created by the - // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and - // cause us to break tests. In addition, when the browser resolves a URL for XHR, it - // doesn't know about mocked locations and resolves URLs to the real document - which is - // exactly the behavior needed here. There is little value is mocking these our for this - // service. - originUrl = resolve(window.location.href, true); +// NOTE: The usage of window and document instead of $window and $document here is +// deliberate. This service depends on the specific behavior of anchor nodes created by the +// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and +// cause us to break tests. In addition, when the browser resolves a URL for XHR, it +// doesn't know about mocked locations and resolves URLs to the real document - which is +// exactly the behavior needed here. There is little value is mocking these out for this +// service. +var urlParsingNode = document.createElement("a"); +var originUrl = urlResolve(window.location.href, true); - /** - * @description - * Normalizes and optionally parses a URL. - * - * NOTE: This is a private service. The API is subject to change unpredictably in any commit. - * - * Implementation Notes for non-IE browsers - * ---------------------------------------- - * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, - * results both in the normalizing and parsing of the URL. Normalizing means that a relative - * URL will be resolved into an absolute URL in the context of the application document. - * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related - * properties are all populated to reflect the normalized URL. This approach has wide - * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See - * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html - * - * Implementation Notes for IE - * --------------------------- - * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other - * browsers. However, the parsed components will not be set if the URL assigned did not specify - * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We - * work around that by performing the parsing in a 2nd step by taking a previously normalized - * URL (e.g. by assining to a.href) and assigning it a.href again. This correctly populates the - * properties such as protocol, hostname, port, etc. - * - * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one - * uses the inner HTML approach to assign the URL as part of an HTML snippet - - * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. - * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. - * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that - * method and IE < 8 is unsupported. - * - * References: - * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement - * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html - * http://url.spec.whatwg.org/#urlutils - * https://github.com/angular/angular.js/pull/2902 - * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ - * - * @param {string} url The URL to be parsed. - * @param {boolean=} parse When true, returns an object for the parsed URL. Otherwise, returns - * a single string that is the normalized URL. - * @returns {object|string} When parse is true, returns the normalized URL as a string. - * Otherwise, returns an object with the following members. - * - * | member name | Description | - * |===============|================| - * | href | A normalized version of the provided URL if it was not an absolute URL | - * | protocol | The protocol including the trailing colon | - * | host | The host and port (if the port is non-default) of the normalizedUrl | - * - * These fields from the UrlUtils interface are currently not needed and hence not returned. - * - * | member name | Description | - * |===============|================| - * | hostname | The host without the port of the normalizedUrl | - * | pathname | The path following the host in the normalizedUrl | - * | hash | The URL hash if present | - * | search | The query string | - * - */ - function resolve(url, parse) { - var href = url; - if (msie) { - // Normalize before parse. Refer Implementation Notes on why this is - // done in two steps on IE. - urlParsingNode.setAttribute("href", href); - href = urlParsingNode.href; - } - urlParsingNode.setAttribute('href', href); +/** + * + * Implementation Notes for non-IE browsers + * ---------------------------------------- + * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, + * results both in the normalizing and parsing of the URL. Normalizing means that a relative + * URL will be resolved into an absolute URL in the context of the application document. + * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related + * properties are all populated to reflect the normalized URL. This approach has wide + * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * + * Implementation Notes for IE + * --------------------------- + * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other + * browsers. However, the parsed components will not be set if the URL assigned did not specify + * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We + * work around that by performing the parsing in a 2nd step by taking a previously normalized + * URL (e.g. by assining to a.href) and assigning it a.href again. This correctly populates the + * properties such as protocol, hostname, port, etc. + * + * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one + * uses the inner HTML approach to assign the URL as part of an HTML snippet - + * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. + * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. + * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that + * method and IE < 8 is unsupported. + * + * References: + * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * http://url.spec.whatwg.org/#urlutils + * https://github.com/angular/angular.js/pull/2902 + * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ + * + * @function + * @param {string} url The URL to be parsed. + * @description Normalizes and parses a URL. + * @returns {object} Returns the normalized URL as a dictionary. + * + * | member name | Description | + * |---------------|----------------| + * | href | A normalized version of the provided URL if it was not an absolute URL | + * | protocol | The protocol including the trailing colon | + * | host | The host and port (if the port is non-default) of the normalizedUrl | + * | search | The search params, minus the question mark | + * | hash | The hash string, minus the hash symbol + * | hostname | The hostname + * | port | The port, without ":" + * | pathname | The pathname, beginning with "/" + * + */ +function urlResolve(url) { + var href = url; + if (msie) { + // Normalize before parse. Refer Implementation Notes on why this is + // done in two steps on IE. + urlParsingNode.setAttribute("href", href); + href = urlParsingNode.href; + } - if (!parse) { - return urlParsingNode.href; - } - // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils - return { - href: urlParsingNode.href, - protocol: urlParsingNode.protocol, - host: urlParsingNode.host - // Currently unused and hence commented out. - // hostname: urlParsingNode.hostname, - // port: urlParsingNode.port, - // pathname: urlParsingNode.pathname, - // hash: urlParsingNode.hash, - // search: urlParsingNode.search - }; - } + urlParsingNode.setAttribute('href', href); - return { - resolve: resolve, - /** - * Parse a request URL and determine whether this is a same-origin request as the application document. - * - * @param {string|object} requestUrl The url of the request as a string that will be resolved - * or a parsed URL object. - * @returns {boolean} Whether the request is for the same origin as the application document. - */ - isSameOrigin: function isSameOrigin(requestUrl) { - var parsed = (typeof requestUrl === 'string') ? resolve(requestUrl, true) : requestUrl; - return (parsed.protocol === originUrl.protocol && - parsed.host === originUrl.host); - } - }; - }]; + // $$urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + return { + href: urlParsingNode.href, + protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', + host: urlParsingNode.host, + search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', + hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', + hostname: urlParsingNode.hostname, + port: urlParsingNode.port, + pathname: urlParsingNode.pathname && urlParsingNode.pathname.charAt(0) === '/' ? + urlParsingNode.pathname : '/' + urlParsingNode.pathname + }; +} + + +/** + * Parse a request URL and determine whether this is a same-origin request as the application document. + * + * @param {string|object} requestUrl The url of the request as a string that will be resolved + * or a parsed URL object. + * @returns {boolean} Whether the request is for the same origin as the application document. + */ +function urlIsSameOrigin(requestUrl) { + var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; + return (parsed.protocol === originUrl.protocol && + parsed.host === originUrl.host); } /** @@ -11524,9 +13342,9 @@ function $WindowProvider(){ * @name ng.$filterProvider * @description * - * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To - * achieve this a filter definition consists of a factory function which is annotated with dependencies and is - * responsible for creating a filter function. + * Filters are just functions which transform input to an output. However filters need to be + * Dependency Injected. To achieve this a filter definition consists of a factory function which is + * annotated with dependencies and is responsible for creating a filter function. * *
  *   // Filter registration
@@ -11549,7 +13367,9 @@ function $WindowProvider(){
  *   }
  * 
* - * The filter function is registered with the `$injector` under the filter name suffix with `Filter`. + * The filter function is registered with the `$injector` under the filter name suffix with + * `Filter`. + * *
  *   it('should be the same instance', inject(
  *     function($filterProvider) {
@@ -11564,8 +13384,7 @@ function $WindowProvider(){
  *
  *
  * For more information about how angular filters work, and how to create your own filters, see
- * {@link guide/dev_guide.templates.filters Understanding Angular Filters} in the angular Developer
- * Guide.
+ * {@link guide/filter Filters} in the Angular Developer Guide.
  */
 /**
  * @ngdoc method
@@ -11597,19 +13416,48 @@ $FilterProvider.$inject = ['$provide'];
 function $FilterProvider($provide) {
   var suffix = 'Filter';
 
+  /**
+   * @ngdoc function
+   * @name ng.$controllerProvider#register
+   * @methodOf ng.$controllerProvider
+   * @param {string|Object} name Name of the filter function, or an object map of filters where
+   *    the keys are the filter names and the values are the filter factories.
+   * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
+   *    of the registered filter instances.
+   */
   function register(name, factory) {
-    return $provide.factory(name + suffix, factory);
+    if(isObject(name)) {
+      var filters = {};
+      forEach(name, function(filter, key) {
+        filters[key] = register(key, filter);
+      });
+      return filters;
+    } else {
+      return $provide.factory(name + suffix, factory);
+    }
   }
   this.register = register;
 
   this.$get = ['$injector', function($injector) {
     return function(name) {
       return $injector.get(name + suffix);
-    }
+    };
   }];
 
   ////////////////////////////////////////
 
+  /* global
+    currencyFilter: false,
+    dateFilter: false,
+    filterFilter: false,
+    jsonFilter: false,
+    limitToFilter: false,
+    lowercaseFilter: false,
+    numberFilter: false,
+    orderByFilter: false,
+    uppercaseFilter: false,
+  */
+
   register('currency', currencyFilter);
   register('date', dateFilter);
   register('filter', filterFilter);
@@ -11629,9 +13477,6 @@ function $FilterProvider($provide) {
  * @description
  * Selects a subset of items from `array` and returns it as a new array.
  *
- * Note: This function is used to augment the `Array` type in Angular expressions. See
- * {@link ng.$filter} for more information about Angular arrays.
- *
  * @param {Array} array The source array.
  * @param {string|Object|function()} expression The predicate to be used for selecting items from
  *   `array`.
@@ -11726,9 +13571,12 @@ function $FilterProvider($provide) {
    
  */
 function filterFilter() {
-  return function(array, expression, comperator) {
+  return function(array, expression, comparator) {
     if (!isArray(array)) return array;
-    var predicates = [];
+
+    var comparatorType = typeof(comparator),
+        predicates = [];
+
     predicates.check = function(value) {
       for (var j = 0; j < predicates.length; j++) {
         if(!predicates[j](value)) {
@@ -11737,22 +13585,20 @@ function filterFilter() {
       }
       return true;
     };
-    switch(typeof comperator) {
-      case "function":
-        break;
-      case "boolean":
-        if(comperator == true) {
-          comperator = function(obj, text) {
-            return angular.equals(obj, text);
-          }
-          break;
-        }
-      default:
-        comperator = function(obj, text) {
-          text = (''+text).toLowerCase();
-          return (''+obj).toLowerCase().indexOf(text) > -1
+
+    if (comparatorType !== 'function') {
+      if (comparatorType === 'boolean' && comparator) {
+        comparator = function(obj, text) {
+          return angular.equals(obj, text);
         };
+      } else {
+        comparator = function(obj, text) {
+          text = (''+text).toLowerCase();
+          return (''+obj).toLowerCase().indexOf(text) > -1;
+        };
+      }
     }
+
     var search = function(obj, text){
       if (typeof text == 'string' && text.charAt(0) === '!') {
         return !search(obj, text.substr(1));
@@ -11761,12 +13607,11 @@ function filterFilter() {
         case "boolean":
         case "number":
         case "string":
-          return comperator(obj, text);
+          return comparator(obj, text);
         case "object":
           switch (typeof text) {
             case "object":
-              return comperator(obj, text);
-              break;
+              return comparator(obj, text);
             default:
               for ( var objKey in obj) {
                 if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
@@ -11791,20 +13636,23 @@ function filterFilter() {
       case "boolean":
       case "number":
       case "string":
+        // Set up expression object and fall through
         expression = {$:expression};
+        // jshint -W086
       case "object":
+        // jshint +W086
         for (var key in expression) {
           if (key == '$') {
             (function() {
               if (!expression[key]) return;
-              var path = key
+              var path = key;
               predicates.push(function(value) {
                 return search(value, expression[path]);
               });
             })();
           } else {
             (function() {
-              if (!expression[key]) return;
+              if (typeof(expression[key]) == 'undefined') { return; }
               var path = key;
               predicates.push(function(value) {
                 return search(getter(value,path), expression[path]);
@@ -11827,7 +13675,7 @@ function filterFilter() {
       }
     }
     return filtered;
-  }
+  };
 }
 
 /**
@@ -11895,7 +13743,7 @@ function currencyFilter($locale) {
  * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
  * If this is not provided then the fraction size is computed from the current locale's number
  * formatting pattern. In the case of the default locale, it will be 3.
- * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
+ * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
  *
  * @example
    
@@ -11974,13 +13822,13 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
     var whole = fraction[0];
     fraction = fraction[1] || '';
 
-    var pos = 0,
+    var i, pos = 0,
         lgroup = pattern.lgSize,
         group = pattern.gSize;
 
     if (whole.length >= (lgroup + group)) {
       pos = whole.length - lgroup;
-      for (var i = 0; i < pos; i++) {
+      for (i = 0; i < pos; i++) {
         if ((pos - i)%group === 0 && i !== 0) {
           formatedText += groupSep;
         }
@@ -12090,7 +13938,7 @@ var DATE_FORMATS = {
 };
 
 var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
-    NUMBER_STRING = /^\d+$/;
+    NUMBER_STRING = /^\-?\d+$/;
 
 /**
  * @ngdoc filter
@@ -12194,7 +14042,7 @@ function dateFilter($locale) {
       }
       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 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);
@@ -12315,13 +14163,10 @@ var uppercaseFilter = valueFn(uppercase);
  * are taken from either the beginning or the end of the source array or string, as specified by
  * the value and sign (positive or negative) of `limit`.
  *
- * Note: This function is used to augment the `Array` type in Angular expressions. See
- * {@link ng.$filter} for more information about Angular arrays.
- *
  * @param {Array|string} input Source array or string to be limited.
- * @param {string|number} limit The length of the returned array or string. If the `limit` number 
+ * @param {string|number} limit The length of the returned array or string. If the `limit` number
  *     is positive, `limit` number of items from the beginning of the source array/string are copied.
- *     If the number is negative, `limit` number  of items from the end of the source array/string 
+ *     If the number is negative, `limit` number  of items from the end of the source array/string
  *     are copied. The `limit` will be trimmed if it exceeds `array.length`
  * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
  *     had less than `limit` elements.
@@ -12371,7 +14216,7 @@ var uppercaseFilter = valueFn(uppercase);
 function limitToFilter(){
   return function(input, limit) {
     if (!isArray(input) && !isString(input)) return input;
-    
+
     limit = int(limit);
 
     if (isString(input)) {
@@ -12405,7 +14250,7 @@ function limitToFilter(){
     }
 
     return out;
-  }
+  };
 }
 
 /**
@@ -12416,9 +14261,6 @@ function limitToFilter(){
  * @description
  * Orders a specified `array` by the `expression` predicate.
  *
- * Note: this function is used to augment the `Array` type in Angular expressions. See
- * {@link ng.$filter} for more information about Angular arrays.
- *
  * @param {Array} array The array to sort.
  * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
  *    used by the comparator to determine the order of elements.
@@ -12457,7 +14299,7 @@ function limitToFilter(){
          
+                 (^)
@@ -12533,22 +14375,24 @@ function orderByFilter($parse){
       var t1 = typeof v1;
       var t2 = typeof v2;
       if (t1 == t2) {
-        if (t1 == "string") v1 = v1.toLowerCase();
-        if (t1 == "string") v2 = v2.toLowerCase();
+        if (t1 == "string") {
+           v1 = v1.toLowerCase();
+           v2 = v2.toLowerCase();
+        }
         if (v1 === v2) return 0;
         return v1 < v2 ? -1 : 1;
       } else {
         return t1 < t2 ? -1 : 1;
       }
     }
-  }
+  };
 }
 
 function ngDirective(directive) {
   if (isFunction(directive)) {
     directive = {
       link: directive
-    }
+    };
   }
   directive.restrict = directive.restrict || 'AC';
   return valueFn(directive);
@@ -12560,12 +14404,12 @@ function ngDirective(directive) {
  * @restrict E
  *
  * @description
- * Modifies the default behavior of html A tag, so that the default action is prevented when href
- * attribute is empty.
+ * Modifies the default behavior of the html A tag so that the default action is prevented when
+ * the href attribute is empty.
  *
- * The reasoning for this change is to allow easy creation of action links with `ngClick` directive
+ * This change permits the easy creation of action links with the `ngClick` directive
  * without changing the location or causing page reloads, e.g.:
- * `Save`
+ * `Add Item`
  */
 var htmlAnchorDirective = valueFn({
   restrict: 'E',
@@ -12593,7 +14437,7 @@ var htmlAnchorDirective = valueFn({
           event.preventDefault();
         }
       });
-    }
+    };
   }
 });
 
@@ -12603,13 +14447,15 @@ var htmlAnchorDirective = valueFn({
  * @restrict A
  *
  * @description
- * Using Angular markup like {{hash}} in an href attribute makes
- * the page open to a wrong URL, if the user clicks that link before
- * angular has a chance to replace the {{hash}} with actual URL, the
- * link will be broken and will most likely return a 404 error.
+ * Using Angular markup like `{{hash}}` in an href attribute will
+ * make the link go to the wrong URL if the user clicks it before
+ * Angular has a chance to replace the `{{hash}}` markup with its
+ * value. Until Angular replaces the markup the link will be broken
+ * and will most likely return a 404 error.
+ *
  * The `ngHref` directive solves this problem.
  *
- * The buggy way to write it:
+ * The wrong way to write it:
  * 
  * 
  * 
@@ -12623,7 +14469,8 @@ var htmlAnchorDirective = valueFn({ * @param {template} ngHref any string which can contain `{{}}` markup. * * @example - * This example uses `link` variable inside `href` attribute: + * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes + * in links and their different behaviors:
@@ -12741,10 +14588,10 @@ var htmlAnchorDirective = valueFn({ * * * - * The HTML specs do not require browsers to preserve the special attributes such as disabled. - * (The presence of them means true and absence means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce the `ngDisabled` directive. + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as disabled. (Their presence means true and their absence means false.) + * This prevents the Angular compiler from retrieving the binding expression. + * The `ngDisabled` directive solves this problem for the `disabled` attribute. * * @example @@ -12762,7 +14609,8 @@ var htmlAnchorDirective = valueFn({ * * @element INPUT - * @param {expression} ngDisabled Angular expression that will be evaluated. + * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, + * then special attribute "disabled" will be set on the element */ @@ -12772,10 +14620,10 @@ var htmlAnchorDirective = valueFn({ * @restrict A * * @description - * The HTML specs do not require browsers to preserve the special attributes such as checked. - * (The presence of them means true and absence means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce the `ngChecked` directive. + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as checked. (Their presence means true and their absence means false.) + * This prevents the Angular compiler from retrieving the binding expression. + * The `ngChecked` directive solves this problem for the `checked` attribute. * @example @@ -12792,7 +14640,8 @@ var htmlAnchorDirective = valueFn({ * * @element INPUT - * @param {expression} ngChecked Angular expression that will be evaluated. + * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, + * then special attribute "checked" will be set on the element */ @@ -12802,10 +14651,10 @@ var htmlAnchorDirective = valueFn({ * @restrict A * * @description - * The HTML specs do not require browsers to preserve the special attributes such as readonly. - * (The presence of them means true and absence means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce the `ngReadonly` directive. + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as readonly. (Their presence means true and their absence means false.) + * This prevents the Angular compiler from retrieving the binding expression. + * The `ngReadonly` directive solves this problem for the `readonly` attribute. * @example @@ -12822,7 +14671,8 @@ var htmlAnchorDirective = valueFn({ * * @element INPUT - * @param {string} expression Angular expression that will be evaluated. + * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, + * then special attribute "readonly" will be set on the element */ @@ -12832,10 +14682,10 @@ var htmlAnchorDirective = valueFn({ * @restrict A * * @description - * The HTML specs do not require browsers to preserve the special attributes such as selected. - * (The presence of them means true and absence means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduced the `ngSelected` directive. + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as selected. (Their presence means true and their absence means false.) + * This prevents the Angular compiler from retrieving the binding expression. + * The `ngSelected` directive solves this problem for the `selected` atttribute. * @example @@ -12855,7 +14705,8 @@ var htmlAnchorDirective = valueFn({ * * @element OPTION - * @param {string} expression Angular expression that will be evaluated. + * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, + * then special attribute "selected" will be set on the element */ /** @@ -12864,10 +14715,10 @@ var htmlAnchorDirective = valueFn({ * @restrict A * * @description - * The HTML specs do not require browsers to preserve the special attributes such as open. - * (The presence of them means true and absence means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce the `ngOpen` directive. + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as open. (Their presence means true and their absence means false.) + * This prevents the Angular compiler from retrieving the binding expression. + * The `ngOpen` directive solves this problem for the `open` attribute. * * @example @@ -12887,7 +14738,8 @@ var htmlAnchorDirective = valueFn({ * * @element DETAILS - * @param {string} expression Angular expression that will be evaluated. + * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, + * then special attribute "open" will be set on the element */ var ngAttributeAliasDirectives = {}; @@ -12938,6 +14790,7 @@ forEach(['src', 'srcset', 'href'], function(attrName) { }; }); +/* global -nullFormCtrl */ var nullFormCtrl = { $addControl: noop, $removeControl: noop, @@ -12958,7 +14811,7 @@ var nullFormCtrl = { * @property {Object} $error Is an object hash, containing references to all invalid controls or * forms, where: * - * - keys are validation tokens (error names) — such as `required`, `url` or `email`), + * - keys are validation tokens (error names) — such as `required`, `url` or `email`), * - values are arrays of controls or forms that are invalid with given error. * * @description @@ -13010,9 +14863,12 @@ function FormController(element, attrs) { * Input elements using ngModelController do this automatically when they are linked. */ form.$addControl = function(control) { + // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored + // and not added to the scope. Now we throw an error. + assertNotHasOwnProperty(control.$name, 'input'); controls.push(control); - if (control.$name && !form.hasOwnProperty(control.$name)) { + if (control.$name) { form[control.$name] = control; } }; @@ -13140,7 +14996,7 @@ function FormController(element, attrs) { * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a * sub-group of controls needs to be determined. * - * @param {string=} name|ngForm Name of the form. If specified, the form controller will be published into + * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into * related scope, under this name. * */ @@ -13154,15 +15010,19 @@ function FormController(element, attrs) { * Directive that instantiates * {@link ng.directive:form.FormController FormController}. * - * If `name` attribute is specified, the form controller is published onto the current scope under + * If the `name` attribute is specified, the form controller is published onto the current scope under * this name. * * # Alias: {@link ng.directive:ngForm `ngForm`} * - * In angular forms can be nested. This means that the outer form is valid when all of the child - * forms are valid as well. However browsers do not allow nesting of `
` elements, for this - * reason angular provides {@link ng.directive:ngForm `ngForm`} alias - * which behaves identical to `` but allows form nesting. + * In Angular forms can be nested. This means that the outer form is valid when all of the child + * forms are valid as well. However, browsers do not allow nesting of `` elements, so + * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to + * `` but can be nested. This allows you to have nested forms, which is very useful when + * using Angular validation directives in forms that are dynamically generated using the + * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` + * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an + * `ngForm` directive and nest these in an outer `form` element. * * * # CSS classes @@ -13172,12 +15032,12 @@ function FormController(element, attrs) { * - `ng-dirty` Is set if the form is dirty. * * - * # Submitting a form and preventing default action + * # Submitting a form and preventing the default action * * Since the role of forms in client-side Angular applications is different than in classical * roundtrip apps, it is desirable for the browser not to translate the form submission into a full * page reload that sends the data to the server. Instead some javascript logic should be triggered - * to handle the form submission in application specific way. + * to handle the form submission in an application-specific way. * * For this reason, Angular prevents the default action (form submission to the server) unless the * `` element has an `action` attribute specified. @@ -13189,12 +15049,13 @@ function FormController(element, attrs) { * - {@link ng.directive:ngClick ngClick} directive on the first * button or input field of type submit (input[type=submit]) * - * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This - * is because of the following form submission rules coming from the html spec: + * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} + * or {@link ng.directive:ngClick ngClick} directives. + * This is because of the following form submission rules in the HTML specification: * * - If a form has only one input field then hitting enter in this field triggers form submit * (`ngSubmit`) - * - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter + * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter * doesn't trigger submit * - if a form has one or more input fields and one or more buttons or input[type=submit] then * hitting enter in any of the input fields will trigger the click handler on the *first* button or @@ -13239,7 +15100,7 @@ var formDirectiveFactory = function(isNgForm) { return ['$timeout', function($timeout) { var formDirective = { name: 'form', - restrict: 'E', + restrict: isNgForm ? 'EAC' : 'E', controller: FormController, compile: function() { return { @@ -13288,13 +15149,21 @@ var formDirectiveFactory = function(isNgForm) { } }; - return isNgForm ? extend(copy(formDirective), {restrict: 'EAC'}) : formDirective; + return formDirective; }]; }; var formDirective = formDirectiveFactory(); var ngFormDirective = formDirectiveFactory(true); +/* global + + -VALID_CLASS, + -INVALID_CLASS, + -PRISTINE_CLASS, + -DIRTY_CLASS +*/ + var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; @@ -13323,8 +15192,7 @@ var inputType = { * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. - * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trimming the - * input. + * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. * * @example @@ -13679,11 +15547,6 @@ var inputType = { }; -function isEmpty(value) { - return isUndefined(value) || value === '' || value === null || value !== value; -} - - function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { var listener = function() { @@ -13740,7 +15603,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { ctrl.$render = function() { - element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); + element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); }; // pattern validator @@ -13749,7 +15612,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { match; var validate = function(regexp, value) { - if (isEmpty(value) || regexp.test(value)) { + if (ctrl.$isEmpty(value) || regexp.test(value)) { ctrl.$setValidity('pattern', true); return value; } else { @@ -13763,7 +15626,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { if (match) { pattern = new RegExp(match[1], match[2]); patternValidator = function(value) { - return validate(pattern, value) + return validate(pattern, value); }; } else { patternValidator = function(value) { @@ -13786,7 +15649,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { if (attr.ngMinlength) { var minlength = int(attr.ngMinlength); var minLengthValidator = function(value) { - if (!isEmpty(value) && value.length < minlength) { + if (!ctrl.$isEmpty(value) && value.length < minlength) { ctrl.$setValidity('minlength', false); return undefined; } else { @@ -13803,7 +15666,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { if (attr.ngMaxlength) { var maxlength = int(attr.ngMaxlength); var maxLengthValidator = function(value) { - if (!isEmpty(value) && value.length > maxlength) { + if (!ctrl.$isEmpty(value) && value.length > maxlength) { ctrl.$setValidity('maxlength', false); return undefined; } else { @@ -13821,7 +15684,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); ctrl.$parsers.push(function(value) { - var empty = isEmpty(value); + var empty = ctrl.$isEmpty(value); if (empty || NUMBER_REGEXP.test(value)) { ctrl.$setValidity('number', true); return value === '' ? null : (empty ? value : parseFloat(value)); @@ -13832,13 +15695,13 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { }); ctrl.$formatters.push(function(value) { - return isEmpty(value) ? '' : '' + value; + return ctrl.$isEmpty(value) ? '' : '' + value; }); if (attr.min) { - var min = parseFloat(attr.min); var minValidator = function(value) { - if (!isEmpty(value) && value < min) { + var min = parseFloat(attr.min); + if (!ctrl.$isEmpty(value) && value < min) { ctrl.$setValidity('min', false); return undefined; } else { @@ -13852,9 +15715,9 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { } if (attr.max) { - var max = parseFloat(attr.max); var maxValidator = function(value) { - if (!isEmpty(value) && value > max) { + var max = parseFloat(attr.max); + if (!ctrl.$isEmpty(value) && value > max) { ctrl.$setValidity('max', false); return undefined; } else { @@ -13869,7 +15732,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { ctrl.$formatters.push(function(value) { - if (isEmpty(value) || isNumber(value)) { + if (ctrl.$isEmpty(value) || isNumber(value)) { ctrl.$setValidity('number', true); return value; } else { @@ -13883,7 +15746,7 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); var urlValidator = function(value) { - if (isEmpty(value) || URL_REGEXP.test(value)) { + if (ctrl.$isEmpty(value) || URL_REGEXP.test(value)) { ctrl.$setValidity('url', true); return value; } else { @@ -13900,7 +15763,7 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); var emailValidator = function(value) { - if (isEmpty(value) || EMAIL_REGEXP.test(value)) { + if (ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value)) { ctrl.$setValidity('email', true); return value; } else { @@ -13952,6 +15815,11 @@ function checkboxInputType(scope, element, attr, ctrl) { element[0].checked = ctrl.$viewValue; }; + // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. + ctrl.$isEmpty = function(value) { + return value !== trueValue; + }; + ctrl.$formatters.push(function(value) { return value === trueValue; }); @@ -14113,7 +15981,7 @@ var VALID_CLASS = 'ng-valid', the control reads value from the DOM. Each function is called, in turn, passing the value through to the next. Used to sanitize / convert the value as well as validation. For validation, the parsers should update the validity state using - {@link ng.directive:ngModel.NgModelController#$setValidity $setValidity()}, + {@link ng.directive:ngModel.NgModelController#methods_$setValidity $setValidity()}, and return `undefined` for invalid values. * @@ -14138,19 +16006,19 @@ var VALID_CLASS = 'ng-valid', * @description * * `NgModelController` provides API for the `ng-model` directive. The controller contains - * services for data-binding, validation, CSS update, value formatting and parsing. It - * specifically does not contain any logic which deals with DOM rendering or listening to - * DOM events. The `NgModelController` is meant to be extended by other directives where, the - * directive provides DOM manipulation and the `NgModelController` provides the data-binding. - * Note that you cannot use `NgModelController` in a directive with an isolated scope, - * as, in that case, the `ng-model` value gets put into the isolated scope and does not get - * propogated to the parent scope. - * + * 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. * + * ## 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. * + * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element + * contents be edited in place by the user. This will not work on older browsers. + * * [contenteditable] { @@ -14221,6 +16089,40 @@ var VALID_CLASS = 'ng-valid', * * + * ## Isolated Scope Pitfall + * + * Note that if you have a directive with an isolated scope, you cannot require `ngModel` + * since the model value will be looked up on the isolated scope rather than the outer scope. + * When the directive updates the model value, calling `ngModel.$setViewValue()` the property + * on the outer scope will not be updated. However you can get around this by using $parent. + * + * Here is an example of this situation. You'll notice that the first div is not updating the input. + * However the second div can update the input properly. + * + * + + angular.module('badIsolatedDirective', []).directive('isolate', function() { + return { + require: 'ngModel', + scope: { }, + template: '', + link: function(scope, element, attrs, ngModel) { + scope.$watch('innerModel', function(value) { + console.log(value); + ngModel.$setViewValue(value); + }); + } + }; + }); + + + +
+
+
+ *
+ * + * */ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', function($scope, $exceptionHandler, $attr, $element, $parse) { @@ -14254,6 +16156,25 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ */ this.$render = noop; + /** + * @ngdoc function + * @name { ng.directive:ngModel.NgModelController#$isEmpty + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * This is called when we need to determine if the value of the 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. + */ + this.$isEmpty = function(value) { + return isUndefined(value) || value === '' || value === null || value !== value; + }; + var parentForm = $element.inheritedData('$formController') || nullFormCtrl, invalidCount = 0, // used to easily determine if we are valid $error = this.$error = {}; // keep invalid keys here @@ -14290,7 +16211,10 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). */ this.$setValidity = function(validationErrorKey, isValid) { + // Purposeful use of ! here to cast isValid to boolean in case it is undefined + // jshint -W018 if ($error[validationErrorKey] === !isValid) return; + // jshint +W018 if (isValid) { if ($error[validationErrorKey]) invalidCount--; @@ -14370,7 +16294,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ } catch(e) { $exceptionHandler(e); } - }) + }); } }; @@ -14407,23 +16331,27 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * @element input * * @description - * Is a directive that tells Angular to do two-way data binding. It works together with `input`, - * `select`, `textarea` and even custom form controls that use {@link ng.directive:ngModel.NgModelController - * NgModelController} exposed by this directive. + * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a + * property on the scope using {@link ng.directive:ngModel.NgModelController NgModelController}, + * which is created and exposed by this directive. * * `ngModel` is responsible for: * - * - binding the view into the model, which other directives such as `input`, `textarea` or `select` - * require, - * - providing validation behavior (i.e. required, number, email, url), - * - keeping state of the control (valid/invalid, dirty/pristine, validation errors), - * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`), - * - register the control with parent {@link ng.directive:form form}. + * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` + * require. + * - Providing validation behavior (i.e. required, number, email, url). + * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). + * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`). + * - Registering the control with its parent {@link ng.directive:form form}. * * Note: `ngModel` will try to bind to the property given by evaluating the expression on the * current scope. If the property doesn't already exist on this scope, it will be created * implicitly and added to the scope. * + * For best practices on using `ngModel`, see: + * + * - {@link https://github.com/angular/angular.js/wiki/Understanding-Scopes} + * * For basic examples, how to use `ngModel`, see: * * - {@link ng.directive:input input} @@ -14449,7 +16377,7 @@ var ngModelDirective = function() { formCtrl.$addControl(modelCtrl); - element.on('$destroy', function() { + scope.$on('$destroy', function() { formCtrl.$removeControl(modelCtrl); }); } @@ -14460,7 +16388,6 @@ var ngModelDirective = function() { /** * @ngdoc directive * @name ng.directive:ngChange - * @restrict E * * @description * Evaluate given expression when user changes the input. @@ -14469,6 +16396,8 @@ var ngModelDirective = function() { * Note, this directive requires `ngModel` to be present. * * @element input + * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change + * in input value. * * @example * @@ -14523,7 +16452,7 @@ var requiredDirective = function() { attr.required = true; // force truthy in case we are on non input element var validator = function(value) { - if (attr.required && (isEmpty(value) || value === false)) { + if (attr.required && ctrl.$isEmpty(value)) { ctrl.$setValidity('required', false); return; } else { @@ -14548,7 +16477,8 @@ var requiredDirective = function() { * @name ng.directive:ngList * * @description - * Text input that converts between comma-separated string into an array of strings. + * Text input that converts between a delimited string and an array of strings. The delimiter + * can be a fixed string (by default a comma) or a regular expression. * * @element input * @param {string=} ngList optional delimiter that should be used to split the value. If @@ -14583,7 +16513,7 @@ var requiredDirective = function() { it('should be invalid if empty', function() { input('names').enter(''); - expect(binding('names')).toEqual('[]'); + expect(binding('names')).toEqual(''); expect(binding('myForm.namesInput.$valid')).toEqual('false'); expect(element('span.error').css('display')).not().toBe('none'); }); @@ -14598,6 +16528,9 @@ var ngListDirective = function() { separator = match && new RegExp(match[1]) || attr.ngList || ','; var parse = function(viewValue) { + // If the viewValue is invalid (say required but empty) it will be `undefined` + if (isUndefined(viewValue)) return; + var list = []; if (viewValue) { @@ -14617,23 +16550,77 @@ var ngListDirective = function() { return undefined; }); + + // Override the standard $isEmpty because an empty array means the input is empty. + ctrl.$isEmpty = function(value) { + return !value || !value.length; + }; } }; }; var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; - +/** + * @ngdoc directive + * @name ng.directive:ngValue + * + * @description + * Binds the given expression to the value of `input[select]` or `input[radio]`, so + * that when the element is selected, the `ngModel` of that element is set to the + * bound value. + * + * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as + * shown below. + * + * @element input + * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute + * of the `input` element + * + * @example + + + + +

Which is your favorite?

+ + +
You chose {{my.favorite}}
+ +
+ + it('should initialize to model', function() { + expect(binding('my.favorite')).toEqual('unicorns'); + }); + it('should bind the values to the inputs', function() { + input('my.favorite').select('pizza'); + expect(binding('my.favorite')).toEqual('pizza'); + }); + +
+ */ var ngValueDirective = function() { return { priority: 100, compile: function(tpl, tplAttr) { if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { - return function(scope, elm, attr) { + return function ngValueConstantLink(scope, elm, attr) { attr.$set('value', scope.$eval(attr.ngValue)); }; } else { - return function(scope, elm, attr) { + return function ngValueLink(scope, elm, attr) { scope.$watch(attr.ngValue, function valueWatchAction(value) { attr.$set('value', value); }); @@ -14646,6 +16633,7 @@ var ngValueDirective = function() { /** * @ngdoc directive * @name ng.directive:ngBind + * @restrict AC * * @description * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element @@ -14655,8 +16643,8 @@ var ngValueDirective = function() { * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like * `{{ expression }}` which is similar but less verbose. * - * It is preferrable to use `ngBind` instead of `{{ expression }}` when a template is momentarily - * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an + * It is preferrable to use `ngBind` instead of `{{ expression }}` when a template is momentarily + * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an * element attribute, it makes the bindings invisible to the user while the page is loading. * * An alternative solution to this problem would be using the @@ -14692,6 +16680,9 @@ var ngValueDirective = function() { var ngBindDirective = ngDirective(function(scope, element, attr) { element.addClass('ng-binding').data('$binding', attr.ngBind); scope.$watch(attr.ngBind, function ngBindWatchAction(value) { + // We are purposefully using == here rather than === because we want to + // catch when value is "null or undefined" + // jshint -W041 element.text(value == undefined ? '' : value); }); }); @@ -14753,7 +16744,7 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) { attr.$observe('ngBindTemplate', function(value) { element.text(value); }); - } + }; }]; @@ -14767,7 +16758,7 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) { * 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.) 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 + * an explicitly trusted value via {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}. See the example * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. * * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you @@ -14775,12 +16766,39 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) { * * @element ANY * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. + * + * @example + * Try it here: enter text in text box and watch the greeting change. + + + +
+

+
+
+ + it('should check ng-bind-html', function() { + expect(using('.doc-example-live').binding('myHTML')). + toBe('I am an HTMLstring with
links! and other stuff'); + }); + + */ -var ngBindHtmlDirective = ['$sce', function($sce) { +var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { return function(scope, element, attr) { element.addClass('ng-binding').data('$binding', attr.ngBindHtml); - scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function ngBindHtmlWatchAction(value) { - element.html(value || ''); + + var parsed = $parse(attr.ngBindHtml); + function getStringValue() { return (parsed(scope) || '').toString(); } + + scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { + element.html($sce.getTrustedHtml(parsed(scope)) || ''); }); }; }]; @@ -14791,7 +16809,7 @@ function classDirective(name, selector) { return { restrict: 'AC', link: function(scope, element, attr) { - var oldVal = undefined; + var oldVal; scope.$watch(attr[name], ngClassWatchAction, true); @@ -14802,6 +16820,7 @@ function classDirective(name, selector) { if (name !== 'ngClass') { scope.$watch('$index', function($index, old$index) { + // jshint bitwise: false var mod = $index & 1; if (mod !== old$index & 1) { if (mod === selector) { @@ -14848,7 +16867,7 @@ function classDirective(name, selector) { } return classVal; - }; + } } }; }; @@ -14857,9 +16876,10 @@ function classDirective(name, selector) { /** * @ngdoc directive * @name ng.directive:ngClass + * @restrict AC * * @description - * The `ngClass` allows you to set CSS classes on HTML an element, dynamically, by databinding + * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding * an expression that represents all classes to be added. * * The directive won't add duplicate classes if a particular class was already set. @@ -14878,7 +16898,7 @@ function classDirective(name, selector) { * names of the properties whose values are truthy will be added as css classes to the * element. * - * @example Example that demostrates basic bindings via ngClass directive. + * @example Example that demonstrates basic bindings via ngClass directive.

Map Syntax Example

@@ -14936,33 +16956,25 @@ function classDirective(name, selector) { ## Animations - Example that demostrates how addition and removal of classes can be animated. + The example below demonstrates how to perform animations using ngClass.
- Sample Text + Sample Text
- .my-class-add, .my-class-remove { + .base-class { -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; } - .my-class, - .my-class-add.my-class-add-active { + .base-class.my-class { color: red; font-size:3em; } - - .my-class-remove.my-class-remove-active { - font-size:1.0em; - color:black; - } it('should check ng-class', function() { @@ -14981,19 +16993,28 @@ function classDirective(name, selector) { });
+ + + ## ngClass and pre-existing CSS3 Transitions/Animations + The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. + Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder + any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure + to view the step by step details of {@link ngAnimate.$animate#methods_addclass $animate.addClass} and + {@link ngAnimate.$animate#methods_removeclass $animate.removeClass}. */ var ngClassDirective = classDirective('', true); /** * @ngdoc directive * @name ng.directive:ngClassOdd + * @restrict AC * * @description * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link ng.directive:ngClass ngClass}, except it works in - * conjunction with `ngRepeat` and takes affect only on odd (even) rows. + * {@link ng.directive:ngClass ngClass}, except they work in + * conjunction with `ngRepeat` and take effect only on odd (even) rows. * - * This directive can be applied only within a scope of an + * This directive can be applied only within the scope of an * {@link ng.directive:ngRepeat ngRepeat}. * * @element ANY @@ -15034,13 +17055,14 @@ var ngClassOddDirective = classDirective('Odd', 0); /** * @ngdoc directive * @name ng.directive:ngClassEven + * @restrict AC * * @description * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link ng.directive:ngClass ngClass}, except it works in - * conjunction with `ngRepeat` and takes affect only on odd (even) rows. + * {@link ng.directive:ngClass ngClass}, except they work in + * conjunction with `ngRepeat` and take effect only on odd (even) rows. * - * This directive can be applied only within a scope of an + * This directive can be applied only within the scope of an * {@link ng.directive:ngRepeat ngRepeat}. * * @element ANY @@ -15081,17 +17103,20 @@ var ngClassEvenDirective = classDirective('Even', 1); /** * @ngdoc directive * @name ng.directive:ngCloak + * @restrict AC * * @description * The `ngCloak` directive is used to prevent the Angular html template from being briefly * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this * directive to avoid the undesirable flicker effect caused by the html template display. * - * The directive can be applied to the `` element, but typically a fine-grained application is - * preferred in order to benefit from progressive rendering of the browser view. + * The directive can be applied to the `` element, but the preferred usage is to apply + * multiple `ngCloak` directives to small portions of the page to permit progressive rendering + * of the browser view. * - * `ngCloak` works in cooperation with a css rule that is embedded within `angular.js` and - * `angular.min.js` files. Following is the css rule: + * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and + * `angular.min.js`. + * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). * *
  * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
@@ -15100,17 +17125,17 @@ var ngClassEvenDirective = classDirective('Even', 1);
  * 
* * When this css rule is loaded by the browser, all html elements (including their children) that - * are tagged with the `ng-cloak` directive are hidden. When Angular comes across this directive - * during the compilation of the template it deletes the `ngCloak` element attribute, which - * makes the compiled element visible. + * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive + * during the compilation of the template it deletes the `ngCloak` element attribute, making + * the compiled element visible. * - * For the best result, `angular.js` script must be loaded in the head section of the html file; - * alternatively, the css rule (above) must be included in the external stylesheet of the + * For the best result, the `angular.js` script must be loaded in the head section of the html + * document; alternatively, the css rule above must be included in the external stylesheet of the * application. * * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css - * class `ngCloak` in addition to `ngCloak` directive as shown in the example below. + * class `ngCloak` in addition to the `ngCloak` directive as shown in the example below. * * @element ANY * @@ -15143,24 +17168,28 @@ var ngCloakDirective = ngDirective({ * @name ng.directive:ngController * * @description - * The `ngController` directive assigns behavior to a scope. This is a key aspect of how angular + * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular * supports the principles behind the Model-View-Controller design pattern. * * MVC components in angular: * - * * Model — The Model is data in scope properties; scopes are attached to the DOM. - * * View — The template (HTML with data bindings) is rendered into the View. - * * Controller — The `ngController` directive specifies a Controller class; the class has - * methods that typically express the business logic behind the application. + * * Model — The Model is scope properties; scopes are attached to the DOM where scope properties + * are accessed through bindings. + * * View — The template (HTML with data bindings) that is rendered into the View. + * * Controller — The `ngController` directive specifies a Controller class; the class contains business + * logic behind the application to decorate the scope with functions and values * - * Note that an alternative way to define controllers is via the {@link ngRoute.$route $route} service. + * Note that you can also attach controllers to the DOM by declaring it in a route definition + * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller + * again using `ng-controller` in the template itself. This will cause the controller to be attached + * and executed twice. * * @element ANY * @scope * @param {expression} ngController Name of a globally accessible constructor function or an * {@link guide/expression expression} that on the current scope evaluates to a - * constructor function. The controller instance can further be published into the scope - * by adding `as localName` the controller name attribute. + * constructor function. The controller instance can be published into a scope property + * by specifying `as propertyName`. * * @example * Here is a simple form for editing user contact information. Adding, removing, clearing, and @@ -15168,8 +17197,8 @@ var ngCloakDirective = ngDirective({ * easily be called from the angular markup. Notice that the scope becomes the `this` for the * controller's instance. This allows for easy access to the view data from the controller. Also * notice that any changes to the data are automatically reflected in the View without the need - * for a manual update. The example is included in two different declaration styles based on - * your style preferences. + * for a manual update. The example is shown in two different declaration styles you may use + * according to preference. +
+
+
+ list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}}; +
+
+
- it('should check greeting', function() { - expect(binding('greeting')).toBe('Hello'); - expect(binding('person')).toBe('World'); + it('should alias index positions', function() { + expect(element('.example-init').text()) + .toBe('list[ 0 ][ 0 ] = a;' + + 'list[ 0 ][ 1 ] = b;' + + 'list[ 1 ][ 0 ] = c;' + + 'list[ 1 ][ 1 ] = d;'); });
@@ -15999,24 +18105,27 @@ var ngInitDirective = ngDirective({ pre: function(scope, element, attrs) { scope.$eval(attrs.ngInit); } - } + }; } }); /** * @ngdoc directive * @name ng.directive:ngNonBindable + * @restrict AC * @priority 1000 * * @description - * Sometimes it is necessary to write code which looks like bindings but which should be left alone - * by angular. Use `ngNonBindable` to make angular ignore a chunk of HTML. + * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current + * DOM element. This is useful if the element contains what appears to be Angular directives and + * bindings but which should be ignored by Angular. This could be the case if you have a site that + * displays snippets of code, for instance. * * @element ANY * * @example - * In this example there are two location where a simple binding (`{{}}`) is present, but the one - * wrapped in `ngNonBindable` is left alone. + * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, + * but the one wrapped in `ngNonBindable` is left alone. * * @example @@ -16323,17 +18432,17 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These * formats are currently supported: * - * * `variable in expression` – where variable is the user defined loop variable and `expression` + * * `variable in expression` – where variable is the user defined loop variable and `expression` * is a scope expression giving the collection to enumerate. * * For example: `album in artist.albums`. * - * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, + * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, * and `expression` is the scope expression giving the collection to enumerate. * * For example: `(name, age) in {'adam':10, 'amalie':12}`. * - * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function + * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function * which can be used to associate the objects in the collection with the DOM elements. If no tracking function * is specified the ng-repeat associates elements by identity in the collection. It is an error to have * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are @@ -16387,49 +18496,35 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp border:1px solid black; list-style:none; margin:0; - padding:0; + padding:0 10px; } - .example-animate-container > li { - padding:10px; + .animate-repeat { + line-height:40px; list-style:none; + box-sizing:border-box; } + .animate-repeat.ng-move, .animate-repeat.ng-enter, - .animate-repeat.ng-leave, - .animate-repeat.ng-move { + .animate-repeat.ng-leave { -webkit-transition:all linear 0.5s; - -moz-transition:all linear 0.5s; - -o-transition:all linear 0.5s; transition:all linear 0.5s; } + .animate-repeat.ng-leave.ng-leave-active, + .animate-repeat.ng-move, .animate-repeat.ng-enter { - line-height:0; opacity:0; - padding-top:0; - padding-bottom:0; + max-height:0; } + + .animate-repeat.ng-leave, + .animate-repeat.ng-move.ng-move-active, .animate-repeat.ng-enter.ng-enter-active { - line-height:20px; opacity:1; - padding:10px; + max-height:40px; } - - .animate-repeat.ng-leave { - opacity:1; - line-height:20px; - padding:10px; - } - .animate-repeat.ng-leave.ng-leave-active { - opacity:0; - line-height:0; - padding-top:0; - padding-bottom:0; - } - - .animate-repeat.ng-move { } - .animate-repeat.ng-move.ng-move-active { }
it('should render initial data set', function() { @@ -16461,11 +18556,13 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { transclude: 'element', priority: 1000, terminal: true, + $$tlb: true, compile: function(element, attr, linker) { return function($scope, $element, $attr){ var expression = $attr.ngRepeat; var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/), - trackByExp, trackByExpGetter, trackByIdFn, trackByIdArrayFn, trackByIdObjFn, lhs, rhs, valueIdentifier, keyIdentifier, + trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, + lhs, rhs, valueIdentifier, keyIdentifier, hashFnLocals = {$id: hashKey}; if (!match) { @@ -16479,7 +18576,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { if (trackByExp) { trackByExpGetter = $parse(trackByExp); - trackByIdFn = function(key, value, index) { + trackByIdExpFn = function(key, value, index) { // assign key, value, and $index to the locals so that they can be used in hash functions if (keyIdentifier) hashFnLocals[keyIdentifier] = key; hashFnLocals[valueIdentifier] = value; @@ -16489,10 +18586,10 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { } else { trackByIdArrayFn = function(key, value) { return hashKey(value); - } + }; trackByIdObjFn = function(key) { return key; - } + }; } match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); @@ -16522,16 +18619,18 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { childScope, key, value, // key/value of iteration trackById, + trackByIdFn, collectionKeys, block, // last object information {scope, element, id} - nextBlockOrder = []; + nextBlockOrder = [], + elementsToRemove; if (isArrayLike(collection)) { collectionKeys = collection; - trackByIdFn = trackByIdFn || trackByIdArrayFn; + trackByIdFn = trackByIdExpFn || trackByIdArrayFn; } else { - trackByIdFn = trackByIdFn || trackByIdObjFn; + trackByIdFn = trackByIdExpFn || trackByIdObjFn; // if object, extract keys, sort them and use to determine order of iteration over obj props collectionKeys = []; for (key in collection) { @@ -16550,8 +18649,9 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; trackById = trackByIdFn(key, value, index); + assertNotHasOwnProperty(trackById, '`track by` id'); if(lastBlockMap.hasOwnProperty(trackById)) { - block = lastBlockMap[trackById] + block = lastBlockMap[trackById]; delete lastBlockMap[trackById]; nextBlockMap[trackById] = block; nextBlockOrder[index] = block; @@ -16572,10 +18672,12 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { // remove existing items for (key in lastBlockMap) { + // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn if (lastBlockMap.hasOwnProperty(key)) { block = lastBlockMap[key]; - $animate.leave(block.elements); - forEach(block.elements, function(element) { element[NG_REMOVED] = true}); + elementsToRemove = getBlockElements(block); + $animate.leave(elementsToRemove); + forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); block.scope.$destroy(); } } @@ -16585,6 +18687,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; block = nextBlockOrder[index]; + if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode; if (block.startNode) { // if we have already seen this object, then we need to reuse the @@ -16596,11 +18699,9 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { nextNode = nextNode.nextSibling; } while(nextNode && nextNode[NG_REMOVED]); - if (block.startNode == nextNode) { - // do nothing - } else { + if (block.startNode != nextNode) { // existing item which got moved - $animate.move(block.elements, null, jqLite(previousNode)); + $animate.move(getBlockElements(block), null, jqLite(previousNode)); } previousNode = block.endNode; } else { @@ -16614,15 +18715,17 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { childScope.$first = (index === 0); childScope.$last = (index === (arrayLength - 1)); childScope.$middle = !(childScope.$first || childScope.$last); - childScope.$odd = !(childScope.$even = index%2==0); + // jshint bitwise: false + childScope.$odd = !(childScope.$even = (index&1) === 0); + // jshint bitwise: true if (!block.startNode) { linker(childScope, function(clone) { + clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); $animate.enter(clone, null, jqLite(previousNode)); previousNode = clone; block.scope = childScope; - block.startNode = clone[0]; - block.elements = clone; + block.startNode = previousNode && previousNode.endNode ? previousNode.endNode : clone[0]; block.endNode = clone[clone.length - 1]; nextBlockMap[block.id] = block; }); @@ -16640,10 +18743,11 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { * @name ng.directive:ngShow * * @description - * The `ngShow` directive shows and hides the given HTML element conditionally based on the expression - * provided to the ngShow attribute. The show and hide mechanism is a achieved by removing and adding - * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is a predefined CSS class present - * in AngularJS which sets the display style to none (using an !important flag). + * The `ngShow` directive shows or hides the given HTML element based on the expression + * provided to the ngShow attribute. The element is shown or hidden by removing or adding + * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined + * in AngularJS and sets the display style to none (using an !important flag). + * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). * *
  * 
@@ -16689,9 +18793,9 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
  * ## A note about animations with ngShow
  *
  * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
- * is true and false. This system works similar to the animation system present with ngClass, however, the
- * only difference is that you must also include the !important flag to override the display property so
- * that you can perform an animation when the element is hidden during the time of the animation.
+ * is true and false. This system works like the animation system present with ngClass except that
+ * you must also include the !important flag to override the display property
+ * so that you can perform an animation when the element is hidden during the time of the animation.
  *
  * 
  * //
@@ -16734,24 +18838,9 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
       
     
     
-      .animate-show.ng-hide-add, 
-      .animate-show.ng-hide-remove {
+      .animate-show {
         -webkit-transition:all linear 0.5s;
-        -moz-transition:all linear 0.5s;
-        -o-transition:all linear 0.5s;
         transition:all linear 0.5s;
-        display:block!important;
-      }
-
-      .animate-show.ng-hide-add.ng-hide-add-active,
-      .animate-show.ng-hide-remove {
-        line-height:0;
-        opacity:0;
-        padding:0 10px;
-      }
-
-      .animate-show.ng-hide-add,
-      .animate-show.ng-hide-remove.ng-hide-remove-active {
         line-height:20px;
         opacity:1;
         padding:10px;
@@ -16759,6 +18848,17 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
         background:white;
       }
 
+      .animate-show.ng-hide-add,
+      .animate-show.ng-hide-remove {
+        display:block!important;
+      }
+
+      .animate-show.ng-hide {
+        line-height:0;
+        opacity:0;
+        padding:0 10px;
+      }
+
       .check-element {
         padding:10px;
         border:1px solid black;
@@ -16792,10 +18892,11 @@ var ngShowDirective = ['$animate', function($animate) {
  * @name ng.directive:ngHide
  *
  * @description
- * The `ngHide` directive shows and hides the given HTML element conditionally based on the expression
- * provided to the ngHide attribute. The show and hide mechanism is a achieved by removing and adding
- * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is a predefined CSS class present
- * in AngularJS which sets the display style to none (using an !important flag).
+ * The `ngHide` directive shows or hides the given HTML element based on the expression
+ * provided to the ngHide attribute. The element is shown or hidden by removing or adding
+ * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
+ * in AngularJS and sets the display style to none (using an !important flag).
+ * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
  *
  * 
  * 
@@ -16841,8 +18942,8 @@ var ngShowDirective = ['$animate', function($animate) {
  * ## A note about animations with ngHide
  *
  * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
- * is true and false. This system works similar to the animation system present with ngClass, however, the
- * only difference is that you must also include the !important flag to override the display property so
+ * is true and false. This system works like the animation system present with ngClass, except that
+ * you must also include the !important flag to override the display property so
  * that you can perform an animation when the element is hidden during the time of the animation.
  *
  * 
@@ -16886,24 +18987,9 @@ var ngShowDirective = ['$animate', function($animate) {
       
     
     
-      .animate-hide.ng-hide-add, 
-      .animate-hide.ng-hide-remove {
+      .animate-hide {
         -webkit-transition:all linear 0.5s;
-        -moz-transition:all linear 0.5s;
-        -o-transition:all linear 0.5s;
         transition:all linear 0.5s;
-        display:block!important;
-      }
-
-      .animate-hide.ng-hide-add.ng-hide-add-active,
-      .animate-hide.ng-hide-remove {
-        line-height:0;
-        opacity:0;
-        padding:0 10px;
-      }
-
-      .animate-hide.ng-hide-add,
-      .animate-hide.ng-hide-remove.ng-hide-remove-active {
         line-height:20px;
         opacity:1;
         padding:10px;
@@ -16911,6 +18997,17 @@ var ngShowDirective = ['$animate', function($animate) {
         background:white;
       }
 
+      .animate-hide.ng-hide-add,
+      .animate-hide.ng-hide-remove {
+        display:block!important;
+      }
+
+      .animate-hide.ng-hide {
+        line-height:0;
+        opacity:0;
+        padding:0 10px;
+      }
+
       .check-element {
         padding:10px;
         border:1px solid black;
@@ -16941,6 +19038,7 @@ var ngHideDirective = ['$animate', function($animate) {
 /**
  * @ngdoc directive
  * @name ng.directive:ngStyle
+ * @restrict AC
  *
  * @description
  * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
@@ -17004,7 +19102,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
  * attribute is displayed.
  *
  * @animations
- * enter - happens after the ngSwtich contents change and the matched child element is placed inside the container
+ * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
  * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
  *
  * @usage
@@ -17015,6 +19113,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
  * 
  *
  * @scope
+ * @priority 800
  * @param {*} ngSwitch|on expression to match against ng-switch-when.
  * @paramDescription
  * On child elements add:
@@ -17037,9 +19136,9 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
         
-
Settings Div
-
Home Span
-
default
+
Settings Div
+
Home Span
+
default
@@ -17058,15 +19157,12 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) { overflow:hidden; } - .animate-switch-container > div { + .animate-switch { padding:10px; } - .animate-switch-container > .ng-enter, - .animate-switch-container > .ng-leave { + .animate-switch.ng-animate { -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; position:absolute; @@ -17076,19 +19172,14 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) { bottom:0; } - .animate-switch-container > .ng-enter { + .animate-switch.ng-leave.ng-leave-active, + .animate-switch.ng-enter { top:-50px; } - .animate-switch-container > .ng-enter.ng-enter-active { + .animate-switch.ng-leave, + .animate-switch.ng-enter.ng-enter-active { top:0; } - - .animate-switch-container > .ng-leave { - top:0; - } - .animate-switch-container > .ng-leave.ng-leave-active { - top:50px; - } it('should start in settings', function() { @@ -17144,12 +19235,12 @@ var ngSwitchDirective = ['$animate', function($animate) { } }); } - } + }; }]; var ngSwitchWhenDirective = ngDirective({ transclude: 'element', - priority: 500, + priority: 800, require: '^ngSwitch', compile: function(element, attrs, transclude) { return function(scope, element, attr, ctrl) { @@ -17161,7 +19252,7 @@ var ngSwitchWhenDirective = ngDirective({ var ngSwitchDefaultDirective = ngDirective({ transclude: 'element', - priority: 500, + priority: 800, require: '^ngSwitch', compile: function(element, attrs, transclude) { return function(scope, element, attr, ctrl) { @@ -17174,9 +19265,12 @@ var ngSwitchDefaultDirective = ngDirective({ /** * @ngdoc directive * @name ng.directive:ngTransclude + * @restrict AC * * @description - * Insert the transcluded DOM here. + * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. + * + * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. * * @element ANY * @@ -17220,27 +19314,38 @@ var ngSwitchDefaultDirective = ngDirective({ * */ var ngTranscludeDirective = ngDirective({ - controller: ['$transclude', '$element', '$scope', function($transclude, $element, $scope) { - // use evalAsync so that we don't process transclusion before directives on the parent element even when the - // transclusion replaces the current element. (we can't use priority here because that applies only to compile fns - // and not controllers - $scope.$evalAsync(function() { - $transclude(function(clone) { - $element.append(clone); - }); + controller: ['$element', '$transclude', function($element, $transclude) { + if (!$transclude) { + throw minErr('ngTransclude')('orphan', + 'Illegal use of ngTransclude directive in the template! ' + + 'No parent directive that requires a transclusion found. ' + + 'Element: {0}', + startingTag($element)); + } + + // remember the transclusion fn but call it during linking so that we don't process transclusion before directives on + // the parent element even when the transclusion replaces the current element. (we can't use priority here because + // that applies only to compile fns and not controllers + this.$transclude = $transclude; + }], + + link: function($scope, $element, $attrs, controller) { + controller.$transclude(function(clone) { + $element.html(''); + $element.append(clone); }); - }] + } }); /** * @ngdoc directive * @name ng.directive:script + * @restrict E * * @description * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the * template can be used by `ngInclude`, `ngView` or directive templates. * - * @restrict E * @param {'text/ng-template'} type must be set to `'text/ng-template'` * * @example @@ -17277,6 +19382,7 @@ var scriptDirective = ['$templateCache', function($templateCache) { }; }]; +var ngOptionsMinErr = minErr('ngOptions'); /** * @ngdoc directive * @name ng.directive:select @@ -17287,22 +19393,22 @@ var scriptDirective = ['$templateCache', function($templateCache) { * * # `ngOptions` * - * Optionally `ngOptions` attribute can be used to dynamically generate a list of `
Name - (^) Phone Number Age