diff --git a/js/lib/angular-mocks.js b/js/lib/angular-mocks.js old mode 100644 new mode 100755 index 9fec971..889f6ad --- a/js/lib/angular-mocks.js +++ b/js/lib/angular-mocks.js @@ -1,6 +1,5 @@ - /** - * @license AngularJS v"NG_VERSION_FULL" + * @license AngularJS v1.0.6 * (c) 2010-2012 Google, Inc. http://angularjs.org * License: MIT * @@ -203,6 +202,30 @@ angular.mock.$Browser.prototype = { * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration * information. + * + * + *
+ *   describe('$exceptionHandlerProvider', function() {
+ *
+ *     it('should capture log messages and exceptions', function() {
+ *
+ *       module(function($exceptionHandlerProvider) {
+ *         $exceptionHandlerProvider.mode('log');
+ *       });
+ *
+ *       inject(function($log, $exceptionHandler, $timeout) {
+ *         $timeout(function() { $log.log(1); });
+ *         $timeout(function() { $log.log(2); throw 'banana peel'; });
+ *         $timeout(function() { $log.log(3); });
+ *         expect($exceptionHandler.errors).toEqual([]);
+ *         expect($log.assertEmpty());
+ *         $timeout.flush();
+ *         expect($exceptionHandler.errors).toEqual(['banana peel']);
+ *         expect($log.log.logs).toEqual([[1], [2], [3]]);
+ *       });
+ *     });
+ *   });
+ * 
*/ angular.mock.$ExceptionHandlerProvider = function() { @@ -221,8 +244,8 @@ angular.mock.$ExceptionHandlerProvider = function() { * - `rethrow`: If any errors are are passed into the handler in tests, it typically * means that there is a bug in the application or test, so this mock will * make these tests fail. - * - `log`: Sometimes it is desirable to test that an error is throw, for this case the `log` mode stores the - * error and allows later assertion of it. + * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an + * array of errors in `$exceptionHandler.errors`, to allow later assertion of them. * See {@link ngMock.$log#assertEmpty assertEmpty()} and * {@link ngMock.$log#reset reset()} */ @@ -407,7 +430,7 @@ angular.mock.$LogProvider = function() { * * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. * - * Mock of the Date type which has its timezone specified via constroctor arg. + * Mock of the Date type which has its timezone specified via constructor arg. * * The main purpose is to create Date-like instances with timezone fixed to the specified timezone * offset, so that we can test code that depends on local timezone settings without dependency on @@ -562,7 +585,7 @@ angular.mock.$LogProvider = function() { /** * @ngdoc function - * @name angular.mock.debug + * @name angular.mock.dump * @description * * *NOTE*: this is not an injectable instance, just a globally available function. @@ -745,7 +768,7 @@ angular.mock.dump = function(object) { } // testing controller - var $http; + var $httpBackend; beforeEach(inject(function($injector) { $httpBackend = $injector.get('$httpBackend'); @@ -1591,9 +1614,29 @@ window.jasmine && (function(window) { afterEach(function() { var spec = getCurrentSpec(); + var injector = spec.$injector; + spec.$injector = null; spec.$modules = null; + + if (injector) { + injector.get('$rootElement').unbind(); + injector.get('$browser').pollFns.length = 0; + } + angular.mock.clearDataCache(); + + // clean up jquery's fragment cache + angular.forEach(angular.element.fragments, function(val, key) { + delete angular.element.fragments[key]; + }); + + MockXhr.$$lastInstance = null; + + angular.forEach(angular.callbacks, function(val, key) { + delete angular.callbacks[key]; + }); + angular.callbacks.counter = 0; }); function getCurrentSpec() { @@ -1610,7 +1653,7 @@ window.jasmine && (function(window) { * @name angular.mock.module * @description * - * *NOTE*: This is function is also published on window for easy access.
+ * *NOTE*: This function is also published on window for easy access.
* *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. * * This function registers a module configuration code. It collects the configuration information @@ -1644,8 +1687,12 @@ window.jasmine && (function(window) { * @name angular.mock.inject * @description * +<<<<<<< HEAD * *NOTE*: This is function is also published on window for easy access.
* *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. +======= + * *NOTE*: This function is also published on window for easy access.
+>>>>>>> 8dca056... docs(mocks): fix typos * * The inject function wraps a function into an injectable function. The inject() creates new * instance of {@link AUTO.$injector $injector} per test, which is then used for @@ -1694,7 +1741,7 @@ window.jasmine && (function(window) { */ window.inject = angular.mock.inject = function() { var blockFns = Array.prototype.slice.call(arguments, 0); - var stack = new Error('Declaration Location').stack; + var errorForStack = new Error('Declaration Location'); return isSpecRunning() ? workFn() : workFn; ///////////////////// function workFn() { @@ -1710,10 +1757,12 @@ window.jasmine && (function(window) { try { injector.invoke(blockFns[i] || angular.noop, this); } catch (e) { - if(e.stack) e.stack += '\n' + stack; + if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; throw e; + } finally { + errorForStack = null; } } } - } + }; })(window); diff --git a/js/lib/angular.js b/js/lib/angular.js old mode 100644 new mode 100755 index 8d73af9..5b431ae --- a/js/lib/angular.js +++ b/js/lib/angular.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.0.1 + * @license AngularJS v1.0.6 * (c) 2010-2012 Google, Inc. http://angularjs.org * License: MIT */ @@ -34,12 +34,12 @@ var uppercase = function(string){return isString(string) ? string.toUpperCase() var manualLowercase = function(s) { return isString(s) - ? s.replace(/[A-Z]/g, function(ch) {return fromCharCode(ch.charCodeAt(0) | 32);}) + ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) : s; }; var manualUppercase = function(s) { return isString(s) - ? s.replace(/[a-z]/g, function(ch) {return fromCharCode(ch.charCodeAt(0) & ~32);}) + ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) : s; }; @@ -52,11 +52,8 @@ if ('i' !== 'I'.toLowerCase()) { uppercase = manualUppercase; } -function fromCharCode(code) {return String.fromCharCode(code);} - -var Error = window.Error, - /** holds major version number for IE or NaN for real browsers */ +var /** holds major version number for IE or NaN for real browsers */ msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]), jqLite, // delay binding since jQuery could be loaded after us. jQuery, // delay binding @@ -97,6 +94,30 @@ var Error = window.Error, * @param {Object=} context Object to become context (`this`) for the iterator function. * @returns {Object|Array} Reference to `obj`. */ + + +/** + * @private + * @param {*} obj + * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...) + */ +function isArrayLike(obj) { + if (!obj || (typeof obj.length !== 'number')) return false; + + // We have on object which has length property. Should we treat it as array? + if (typeof obj.hasOwnProperty != 'function' && + typeof obj.constructor != 'function') { + // This is here for IE8: it is a bogus object treat it as array; + return true; + } else { + return obj instanceof JQLite || // JQLite + (jQuery && obj instanceof jQuery) || // jQuery + toString.call(obj) !== '[object Object]' || // some browser native object + typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj) + } +} + + function forEach(obj, iterator, context) { var key; if (obj) { @@ -108,7 +129,7 @@ function forEach(obj, iterator, context) { } } else if (obj.forEach && obj.forEach !== forEach) { obj.forEach(iterator, context); - } else if (isObject(obj) && isNumber(obj.length)) { + } else if (isArrayLike(obj)) { for (key = 0; key < obj.length; key++) iterator.call(context, obj[key], key); } else { @@ -153,7 +174,7 @@ function reverseParams(iteratorFn) { /** * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric * characters such as '012ABC'. The reason why we are not using simply a number counter is that - * the number string gets longer over time, and it can also overflow, where as the the nextId + * the number string gets longer over time, and it can also overflow, where as the nextId * will grow much slower, it is a string, and it will never overflow. * * @returns an unique alpha-numeric string @@ -549,9 +570,7 @@ function copy(source, destination){ } else { if (source === destination) throw Error("Can't copy equivalent objects or arrays"); if (isArray(source)) { - while(destination.length) { - destination.pop(); - } + destination.length = 0; for ( var i = 0; i < source.length; i++) { destination.push(copy(source[i])); } @@ -627,13 +646,15 @@ function equals(o1, o2) { if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2)) return false; keySet = {}; for(key in o1) { - if (key.charAt(0) !== '$' && !isFunction(o1[key]) && !equals(o1[key], o2[key])) { - return false; - } + if (key.charAt(0) === '$' || isFunction(o1[key])) continue; + if (!equals(o1[key], o2[key])) return false; keySet[key] = true; } for(key in o2) { - if (!keySet[key] && key.charAt(0) !== '$' && !isFunction(o2[key])) return false; + if (!keySet[key] && + key.charAt(0) !== '$' && + o2[key] !== undefined && + !isFunction(o2[key])) return false; } return true; } @@ -760,9 +781,18 @@ function startingTag(element) { // are not allowed to have children. So we just ignore it. element.html(''); } catch(e) {} - return jqLite('
').append(element).html(). - match(/^(<[^>]+>)/)[1]. - replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); + // As Per DOM Standards + var TEXT_NODE = 3; + var elemHtml = jqLite('
').append(element).html(); + try { + return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : + elemHtml. + match(/^(<[^>]+>)/)[1]. + replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); + } catch(e) { + return lowercase(elemHtml); + } + } @@ -794,7 +824,7 @@ function toKeyValue(obj) { /** - * We need our custom mehtod because encodeURIComponent is too agressive and doesn't follow + * We need our custom method because encodeURIComponent is too agressive and doesn't follow * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path * segments: * segment = *pchar @@ -829,7 +859,7 @@ function encodeUriQuery(val, pctEncodeSpaces) { replace(/%3A/gi, ':'). replace(/%24/g, '$'). replace(/%2C/gi, ','). - replace((pctEncodeSpaces ? null : /%20/g), '+'); + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); } @@ -838,7 +868,7 @@ function encodeUriQuery(val, pctEncodeSpaces) { * @name ng.directive:ngApp * * @element ANY - * @param {angular.Module} ngApp on optional application + * @param {angular.Module} ngApp an optional application * {@link angular.module module} name to load. * * @description @@ -846,7 +876,7 @@ function encodeUriQuery(val, pctEncodeSpaces) { * Use this directive to auto-bootstrap on application. Only * one directive can be used per HTML document. The directive * designates the root of the application and is typically placed - * ot the root of the page. + * at the root of the page. * * In the example below if the `ngApp` directive would not be placed * on the `html` element then the document would not be compiled @@ -918,22 +948,38 @@ function angularInit(element, bootstrap) { * @returns {AUTO.$injector} Returns the newly created injector for this app. */ function bootstrap(element, modules) { - element = jqLite(element); - modules = modules || []; - modules.unshift(['$provide', function($provide) { - $provide.value('$rootElement', element); - }]); - modules.unshift('ng'); - var injector = createInjector(modules); - injector.invoke( - ['$rootScope', '$rootElement', '$compile', '$injector', function(scope, element, compile, injector){ - scope.$apply(function() { - element.data('$injector', injector); - compile(element)(scope); - }); - }] - ); - return injector; + var resumeBootstrapInternal = function() { + element = jqLite(element); + modules = modules || []; + modules.unshift(['$provide', function($provide) { + $provide.value('$rootElement', element); + }]); + modules.unshift('ng'); + var injector = createInjector(modules); + injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', + function(scope, element, compile, injector) { + scope.$apply(function() { + element.data('$injector', injector); + compile(element)(scope); + }); + }] + ); + return injector; + }; + + var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; + + if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { + return resumeBootstrapInternal(); + } + + window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); + angular.resumeBootstrap = function(extraModules) { + forEach(extraModules, function(module) { + modules.push(module); + }); + resumeBootstrapInternal(); + }; } var SNAKE_CASE_REGEXP = /[A-Z]/g; @@ -1015,7 +1061,7 @@ function setupModuleLoader(window) { * * # Module * - * A module is a collocation of services, directives, filters, and configure information. Module + * A module is a collocation of services, directives, filters, and configuration information. Module * is used to configure the {@link AUTO.$injector $injector}. * *
@@ -1045,7 +1091,7 @@ function setupModuleLoader(window) {
      * @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 {Function} configFn Option configuration function for the module. Same as
+     * @param {Function} configFn Optional configuration function for the module. Same as
      *        {@link angular.Module#config Module#config()}.
      * @returns {module} new module with the {@link angular.Module} api.
      */
@@ -1178,7 +1224,7 @@ function setupModuleLoader(window) {
            * @param {Function} directiveFactory Factory function for creating new instance of
            * directives.
            * @description
-           * See {@link ng.$compileProvider.directive $compileProvider.directive()}.
+           * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
            */
           directive: invokeLater('$compileProvider', 'directive'),
 
@@ -1200,8 +1246,8 @@ function setupModuleLoader(window) {
            * @param {Function} initializationFn Execute this function after injector creation.
            *    Useful for application initialization.
            * @description
-           * Use this method to register work which needs to be performed when the injector with
-           * with the current module is finished loading.
+           * Use this method to register work which should be performed when the injector is done
+           * loading all modules.
            */
           run: function(block) {
             runBlocks.push(block);
@@ -1240,18 +1286,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.0.1',    // all of these placeholder strings will be replaced by rake's
-  major: 1,    // compile task
+  full: '1.0.6',    // all of these placeholder strings will be replaced by grunt's
+  major: 1,    // package task
   minor: 0,
-  dot: 1,
-  codeName: 'thorium-shielding'
+  dot: 6,
+  codeName: 'universal-irreversibility'
 };
 
 
@@ -1418,11 +1464,12 @@ function publishExternalAPI(angular){
  * - [replaceWith()](http://api.jquery.com/replaceWith/)
  * - [text()](http://api.jquery.com/text/)
  * - [toggleClass()](http://api.jquery.com/toggleClass/)
+ * - [triggerHandler()](http://api.jquery.com/triggerHandler/) - Doesn't pass native event objects to handlers.
  * - [unbind()](http://api.jquery.com/unbind/)
  * - [val()](http://api.jquery.com/val/)
  * - [wrap()](http://api.jquery.com/wrap/)
  *
- * ## In addtion to the above, Angular privides an additional method to both jQuery and jQuery lite:
+ * ## In addtion to the above, Angular provides additional methods to both jQuery and jQuery lite:
  *
  * - `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
@@ -1493,12 +1540,7 @@ function JQLitePatchJQueryRemove(name, dispatchThis) {
       for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
         element = jqLite(set[setIndex]);
         if (fireEvent) {
-          events = element.data('events');
-          if ( (fns = events && events.$destroy) ) {
-            forEach(fns, function(fn){
-              fn.handler();
-            });
-          }
+          element.triggerHandler('$destroy');
         } else {
           fireEvent = !fireEvent;
         }
@@ -1529,7 +1571,7 @@ function JQLite(element) {
     var div = document.createElement('div');
     // Read about the NoScope elements here:
     // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
-    div.innerHTML = '
 
' + element; // IE insanity to make NoScope elements work! + div.innerHTML = '
 
' + element; // IE insanity to make NoScope elements work! div.removeChild(div.firstChild); // remove the superfluous div JQLiteAddNodes(this, div.childNodes); this.remove(); // detach the elements from the temporary DOM div. @@ -1630,9 +1672,9 @@ function JQLiteHasClass(element, selector) { indexOf( " " + selector + " " ) > -1); } -function JQLiteRemoveClass(element, selector) { - if (selector) { - forEach(selector.split(' '), function(cssClass) { +function JQLiteRemoveClass(element, cssClasses) { + if (cssClasses) { + forEach(cssClasses.split(' '), function(cssClass) { element.className = trim( (" " + element.className + " ") .replace(/[\n\t]/g, " ") @@ -1642,9 +1684,9 @@ function JQLiteRemoveClass(element, selector) { } } -function JQLiteAddClass(element, selector) { - if (selector) { - forEach(selector.split(' '), function(cssClass) { +function JQLiteAddClass(element, cssClasses) { + if (cssClasses) { + forEach(cssClasses.split(' '), function(cssClass) { if (!JQLiteHasClass(element, cssClass)) { element.className = trim(element.className + ' ' + trim(cssClass)); } @@ -2015,14 +2057,14 @@ forEach({ children: function(element) { var children = []; forEach(element.childNodes, function(element){ - if (element.nodeName != '#text') + if (element.nodeType === 1) children.push(element); }); return children; }, contents: function(element) { - return element.childNodes; + return element.childNodes || []; }, append: function(element, node) { @@ -2085,14 +2127,31 @@ forEach({ }, next: function(element) { - return element.nextSibling; + if (element.nextElementSibling) { + return element.nextElementSibling; + } + + // IE8 doesn't have nextElementSibling + var elm = element.nextSibling; + while (elm != null && elm.nodeType !== 1) { + elm = elm.nextSibling; + } + return elm; }, find: function(element, selector) { return element.getElementsByTagName(selector); }, - clone: JQLiteClone + clone: JQLiteClone, + + triggerHandler: function(element, eventName) { + var eventFns = (JQLiteExpandoStore(element, 'events') || {})[eventName]; + + forEach(eventFns, function(fn) { + fn.call(element, null); + }); + } }, function(fn, name){ /** * chaining functions @@ -2210,6 +2269,16 @@ HashQueueMap.prototype = { return array.shift(); } } + }, + + /** + * return the first item without deleting it + */ + peek: function(key) { + var array = this[hashKey(key)]; + if (array) { + return array[0]; + } } }; @@ -2233,7 +2302,7 @@ HashQueueMap.prototype = { * // create an injector * var $injector = angular.injector(['ng']); * - * // use the injector to kick of your application + * // use the injector to kick off your application * // use the type inference to auto inject arguments, or use implicit injection * $injector.invoke(function($rootScope, $compile, $document){ * $compile($document)($rootScope); @@ -2253,7 +2322,7 @@ HashQueueMap.prototype = { var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; -var FN_ARG = /^\s*(_?)(.+?)\1\s*$/; +var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; function annotate(fn) { var $inject, @@ -2313,15 +2382,15 @@ function annotate(fn) { * *
  *   // inferred (only works if code not minified/obfuscated)
- *   $inject.invoke(function(serviceA){});
+ *   $injector.invoke(function(serviceA){});
  *
  *   // annotated
  *   function explicit(serviceA) {};
  *   explicit.$inject = ['serviceA'];
- *   $inject.invoke(explicit);
+ *   $injector.invoke(explicit);
  *
  *   // inline
- *   $inject.invoke(['serviceA', function(serviceA){}]);
+ *   $injector.invoke(['serviceA', function(serviceA){}]);
  * 
* * ## Inference @@ -2405,7 +2474,7 @@ function annotate(fn) { * This method does not work with code minfication / obfuscation. For this reason the following annotation strategies * are supported. * - * # The `$injector` property + * # 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. @@ -2467,7 +2536,7 @@ 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 the `Provider` suffixed to them. + * The providers share the same name as the instance they create with `Provider` suffixed to them. * * 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. @@ -2653,7 +2722,7 @@ function createInjector(modulesToLoad) { } function provider(name, provider_) { - if (isFunction(provider_)) { + if (isFunction(provider_) || isArray(provider_)) { provider_ = providerInjector.instantiate(provider_); } if (!provider_.$get) { @@ -2770,7 +2839,7 @@ function createInjector(modulesToLoad) { args.push( locals && locals.hasOwnProperty(key) ? locals[key] - : getService(key, path) + : getService(key) ); } if (!fn.$inject) { @@ -2815,6 +2884,7 @@ function createInjector(modulesToLoad) { }; } } + /** * @ngdoc function * @name ng.$anchorScroll @@ -2870,11 +2940,12 @@ function $AnchorScrollProvider() { } // does not scroll when user clicks on anchor link that is currently on - // (no url change, no $locaiton.hash() change), browser native does scroll + // (no url change, no $location.hash() change), browser native does scroll if (autoScrollingEnabled) { - $rootScope.$watch(function() {return $location.hash();}, function() { - $rootScope.$evalAsync(scroll); - }); + $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, + function autoScrollWatchAction() { + $rootScope.$evalAsync(scroll); + }); } return scroll; @@ -3118,7 +3189,7 @@ function Browser(window, document, $log, $sniffer) { */ self.baseHref = function() { var href = baseElement.attr('href'); - return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href; + return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : ''; }; ////////////////////////////////////////////////////////////// @@ -3157,14 +3228,15 @@ function Browser(window, document, $log, $sniffer) { } else { if (isString(value)) { cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1; + + // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: + // - 300 cookies + // - 20 cookies per unique domain + // - 4096 bytes per cookie if (cookieLength > 4096) { $log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+ cookieLength + " > 4096 bytes)!"); } - if (lastCookies.length > 20) { - $log.warn("Cookie '"+ name +"' possibly not set or overflowed because too many cookies " + - "were already set (" + lastCookies.length + " > 20 )"); - } } } } else { @@ -3241,6 +3313,7 @@ function $BrowserProvider(){ return new Browser($window, $document, $log, $sniffer); }]; } + /** * @ngdoc object * @name ng.$cacheFactory @@ -3252,16 +3325,16 @@ function $BrowserProvider(){ * @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. - * - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache. - * - `{{*}} `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. + * - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache. + * - `{{*}}` `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() { @@ -3313,6 +3386,8 @@ function $CacheFactoryProvider() { remove: function(key) { var lruEntry = lruHash[key]; + if (!lruEntry) return; + if (lruEntry == freshEnd) freshEnd = lruEntry.p; if (lruEntry == staleEnd) staleEnd = lruEntry.n; link(lruEntry.n,lruEntry.p); @@ -3441,7 +3516,7 @@ var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: '; * 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 + * {@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. * @@ -3559,42 +3634,30 @@ var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: '; * * @description */ - -/** - * @ngdoc function - * @name ng.$compileProvider#directive - * @methodOf ng.$compileProvider - * @function - * - * @description - * Register a new directive with compiler - * - * @param {string} name name of the directive. - * @param {function} directiveFactory An injectable directive factory function. - * @returns {ng.$compileProvider} Self for chaining. - */ $CompileProvider.$inject = ['$provide']; function $CompileProvider($provide) { var hasDirectives = {}, Suffix = 'Directive', COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, - MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: '; + MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ', + urlSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/; /** * @ngdoc function - * @name ng.$compileProvider.directive + * @name ng.$compileProvider#directive * @methodOf ng.$compileProvider * @function * * @description - * Register directives with the compiler. + * Register a new directives with the compiler. * * @param {string} name Name of the directive in camel-case. (ie ngBind which will match as * ng-bind). * @param {function} directiveFactory An injectable directive factroy function. See {@link guide/directive} for more * info. + * @returns {ng.$compileProvider} Self for chaining. */ this.directive = function registerDirective(name, directiveFactory) { if (isString(name)) { @@ -3632,11 +3695,41 @@ function $CompileProvider($provide) { }; + /** + * @ngdoc function + * @name ng.$compileProvider#urlSanitizationWhitelist + * @methodOf ng.$compileProvider + * @function + * + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during a[href] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to a[href] via data-binding is first normalized and turned into an + * absolute url. Afterwards the url is matched against the `urlSanitizationWhitelist` regular + * expression. If a match is found the original url is written into the dom. Otherwise the + * absolute url is prefixed with `'unsafe:'` string and only then it is written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.urlSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + urlSanitizationWhitelist = regexp; + return this; + } + return urlSanitizationWhitelist; + }; + + this.$get = [ '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', - '$controller', '$rootScope', + '$controller', '$rootScope', '$document', function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, - $controller, $rootScope) { + $controller, $rootScope, $document) { var Attributes = function(element, attr) { this.$$element = element; @@ -3658,7 +3751,8 @@ function $CompileProvider($provide) { */ $set: function(key, value, writeAttr, attrName) { var booleanKey = getBooleanAttrName(this.$$element[0], key), - $$observers = this.$$observers; + $$observers = this.$$observers, + normalizedVal; if (booleanKey) { this.$$element.prop(key, value); @@ -3677,6 +3771,19 @@ function $CompileProvider($provide) { } } + + // sanitize a[href] values + if (nodeName_(this.$$element[0]) === 'A' && key === 'href') { + urlSanitizationNode.setAttribute('href', value); + + // href property always returns normalized absolute url, so we can match against that + normalizedVal = urlSanitizationNode.href; + if (!normalizedVal.match(urlSanitizationWhitelist)) { + this[key] = value = 'unsafe:' + normalizedVal; + } + } + + if (writeAttr !== false) { if (value === null || value === undefined) { this.$$element.removeAttr(attrName); @@ -3720,31 +3827,48 @@ function $CompileProvider($provide) { } }; + var urlSanitizationNode = $document[0].createElement('a'), + startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(), + denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') + ? identity + : function denormalizeTemplate(template) { + return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); + }; + + return compile; //================================ - function compile($compileNode, transcludeFn, maxPriority) { - if (!($compileNode instanceof jqLite)) { - // jquery always rewraps, where as we need to preserve the original selector so that we can modify it. - $compileNode = jqLite($compileNode); + function compile($compileNodes, transcludeFn, maxPriority) { + if (!($compileNodes instanceof jqLite)) { + // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it. + $compileNodes = jqLite($compileNodes); } // We can not compile top level text elements since text nodes can be merged and we will // not be able to attach scope data to them, so we will wrap them in - forEach($compileNode, function(node, index){ - if (node.nodeType == 3 /* text node */) { - $compileNode[index] = jqLite(node).wrap('').parent()[0]; + forEach($compileNodes, function(node, index){ + if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { + $compileNodes[index] = jqLite(node).wrap('').parent()[0]; } }); - var compositeLinkFn = compileNodes($compileNode, transcludeFn, $compileNode, maxPriority); - return function(scope, cloneConnectFn){ + var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority); + return function publicLinkFn(scope, cloneConnectFn){ assertArg(scope, 'scope'); // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart // and sometimes changes the structure of the DOM. var $linkNode = cloneConnectFn - ? JQLitePrototype.clone.call($compileNode) // IMPORTANT!!! - : $compileNode; - $linkNode.data('$scope', scope); + ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! + : $compileNodes; + + // Attach scope only to non-text nodes. + for(var i = 0, ii = $linkNode.length; i'); + jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); compileNode = $compileNode[0]; replaceWith($rootElement, jqLite($template[0]), compileNode); childTranscludeFn = compile($template, transcludeFn, terminalPriority); @@ -3999,14 +4131,17 @@ function $CompileProvider($provide) { } } - if (directiveValue = directive.template) { + if ((directiveValue = directive.template)) { assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; - - $template = jqLite('
' + trim(directiveValue) + '
').contents(); - compileNode = $template[0]; + directiveValue = denormalizeTemplate(directiveValue); if (directive.replace) { + $template = jqLite('
' + + trim(directiveValue) + + '
').contents(); + compileNode = $template[0]; + if ($template.length != 1 || compileNode.nodeType !== 1) { throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue); } @@ -4117,18 +4252,20 @@ function $CompileProvider($provide) { } $element = attrs.$$element; - if (newScopeDirective && isObject(newScopeDirective.scope)) { + if (newIsolateScopeDirective) { var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/; var parentScope = scope.$parent || scope; - forEach(newScopeDirective.scope, function(definiton, scopeName) { + forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) { var match = definiton.match(LOCAL_REGEXP) || [], attrName = match[2]|| scopeName, mode = match[1], // @, =, or & lastValue, parentGet, parentSet; + scope.$$isolateBindings[scopeName] = mode + attrName; + switch (mode) { case '@': { @@ -4145,10 +4282,10 @@ function $CompileProvider($provide) { // reset the change, or we will throw this exception on every $digest lastValue = scope[scopeName] = parentGet(parentScope); throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] + - ' (directive: ' + newScopeDirective.name + ')'); + ' (directive: ' + newIsolateScopeDirective.name + ')'); }; lastValue = scope[scopeName] = parentGet(parentScope); - scope.$watch(function() { + scope.$watch(function parentValueWatch() { var parentValue = parentGet(parentScope); if (parentValue !== scope[scopeName]) { @@ -4158,7 +4295,7 @@ function $CompileProvider($provide) { lastValue = scope[scopeName] = parentValue; } else { // if the parent can be assigned then do so - parentSet(parentScope, lastValue = scope[scopeName]); + parentSet(parentScope, parentValue = lastValue = scope[scopeName]); } } return parentValue; @@ -4176,7 +4313,7 @@ function $CompileProvider($provide) { default: { throw Error('Invalid isolate scope definition for directive ' + - newScopeDirective.name + ': ' + definiton); + newIsolateScopeDirective.name + ': ' + definiton); } } }); @@ -4310,7 +4447,7 @@ function $CompileProvider($provide) { origAsyncDirective = directives.shift(), // The fact that we have to copy and patch the directive seems wrong! derivedSyncDirective = extend({}, origAsyncDirective, { - controller: null, templateUrl: null, transclude: null + controller: null, templateUrl: null, transclude: null, scope: null }); $compileNode.html(''); @@ -4319,6 +4456,8 @@ function $CompileProvider($provide) { success(function(content) { var compileNode, tempTemplateAttrs, $template; + content = denormalizeTemplate(content); + if (replace) { $template = jqLite('
' + trim(content) + '
').contents(); compileNode = $template[0]; @@ -4337,8 +4476,8 @@ function $CompileProvider($provide) { } directives.unshift(derivedSyncDirective); - afterTemplateNodeLinkFn = applyDirectivesToNode(directives, $compileNode, tAttrs, childTranscludeFn); - afterTemplateChildLinkFn = compileNodes($compileNode.contents(), childTranscludeFn); + afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn); + afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); while(linkQueue.length) { @@ -4400,12 +4539,12 @@ function $CompileProvider($provide) { if (interpolateFn) { directives.push({ priority: 0, - compile: valueFn(function(scope, node) { + compile: valueFn(function textInterpolateLinkFn(scope, node) { var parent = node.parent(), bindings = parent.data('$binding') || []; bindings.push(interpolateFn); safeAddClass(parent.data('$binding', bindings), 'ng-binding'); - scope.$watch(interpolateFn, function(value) { + scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { node[0].nodeValue = value; }); }) @@ -4417,13 +4556,13 @@ function $CompileProvider($provide) { function addAttrInterpolateDirective(node, directives, value, name) { var interpolateFn = $interpolate(value, true); - // no interpolation found -> ignore if (!interpolateFn) return; + directives.push({ priority: 100, - compile: valueFn(function(scope, element, attr) { + compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) { var $$observers = (attr.$$observers || (attr.$$observers = {})); if (name === 'class') { @@ -4435,7 +4574,7 @@ function $CompileProvider($provide) { attr[name] = undefined; ($$observers[name] || ($$observers[name] = [])).$$inter = true; (attr.$$observers && attr.$$observers[name].$$scope || scope). - $watch(interpolateFn, function(value) { + $watch(interpolateFn, function interpolateFnWatchAction(value) { attr.$set(name, value); }); }) @@ -4603,7 +4742,7 @@ function $ControllerProvider() { * @description * `$controller` service is responsible for instantiating controllers. * - * It's just simple call to {@link AUTO.$injector $injector}, but extracted into + * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into * a service, so that one can override this service with {@link https://gist.github.com/1649788 * BC version}. */ @@ -4648,11 +4787,12 @@ function $DocumentProvider(){ * the browser console. * * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by - * {@link ngMock.$exceptionHandler mock $exceptionHandler} + * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. * * @param {Error} exception Exception associated with the error. * @param {string=} cause optional information about the context in which * the error was thrown. + * */ function $ExceptionHandlerProvider() { this.$get = ['$log', function($log){ @@ -4663,13 +4803,13 @@ function $ExceptionHandlerProvider() { } /** - * @ngdoc function + * @ngdoc object * @name ng.$interpolateProvider * @function * * @description * - * Used for configuring the interpolation markup. Deafults to `{{` and `}}`. + * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. */ function $InterpolateProvider() { var startSymbol = '{{'; @@ -4682,7 +4822,8 @@ function $InterpolateProvider() { * @description * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. * - * @prop {string=} value new value to set the starting symbol to. + * @param {string=} value new value to set the starting symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ this.startSymbol = function(value){ if (value) { @@ -4700,14 +4841,15 @@ function $InterpolateProvider() { * @description * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. * - * @prop {string=} value new value to set the ending symbol to. + * @param {string=} value new value to set the ending symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ this.endSymbol = function(value){ if (value) { endSymbol = value; return this; } else { - return startSymbol; + return endSymbol; } }; @@ -4749,7 +4891,7 @@ function $InterpolateProvider() { * against. * */ - return function(text, mustHaveExpression) { + function $interpolate(text, mustHaveExpression) { var startIndex, endIndex, index = 0, @@ -4801,11 +4943,47 @@ function $InterpolateProvider() { fn.parts = parts; return fn; } - }; + } + + + /** + * @ngdoc method + * @name ng.$interpolate#startSymbol + * @methodOf ng.$interpolate + * @description + * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. + * + * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change + * the symbol. + * + * @returns {string} start symbol. + */ + $interpolate.startSymbol = function() { + return startSymbol; + } + + + /** + * @ngdoc method + * @name ng.$interpolate#endSymbol + * @methodOf ng.$interpolate + * @description + * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. + * + * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change + * the symbol. + * + * @returns {string} start symbol. + */ + $interpolate.endSymbol = function() { + return endSymbol; + } + + return $interpolate; }]; } -var URL_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/, +var URL_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/, PATH_MATCH = /^([^\?#]*)?(\?([^#]*))?(#(.*))?$/, HASH_MATCH = PATH_MATCH, DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; @@ -4884,7 +5062,8 @@ function convertToHashbangUrl(url, basePath, hashPrefix) { var match = matchUrl(url); // already hashbang url - if (decodeURIComponent(match.path) == basePath) { + if (decodeURIComponent(match.path) == basePath && !isUndefined(match.hash) && + match.hash.indexOf(hashPrefix) === 0) { return url; // convert html5 url -> hashbang url } else { @@ -5395,6 +5574,7 @@ function $LocationProvider(){ var changeCounter = 0; $rootScope.$watch(function $locationWatch() { var oldUrl = $browser.url(); + var currentReplace = $location.$$replace; if (!changeCounter || oldUrl != $location.absUrl()) { changeCounter++; @@ -5403,12 +5583,12 @@ function $LocationProvider(){ defaultPrevented) { $location.$$parse(oldUrl); } else { - $browser.url($location.absUrl(), $location.$$replace); - $location.$$replace = false; + $browser.url($location.absUrl(), currentReplace); afterLocationChange(oldUrl); } }); } + $location.$$replace = false; return changeCounter; }); @@ -5433,27 +5613,25 @@ function $LocationProvider(){ * The main purpose of this service is to simplify debugging and troubleshooting. * * @example - - - -
-

Reload this page with open console, enter text and hit the log button...

- Message: - - - - - -
-
- - -
+ + + function LogCtrl($scope, $log) { + $scope.$log = $log; + $scope.message = 'Hello World!'; + } + + +
+

Reload this page with open console, enter text and hit the log button...

+ Message: + + + + + +
+
+
*/ function $LogProvider(){ @@ -5541,7 +5719,15 @@ var OPERATORS = { 'true':function(){return true;}, 'false':function(){return false;}, undefined:noop, - '+':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); + if (isDefined(a)) { + if (isDefined(b)) { + return a + b; + } + 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){return a(self, locals)*b(self, locals);}, '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, @@ -6368,9 +6554,10 @@ function getterFn(path, csp) { * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * - * * `context`: an object against which any expressions embedded in the strings are evaluated - * against (Topically a scope object). - * * `locals`: local variables context object, useful for overriding values in `context`. + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (tipically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. * * The return function also has an `assign` property, if the expression is assignable, which * allows one to set values to expressions. @@ -6406,8 +6593,8 @@ function $ParseProvider() { * interface for interacting with an object that represents the result of an action that is * performed asynchronously, and may or may not be finished at any given point in time. * - * From the perspective of dealing with error handling, deferred and promise apis are to - * asynchronous programing what `try`, `catch` and `throw` keywords are to synchronous programing. + * From the perspective of dealing with error handling, deferred and promise APIs are to + * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. * *
  *   // for the purpose of this example let's assume that variables `$q` and `scope` are
@@ -6436,12 +6623,12 @@ function $ParseProvider() {
  *     alert('Success: ' + greeting);
  *   }, function(reason) {
  *     alert('Failed: ' + reason);
- *   );
+ *   });
  * 
* * 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). + * [guarantees that promise and deferred APIs make](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. @@ -6453,19 +6640,19 @@ 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 + * 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. * * **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`. * * **Properties** * - * - promise – `{Promise}` – promise object associated with this deferred. + * - promise – `{Promise}` – promise object associated with this deferred. * * * # The Promise API @@ -6478,7 +6665,7 @@ function $ParseProvider() { * * **Methods** * - * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved + * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved * or rejected 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. * @@ -6496,7 +6683,7 @@ function $ParseProvider() { * return result + 1; * }); * - * // promiseB will be resolved immediately after promiseA is resolved and it's value will be + * // promiseB will be resolved immediately after promiseA is resolved and its value will be * // the result of promiseA incremented by 1 *
* @@ -6517,6 +6704,30 @@ function $ParseProvider() { * you can treat promises attached to a scope as if they were the resulting values. * - Q has many more features that $q, but that comes at a cost of bytes. $q is tiny, but contains * all the important functionality needed for common async tasks. + * + * # Testing + * + *
+ *    it('should simulate promise', inject(function($q, $rootScope) {
+ *      var deferred = $q.defer();
+ *      var promise = deferred.promise;
+ *      var resolvedValue;
+ * 
+ *      promise.then(function(value) { resolvedValue = value; });
+ *      expect(resolvedValue).toBeUndefined();
+ * 
+ *      // Simulate resolving of promise
+ *      deferred.resolve(123);
+ *      // Note that the 'then' function does not get called synchronously.
+ *      // This is because we want the promise API to always be async, whether or not
+ *      // it got called synchronously or asynchronously.
+ *      expect(resolvedValue).toBeUndefined();
+ * 
+ *      // Propagate promise resolution to 'then' functions using $apply().
+ *      $rootScope.$apply();
+ *      expect(resolvedValue).toEqual(123);
+ *    });
+ *  
*/ function $QProvider() { @@ -6682,12 +6893,12 @@ function qFactory(nextTick, exceptionHandler) { * @methodOf ng.$q * @description * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. - * This is useful when you are dealing with on object that might or might not be a promise, or if + * This is useful when you are dealing with an object that might or might not be a promise, or if * the promise comes from a source that can't be trusted. * * @param {*} value Value or a promise * @returns {Promise} Returns a single promise that will be resolved with an array of values, - * each value coresponding to the promise at the same index in the `promises` array. If any of + * each value corresponding to the promise at the same index in the `promises` array. If any of * the promises is resolved with a rejection, this resulting promise will be resolved with the * same rejection. */ @@ -6749,7 +6960,7 @@ function qFactory(nextTick, exceptionHandler) { * * @param {Array.} promises An array of promises. * @returns {Promise} Returns a single promise that will be resolved with an array of values, - * each value coresponding to the promise at the same index in the `promises` array. If any of + * each value corresponding to the promise at the same index in the `promises` array. If any of * the promises is resolved with a rejection, this resulting promise will be resolved with the * same rejection. */ @@ -6803,33 +7014,39 @@ function $RouteProvider(){ * * @param {string} path Route path (matched against `$location.path`). If `$location.path` * contains redundant trailing slash or is missing one, the route will still match and the - * `$location.path` will be updated to add or drop the trailing slash to exacly match the + * `$location.path` will be updated to add or drop the trailing slash to exactly match the * route definition. + * + * `path` can contain named groups starting with a colon (`:name`). All characters up to the + * next slash are matched and stored in `$routeParams` under the given `name` when the route + * matches. + * * @param {Object} route Mapping information to be assigned to `$route.current` on route * match. * * Object properties: * - * - `controller` – `{function()=}` – Controller fn that should be associated with newly - * created scope. - * - `template` – `{string=}` – html template as a string that should be used by + * - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly + * created scope or the name of a {@link angular.Module#controller registered controller} + * if passed as a string. + * - `template` – `{string=}` – html template as a string that should be used by * {@link ng.directive:ngView ngView} or * {@link ng.directive:ngInclude ngInclude} directives. * this property takes precedence over `templateUrl`. - * - `templateUrl` – `{string=}` – path to an html template that should be used by + * - `templateUrl` – `{string=}` – path to an html template that should be used by * {@link ng.directive:ngView ngView}. * - `resolve` - `{Object.=}` - An optional map of dependencies which should * be injected into the controller. If any of these dependencies are promises, they will be * resolved and converted to a value before the controller is instantiated and the - * `$aftreRouteChange` event is fired. The map object is: + * `$routeChangeSuccess` event is fired. The map object is: * - * - `key` – `{string}`: a name of a dependency to be injected into the controller. + * - `key` – `{string}`: a name of a dependency to be injected into the controller. * - `factory` - `{string|function}`: If `string` then it is an alias for a service. * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} * and the return value is treated as the dependency. If the result is a promise, it is resolved * before its value is injected into the controller. * - * - `redirectTo` – {(string|function())=} – value to update + * - `redirectTo` – {(string|function())=} – value to update * {@link ng.$location $location} path with and trigger route redirection. * * If `redirectTo` is a function, it will be called with the following parameters: @@ -7040,8 +7257,9 @@ function $RouteProvider(){ * {@link ng.directive:ngView ngView} listens for the directive * to instantiate the controller and render the view. * + * @param {Object} angularEvent Synthetic event object. * @param {Route} current Current route information. - * @param {Route} previous Previous route information. + * @param {Route|Undefined} previous Previous route information, or undefined if current is first route entered. */ /** @@ -7068,8 +7286,7 @@ function $RouteProvider(){ * instance of the Controller. */ - var matcher = switchRouteMatcher, - forceReload = false, + var forceReload = false, $route = { routes: routes, @@ -7097,21 +7314,36 @@ function $RouteProvider(){ ///////////////////////////////////////////////////// + /** + * @param on {string} current url + * @param when {string} route when template to match the url against + * @return {?Object} + */ function switchRouteMatcher(on, when) { // TODO(i): this code is convoluted and inefficient, we should construct the route matching // regex only once and then reuse it - var regex = '^' + when.replace(/([\.\\\(\)\^\$])/g, "\\$1") + '$', + + // Escape regexp special characters. + when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$'; + var regex = '', params = [], dst = {}; - forEach(when.split(/\W/), function(param) { - if (param) { - var paramRegExp = new RegExp(":" + param + "([\\W])"); - if (regex.match(paramRegExp)) { - regex = regex.replace(paramRegExp, "([^\\/]*)$1"); - params.push(param); - } - } - }); + + var re = /:(\w+)/g, + paramMatch, + lastMatchedIndex = 0; + + while ((paramMatch = re.exec(when)) !== null) { + // Find each :param in `when` and replace it with a capturing group. + // Append all other sections of when unchanged. + regex += when.slice(lastMatchedIndex, paramMatch.index); + regex += '([^\\/]*)'; + params.push(paramMatch[1]); + lastMatchedIndex = re.lastIndex; + } + // Append trailing path part. + regex += when.substr(lastMatchedIndex); + var match = on.match(new RegExp(regex)); if (match) { forEach(params, function(name, index) { @@ -7125,7 +7357,7 @@ function $RouteProvider(){ var next = parseRoute(), last = $route.current; - if (next && last && next.$route === last.$route + if (next && last && next.$$route === last.$$route && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) { last.params = next.params; copy(last.params, $routeParams); @@ -7155,7 +7387,7 @@ function $RouteProvider(){ forEach(next.resolve || {}, function(value, key) { keys.push(key); - values.push(isFunction(value) ? $injector.invoke(value) : $injector.get(value)); + values.push(isString(value) ? $injector.get(value) : $injector.invoke(value)); }); if (isDefined(template = next.template)) { } else if (isDefined(template = next.templateUrl)) { @@ -7200,11 +7432,11 @@ function $RouteProvider(){ // Match a route var params, match; forEach(routes, function(route, path) { - if (!match && (params = matcher($location.path(), path))) { + if (!match && (params = switchRouteMatcher($location.path(), path))) { match = inherit(route, { params: extend({}, $location.search(), params), pathParams: params}); - match.$route = route; + match.$$route = route; } }); // No route matched; fallback to "otherwise" route @@ -7352,7 +7584,7 @@ function $RootScopeProvider(){ expect(scope.greeting).toEqual(undefined); scope.$watch('name', function() { - this.greeting = this.salutation + ' ' + this.name + '!'; + scope.greeting = scope.salutation + ' ' + scope.name + '!'; }); // initialize the watch expect(scope.greeting).toEqual(undefined); @@ -7395,8 +7627,10 @@ function $RootScopeProvider(){ this.$$nextSibling = this.$$prevSibling = this.$$childHead = this.$$childTail = null; this['this'] = this.$root = this; + this.$$destroyed = false; this.$$asyncQueue = []; this.$$listeners = {}; + this.$$isolateBindings = {}; } /** @@ -7426,9 +7660,9 @@ function $RootScopeProvider(){ * 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 scoped does not prototypically inherit from the - * parent scope. The scope is isolated, as it can not se parent scope properties. - * When creating widgets it is useful for the widget to not accidently read parent + * @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 * state. * * @returns {Object} The newly created child scope. @@ -7482,18 +7716,18 @@ function $RootScopeProvider(){ * 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 + * 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 + * {@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 100 to prevent infinity loop deadlock. + * 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 an `watchExpression` function with no `listener`. (Since `watchExpression`, + * 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.) * @@ -7513,7 +7747,7 @@ function $RootScopeProvider(){ scope.counter = 0; expect(scope.counter).toEqual(0); - scope.$watch('name', function(newValue, oldValue) { counter = counter + 1; }); + scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; }); expect(scope.counter).toEqual(0); scope.$digest(); @@ -7539,7 +7773,7 @@ function $RootScopeProvider(){ * - `string`: Evaluated as {@link guide/expression expression} * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters. * - * @param {boolean=} objectEquality Compare object for equality rather then for refference. + * @param {boolean=} objectEquality Compare object for equality rather than for reference. * @returns {function()} Returns a deregistration function for this listener. */ $watch: function(watchExp, listener, objectEquality) { @@ -7587,9 +7821,9 @@ function $RootScopeProvider(){ * * Usually you don't call `$digest()` directly in * {@link ng.directive:ngController controllers} or in - * {@link ng.$compileProvider.directive directives}. + * {@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#directive directives}) 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()} @@ -7606,7 +7840,7 @@ function $RootScopeProvider(){ expect(scope.counter).toEqual(0); scope.$watch('name', function(newValue, oldValue) { - counter = counter + 1; + scope.counter = scope.counter + 1; }); expect(scope.counter).toEqual(0); @@ -7714,7 +7948,7 @@ function $RootScopeProvider(){ * @function * * @description - * Remove the current scope (and all of its children) from the parent scope. Removal implies + * Removes the current scope (and all of its children) from the parent scope. Removal implies * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer * propagate to the current scope and its children. Removal also implies that the current * scope is eligible for garbage collection. @@ -7728,15 +7962,22 @@ function $RootScopeProvider(){ * perform any necessary cleanup. */ $destroy: function() { - if ($rootScope == this) return; // we can't remove the root node; + // we can't destroy the root scope or a scope that has been already destroyed + if ($rootScope == this || this.$$destroyed) return; var parent = this.$parent; this.$broadcast('$destroy'); + this.$$destroyed = true; if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; + + // This is bogus code that works around Chrome's GC leak + // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 + this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = + this.$$childTail = null; }, /** @@ -7747,7 +7988,7 @@ function $RootScopeProvider(){ * * @description * Executes the `expression` on the current scope returning the result. Any exceptions in the - * expression are propagated (uncaught). This is useful when evaluating engular expressions. + * expression are propagated (uncaught). This is useful when evaluating Angular expressions. * * # Example *
@@ -7868,23 +8109,23 @@ function $RootScopeProvider(){
        * @function
        *
        * @description
-       * Listen on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
+       * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
        * event life cycle.
        *
-       * @param {string} name Event name to listen on.
-       * @param {function(event)} listener Function to call when the event is emitted.
-       * @returns {function()} Returns a deregistration function for this listener.
+       * The event listener function format is: `function(event, args...)`. The `event` object
+       * passed into the listener has the following attributes:
        *
-       * The event listener function format is: `function(event)`. 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.
+       *   - `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.
+       *   - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
        *
-       *   - `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.
-       *   - `defaultPrevented` - {boolean}: true if `preventDefault` was called.
+       * @param {string} name Event name to listen on.
+       * @param {function(event, args...)} listener Function to call when the event is emitted.
+       * @returns {function()} Returns a deregistration function for this listener.
        */
       $on: function(name, listener) {
         var namedListeners = this.$$listeners[name];
@@ -7894,7 +8135,7 @@ function $RootScopeProvider(){
         namedListeners.push(listener);
 
         return function() {
-          arrayRemove(namedListeners, listener);
+          namedListeners[indexOf(namedListeners, listener)] = null;
         };
       },
 
@@ -7942,6 +8183,14 @@ function $RootScopeProvider(){
           namedListeners = scope.$$listeners[name] || empty;
           event.currentScope = scope;
           for (i=0, length=namedListeners.length; i
      *
      * Since the returned value of calling the $http function is a Promise object, you can also use
-     * the `then` method to register callbacks, and these callbacks will receive a single argument –
+     * the `then` method to register callbacks, and these callbacks will receive a single argument –
      * an object representing the response. See the api signature and type info below for more
      * details.
      *
+     * A response status code that falls in the [200, 300) range is considered a success status and
+     * will result in the success callback being called. Note that if the response is a redirect,
+     * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
+     * called for such responses.
      *
      * # Shortcut methods
      *
@@ -8364,10 +8626,14 @@ function $HttpProvider() {
      *  - if XSRF prefix is detected, strip it (see Security Considerations section below)
      *  - if json response is detected, deserialize it using a JSON parser
      *
-     * To override these transformation locally, specify transform functions as `transformRequest`
-     * and/or `transformResponse` properties of the config object. To globally override the default
-     * transforms, override the `$httpProvider.defaults.transformRequest` and
-     * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`.
+     * To globally augment or override the default transforms, modify the `$httpProvider.defaults.transformRequest` and
+     * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`. These properties are by default an
+     * array of transform functions, which allows you to `push` or `unshift` a new transformation function into the
+     * transformation chain. You can also decide to completely override any default transformations by assigning your
+     * transformation functions to these properties directly without the array wrapper.
+     *
+     * Similarly, to locally override the request/response transforms, augment the `transformRequest` and/or
+     * `transformResponse` properties of the config object passed into `$http`.
      *
      *
      * # Caching
@@ -8397,7 +8663,7 @@ function $HttpProvider() {
      *
      * The interceptors are service factories that are registered with the $httpProvider by
      * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and
-     * injected with dependencies (if specified) and returns the interceptor  — a function that
+     * injected with dependencies (if specified) and returns the interceptor  — a function that
      * takes a {@link ng.$q promise} and returns the original or a new promise.
      *
      * 
@@ -8483,23 +8749,23 @@ function $HttpProvider() {
      * @param {object} config Object describing the request to be made and how it should be
      *    processed. The object has following properties:
      *
-     *    - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
-     *    - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
-     *    - **params** – `{Object.}` – Map of strings or objects which will be turned to
+     *    - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
+     *    - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
+     *    - **params** – `{Object.}` – Map of strings or objects which will be turned to
      *      `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified.
-     *    - **data** – `{string|Object}` – Data to be sent as the request message data.
-     *    - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
-     *    - **transformRequest** – `{function(data, headersGetter)|Array.}` –
+     *    - **data** – `{string|Object}` – Data to be sent as the request message data.
+     *    - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
+     *    - **transformRequest** – `{function(data, headersGetter)|Array.}` –
      *      transform function or an array of such functions. The transform function takes the http
      *      request body and headers and returns its transformed (typically serialized) version.
-     *    - **transformResponse** – `{function(data, headersGetter)|Array.}` –
+     *    - **transformResponse** – `{function(data, headersGetter)|Array.}` –
      *      transform function or an array of such functions. The transform function takes the http
      *      response body and headers and returns its transformed (typically deserialized) version.
-     *    - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
+     *    - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
      *      GET request, otherwise if a cache instance built with
      *      {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
      *      caching.
-     *    - **timeout** – `{number}` – timeout in milliseconds.
+     *    - **timeout** – `{number}` – timeout in milliseconds.
      *    - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the
      *      XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
      *      requests with credentials} for more information.
@@ -8512,10 +8778,10 @@ function $HttpProvider() {
      *   these functions are destructured representation of the response object passed into the
      *   `then` method. The response object has these properties:
      *
-     *   - **data** – `{string|Object}` – The response body transformed with the transform functions.
-     *   - **status** – `{number}` – HTTP status code of the response.
-     *   - **headers** – `{function([headerName])}` – Header getter function.
-     *   - **config** – `{Object}` – The configuration object that was used to generate the request.
+     *   - **data** – `{string|Object}` – The response body transformed with the transform functions.
+     *   - **status** – `{number}` – HTTP status code of the response.
+     *   - **headers** – `{function([headerName])}` – Header getter function.
+     *   - **config** – `{Object}` – The configuration object that was used to generate the request.
      *
      * @property {Array.} pendingRequests Array of config objects for currently pending
      *   requests. This is primarily meant to be used for debugging purposes.
@@ -8885,6 +9151,7 @@ function $HttpProvider() {
 
   }];
 }
+
 var XHR = window.XMLHttpRequest || function() {
   try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
   try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
@@ -8952,8 +9219,30 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
       // always async
       xhr.onreadystatechange = function() {
         if (xhr.readyState == 4) {
-          completeRequest(
-              callback, status || xhr.status, xhr.responseText, xhr.getAllResponseHeaders());
+          var responseHeaders = xhr.getAllResponseHeaders();
+
+          // TODO(vojta): remove once Firefox 21 gets released.
+          // begin: workaround to overcome Firefox CORS http response headers bug
+          // https://bugzilla.mozilla.org/show_bug.cgi?id=608735
+          // Firefox already patched in nightly. Should land in Firefox 21.
+
+          // CORS "simple response headers" http://www.w3.org/TR/cors/
+          var value,
+              simpleHeaders = ["Cache-Control", "Content-Language", "Content-Type",
+                                  "Expires", "Last-Modified", "Pragma"];
+          if (!responseHeaders) {
+            responseHeaders = "";
+            forEach(simpleHeaders, function (header) {
+              var value = xhr.getResponseHeader(header);
+              if (value) {
+                  responseHeaders += header + ": " + value + "\n";
+              }
+            });
+          }
+          // end of the workaround.
+
+          completeRequest(callback, status || xhr.status, xhr.responseText,
+                          responseHeaders);
         }
       };
 
@@ -9020,7 +9309,7 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
  * $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() {
@@ -9111,7 +9400,7 @@ function $TimeoutProvider() {
       * @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.
-      * @returns {*} Promise that will be resolved when the timeout is reached. The value this
+      * @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.
       */
     function timeout(fn, delay, invokeApply) {
@@ -9175,7 +9464,7 @@ function $TimeoutProvider() {
  *
  * 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 the filter function.
+ * responsible for creating a filter function.
  *
  * 
  *   // Filter registration
@@ -9313,22 +9602,22 @@ function $FilterProvider($provide) {
 
        Search: 
        
-         
+         
-         
+         
NamePhone
NamePhone
{{friend.name}} {{friend.phone}}

Any:
Name only
- Phone only
+ Phone only
- + - +
NamePhone
NamePhone
{{friend.name}} {{friend.phone}}
@@ -9352,7 +9641,7 @@ function $FilterProvider($provide) { */ function filterFilter() { return function(array, expression) { - if (!(array instanceof Array)) return array; + if (!isArray(array)) return array; var predicates = []; predicates.check = function(value) { for (var j = 0; j < predicates.length; j++) { @@ -9496,7 +9785,7 @@ function currencyFilter($locale) { * * @param {number|string} number Number to format. * @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to. - * @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 @@ -9550,9 +9839,18 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { formatedText = '', parts = []; + var hasExponent = false; if (numStr.indexOf('e') !== -1) { - formatedText = numStr; - } else { + var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); + if (match && match[2] == '-' && match[3] > fractionSize + 1) { + numStr = '0'; + } else { + formatedText = numStr; + hasExponent = true; + } + } + + if (!hasExponent) { var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; // determine fractionSize if it is not specified @@ -9592,7 +9890,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { fraction += '0'; } - if (fractionSize) formatedText += decimalSep + fraction.substr(0, fractionSize); + if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); } parts.push(isNegative ? pattern.negPre : pattern.posPre); @@ -9635,8 +9933,13 @@ function dateStrGetter(name, shortForm) { } function timeZoneGetter(date) { - var offset = date.getTimezoneOffset(); - return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2); + var zone = -1 * date.getTimezoneOffset(); + var paddedZone = (zone >= 0) ? "+" : ""; + + paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + + padNumber(Math.abs(zone % 60), 2); + + return paddedZone; } function ampmGetter(date, formats) { @@ -9700,7 +10003,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+ * * `'ss'`: Second in minute, padded (00-59) * * `'s'`: Second in minute (0-59) * * `'a'`: am/pm marker - * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200) + * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) * * `format` string can also be one of the following predefined * {@link guide/i18n localizable formats}: @@ -9722,7 +10025,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+ * * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's - * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). + * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is + * specified in the string input, the time is considered to be in the local timezone. * @param {string=} format Formatting rules (see Description). If not specified, * `mediumDate` is used. * @returns {string} Formatted string or the input if input is not recognized as date/millis. @@ -9742,7 +10046,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+ expect(binding("1288323623006 | date:'medium'")). toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")). - toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/); + toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")). toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); }); @@ -9753,7 +10057,7 @@ dateFilter.$inject = ['$locale']; function dateFilter($locale) { - var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; + var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; function jsonStringToDate(string){ var match; if (match = string.match(R_ISO8601_STR)) { @@ -10011,12 +10315,12 @@ function limitToFilter(){ (^) Phone Number Age - + {{friend.name}} {{friend.phone}} {{friend.age}} - + @@ -10048,7 +10352,7 @@ function limitToFilter(){ orderByFilter.$inject = ['$parse']; function orderByFilter($parse){ return function(array, sortPredicate, reverseOrder) { - if (!(array instanceof Array)) return array; + if (!isArray(array)) return array; if (!sortPredicate) return array; sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; sortPredicate = map(sortPredicate, function(predicate){ @@ -10105,21 +10409,36 @@ function ngDirective(directive) { return valueFn(directive); } -/* +/** + * @ngdoc directive + * @name ng.directive:a + * @restrict E + * + * @description * Modifies the default behavior of html A tag, so that the default action is prevented when href * attribute is empty. * * The reasoning for this change is to allow easy creation of action links with `ngClick` directive * without changing the location or causing page reloads, e.g.: - * Save + * `Save` */ var htmlAnchorDirective = valueFn({ restrict: 'E', compile: function(element, attr) { - // turn link into a link in IE - // but only if it doesn't have name attribute, in which case it's an anchor - if (!attr.href) { - attr.$set('href', ''); + + if (msie <= 8) { + + // turn link into a stylable link in IE + // but only if it doesn't have name attribute, in which case it's an anchor + if (!attr.href && !attr.name) { + attr.$set('href', ''); + } + + // add a comment node to anchors to workaround IE bug that causes element content to be reset + // to new attribute content if attribute is updated with value containing @ and element also + // contains value with @ + // see issue #1949 + element.append(document.createComment('IE fix')); } return function(scope, element) { @@ -10199,7 +10518,7 @@ var htmlAnchorDirective = valueFn({ it('should execute ng-click but not reload when no href but name specified', function() { element('#link-5').click(); expect(input('value').val()).toEqual('5'); - expect(element('#link-5').attr('href')).toBe(''); + expect(element('#link-5').attr('href')).toBe(undefined); }); it('should only change url when only ng-href', function() { @@ -10417,7 +10736,7 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) { priority: 100, compile: function() { return function(scope, element, attr) { - scope.$watch(attr[normalized], function(value) { + scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { attr.$set(attrName, !!value); }); }; @@ -10435,12 +10754,16 @@ forEach(['src', 'href'], function(attrName) { priority: 99, // it needs to run after the attributes are interpolated link: function(scope, element, attr) { attr.$observe(normalized, function(value) { + if (!value) + return; + attr.$set(attrName, value); // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need - // to set the property as well to achieve the desired effect - if (msie) element.prop(attrName, value); + // to set the property as well to achieve the desired effect. + // we use attr[attrName] value since $set can sanitize the url. + if (msie) element.prop(attrName, attr[attrName]); }); } }; @@ -10460,13 +10783,13 @@ var nullFormCtrl = { * * @property {boolean} $pristine True if user has not interacted with the form yet. * @property {boolean} $dirty True if user has already interacted with the form. - * @property {boolean} $valid True if all of the containg forms and controls are valid. + * @property {boolean} $valid True if all of the containing forms and controls are valid. * @property {boolean} $invalid True if at least one containing control or form is invalid. * * @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 @@ -10563,6 +10886,7 @@ function FormController(element, attrs) { element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); form.$dirty = true; form.$pristine = false; + parentForm.$setDirty(); }; } @@ -10578,7 +10902,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=} ngForm|name Name of the form. If specified, the form controller will be published into + * @param {string=} name|ngForm Name of the form. If specified, the form controller will be published into * related scope, under this name. * */ @@ -10651,12 +10975,12 @@ function FormController(element, attrs) {
userType: - Required!
+ Required!
userType = {{userType}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
- myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}
+ myForm.$error.required = {{!!myForm.$error.required}}
@@ -10673,41 +10997,65 @@ function FormController(element, attrs) {
*/ -var formDirectiveDir = { - name: 'form', - restrict: 'E', - controller: FormController, - compile: function() { - return { - pre: function(scope, formElement, attr, controller) { - if (!attr.action) { - formElement.bind('submit', function(event) { - event.preventDefault(); - }); - } +var formDirectiveFactory = function(isNgForm) { + return ['$timeout', function($timeout) { + var formDirective = { + name: 'form', + restrict: 'E', + controller: FormController, + compile: function() { + return { + pre: function(scope, formElement, attr, controller) { + if (!attr.action) { + // we can't use jq events because if a form is destroyed during submission the default + // action is not prevented. see #1238 + // + // IE 9 is not affected because it doesn't fire a submit event and try to do a full + // page reload if the form was destroyed by submission of the form via a click handler + // on a button in the form. Looks like an IE9 specific bug. + var preventDefaultListener = function(event) { + event.preventDefault + ? event.preventDefault() + : event.returnValue = false; // IE + }; - var parentFormCtrl = formElement.parent().controller('form'), - alias = attr.name || attr.ngForm; + addEventListenerFn(formElement[0], 'submit', preventDefaultListener); + + // unregister the preventDefault listener so that we don't not leak memory but in a + // way that will achieve the prevention of the default action. + formElement.bind('$destroy', function() { + $timeout(function() { + removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); + }, 0, false); + }); + } + + var parentFormCtrl = formElement.parent().controller('form'), + alias = attr.name || attr.ngForm; - if (alias) { - scope[alias] = controller; - } - if (parentFormCtrl) { - formElement.bind('$destroy', function() { - parentFormCtrl.$removeControl(controller); if (alias) { - scope[alias] = undefined; + scope[alias] = controller; } - extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards - }); - } + if (parentFormCtrl) { + formElement.bind('$destroy', function() { + parentFormCtrl.$removeControl(controller); + if (alias) { + scope[alias] = undefined; + } + extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards + }); + } + } + }; } }; - } + + return isNgForm ? extend(copy(formDirective), {restrict: 'EAC'}) : formDirective; + }]; }; -var formDirective = valueFn(formDirectiveDir); -var ngFormDirective = valueFn(extend(copy(formDirectiveDir), {restrict: 'EAC'})); +var formDirective = formDirectiveFactory(); +var ngFormDirective = formDirectiveFactory(true); 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,4}$/; @@ -10724,7 +11072,10 @@ var inputType = { * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} required Adds `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -10794,6 +11145,9 @@ var inputType = { * @param {string=} min Sets the `min` validation error key if the value entered is less then `min`. * @param {string=} max Sets the `max` validation error key if the value entered is greater then `min`. * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -10860,6 +11214,9 @@ var inputType = { * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -10925,6 +11282,9 @@ var inputType = { * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -11347,6 +11707,9 @@ function checkboxInputType(scope, element, attr, ctrl) { * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -11371,6 +11734,7 @@ function checkboxInputType(scope, element, attr, ctrl) { * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {boolean=} ngRequired Sets `required` attribute if set to true * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -11704,22 +12068,25 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ // model -> value var ctrl = this; - $scope.$watch(ngModelGet, function(value) { - // ignore change from view - if (ctrl.$modelValue === value) return; + $scope.$watch(function ngModelWatch() { + var value = ngModelGet($scope); - var formatters = ctrl.$formatters, - idx = formatters.length; + // if scope model value and ngModel value are out of sync + if (ctrl.$modelValue !== value) { - ctrl.$modelValue = value; - while(idx--) { - value = formatters[idx](value); - } + var formatters = ctrl.$formatters, + idx = formatters.length; - if (ctrl.$viewValue !== value) { - ctrl.$viewValue = value; - ctrl.$render(); + ctrl.$modelValue = value; + while(idx--) { + value = formatters[idx](value); + } + + if (ctrl.$viewValue !== value) { + ctrl.$viewValue = value; + ctrl.$render(); + } } }); }]; @@ -11868,7 +12235,7 @@ var requiredDirective = function() { * @name ng.directive:ngList * * @description - * Text input that converts between comma-seperated string into an array of strings. + * Text input that converts between comma-separated string into an array of strings. * * @element input * @param {string=} ngList optional delimiter that should be used to split the value. If @@ -11928,7 +12295,7 @@ var ngListDirective = function() { ctrl.$parsers.push(parse); ctrl.$formatters.push(function(value) { - if (isArray(value) && !equals(parse(ctrl.$viewValue), value)) { + if (isArray(value)) { return value.join(', '); } @@ -11951,7 +12318,7 @@ var ngValueDirective = function() { }; } else { return function(scope, elm, attr) { - scope.$watch(attr.ngValue, function(value) { + scope.$watch(attr.ngValue, function valueWatchAction(value) { attr.$set('value', value, false); }); }; @@ -12009,7 +12376,7 @@ var ngValueDirective = function() { */ var ngBindDirective = ngDirective(function(scope, element, attr) { element.addClass('ng-binding').data('$binding', attr.ngBind); - scope.$watch(attr.ngBind, function(value) { + scope.$watch(attr.ngBind, function ngBindWatchAction(value) { element.text(value == undefined ? '' : value); }); }); @@ -12092,7 +12459,7 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) { var ngBindHtmlUnsafeDirective = [function() { return function(scope, element, attr) { element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe); - scope.$watch(attr.ngBindHtmlUnsafe, function(value) { + scope.$watch(attr.ngBindHtmlUnsafe, function ngBindHtmlUnsafeWatchAction(value) { element.html(value || ''); }); }; @@ -12101,17 +12468,57 @@ var ngBindHtmlUnsafeDirective = [function() { function classDirective(name, selector) { name = 'ngClass' + name; return ngDirective(function(scope, element, attr) { - scope.$watch(attr[name], function(newVal, oldVal) { + var oldVal = undefined; + + scope.$watch(attr[name], ngClassWatchAction, true); + + attr.$observe('class', function(value) { + var ngClass = scope.$eval(attr[name]); + ngClassWatchAction(ngClass, ngClass); + }); + + + if (name !== 'ngClass') { + scope.$watch('$index', function($index, old$index) { + var mod = $index % 2; + if (mod !== old$index % 2) { + if (mod == selector) { + addClass(scope.$eval(attr[name])); + } else { + removeClass(scope.$eval(attr[name])); + } + } + }); + } + + + function ngClassWatchAction(newVal) { if (selector === true || scope.$index % 2 === selector) { if (oldVal && (newVal !== oldVal)) { - if (isObject(oldVal) && !isArray(oldVal)) - oldVal = map(oldVal, function(v, k) { if (v) return k }); - element.removeClass(isArray(oldVal) ? oldVal.join(' ') : oldVal); - } - if (isObject(newVal) && !isArray(newVal)) - newVal = map(newVal, function(v, k) { if (v) return k }); - if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal); } - }, true); + removeClass(oldVal); + } + addClass(newVal); + } + oldVal = newVal; + } + + + function removeClass(classVal) { + if (isObject(classVal) && !isArray(classVal)) { + classVal = map(classVal, function(v, k) { if (v) return k }); + } + element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal); + } + + + function addClass(classVal) { + if (isObject(classVal) && !isArray(classVal)) { + classVal = map(classVal, function(v, k) { if (v) return k }); + } + if (classVal) { + element.addClass(isArray(classVal) ? classVal.join(' ') : classVal); + } + } }); } @@ -12125,7 +12532,7 @@ function classDirective(name, selector) { * * The directive won't add duplicate classes if a particular class was already set. * - * When the expression changes, the previously added classes are removed and only then the classes + * When the expression changes, the previously added classes are removed and only then the * new classes are added. * * @element ANY @@ -12276,7 +12683,7 @@ var ngClassEvenDirective = classDirective('Even', 1); * `angular.min.js` files. Following is the css rule: * *
- * [ng\:cloak], [ng-cloak], .ng-cloak {
+ * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
  *   display: none;
  * }
  * 
@@ -12330,9 +12737,9 @@ var ngCloakDirective = ngDirective({ * * 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 + * * 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. * * Note that an alternative way to define controllers is via the `{@link ng.$route}` @@ -12770,7 +13177,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile' element.html(''); }; - scope.$watch(srcExp, function(src) { + scope.$watch(srcExp, function ngIncludeWatchAction(src) { var thisChangeId = ++changeCounter; if (src) { @@ -13045,14 +13452,17 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp whenExp = element.attr(attr.$attr.when), // this is because we have {{}} in attrs offset = attr.offset || 0, whens = scope.$eval(whenExp), - whensExpFns = {}; + whensExpFns = {}, + startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(); forEach(whens, function(expression, key) { whensExpFns[key] = - $interpolate(expression.replace(BRACE, '{{' + numberExp + '-' + offset + '}}')); + $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + + offset + endSymbol)); }); - scope.$watch(function() { + scope.$watch(function ngPluralizeWatch() { var value = parseFloat(scope.$eval(numberExp)); if (!isNaN(value)) { @@ -13063,7 +13473,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp } else { return ''; } - }, function(newVal) { + }, function ngPluralizeWatchAction(newVal) { element.text(newVal); }); } @@ -13081,10 +13491,10 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp * * Special properties are exposed on the local scope of each template instance, including: * - * * `$index` – `{number}` – iterator offset of the repeated element (0..length-1) - * * `$first` – `{boolean}` – true if the repeated element is first in the iterator. - * * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator. - * * `$last` – `{boolean}` – true if the repeated element is last in the iterator. + * * `$index` – `{number}` – iterator offset of the repeated element (0..length-1) + * * `$first` – `{boolean}` – true if the repeated element is first in the iterator. + * * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator. + * * `$last` – `{boolean}` – true if the repeated element is last in the iterator. * * * @element ANY @@ -13093,12 +13503,12 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. Two * 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: `track in cd.tracks`. * - * * `(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}`. @@ -13158,17 +13568,21 @@ var ngRepeatDirective = ngDirective({ // We need an array of these objects since the same object can be returned from the iterator. // We expect this to be a rare case. var lastOrder = new HashQueueMap(); - scope.$watch(function(scope){ + + scope.$watch(function ngRepeatWatch(scope){ var index, length, collection = scope.$eval(rhs), - collectionLength = size(collection, true), - childScope, + cursor = iterStartElement, // current position of the node // Same as lastOrder but it has the current state. It will become the // lastOrder on the next iteration. nextOrder = new HashQueueMap(), + arrayBound, + childScope, key, value, // key/value of iteration - array, last, // last object information {scope, element, index} - cursor = iterStartElement; // current position of the node + array, + last; // last object information {scope, element, index} + + if (!isArray(collection)) { // if object, extract keys, sort them and use to determine order of iteration over obj props @@ -13183,11 +13597,15 @@ var ngRepeatDirective = ngDirective({ array = collection || []; } + arrayBound = array.length-1; + // we are not using forEach for perf reasons (trying to avoid #call) for (index = 0, length = array.length; index < length; index++) { key = (collection === array) ? index : array[index]; value = collection[key]; + last = lastOrder.shift(value); + if (last) { // if we have already seen this object, then we need to reuse the // associated scope/element @@ -13216,7 +13634,7 @@ var ngRepeatDirective = ngDirective({ childScope.$index = index; childScope.$first = (index === 0); - childScope.$last = (index === (collectionLength - 1)); + childScope.$last = (index === arrayBound); childScope.$middle = !(childScope.$first || childScope.$last); if (!last) { @@ -13284,7 +13702,7 @@ var ngRepeatDirective = ngDirective({ */ //TODO(misko): refactor to remove element from the DOM var ngShowDirective = ngDirective(function(scope, element, attr){ - scope.$watch(attr.ngShow, function(value){ + scope.$watch(attr.ngShow, function ngShowWatchAction(value){ element.css('display', toBoolean(value) ? '' : 'none'); }); }); @@ -13295,11 +13713,11 @@ var ngShowDirective = ngDirective(function(scope, element, attr){ * @name ng.directive:ngHide * * @description - * The `ngHide` and `ngShow` directives hide or show a portion - * of the HTML conditionally. + * The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML) + * conditionally. * * @element ANY - * @param {expression} ngHide If the {@link guide/expression expression} truthy then + * @param {expression} ngHide If the {@link guide/expression expression} is truthy then * the element is shown or hidden respectively. * * @example @@ -13324,7 +13742,7 @@ var ngShowDirective = ngDirective(function(scope, element, attr){ */ //TODO(misko): refactor to remove element from the DOM var ngHideDirective = ngDirective(function(scope, element, attr){ - scope.$watch(attr.ngHide, function(value){ + scope.$watch(attr.ngHide, function ngHideWatchAction(value){ element.css('display', toBoolean(value) ? 'none' : ''); }); }); @@ -13367,7 +13785,7 @@ var ngHideDirective = ngDirective(function(scope, element, attr){ */ var ngStyleDirective = ngDirective(function(scope, element, attr) { - scope.$watch(attr.ngStyle, function(newStyles, oldStyles) { + scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { if (oldStyles && (newStyles !== oldStyles)) { forEach(oldStyles, function(val, style) { element.css(style, '');}); } @@ -13383,11 +13801,13 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) { * @description * Conditionally change the DOM structure. * - * @usageContent - * ... + * @usage + * + * ... * ... * ... * ... + * * * @scope * @param {*} ngSwitch|on expression to match against ng-switch-when. @@ -13437,52 +13857,54 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) { var NG_SWITCH = 'ng-switch'; var ngSwitchDirective = valueFn({ restrict: 'EA', - compile: function(element, attr) { + require: 'ngSwitch', + // asks for $scope to fool the BC controller module + controller: ['$scope', function ngSwitchController() { + this.cases = {}; + }], + link: function(scope, element, attr, ctrl) { var watchExpr = attr.ngSwitch || attr.on, - cases = {}; - - element.data(NG_SWITCH, cases); - return function(scope, element){ - var selectedTransclude, - selectedElement, - selectedScope; - - scope.$watch(watchExpr, function(value) { - if (selectedElement) { - selectedScope.$destroy(); - selectedElement.remove(); - selectedElement = selectedScope = null; - } - if ((selectedTransclude = cases['!' + value] || cases['?'])) { - scope.$eval(attr.change); - selectedScope = scope.$new(); - selectedTransclude(selectedScope, function(caseElement) { - selectedElement = caseElement; - element.append(caseElement); - }); - } - }); - }; + selectedTransclude, + selectedElement, + selectedScope; + + scope.$watch(watchExpr, function ngSwitchWatchAction(value) { + if (selectedElement) { + selectedScope.$destroy(); + selectedElement.remove(); + selectedElement = selectedScope = null; + } + if ((selectedTransclude = ctrl.cases['!' + value] || ctrl.cases['?'])) { + scope.$eval(attr.change); + selectedScope = scope.$new(); + selectedTransclude(selectedScope, function(caseElement) { + selectedElement = caseElement; + element.append(caseElement); + }); + } + }); } }); var ngSwitchWhenDirective = ngDirective({ transclude: 'element', priority: 500, + require: '^ngSwitch', compile: function(element, attrs, transclude) { - var cases = element.inheritedData(NG_SWITCH); - assertArg(cases); - cases['!' + attrs.ngSwitchWhen] = transclude; + return function(scope, element, attr, ctrl) { + ctrl.cases['!' + attrs.ngSwitchWhen] = transclude; + }; } }); var ngSwitchDefaultDirective = ngDirective({ transclude: 'element', priority: 500, + require: '^ngSwitch', compile: function(element, attrs, transclude) { - var cases = element.inheritedData(NG_SWITCH); - assertArg(cases); - cases['?'] = transclude; + return function(scope, element, attr, ctrl) { + ctrl.cases['?'] = transclude; + }; } }); @@ -13571,7 +13993,7 @@ var ngTranscludeDirective = ngDirective({
$location.path() = {{$location.path()}}
-
$route.current.template = {{$route.current.template}}
+
$route.current.templateUrl = {{$route.current.templateUrl}}
$route.current.params = {{$route.current.params}}
$route.current.scope.name = {{$route.current.scope.name}}
$routeParams = {{$routeParams}}
@@ -13690,7 +14112,7 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c if (current.controller) { locals.$scope = lastScope; controller = $controller(current.controller, locals); - element.contents().data('$ngControllerController', controller); + element.children().data('$ngControllerController', controller); } link(lastScope); @@ -13765,7 +14187,7 @@ var scriptDirective = ['$templateCache', function($templateCache) { * Optionally `ngOptions` attribute can be used to dynamically generate a list of `