diff --git a/.gitignore b/.gitignore index 5b31749..b8121a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ -.idea -.DS_Store* -ehthumbs.db -Icon? -Thumbs.db +**/.DS_Store +nbproject +manifest.mf +build.xml + +.project +.settings +.idea/* diff --git a/README.md b/README.md index f349f3a..a33376c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ Memory-Game =========== -AngularJS example app that implements the famous memory game of finding matching pairs of cards. Popular with toddlers everywhere! \ No newline at end of file +AngularJS example app that implements the famous memory game of finding matching pairs of cards. Popular with toddlers everywhere! + + +View demo at: diff --git a/app/css/app.css b/app/css/app.css index 217fe06..a938e9e 100755 --- a/app/css/app.css +++ b/app/css/app.css @@ -8,7 +8,6 @@ div { width: 165px; height: 200px; position: relative; - border: 1px solid #CCC; -webkit-perspective: 800px; -moz-perspective: 800px; -ms-perspective: 800px; diff --git a/app/index-async.html b/app/index-async.html new file mode 100644 index 0000000..b530660 --- /dev/null +++ b/app/index-async.html @@ -0,0 +1,57 @@ + + + + + + + My AngularJS App + + + + + +
+ +
Angular seed app: v
+ + + diff --git a/app/index.html b/app/index.html index 605436f..0ff7c5b 100755 --- a/app/index.html +++ b/app/index.html @@ -14,11 +14,18 @@ -
-
- - +
+ + +
+
+ + +
+
diff --git a/app/js/app.js b/app/js/app.js index 161034b..144a679 100755 --- a/app/js/app.js +++ b/app/js/app.js @@ -16,3 +16,27 @@ memoryGameApp.factory('game', function() { memoryGameApp.controller('GameCtrl', function GameCtrl($scope, game) { $scope.game = game; }); + + +//usages: +//- in the repeater as: +//- card currently being matched as: + +memoryGameApp.directive('mgCard', function() { + return { + restrict: 'E', + // instead of inlining the template string here, one could use templateUrl: 'mg-card.html' + // and then either create a mg-card.html file with the content or add + // element to + // index.html + template: '
' + + '
' + + '' + + '' + + '
' + + '
', + scope: { + tile: '=' + } + } +}); diff --git a/app/lib/angular/angular-cookies.js b/app/lib/angular/angular-cookies.js index f1f4b8a..dc4d5ac 100755 --- a/app/lib/angular/angular-cookies.js +++ b/app/lib/angular/angular-cookies.js @@ -1,21 +1,21 @@ /** - * @license AngularJS v1.0.0rc6 + * @license AngularJS v1.0.6 * (c) 2010-2012 Google, Inc. http://angularjs.org * License: MIT */ -(function(angular) { +(function(window, angular, undefined) { 'use strict'; /** * @ngdoc overview - * @name angular.module.ngCookies + * @name ngCookies */ angular.module('ngCookies', ['ng']). /** * @ngdoc object - * @name angular.module.ngCookies.$cookies + * @name ngCookies.$cookies * @requires $browser * * @description @@ -25,6 +25,18 @@ angular.module('ngCookies', ['ng']). * this object, new cookies are created/deleted at the end of current $eval. * * @example + + + + + */ factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { var cookies = {}, @@ -109,7 +121,7 @@ angular.module('ngCookies', ['ng']). /** * @ngdoc object - * @name angular.module.ngCookies.$cookieStore + * @name ngCookies.$cookieStore * @requires $cookies * * @description @@ -123,8 +135,8 @@ angular.module('ngCookies', ['ng']). return { /** * @ngdoc method - * @name angular.module.ngCookies.$cookieStore#get - * @methodOf angular.module.ngCookies.$cookieStore + * @name ngCookies.$cookieStore#get + * @methodOf ngCookies.$cookieStore * * @description * Returns the value of given cookie key @@ -138,8 +150,8 @@ angular.module('ngCookies', ['ng']). /** * @ngdoc method - * @name angular.module.ngCookies.$cookieStore#put - * @methodOf angular.module.ngCookies.$cookieStore + * @name ngCookies.$cookieStore#put + * @methodOf ngCookies.$cookieStore * * @description * Sets a value for given cookie key @@ -153,8 +165,8 @@ angular.module('ngCookies', ['ng']). /** * @ngdoc method - * @name angular.module.ngCookies.$cookieStore#remove - * @methodOf angular.module.ngCookies.$cookieStore + * @name ngCookies.$cookieStore#remove + * @methodOf ngCookies.$cookieStore * * @description * Remove given cookie @@ -168,4 +180,5 @@ angular.module('ngCookies', ['ng']). }]); -})(window.angular); + +})(window, window.angular); diff --git a/app/lib/angular/angular-cookies.min.js b/app/lib/angular/angular-cookies.min.js index 0eecce6..ee88529 100755 --- a/app/lib/angular/angular-cookies.min.js +++ b/app/lib/angular/angular-cookies.min.js @@ -1,7 +1,7 @@ /* - AngularJS v1.0.0rc6 + AngularJS v1.0.6 (c) 2010-2012 Google, Inc. http://angularjs.org License: MIT */ -(function(f){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,c){var b={},g={},h,i=!1,j=f.copy,k=f.isUndefined;c.addPollFn(function(){var a=c.cookies();h!=a&&(h=a,j(a,g),j(a,b),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(b[a])&&c.cookies(a,void 0);for(a in b)e=b[a],f.isString(e)?e!==g[a]&&(c.cookies(a,e),d=!0):f.isDefined(g[a])?b[a]=g[a]:delete b[a];if(d)for(a in e=c.cookies(),b)b[a]!==e[a]&&(k(e[a])?delete b[a]:b[a]=e[a])});return b}]).factory("$cookieStore", -["$cookies",function(d){return{get:function(c){return f.fromJson(d[c])},put:function(c,b){d[c]=f.toJson(b)},remove:function(c){delete d[c]}}}])})(window.angular); +(function(m,f,l){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,c){var b={},g={},h,i=!1,j=f.copy,k=f.isUndefined;c.addPollFn(function(){var a=c.cookies();h!=a&&(h=a,j(a,g),j(a,b),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(b[a])&&c.cookies(a,l);for(a in b)e=b[a],f.isString(e)?e!==g[a]&&(c.cookies(a,e),d=!0):f.isDefined(g[a])?b[a]=g[a]:delete b[a];if(d)for(a in e=c.cookies(),b)b[a]!==e[a]&&(k(e[a])?delete b[a]:b[a]=e[a])});return b}]).factory("$cookieStore", +["$cookies",function(d){return{get:function(c){return f.fromJson(d[c])},put:function(c,b){d[c]=f.toJson(b)},remove:function(c){delete d[c]}}}])})(window,window.angular); diff --git a/app/lib/angular/angular-loader.js b/app/lib/angular/angular-loader.js index e514366..e42951d 100755 --- a/app/lib/angular/angular-loader.js +++ b/app/lib/angular/angular-loader.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.0.0rc6 + * @license AngularJS v1.0.6 * (c) 2010-2012 Google, Inc. http://angularjs.org * License: MIT */ @@ -36,8 +36,8 @@ function setupModuleLoader(window) { * * # Module * - * A module is a collocation of services, directives, filters, and configure information. Module - * is used to configure the {@link angular.module.AUTO.$injector $injector}. + * A module is a collocation of services, directives, filters, and configuration information. Module + * is used to configure the {@link AUTO.$injector $injector}. * *
      * // Create a new module
@@ -61,13 +61,13 @@ function setupModuleLoader(window) {
      * 
* * However it's more likely that you'll just use - * {@link angular.module.ng.$compileProvider.directive.ngApp ngApp} or + * {@link ng.directive:ngApp ngApp} or * {@link angular.bootstrap} to simplify this process for you. * * @param {!string} name The name of the module to create or retrieve. * @param {Array.=} requires If specified then new module is being created. If unspecified then the * the module is being retrieved for further configuration. - * @param {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. */ @@ -121,7 +121,7 @@ function setupModuleLoader(window) { * @param {string} name service name * @param {Function} providerType Construction function for creating new instance of the service. * @description - * See {@link angular.module.AUTO.$provide#provider $provide.provider()}. + * See {@link AUTO.$provide#provider $provide.provider()}. */ provider: invokeLater('$provide', 'provider'), @@ -132,7 +132,7 @@ function setupModuleLoader(window) { * @param {string} name service name * @param {Function} providerFunction Function for creating new instance of the service. * @description - * See {@link angular.module.AUTO.$provide#factory $provide.factory()}. + * See {@link AUTO.$provide#factory $provide.factory()}. */ factory: invokeLater('$provide', 'factory'), @@ -143,7 +143,7 @@ function setupModuleLoader(window) { * @param {string} name service name * @param {Function} constructor A constructor function that will be instantiated. * @description - * See {@link angular.module.AUTO.$provide#service $provide.service()}. + * See {@link AUTO.$provide#service $provide.service()}. */ service: invokeLater('$provide', 'service'), @@ -154,7 +154,7 @@ function setupModuleLoader(window) { * @param {string} name service name * @param {*} object Service instance object. * @description - * See {@link angular.module.AUTO.$provide#value $provide.value()}. + * See {@link AUTO.$provide#value $provide.value()}. */ value: invokeLater('$provide', 'value'), @@ -166,7 +166,7 @@ function setupModuleLoader(window) { * @param {*} object Constant value. * @description * Because the constant are fixed, they get applied before other provide methods. - * See {@link angular.module.AUTO.$provide#constant $provide.constant()}. + * See {@link AUTO.$provide#constant $provide.constant()}. */ constant: invokeLater('$provide', 'constant', 'unshift'), @@ -177,7 +177,7 @@ function setupModuleLoader(window) { * @param {string} name Filter name. * @param {Function} filterFactory Factory function for creating new instance of filter. * @description - * See {@link angular.module.ng.$filterProvider#register $filterProvider.register()}. + * See {@link ng.$filterProvider#register $filterProvider.register()}. */ filter: invokeLater('$filterProvider', 'register'), @@ -188,7 +188,7 @@ function setupModuleLoader(window) { * @param {string} name Controller name. * @param {Function} constructor Controller constructor function. * @description - * See {@link angular.module.ng.$controllerProvider#register $controllerProvider.register()}. + * See {@link ng.$controllerProvider#register $controllerProvider.register()}. */ controller: invokeLater('$controllerProvider', 'register'), @@ -200,7 +200,7 @@ function setupModuleLoader(window) { * @param {Function} directiveFactory Factory function for creating new instance of * directives. * @description - * See {@link angular.module.ng.$compileProvider.directive $compileProvider.directive()}. + * See {@link ng.$compileProvider#directive $compileProvider.directive()}. */ directive: invokeLater('$compileProvider', 'directive'), @@ -222,8 +222,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); @@ -254,6 +254,7 @@ function setupModuleLoader(window) { }); } + )(window); /** diff --git a/app/lib/angular/angular-loader.min.js b/app/lib/angular/angular-loader.min.js index f6e82d0..30cf600 100755 --- a/app/lib/angular/angular-loader.min.js +++ b/app/lib/angular/angular-loader.min.js @@ -1,5 +1,5 @@ /* - AngularJS v1.0.0rc6 + AngularJS v1.0.6 (c) 2010-2012 Google, Inc. http://angularjs.org License: MIT */ diff --git a/app/lib/angular/angular-resource.js b/app/lib/angular/angular-resource.js index 9e0f76f..1af44ef 100755 --- a/app/lib/angular/angular-resource.js +++ b/app/lib/angular/angular-resource.js @@ -1,20 +1,20 @@ /** - * @license AngularJS v1.0.0rc6 + * @license AngularJS v1.0.6 * (c) 2010-2012 Google, Inc. http://angularjs.org * License: MIT */ -(function(angular) { +(function(window, angular, undefined) { 'use strict'; /** * @ngdoc overview - * @name angular.module.ngResource + * @name ngResource * @description */ - /** +/** * @ngdoc object - * @name angular.module.ngResource.$resource + * @name ngResource.$resource * @requires $http * * @description @@ -22,10 +22,23 @@ * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. * * The returned resource object has action methods which provide high-level behaviors without - * the need to interact with the low level {@link angular.module.ng.$http $http} service. + * the need to interact with the low level {@link ng.$http $http} service. + * + * # Installation + * To use $resource make sure you have included the `angular-resource.js` that comes in Angular + * package. You can also find this file on Google CDN, bower as well as at + * {@link http://code.angularjs.org/ code.angularjs.org}. + * + * Finally load the module in your application: + * + * angular.module('app', ['ngResource']); + * + * and you are ready to get started! * * @param {string} url A parameterized URL template with parameters prefixed by `:` as in - * `/user/:username`. + * `/user/:username`. If you are using a URL with a port number (e.g. + * `http://example.com:8080/api`), you'll need to escape the colon character before the port + * number, like this: `$resource('http://example.com\\:8080/api')`. * * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in * `actions` methods. @@ -65,11 +78,11 @@ * 'remove': {method:'DELETE'}, * 'delete': {method:'DELETE'} }; * - * Calling these methods invoke an {@link angular.module.ng.$http} with the specified http method, + * Calling these methods invoke an {@link ng.$http} with the specified http method, * destination and parameters. When the data is returned from the server then the object is an - * instance of the resource class `save`, `remove` and `delete` actions are available on it as - * methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read, - * update, delete) on server-side data like this: + * instance of the resource class. The actions `save`, `remove` and `delete` are available on it + * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, + * read, update, delete) on server-side data like this: *
         var User = $resource('/user/:userId', {userId:'@id'});
         var user = User.get({userId:123}, function() {
@@ -149,9 +162,9 @@
      });
    
* - * It's worth noting that the success callback for `get`, `query` and other method gets passed - * in the response that came from the server as well as $http header getter function, so one - * could rewrite the above example and get access to http headers as: + * It's worth noting that the success callback for `get`, `query` and other method gets passed + * in the response that came from the server as well as $http header getter function, so one + * could rewrite the above example and get access to http headers as: *
      var User = $resource('/user/:userId', {userId:'@id'});
@@ -230,51 +243,51 @@ angular.module('ngResource', ['ng']).
           return $parse(path)(obj);
         };
 
-  /**
-   * We need our custom mehtod 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
-   *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
-   *    pct-encoded   = "%" HEXDIG HEXDIG
-   *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
-   *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
-   *                     / "*" / "+" / "," / ";" / "="
-   */
-  function encodeUriSegment(val) {
-    return encodeUriQuery(val, true).
-      replace(/%26/gi, '&').
-      replace(/%3D/gi, '=').
-      replace(/%2B/gi, '+');
-  }
-
-
-  /**
-   * This method is intended for encoding *key* or *value* parts of query component. We need a custom
-   * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
-   * encoded per http://tools.ietf.org/html/rfc3986:
-   *    query       = *( pchar / "/" / "?" )
-   *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
-   *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
-   *    pct-encoded   = "%" HEXDIG HEXDIG
-   *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
-   *                     / "*" / "+" / "," / ";" / "="
-   */
-  function encodeUriQuery(val, pctEncodeSpaces) {
-    return encodeURIComponent(val).
-      replace(/%40/gi, '@').
-      replace(/%3A/gi, ':').
-      replace(/%24/g, '$').
-      replace(/%2C/gi, ',').
-      replace((pctEncodeSpaces ? null : /%20/g), '+');
-  }
-
-  function Route(template, defaults) {
+    /**
+     * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
+     * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
+     * segments:
+     *    segment       = *pchar
+     *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
+     *    pct-encoded   = "%" HEXDIG HEXDIG
+     *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
+     *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
+     *                     / "*" / "+" / "," / ";" / "="
+     */
+    function encodeUriSegment(val) {
+      return encodeUriQuery(val, true).
+        replace(/%26/gi, '&').
+        replace(/%3D/gi, '=').
+        replace(/%2B/gi, '+');
+    }
+
+
+    /**
+     * This method is intended for encoding *key* or *value* parts of query component. We need a custom
+     * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
+     * encoded per http://tools.ietf.org/html/rfc3986:
+     *    query       = *( pchar / "/" / "?" )
+     *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
+     *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
+     *    pct-encoded   = "%" HEXDIG HEXDIG
+     *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
+     *                     / "*" / "+" / "," / ";" / "="
+     */
+    function encodeUriQuery(val, pctEncodeSpaces) {
+      return encodeURIComponent(val).
+        replace(/%40/gi, '@').
+        replace(/%3A/gi, ':').
+        replace(/%24/g, '$').
+        replace(/%2C/gi, ',').
+        replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
+    }
+
+    function Route(template, defaults) {
       this.template = template = template + '#';
       this.defaults = defaults || {};
       var urlParams = this.urlParams = {};
       forEach(template.split(/\W/), function(param){
-        if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) {
+        if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) {
           urlParams[param] = true;
         }
       });
@@ -285,12 +298,25 @@ angular.module('ngResource', ['ng']).
       url: function(params) {
         var self = this,
             url = this.template,
+            val,
             encodedVal;
 
         params = params || {};
         forEach(this.urlParams, function(_, urlParam){
-          encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || "");
-          url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1");
+          val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
+          if (angular.isDefined(val) && val !== null) {
+            encodedVal = encodeUriSegment(val);
+            url = url.replace(new RegExp(":" + urlParam + "(\\W)", "g"), encodedVal + "$1");
+          } else {
+            url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W)", "g"), function(match,
+                leadingSlashes, tail) {
+              if (tail.charAt(0) == '/') {
+                return tail;
+              } else {
+                return leadingSlashes + tail;
+              }
+            });
+          }
         });
         url = url.replace(/\/?#$/, '');
         var query = [];
@@ -311,9 +337,10 @@ angular.module('ngResource', ['ng']).
 
       actions = extend({}, DEFAULT_ACTIONS, actions);
 
-      function extractParams(data){
+      function extractParams(data, actionParams){
         var ids = {};
-        forEach(paramDefaults || {}, function(value, key){
+        actionParams = extend({}, paramDefaults, actionParams);
+        forEach(actionParams, function(value, key){
           ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
         });
         return ids;
@@ -324,6 +351,7 @@ angular.module('ngResource', ['ng']).
       }
 
       forEach(actions, function(action, name) {
+        action.method = angular.uppercase(action.method);
         var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH';
         Resource[name] = function(a1, a2, a3, a4) {
           var params = {};
@@ -367,7 +395,7 @@ angular.module('ngResource', ['ng']).
           var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
           $http({
             method: action.method,
-            url: route.url(extend({}, extractParams(data), action.params || {}, params)),
+            url: route.url(extend({}, extractParams(data, action.params || {}), params)),
             data: data
           }).then(function(response) {
               var data = response.data;
@@ -389,11 +417,6 @@ angular.module('ngResource', ['ng']).
         };
 
 
-        Resource.bind = function(additionalParamDefaults){
-          return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
-        };
-
-
         Resource.prototype['$' + name] = function(a1, a2, a3) {
           var params = extractParams(this),
               success = noop,
@@ -419,10 +442,16 @@ angular.module('ngResource', ['ng']).
           Resource[name].call(this, params, data, success, error);
         };
       });
+
+      Resource.bind = function(additionalParamDefaults){
+        return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
+      };
+
       return Resource;
     }
 
     return ResourceFactory;
   }]);
 
-})(window.angular);
+
+})(window, window.angular);
diff --git a/app/lib/angular/angular-resource.min.js b/app/lib/angular/angular-resource.min.js
index 2c6b01e..a918d51 100755
--- a/app/lib/angular/angular-resource.min.js
+++ b/app/lib/angular/angular-resource.min.js
@@ -1,10 +1,10 @@
 /*
- AngularJS v1.0.0rc6
+ AngularJS v1.0.6
  (c) 2010-2012 Google, Inc. http://angularjs.org
  License: MIT
 */
-(function(f){'use strict';f.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(u,v){function g(b,c){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(c?null:/%20/g,"+")}function l(b,c){this.template=b+="#";this.defaults=c||{};var a=this.urlParams={};j(b.split(/\W/),function(c){c&&b.match(RegExp("[^\\\\]:"+c+"\\W"))&&(a[c]=!0)});this.template=b.replace(/\\:/g,":")}function s(b,c,a){function f(d){var b={};
-j(c||{},function(a,w){var m;a.charAt&&a.charAt(0)=="@"?(m=a.substr(1),m=v(m)(d)):m=a;b[w]=m});return b}function e(a){t(a||{},this)}var x=new l(b),a=r({},y,a);j(a,function(d,g){var l=d.method=="POST"||d.method=="PUT"||d.method=="PATCH";e[g]=function(a,b,c,g){var i={},h,k=o,p=null;switch(arguments.length){case 4:p=g,k=c;case 3:case 2:if(q(b)){if(q(a)){k=a;p=b;break}k=b;p=c}else{i=a;h=b;k=c;break}case 1:q(a)?k=a:l?h=a:i=a;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+
-arguments.length+" arguments.";}var n=this instanceof e?this:d.isArray?[]:new e(h);u({method:d.method,url:x.url(r({},f(h),d.params||{},i)),data:h}).then(function(a){var b=a.data;if(b)d.isArray?(n.length=0,j(b,function(a){n.push(new e(a))})):t(b,n);(k||o)(n,a.headers)},p);return n};e.bind=function(d){return s(b,r({},c,d),a)};e.prototype["$"+g]=function(a,b,d){var c=f(this),i=o,h;switch(arguments.length){case 3:c=a;i=b;h=d;break;case 2:case 1:q(a)?(i=a,h=b):(c=a,i=b||o);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+
-arguments.length+" arguments.";}e[g].call(this,c,l?this:void 0,i,h)}});return e}var y={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},o=f.noop,j=f.forEach,r=f.extend,t=f.copy,q=f.isFunction;l.prototype={url:function(b){var c=this,a=this.template,f,b=b||{};j(this.urlParams,function(e,d){f=g(b[d]||c.defaults[d]||"",!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+");a=a.replace(RegExp(":"+d+"(\\W)"),f+"$1")});
-var a=a.replace(/\/?#$/,""),e=[];j(b,function(a,b){c.urlParams[b]||e.push(g(b)+"="+g(a))});e.sort();a=a.replace(/\/*$/,"");return a+(e.length?"?"+e.join("&"):"")}};return s}])})(window.angular);
+(function(C,d,w){'use strict';d.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(x,y){function s(b,e){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,e?"%20":"+")}function t(b,e){this.template=b+="#";this.defaults=e||{};var a=this.urlParams={};h(b.split(/\W/),function(f){f&&RegExp("(^|[^\\\\]):"+f+"\\W").test(b)&&(a[f]=!0)});this.template=b.replace(/\\:/g,":")}function u(b,e,a){function f(m,a){var b=
+{},a=o({},e,a);h(a,function(a,z){var c;a.charAt&&a.charAt(0)=="@"?(c=a.substr(1),c=y(c)(m)):c=a;b[z]=c});return b}function g(a){v(a||{},this)}var k=new t(b),a=o({},A,a);h(a,function(a,b){a.method=d.uppercase(a.method);var e=a.method=="POST"||a.method=="PUT"||a.method=="PATCH";g[b]=function(b,c,d,B){var j={},i,l=p,q=null;switch(arguments.length){case 4:q=B,l=d;case 3:case 2:if(r(c)){if(r(b)){l=b;q=c;break}l=c;q=d}else{j=b;i=c;l=d;break}case 1:r(b)?l=b:e?i=b:j=b;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+
+arguments.length+" arguments.";}var n=this instanceof g?this:a.isArray?[]:new g(i);x({method:a.method,url:k.url(o({},f(i,a.params||{}),j)),data:i}).then(function(b){var c=b.data;if(c)a.isArray?(n.length=0,h(c,function(a){n.push(new g(a))})):v(c,n);(l||p)(n,b.headers)},q);return n};g.prototype["$"+b]=function(a,d,h){var m=f(this),j=p,i;switch(arguments.length){case 3:m=a;j=d;i=h;break;case 2:case 1:r(a)?(j=a,i=d):(m=a,j=d||p);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+
+arguments.length+" arguments.";}g[b].call(this,m,e?this:w,j,i)}});g.bind=function(d){return u(b,o({},e,d),a)};return g}var A={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},p=d.noop,h=d.forEach,o=d.extend,v=d.copy,r=d.isFunction;t.prototype={url:function(b){var e=this,a=this.template,f,g,b=b||{};h(this.urlParams,function(h,c){f=b.hasOwnProperty(c)?b[c]:e.defaults[c];d.isDefined(f)&&f!==null?(g=s(f,!0).replace(/%26/gi,"&").replace(/%3D/gi,
+"=").replace(/%2B/gi,"+"),a=a.replace(RegExp(":"+c+"(\\W)","g"),g+"$1")):a=a.replace(RegExp("(/?):"+c+"(\\W)","g"),function(a,b,c){return c.charAt(0)=="/"?c:b+c})});var a=a.replace(/\/?#$/,""),k=[];h(b,function(a,b){e.urlParams[b]||k.push(s(b)+"="+s(a))});k.sort();a=a.replace(/\/*$/,"");return a+(k.length?"?"+k.join("&"):"")}};return u}])})(window,window.angular);
diff --git a/app/lib/angular/angular-sanitize.js b/app/lib/angular/angular-sanitize.js
index bd5bf97..2ec309a 100755
--- a/app/lib/angular/angular-sanitize.js
+++ b/app/lib/angular/angular-sanitize.js
@@ -1,14 +1,14 @@
 /**
- * @license AngularJS v1.0.0rc6
+ * @license AngularJS v1.0.6
  * (c) 2010-2012 Google, Inc. http://angularjs.org
  * License: MIT
  */
-(function(angular) {
+(function(window, angular, undefined) {
 'use strict';
 
 /**
  * @ngdoc overview
- * @name angular.module.ngSanitize
+ * @name ngSanitize
  * @description
  */
 
@@ -31,7 +31,7 @@
 
 /**
  * @ngdoc service
- * @name angular.module.ngSanitize.$sanitize
+ * @name ngSanitize.$sanitize
  * @function
  *
  * @description
@@ -400,34 +400,32 @@ function htmlSanitizeWriter(buf){
 // define ngSanitize module and register $sanitize service
 angular.module('ngSanitize', []).value('$sanitize', $sanitize);
 
-
-
 /**
  * @ngdoc directive
- * @name angular.module.ngSanitize.directive.ngBindHtml
+ * @name ngSanitize.directive:ngBindHtml
  *
  * @description
  * Creates a binding that will sanitize the result of evaluating the `expression` with the
- * {@link angular.module.ng.$sanitize $sanitize} service and innerHTML the result into the current
- * element.
+ * {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element.
  *
- * See {@link angular.module.ng.$sanitize $sanitize} docs for examples.
+ * See {@link ngSanitize.$sanitize $sanitize} docs for examples.
  *
  * @element ANY
- * @param {expression} ngBindHtml {@link guide/dev_guide.expressions Expression} to evaluate.
+ * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
  */
 angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {
   return function(scope, element, attr) {
     element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
-    scope.$watch(attr.ngBindHtml, function(value) {
+    scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) {
       value = $sanitize(value);
       element.html(value || '');
     });
   };
 }]);
+
 /**
  * @ngdoc filter
- * @name angular.module.ngSanitize.filter.linky
+ * @name ngSanitize.filter:linky
  * @function
  *
  * @description
@@ -437,6 +435,9 @@ angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($san
  * @param {string} text Input text.
  * @returns {string} Html-linkified text.
  *
+ * @usage
+   
+ *
  * @example
    
      
@@ -532,4 +533,5 @@ angular.module('ngSanitize').filter('linky', function() {
   };
 });
 
-})(window.angular);
+
+})(window, window.angular);
diff --git a/app/lib/angular/angular-sanitize.min.js b/app/lib/angular/angular-sanitize.min.js
index aad0f1f..dbddcf5 100755
--- a/app/lib/angular/angular-sanitize.min.js
+++ b/app/lib/angular/angular-sanitize.min.js
@@ -1,13 +1,13 @@
 /*
- AngularJS v1.0.0rc6
+ AngularJS v1.0.6
  (c) 2010-2012 Google, Inc. http://angularjs.org
  License: MIT
 */
-(function(g){'use strict';function i(a){var d={},a=a.split(","),b;for(b=0;b=0;e--)if(f[e]==b)break;if(e>=0){for(c=f.length-1;c>=e;c--)d.end&&d.end(f[c]);f.length=
+(function(I,g){'use strict';function i(a){var d={},a=a.split(","),b;for(b=0;b=0;e--)if(f[e]==b)break;if(e>=0){for(c=f.length-1;c>=e;c--)d.end&&d.end(f[c]);f.length=
 e}}var c,h,f=[],j=a;for(f.last=function(){return f[f.length-1]};a;){h=!0;if(!f.last()||!q[f.last()]){if(a.indexOf("<\!--")===0)c=a.indexOf("--\>"),c>=0&&(d.comment&&d.comment(a.substring(4,c)),a=a.substring(c+3),h=!1);else if(B.test(a)){if(c=a.match(r))a=a.substring(c[0].length),c[0].replace(r,e),h=!1}else if(C.test(a)&&(c=a.match(s)))a=a.substring(c[0].length),c[0].replace(s,b),h=!1;h&&(c=a.indexOf("<"),h=c<0?a:a.substring(0,c),a=c<0?"":a.substring(c),d.chars&&d.chars(k(h)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+
 f.last()+"[^>]*>","i"),function(b,a){a=a.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(a));return""}),e("",f.last());if(a==j)throw"Parse Error: "+a;j=a}e()}function k(a){l.innerHTML=a.replace(//g,">")}function u(a){var d=!1,b=g.bind(a,a.push);return{start:function(a,c,h){a=g.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]==
 !0&&(b("<"),b(a),g.forEach(c,function(a,c){var e=g.lowercase(c);if(G[e]==!0&&(w[e]!==!0||a.match(H)))b(" "),b(c),b('="'),b(t(a)),b('"')}),b(h?"/>":">"))},end:function(a){a=g.lowercase(a);!d&&v[a]==!0&&(b(""));a==d&&(d=!1)},chars:function(a){d||b(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^/g,
 E=//g,H=/^((ftp|https?):\/\/|mailto:|#)/,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=g.extend({},y,x),m=g.extend({},x,i("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),n=g.extend({},y,i("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),
 q=i("script,style"),v=g.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=g.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");g.module("ngSanitize",[]).value("$sanitize",function(a){var d=[];
 z(a,u(d));return d.join("")});g.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,b,e){b.addClass("ng-binding").data("$binding",e.ngBindHtml);d.$watch(e.ngBindHtml,function(c){c=a(c);b.html(c||"")})}}]);g.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(b){if(!b)return b;for(var e=b,c=[],h=u(c),f,g;b=e.match(a);)f=b[0],b[2]==b[3]&&(f="mailto:"+f),g=b.index,
-h.chars(e.substr(0,g)),h.start("a",{href:f}),h.chars(b[0].replace(d,"")),h.end("a"),e=e.substring(g+b[0].length);h.chars(e);return c.join("")}})})(window.angular);
+h.chars(e.substr(0,g)),h.start("a",{href:f}),h.chars(b[0].replace(d,"")),h.end("a"),e=e.substring(g+b[0].length);h.chars(e);return c.join("")}})})(window,window.angular);
diff --git a/app/lib/angular/angular.js b/app/lib/angular/angular.js
index e6b7d7f..5b431ae 100755
--- a/app/lib/angular/angular.js
+++ b/app/lib/angular/angular.js
@@ -1,5 +1,5 @@
 /**
- * @license AngularJS v1.0.0rc6
+ * @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
@@ -67,7 +64,6 @@ var Error             = window.Error,
     /** @name angular */
     angular           = window.angular || (window.angular = {}),
     angularModule,
-    /** @name angular.module.ng */
     nodeName_,
     uid               = ['0', '0', '0'];
 
@@ -98,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) {
@@ -109,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 {
@@ -154,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
@@ -526,7 +546,7 @@ function isLeafNode (node) {
  * * If  `source` is not an object or array, `source` is returned.
  *
  * Note: this function is used to augment the Object type in Angular expressions. See
- * {@link angular.module.ng.$filter} for more information about Angular arrays.
+ * {@link ng.$filter} for more information about Angular arrays.
  *
  * @param {*} source The source that will be used to make a copy.
  *                   Can be any type, including primitives, `null`, and `undefined`.
@@ -550,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]));
       }
@@ -628,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;
       }
@@ -761,7 +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]; + // 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); + } + } @@ -793,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 @@ -828,16 +859,16 @@ function encodeUriQuery(val, pctEncodeSpaces) { replace(/%3A/gi, ':'). replace(/%24/g, '$'). replace(/%2C/gi, ','). - replace((pctEncodeSpaces ? null : /%20/g), '+'); + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); } /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngApp + * @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 @@ -845,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 @@ -910,26 +941,45 @@ function angularInit(element, bootstrap) { * @description * Use this function to manually start up angular application. * - * See: {@link guide/dev_guide.bootstrap.manual_bootstrap Bootstrap} + * See: {@link guide/bootstrap Bootstrap} * * @param {Element} element DOM element which is the root of angular application. * @param {Array=} modules an array of module declarations. See: {@link angular.module modules} - * @returns {angular.module.auto.$injector} Returns the newly created injector for this app. + * @returns {AUTO.$injector} Returns the newly created injector for this app. */ function bootstrap(element, modules) { - element = jqLite(element); - modules = modules || []; - modules.unshift('ng'); - var injector = createInjector(modules); - injector.invoke( - ['$rootScope', '$compile', '$injector', function(scope, 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; @@ -1011,8 +1061,8 @@ function setupModuleLoader(window) { * * # Module * - * A module is a collocation of services, directives, filters, and configure information. Module - * is used to configure the {@link angular.module.AUTO.$injector $injector}. + * A module is a collocation of services, directives, filters, and configuration information. Module + * is used to configure the {@link AUTO.$injector $injector}. * *
      * // Create a new module
@@ -1035,13 +1085,13 @@ function setupModuleLoader(window) {
      * 
* * However it's more likely that you'll just use - * {@link angular.module.ng.$compileProvider.directive.ngApp ngApp} or + * {@link ng.directive:ngApp ngApp} or * {@link angular.bootstrap} to simplify this process for you. * * @param {!string} name The name of the module to create or retrieve. * @param {Array.=} requires If specified then new module is being created. If unspecified then the * the module is being retrieved for further configuration. - * @param {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. */ @@ -1095,7 +1145,7 @@ function setupModuleLoader(window) { * @param {string} name service name * @param {Function} providerType Construction function for creating new instance of the service. * @description - * See {@link angular.module.AUTO.$provide#provider $provide.provider()}. + * See {@link AUTO.$provide#provider $provide.provider()}. */ provider: invokeLater('$provide', 'provider'), @@ -1106,7 +1156,7 @@ function setupModuleLoader(window) { * @param {string} name service name * @param {Function} providerFunction Function for creating new instance of the service. * @description - * See {@link angular.module.AUTO.$provide#factory $provide.factory()}. + * See {@link AUTO.$provide#factory $provide.factory()}. */ factory: invokeLater('$provide', 'factory'), @@ -1117,7 +1167,7 @@ function setupModuleLoader(window) { * @param {string} name service name * @param {Function} constructor A constructor function that will be instantiated. * @description - * See {@link angular.module.AUTO.$provide#service $provide.service()}. + * See {@link AUTO.$provide#service $provide.service()}. */ service: invokeLater('$provide', 'service'), @@ -1128,7 +1178,7 @@ function setupModuleLoader(window) { * @param {string} name service name * @param {*} object Service instance object. * @description - * See {@link angular.module.AUTO.$provide#value $provide.value()}. + * See {@link AUTO.$provide#value $provide.value()}. */ value: invokeLater('$provide', 'value'), @@ -1140,7 +1190,7 @@ function setupModuleLoader(window) { * @param {*} object Constant value. * @description * Because the constant are fixed, they get applied before other provide methods. - * See {@link angular.module.AUTO.$provide#constant $provide.constant()}. + * See {@link AUTO.$provide#constant $provide.constant()}. */ constant: invokeLater('$provide', 'constant', 'unshift'), @@ -1151,7 +1201,7 @@ function setupModuleLoader(window) { * @param {string} name Filter name. * @param {Function} filterFactory Factory function for creating new instance of filter. * @description - * See {@link angular.module.ng.$filterProvider#register $filterProvider.register()}. + * See {@link ng.$filterProvider#register $filterProvider.register()}. */ filter: invokeLater('$filterProvider', 'register'), @@ -1162,7 +1212,7 @@ function setupModuleLoader(window) { * @param {string} name Controller name. * @param {Function} constructor Controller constructor function. * @description - * See {@link angular.module.ng.$controllerProvider#register $controllerProvider.register()}. + * See {@link ng.$controllerProvider#register $controllerProvider.register()}. */ controller: invokeLater('$controllerProvider', 'register'), @@ -1174,7 +1224,7 @@ function setupModuleLoader(window) { * @param {Function} directiveFactory Factory function for creating new instance of * directives. * @description - * See {@link angular.module.ng.$compileProvider.directive $compileProvider.directive()}. + * See {@link ng.$compileProvider#directive $compileProvider.directive()}. */ directive: invokeLater('$compileProvider', 'directive'), @@ -1196,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); @@ -1243,11 +1293,11 @@ function setupModuleLoader(window) { * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.0.0rc6', // 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: 0, - codeName: 'runny-nose' + dot: 6, + codeName: 'universal-irreversibility' }; @@ -1305,6 +1355,7 @@ function publishExternalAPI(angular){ ngClass: ngClassDirective, ngClassEven: ngClassEvenDirective, ngClassOdd: ngClassOddDirective, + ngCsp: ngCspDirective, ngCloak: ngCloakDirective, ngController: ngControllerDirective, ngForm: ngFormDirective, @@ -1337,7 +1388,6 @@ function publishExternalAPI(angular){ $browser: $BrowserProvider, $cacheFactory: $CacheFactoryProvider, $controller: $ControllerProvider, - $defer: $DeferProvider, $document: $DocumentProvider, $exceptionHandler: $ExceptionHandlerProvider, $filter: $FilterProvider, @@ -1353,6 +1403,7 @@ function publishExternalAPI(angular){ $q: $QProvider, $sniffer: $SnifferProvider, $templateCache: $TemplateCacheProvider, + $timeout: $TimeoutProvider, $window: $WindowProvider }); } @@ -1413,18 +1464,19 @@ 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 * camelCase directive name, then the controller for this directive will be retrieved (e.g. * `'ngModel'`). * - `injector()` - retrieves the injector of the current element or its parent. - * - `scope()` - retrieves the {@link api/angular.module.ng.$rootScope.Scope scope} of the current + * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current * element or its parent. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top * parent element is reached. @@ -1433,8 +1485,8 @@ function publishExternalAPI(angular){ * @returns {Object} jQuery object. */ -var jqCache = {}, - jqName = 'ng-' + new Date().getTime(), +var jqCache = JQLite.cache = {}, + jqName = JQLite.expando = 'ng-' + new Date().getTime(), jqId = 1, addEventListenerFn = (window.document.addEventListener ? function(element, type, fn) {element.addEventListener(type, fn, false);} @@ -1443,7 +1495,7 @@ var jqCache = {}, ? function(element, type, fn) {element.removeEventListener(type, fn, false); } : function(element, type, fn) {element.detachEvent('on' + type, fn); }); -function jqNextId() { return (jqId++); } +function jqNextId() { return ++jqId; } var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; @@ -1481,19 +1533,14 @@ function JQLitePatchJQueryRemove(name, dispatchThis) { fireEvent = dispatchThis, set, setIndex, setLength, element, childIndex, childLength, children, - fns, data; + fns, events; while(list.length) { set = list.shift(); for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { element = jqLite(set[setIndex]); if (fireEvent) { - data = element.data('events'); - if ( (fns = data && data.$destroy) ) { - forEach(fns, function(fn){ - fn.handler(); - }); - } + element.triggerHandler('$destroy'); } else { fireEvent = !fireEvent; } @@ -1524,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. @@ -1544,35 +1591,79 @@ function JQLiteDealoc(element){ } } +function JQLiteUnbind(element, type, fn) { + var events = JQLiteExpandoStore(element, 'events'), + handle = JQLiteExpandoStore(element, 'handle'); + + if (!handle) return; //no listeners registered + + if (isUndefined(type)) { + forEach(events, function(eventHandler, type) { + removeEventListenerFn(element, type, eventHandler); + delete events[type]; + }); + } else { + if (isUndefined(fn)) { + removeEventListenerFn(element, type, events[type]); + delete events[type]; + } else { + arrayRemove(events[type], fn); + } + } +} + function JQLiteRemoveData(element) { - var cacheId = element[jqName], - cache = jqCache[cacheId]; - if (cache) { - if (cache.bind) { - forEach(cache.bind, function(fn, type){ - if (type == '$destroy') { - fn({}); - } else { - removeEventListenerFn(element, type, fn); - } - }); + var expandoId = element[jqName], + expandoStore = jqCache[expandoId]; + + if (expandoStore) { + if (expandoStore.handle) { + expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); + JQLiteUnbind(element); } - delete jqCache[cacheId]; + delete jqCache[expandoId]; element[jqName] = undefined; // ie does not allow deletion of attributes on elements. } } -function JQLiteData(element, key, value) { - var cacheId = element[jqName], - cache = jqCache[cacheId || -1]; +function JQLiteExpandoStore(element, key, value) { + var expandoId = element[jqName], + expandoStore = jqCache[expandoId || -1]; + if (isDefined(value)) { - if (!cache) { - element[jqName] = cacheId = jqNextId(); - cache = jqCache[cacheId] = {}; + if (!expandoStore) { + element[jqName] = expandoId = jqNextId(); + expandoStore = jqCache[expandoId] = {}; } - cache[key] = value; + expandoStore[key] = value; } else { - return cache ? cache[key] : null; + return expandoStore && expandoStore[key]; + } +} + +function JQLiteData(element, key, value) { + var data = JQLiteExpandoStore(element, 'data'), + isSetter = isDefined(value), + keyDefined = !isSetter && isDefined(key), + isSimpleGetter = keyDefined && !isObject(key); + + if (!data && !isSimpleGetter) { + JQLiteExpandoStore(element, 'data', data = {}); + } + + if (isSetter) { + data[key] = value; + } else { + if (keyDefined) { + if (isSimpleGetter) { + // don't create data in this case. + return data && data[key]; + } else { + extend(data, key); + } + } else { + return data; + } } } @@ -1581,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, " ") @@ -1593,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)); } @@ -1680,8 +1771,12 @@ forEach('input,select,option,textarea,button,form'.split(','), function(value) { BOOLEAN_ELEMENTS[uppercase(value)] = true; }); -function isBooleanAttr(element, name) { - return BOOLEAN_ELEMENTS[element.nodeName] && BOOLEAN_ATTR[name.toLowerCase()]; +function getBooleanAttrName(element, name) { + // check dom last since we will most likely fail on name + var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; + + // booleanAttr is here twice to minimize DOM access + return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; } forEach({ @@ -1811,10 +1906,16 @@ forEach({ // in a way that survives minification. if (((fn.length == 2 && (fn !== JQLiteHasClass && fn !== JQLiteController)) ? arg1 : arg2) === undefined) { if (isObject(arg1)) { + // we are a write, but the object properties are the key/values for(i=0; i < this.length; i++) { - for (key in arg1) { - fn(this[i], key, arg1[key]); + if (fn === JQLiteData) { + // data() takes the whole object in jQuery + fn(this[i], arg1); + } else { + for (key in arg1) { + fn(this[i], key, arg1[key]); + } } } // return self for chaining @@ -1836,8 +1937,8 @@ forEach({ }; }); -function createEventHandler(element) { - var eventHandler = function (event) { +function createEventHandler(element, events) { + var eventHandler = function (event, type) { if (!event.preventDefault) { event.preventDefault = function() { event.returnValue = false; //ie @@ -1867,14 +1968,14 @@ function createEventHandler(element) { return event.defaultPrevented; }; - forEach(eventHandler.fns, function(fn){ + forEach(events[type || event.type], function(fn) { fn.call(element, event); }); // Remove monkey-patched methods (IE), // as they would cause memory leaks in IE8. - if (msie < 8) { - // IE7 does not allow to delete property on native object + if (msie <= 8) { + // IE7/8 does not allow to delete property on native object event.preventDefault = null; event.stopPropagation = null; event.isDefaultPrevented = null; @@ -1885,7 +1986,7 @@ function createEventHandler(element) { delete event.isDefaultPrevented; } }; - eventHandler.fns = []; + eventHandler.elem = element; return eventHandler; } @@ -1900,63 +2001,45 @@ forEach({ dealoc: JQLiteDealoc, bind: function bindFn(element, type, fn){ - var bind = JQLiteData(element, 'bind'); + var events = JQLiteExpandoStore(element, 'events'), + handle = JQLiteExpandoStore(element, 'handle'); + if (!events) JQLiteExpandoStore(element, 'events', events = {}); + if (!handle) JQLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); - if (!bind) JQLiteData(element, 'bind', bind = {}); forEach(type.split(' '), function(type){ - var eventHandler = bind[type]; - + var eventFns = events[type]; - if (!eventHandler) { + if (!eventFns) { if (type == 'mouseenter' || type == 'mouseleave') { - var mouseenter = bind.mouseenter = createEventHandler(element); - var mouseleave = bind.mouseleave = createEventHandler(element); var counter = 0; + events.mouseenter = []; + events.mouseleave = []; bindFn(element, 'mouseover', function(event) { counter++; if (counter == 1) { - event.type = 'mouseenter'; - mouseenter(event); + handle(event, 'mouseenter'); } }); bindFn(element, 'mouseout', function(event) { counter --; if (counter == 0) { - event.type = 'mouseleave'; - mouseleave(event); + handle(event, 'mouseleave'); } }); - eventHandler = bind[type]; } else { - eventHandler = bind[type] = createEventHandler(element); - addEventListenerFn(element, type, eventHandler); + addEventListenerFn(element, type, handle); + events[type] = []; } + eventFns = events[type] } - eventHandler.fns.push(fn); + eventFns.push(fn); }); }, - unbind: function(element, type, fn) { - var bind = JQLiteData(element, 'bind'); - if (!bind) return; //no listeners registered - - if (isUndefined(type)) { - forEach(bind, function(eventHandler, type) { - removeEventListenerFn(element, type, eventHandler); - delete bind[type]; - }); - } else { - if (isUndefined(fn)) { - removeEventListenerFn(element, type, bind[type]); - delete bind[type]; - } else { - arrayRemove(bind[type].fns, fn); - } - } - }, + unbind: JQLiteUnbind, replaceWith: function(element, replaceNode) { var index, parent = element.parentNode; @@ -1974,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) { @@ -2044,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 @@ -2169,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]; + } } }; @@ -2179,12 +2289,12 @@ HashQueueMap.prototype = { * * @description * Creates an injector function that can be used for retrieving services as well as for - * dependency injection (see {@link guide/dev_guide.di dependency injection}). + * dependency injection (see {@link guide/di dependency injection}). * * @param {Array.} modules A list of module functions or their aliases. See * {@link angular.module}. The `ng` module must be explicitly added. - * @returns {function()} Injector function. See {@link angular.module.AUTO.$injector $injector}. + * @returns {function()} Injector function. See {@link AUTO.$injector $injector}. * * @example * Typical usage @@ -2192,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); @@ -2204,42 +2314,55 @@ HashQueueMap.prototype = { /** * @ngdoc overview - * @name angular.module.AUTO + * @name AUTO * @description * - * Implicit module which gets automatically added to each {@link angular.module.AUTO.$injector $injector}. + * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}. */ 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 inferInjectionArgs(fn) { - assertArgFn(fn); - if (!fn.$inject) { - var args = fn.$inject = []; - var fnText = fn.toString().replace(STRIP_COMMENTS, ''); - var argDecl = fnText.match(FN_ARGS); - forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ - arg.replace(FN_ARG, function(all, underscore, name){ - args.push(name); +function annotate(fn) { + var $inject, + fnText, + argDecl, + last; + + if (typeof fn == 'function') { + if (!($inject = fn.$inject)) { + $inject = []; + fnText = fn.toString().replace(STRIP_COMMENTS, ''); + argDecl = fnText.match(FN_ARGS); + forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ + arg.replace(FN_ARG, function(all, underscore, name){ + $inject.push(name); + }); }); - }); + fn.$inject = $inject; + } + } else if (isArray(fn)) { + last = fn.length - 1; + assertArgFn(fn[last], 'fn') + $inject = fn.slice(0, last); + } else { + assertArgFn(fn, 'fn', true); } - return fn.$inject; + return $inject; } /////////////////////////////////////// /** * @ngdoc object - * @name angular.module.AUTO.$injector + * @name AUTO.$injector * @function * * @description * * `$injector` is used to retrieve object instances as defined by - * {@link angular.module.AUTO.$provide provider}, instantiate types, invoke methods, + * {@link AUTO.$provide provider}, instantiate types, invoke methods, * and load modules. * * The following always holds true: @@ -2259,21 +2382,21 @@ function inferInjectionArgs(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 * * In JavaScript calling `toString()` on a function returns the function definition. The definition can then be - * parsed and the function arguments can be extracted. *NOTE:* This does not work with minfication, and obfuscation + * parsed and the function arguments can be extracted. *NOTE:* This does not work with minification, and obfuscation * tools since these tools change the argument names. * * ## `$inject` Annotation @@ -2285,8 +2408,8 @@ function inferInjectionArgs(fn) { /** * @ngdoc method - * @name angular.module.AUTO.$injector#get - * @methodOf angular.module.AUTO.$injector + * @name AUTO.$injector#get + * @methodOf AUTO.$injector * * @description * Return an instance of the service. @@ -2297,8 +2420,8 @@ function inferInjectionArgs(fn) { /** * @ngdoc method - * @name angular.module.AUTO.$injector#invoke - * @methodOf angular.module.AUTO.$injector + * @name AUTO.$injector#invoke + * @methodOf AUTO.$injector * * @description * Invoke the method and supply the method arguments from the `$injector`. @@ -2307,13 +2430,13 @@ function inferInjectionArgs(fn) { * @param {Object=} self The `this` for the invoked method. * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before * the `$injector` is consulted. - * @return the value returned by the invoked `fn` function. + * @returns {*} the value returned by the invoked `fn` function. */ /** * @ngdoc method - * @name angular.module.AUTO.$injector#instantiate - * @methodOf angular.module.AUTO.$injector + * @name AUTO.$injector#instantiate + * @methodOf AUTO.$injector * @description * Create a new instance of JS type. The method takes a constructor function invokes the new operator and supplies * all of the arguments to the constructor function as specified by the constructor annotation. @@ -2321,18 +2444,99 @@ function inferInjectionArgs(fn) { * @param {function} Type Annotated constructor function. * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before * the `$injector` is consulted. - * @return new instance of `Type`. + * @returns {Object} new instance of `Type`. */ +/** + * @ngdoc method + * @name AUTO.$injector#annotate + * @methodOf AUTO.$injector + * + * @description + * Returns an array of service names which the function is requesting for injection. This API is used by the injector + * to determine which services need to be injected into the function when the function is invoked. There are three + * ways in which the function can be annotated with the needed dependencies. + * + * # Argument names + * + * The simplest form is to extract the dependencies from the arguments of the function. This is done by converting + * the function into a string using `toString()` method and extracting the argument names. + *
+ *   // Given
+ *   function MyController($scope, $route) {
+ *     // ...
+ *   }
+ *
+ *   // Then
+ *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
+ * 
+ * + * This method does not work with code minfication / obfuscation. For this reason the following annotation strategies + * are supported. + * + * # The `$inject` property + * + * If a function has an `$inject` property and its value is an array of strings, then the strings represent names of + * services to be injected into the function. + *
+ *   // Given
+ *   var MyController = function(obfuscatedScope, obfuscatedRoute) {
+ *     // ...
+ *   }
+ *   // Define function dependencies
+ *   MyController.$inject = ['$scope', '$route'];
+ *
+ *   // Then
+ *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
+ * 
+ * + * # The array notation + * + * It is often desirable to inline Injected functions and that's when setting the `$inject` property is very + * inconvenient. In these situations using the array notation to specify the dependencies in a way that survives + * minification is a better choice: + * + *
+ *   // We wish to write this (not minification / obfuscation safe)
+ *   injector.invoke(function($compile, $rootScope) {
+ *     // ...
+ *   });
+ *
+ *   // We are forced to write break inlining
+ *   var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
+ *     // ...
+ *   };
+ *   tmpFn.$inject = ['$compile', '$rootScope'];
+ *   injector.invoke(tempFn);
+ *
+ *   // To better support inline function the inline annotation is supported
+ *   injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
+ *     // ...
+ *   }]);
+ *
+ *   // Therefore
+ *   expect(injector.annotate(
+ *      ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
+ *    ).toEqual(['$compile', '$rootScope']);
+ * 
+ * + * @param {function|Array.} fn Function for which dependent service names need to be retrieved as described + * above. + * + * @returns {Array.} The names of the services which the function requires. + */ + + + /** * @ngdoc object - * @name angular.module.AUTO.$provide + * @name AUTO.$provide * * @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. @@ -2377,8 +2581,8 @@ function inferInjectionArgs(fn) { /** * @ngdoc method - * @name angular.module.AUTO.$provide#provider - * @methodOf angular.module.AUTO.$provide + * @name AUTO.$provide#provider + * @methodOf AUTO.$provide * @description * * Register a provider for a service. The providers can be retrieved and can have additional configuration methods. @@ -2387,17 +2591,17 @@ function inferInjectionArgs(fn) { * @param {(Object|function())} provider If the provider is: * * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using - * {@link angular.module.AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created. + * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created. * - `Constructor`: a new instance of the provider will be created using - * {@link angular.module.AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`. + * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`. * * @returns {Object} registered provider instance */ /** * @ngdoc method - * @name angular.module.AUTO.$provide#factory - * @methodOf angular.module.AUTO.$provide + * @name AUTO.$provide#factory + * @methodOf AUTO.$provide * @description * * A short hand for configuring services if only `$get` method is required. @@ -2411,8 +2615,8 @@ function inferInjectionArgs(fn) { /** * @ngdoc method - * @name angular.module.AUTO.$provide#service - * @methodOf angular.module.AUTO.$provide + * @name AUTO.$provide#service + * @methodOf AUTO.$provide * @description * * A short hand for registering service of given class. @@ -2425,8 +2629,8 @@ function inferInjectionArgs(fn) { /** * @ngdoc method - * @name angular.module.AUTO.$provide#value - * @methodOf angular.module.AUTO.$provide + * @name AUTO.$provide#value + * @methodOf AUTO.$provide * @description * * A short hand for configuring services if the `$get` method is a constant. @@ -2439,13 +2643,13 @@ function inferInjectionArgs(fn) { /** * @ngdoc method - * @name angular.module.AUTO.$provide#constant - * @methodOf angular.module.AUTO.$provide + * @name AUTO.$provide#constant + * @methodOf AUTO.$provide * @description * - * A constant value, but unlike {@link angular.module.AUTO.$provide#value value} it can be injected + * A constant value, but unlike {@link AUTO.$provide#value value} it can be injected * into configuration function (other modules) and it is not interceptable by - * {@link angular.module.AUTO.$provide#decorator decorator}. + * {@link AUTO.$provide#decorator decorator}. * * @param {string} name The name of the constant. * @param {*} value The constant value. @@ -2455,8 +2659,8 @@ function inferInjectionArgs(fn) { /** * @ngdoc method - * @name angular.module.AUTO.$provide#decorator - * @methodOf angular.module.AUTO.$provide + * @name AUTO.$provide#decorator + * @methodOf AUTO.$provide * @description * * Decoration of service, allows the decorator to intercept the service instance creation. The @@ -2465,7 +2669,7 @@ function inferInjectionArgs(fn) { * * @param {string} name The name of the service to decorate. * @param {function()} decorator This function will be invoked when the service needs to be - * instanciated. The function is called using the {@link angular.module.AUTO.$injector#invoke + * instanciated. The function is called using the {@link AUTO.$injector#invoke * injector.invoke} method and is therefore fully injectable. Local injection arguments: * * * `$delegate` - The original service instance, which can be monkey patched, configured, @@ -2518,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) { @@ -2626,30 +2830,23 @@ function createInjector(modulesToLoad) { function invoke(fn, self, locals){ var args = [], - $inject, - length, + $inject = annotate(fn), + length, i, key; - if (typeof fn == 'function') { - $inject = inferInjectionArgs(fn); - length = $inject.length; - } else { - if (isArray(fn)) { - $inject = fn; - length = $inject.length - 1; - fn = $inject[length]; - } - assertArgFn(fn, 'fn'); - } - - for(var i = 0; i < length; i++) { + for(i = 0, length = $inject.length; i < length; i++) { key = $inject[i]; args.push( locals && locals.hasOwnProperty(key) ? locals[key] - : getService(key, path) + : getService(key) ); } + if (!fn.$inject) { + // this means that we must be an array. + fn = fn[length]; + } + // Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke switch (self ? -1 : args.length) { @@ -2682,13 +2879,15 @@ function createInjector(modulesToLoad) { return { invoke: invoke, instantiate: instantiate, - get: getService + get: getService, + annotate: annotate }; } } + /** * @ngdoc function - * @name angular.module.ng.$anchorScroll + * @name ng.$anchorScroll * @requires $window * @requires $location * @requires $rootScope @@ -2741,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; @@ -2753,8 +2953,9 @@ function $AnchorScrollProvider() { } /** - * @ngdoc object - * @name angular.module.ng.$browser + * ! This is a private undocumented service ! + * + * @name ng.$browser * @requires $log * @description * This object has two goals: @@ -2762,19 +2963,18 @@ function $AnchorScrollProvider() { * - hide all the global state in the browser caused by the window object * - abstract away all the browser specific features and inconsistencies * - * For tests we provide {@link angular.module.ngMock.$browser mock implementation} of the `$browser` + * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` * service, which can be used for convenient testing of the application without the interaction with * the real browser apis. */ /** * @param {object} window The global window object. * @param {object} document jQuery wrapped document. - * @param {object} body jQuery wrapped document.body. * @param {function()} XHR XMLHttpRequest constructor. * @param {object} $log console.log or an object with the same interface. * @param {object} $sniffer $sniffer service */ -function Browser(window, document, body, $log, $sniffer) { +function Browser(window, document, $log, $sniffer) { var self = this, rawDocument = document[0], location = window.location, @@ -2839,9 +3039,8 @@ function Browser(window, document, body, $log, $sniffer) { pollTimeout; /** - * @ngdoc method - * @name angular.module.ng.$browser#addPollFn - * @methodOf angular.module.ng.$browser + * @name ng.$browser#addPollFn + * @methodOf ng.$browser * * @param {function()} fn Poll function to add * @@ -2876,12 +3075,12 @@ function Browser(window, document, body, $log, $sniffer) { // URL API ////////////////////////////////////////////////////////////// - var lastBrowserUrl = location.href; + var lastBrowserUrl = location.href, + baseElement = document.find('base'); /** - * @ngdoc method - * @name angular.module.ng.$browser#url - * @methodOf angular.module.ng.$browser + * @name ng.$browser#url + * @methodOf ng.$browser * * @description * GETTER: @@ -2894,7 +3093,7 @@ function Browser(window, document, body, $log, $sniffer) { * Returns its own instance to allow chaining * * NOTE: this api is intended for use only by the $location service. Please use the - * {@link angular.module.ng.$location $location service} to change url. + * {@link ng.$location $location service} to change url. * * @param {string} url New url (when used as setter) * @param {boolean=} replace Should new url replace current history record ? @@ -2902,10 +3101,15 @@ function Browser(window, document, body, $log, $sniffer) { self.url = function(url, replace) { // setter if (url) { + if (lastBrowserUrl == url) return; lastBrowserUrl = url; if ($sniffer.history) { if (replace) history.replaceState(null, '', url); - else history.pushState(null, '', url); + else { + history.pushState(null, '', url); + // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 + baseElement.attr('href', baseElement.attr('href')); + } } else { if (replace) location.replace(url); else location.href = url; @@ -2913,7 +3117,8 @@ function Browser(window, document, body, $log, $sniffer) { return self; // getter } else { - return location.href; + // the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 + return location.href.replace(/%27/g,"'"); } }; @@ -2930,9 +3135,8 @@ function Browser(window, document, body, $log, $sniffer) { } /** - * @ngdoc method - * @name angular.module.ng.$browser#onUrlChange - * @methodOf angular.module.ng.$browser + * @name ng.$browser#onUrlChange + * @methodOf ng.$browser * @TODO(vojta): refactor to use node's syntax for events * * @description @@ -2948,7 +3152,7 @@ function Browser(window, document, body, $log, $sniffer) { * The listener gets called with new url as parameter. * * NOTE: this api is intended for use only by the $location service. Please use the - * {@link angular.module.ng.$location $location service} to monitor url changes in angular apps. + * {@link ng.$location $location service} to monitor url changes in angular apps. * * @param {function(string)} listener Listener function to be called when url changes. * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. @@ -2973,16 +3177,31 @@ function Browser(window, document, body, $log, $sniffer) { return callback; }; + ////////////////////////////////////////////////////////////// + // Misc API + ////////////////////////////////////////////////////////////// + + /** + * Returns current + * (always relative - without domain) + * + * @returns {string=} + */ + self.baseHref = function() { + var href = baseElement.attr('href'); + return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : ''; + }; + ////////////////////////////////////////////////////////////// // Cookies API ////////////////////////////////////////////////////////////// var lastCookies = {}; var lastCookieString = ''; + var cookiePath = self.baseHref(); /** - * @ngdoc method - * @name angular.module.ng.$browser#cookies - * @methodOf angular.module.ng.$browser + * @name ng.$browser#cookies + * @methodOf ng.$browser * * @param {string=} name Cookie name * @param {string=} value Cokkie value @@ -3005,20 +3224,19 @@ function Browser(window, document, body, $log, $sniffer) { if (name) { if (value === undefined) { - rawDocument.cookie = escape(name) + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; + rawDocument.cookie = escape(name) + "=;path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; } else { if (isString(value)) { - rawDocument.cookie = escape(name) + '=' + escape(value); + cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1; - cookieLength = name.length + value.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 { @@ -3041,9 +3259,8 @@ function Browser(window, document, body, $log, $sniffer) { /** - * @ngdoc method - * @name angular.module.ng.$browser#defer - * @methodOf angular.module.ng.$browser + * @name ng.$browser#defer + * @methodOf ng.$browser * @param {function()} fn A function, who's execution should be defered. * @param {number=} [delay=0] of milliseconds to defer the function execution. * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. @@ -3069,10 +3286,8 @@ function Browser(window, document, body, $log, $sniffer) { /** - * THIS DOC IS NOT VISIBLE because ngdocs can't process docs for foo#method.method - * - * @name angular.module.ng.$browser#defer.cancel - * @methodOf angular.module.ng.$browser.defer + * @name ng.$browser#defer.cancel + * @methodOf ng.$browser.defer * * @description * Cancels a defered task identified with `deferId`. @@ -3090,32 +3305,18 @@ function Browser(window, document, body, $log, $sniffer) { return false; }; - - ////////////////////////////////////////////////////////////// - // Misc API - ////////////////////////////////////////////////////////////// - - /** - * Returns current - * (always relative - without domain) - * - * @returns {string=} - */ - self.baseHref = function() { - var href = document.find('base').attr('href'); - return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href; - }; } function $BrowserProvider(){ this.$get = ['$window', '$log', '$sniffer', '$document', function( $window, $log, $sniffer, $document){ - return new Browser($window, $document, $document.find('body'), $log, $sniffer); + return new Browser($window, $document, $log, $sniffer); }]; } + /** * @ngdoc object - * @name angular.module.ng.$cacheFactory + * @name ng.$cacheFactory * * @description * Factory that constructs cache objects. @@ -3130,10 +3331,10 @@ function $BrowserProvider(){ * * - `{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. + * - `{{*}}` `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() { @@ -3185,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); @@ -3266,28 +3469,59 @@ function $CacheFactoryProvider() { }; } +/** + * @ngdoc object + * @name ng.$templateCache + * + * @description + * Cache used for storing html templates. + * + * See {@link ng.$cacheFactory $cacheFactory}. + * + */ function $TemplateCacheProvider() { this.$get = ['$cacheFactory', function($cacheFactory) { return $cacheFactory('templates'); }]; } +/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! + * + * DOM-related variables: + * + * - "node" - DOM Node + * - "element" - DOM Element or Node + * - "$node" or "$element" - jqLite-wrapped node or element + * + * + * Compiler related stuff: + * + * - "linkFn" - linking fn of a single directive + * - "nodeLinkFn" - function that aggregates all linking fns for a particular node + * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node + * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) + */ + + +var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: '; + + /** * @ngdoc function - * @name angular.module.ng.$compile + * @name ng.$compile * @function * * @description * Compiles a piece of HTML string or DOM into a template and produces a template function, which - * can then be used to link {@link angular.module.ng.$rootScope.Scope scope} and the template together. + * can then be used to link {@link ng.$rootScope.Scope scope} and the template together. * * The compilation is a process of walking the DOM tree and trying to match DOM elements to - * {@link angular.module.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. * * The template function can then be used once to produce the view or as it is the case with - * {@link angular.module.ng.$compileProvider.directive.ngRepeat repeater} many-times, in which + * {@link ng.directive:ngRepeat repeater} many-times, in which * case each call results in a view that is a DOM clone of the original template. * @@ -3350,7 +3584,7 @@ function $TemplateCacheProvider() { * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template * (a DOM element/tree) to a scope. Where: * - * * `scope` - A {@link angular.module.ng.$rootScope.Scope Scope} to bind to. + * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the * `template` and call the `cloneAttachFn` function allowing the caller to attach the * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is @@ -3389,21 +3623,43 @@ function $TemplateCacheProvider() { * * * For information on how the compiler works, see the - * {@link guide/dev_guide.compiler Angular HTML Compiler} section of the Developer Guide. + * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. */ +/** + * @ngdoc service + * @name ng.$compileProvider + * @function + * + * @description + */ $CompileProvider.$inject = ['$provide']; function $CompileProvider($provide) { var hasDirectives = {}, Suffix = 'Directive', COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, - CONTENT_REGEXP = /\<\\>/i, - HAS_ROOT_ELEMENT = /^\<[\s\S]*\>$/; + MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ', + urlSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/; - this.directive = function registerDirective(name, directiveFactory) { + /** + * @ngdoc function + * @name ng.$compileProvider#directive + * @methodOf ng.$compileProvider + * @function + * + * @description + * 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)) { assertArg(directiveFactory, 'directive'); if (!hasDirectives.hasOwnProperty(name)) { @@ -3439,56 +3695,44 @@ 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', + '$controller', '$rootScope', '$document', function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, - $controller) { - - var LOCAL_MODE = { - attribute: function(localName, mode, parentScope, scope, attr) { - scope[localName] = attr[localName]; - }, - - evaluate: function(localName, mode, parentScope, scope, attr) { - scope[localName] = parentScope.$eval(attr[localName]); - }, - - bind: function(localName, mode, parentScope, scope, attr) { - var getter = $interpolate(attr[localName]); - scope.$watch( - function() { return getter(parentScope); }, - function(v) { scope[localName] = v; } - ); - }, - - accessor: function(localName, mode, parentScope, scope, attr) { - var getter = noop, - setter = noop, - exp = attr[localName]; - - if (exp) { - getter = $parse(exp); - setter = getter.assign || function() { - throw Error("Expression '" + exp + "' not assignable."); - }; - } - - scope[localName] = function(value) { - return arguments.length ? setter(parentScope, value) : getter(parentScope); - }; - }, - - expression: function(localName, mode, parentScope, scope, attr) { - scope[localName] = function(locals) { - $parse(attr[localName])(parentScope, locals); - }; - } - }; + $controller, $rootScope, $document) { var Attributes = function(element, attr) { this.$$element = element; - this.$$observers = {}; this.$attr = attr || {}; }; @@ -3506,7 +3750,9 @@ function $CompileProvider($provide) { * @param {string=} attrName Optional none normalized name. Defaults to key. */ $set: function(key, value, writeAttr, attrName) { - var booleanKey = isBooleanAttr(this.$$element[0], key.toLowerCase()); + var booleanKey = getBooleanAttrName(this.$$element[0], key), + $$observers = this.$$observers, + normalizedVal; if (booleanKey) { this.$$element.prop(key, value); @@ -3525,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); @@ -3534,7 +3793,7 @@ function $CompileProvider($provide) { } // fire observers - forEach(this.$$observers[key], function(fn) { + $$observers && forEach($$observers[key], function(fn) { try { fn(value); } catch (e) { @@ -3550,43 +3809,70 @@ function $CompileProvider($provide) { * * @param {string} key Normalized key. (ie ngAttribute) . * @param {function(*)} fn Function that will be called whenever the attribute value changes. + * @returns {function(*)} the `fn` Function passed in. */ $observe: function(key, fn) { - // keep only observers for interpolated attrs - if (this.$$observers[key]) { - this.$$observers[key].push(fn); - } + var attrs = this, + $$observers = (attrs.$$observers || (attrs.$$observers = {})), + listeners = ($$observers[key] || ($$observers[key] = [])); + + listeners.push(fn); + $rootScope.$evalAsync(function() { + if (!listeners.$$inter) { + // no one registered attribute interpolation function, so lets call it manually + fn(attrs[key]); + } + }); + return fn; } }; + 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(templateElement, transcludeFn, maxPriority) { - if (!(templateElement instanceof jqLite)) { - // jquery always rewraps, where as we need to preserve the original selector so that we can modify it. - templateElement = jqLite(templateElement); + 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(templateElement, function(node, index){ - if (node.nodeType == 3 /* text node */) { - templateElement[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 linkingFn = compileNodes(templateElement, transcludeFn, templateElement, 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 element = cloneConnectFn - ? JQLitePrototype.clone.call(templateElement) // IMPORTANT!!! - : templateElement; - safeAddClass(element.data('$scope', scope), 'ng-scope'); - if (cloneConnectFn) cloneConnectFn(element, scope); - if (linkingFn) linkingFn(scope, element, element); - return element; + var $linkNode = cloneConnectFn + ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! + : $compileNodes; + + // Attach scope only to non-text nodes. + for(var i = 0, ii = $linkNode.length; i directive.priority) { break; // prevent further processing of directives } if (directiveValue = directive.scope) { - assertNoDuplicate('isolated scope', newIsolatedScopeDirective, directive, element); + assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode); if (isObject(directiveValue)) { - safeAddClass(element, 'ng-isolate-scope'); - newIsolatedScopeDirective = directive; + safeAddClass($compileNode, 'ng-isolate-scope'); + newIsolateScopeDirective = directive; } - safeAddClass(element, 'ng-scope'); + safeAddClass($compileNode, 'ng-scope'); newScopeDirective = newScopeDirective || directive; } @@ -3816,36 +4109,44 @@ function $CompileProvider($provide) { if (directiveValue = directive.controller) { controllerDirectives = controllerDirectives || {}; assertNoDuplicate("'" + directiveName + "' controller", - controllerDirectives[directiveName], directive, element); + controllerDirectives[directiveName], directive, $compileNode); controllerDirectives[directiveName] = directive; } if (directiveValue = directive.transclude) { - assertNoDuplicate('transclusion', transcludeDirective, directive, element); + assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode); transcludeDirective = directive; terminalPriority = directive.priority; if (directiveValue == 'element') { - template = jqLite(templateNode); - templateNode = (element = templateAttrs.$$element = jqLite( - ''))[0]; - replaceWith(rootElement, jqLite(template[0]), templateNode); - childTranscludeFn = compile(template, transcludeFn, terminalPriority); + $template = jqLite(compileNode); + $compileNode = templateAttrs.$$element = + jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); + compileNode = $compileNode[0]; + replaceWith($rootElement, jqLite($template[0]), compileNode); + childTranscludeFn = compile($template, transcludeFn, terminalPriority); } else { - template = jqLite(JQLiteClone(templateNode)); - element.html(''); // clear contents - childTranscludeFn = compile(template.contents(), transcludeFn); + $template = jqLite(JQLiteClone(compileNode)).contents(); + $compileNode.html(''); // clear contents + childTranscludeFn = compile($template, transcludeFn); } } - if (directiveValue = directive.template) { - assertNoDuplicate('template', templateDirective, directive, element); + if ((directiveValue = directive.template)) { + assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; + directiveValue = denormalizeTemplate(directiveValue); - // include the contents of the original element into the template and replace the element - var content = directiveValue.replace(CONTENT_REGEXP, element.html()); - templateNode = jqLite(content)[0]; if (directive.replace) { - replaceWith(rootElement, element, templateNode); + $template = jqLite('
' + + trim(directiveValue) + + '
').contents(); + compileNode = $template[0]; + + if ($template.length != 1 || compileNode.nodeType !== 1) { + throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue); + } + + replaceWith($rootElement, $compileNode, compileNode); var newTemplateAttrs = {$attr: {}}; @@ -3856,7 +4157,7 @@ function $CompileProvider($provide) { // - append the second group with new directives to the first group directives = directives.concat( collectDirectives( - templateNode, + compileNode, directives.splice(i + 1, directives.length - (i + 1)), newTemplateAttrs ) @@ -3865,59 +4166,58 @@ function $CompileProvider($provide) { ii = directives.length; } else { - element.html(content); + $compileNode.html(directiveValue); } } if (directive.templateUrl) { - assertNoDuplicate('template', templateDirective, directive, element); + assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; - delayedLinkingFn = compileTemplateUrl(directives.splice(i, directives.length - i), - /* directiveLinkingFn */ compositeLinkFn, element, templateAttrs, rootElement, - directive.replace, childTranscludeFn); + nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), + nodeLinkFn, $compileNode, templateAttrs, $rootElement, directive.replace, + childTranscludeFn); ii = directives.length; } else if (directive.compile) { try { - linkingFn = directive.compile(element, templateAttrs, childTranscludeFn); - if (isFunction(linkingFn)) { - addLinkingFns(null, linkingFn); - } else if (linkingFn) { - addLinkingFns(linkingFn.pre, linkingFn.post); + linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); + if (isFunction(linkFn)) { + addLinkFns(null, linkFn); + } else if (linkFn) { + addLinkFns(linkFn.pre, linkFn.post); } } catch (e) { - $exceptionHandler(e, startingTag(element)); + $exceptionHandler(e, startingTag($compileNode)); } } if (directive.terminal) { - compositeLinkFn.terminal = true; + nodeLinkFn.terminal = true; terminalPriority = Math.max(terminalPriority, directive.priority); } } - linkingFn = delayedLinkingFn || compositeLinkFn; - linkingFn.scope = newScopeDirective && newScopeDirective.scope; - linkingFn.transclude = transcludeDirective && childTranscludeFn; + nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope; + nodeLinkFn.transclude = transcludeDirective && childTranscludeFn; - // if we have templateUrl, then we have to delay linking - return linkingFn; + // might be normal or delayed nodeLinkFn depending on if templateUrl is present + return nodeLinkFn; //////////////////// - function addLinkingFns(pre, post) { + function addLinkFns(pre, post) { if (pre) { pre.require = directive.require; - preLinkingFns.push(pre); + preLinkFns.push(pre); } if (post) { post.require = directive.require; - postLinkingFns.push(post); + postLinkFns.push(post); } } - function getControllers(require, element) { + function getControllers(require, $element) { var value, retrievalMethod = 'data', optional = false; if (isString(require)) { while((value = require.charAt(0)) == '^' || value == '?') { @@ -3927,7 +4227,7 @@ function $CompileProvider($provide) { } optional = optional || value == '?'; } - value = element[retrievalMethod]('$' + require + 'Controller'); + value = $element[retrievalMethod]('$' + require + 'Controller'); if (!value && !optional) { throw Error("No controller: " + require); } @@ -3935,29 +4235,87 @@ function $CompileProvider($provide) { } else if (isArray(require)) { value = []; forEach(require, function(require) { - value.push(getControllers(require, element)); + value.push(getControllers(require, $element)); }); } return value; } - /* directiveLinkingFn */ - function compositeLinkFn(/* nodesetLinkingFn */ childLinkingFn, - scope, linkNode, rootElement, boundTranscludeFn) { - var attrs, element, i, ii, linkingFn, controller; + function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { + var attrs, $element, i, ii, linkFn, controller; - if (templateNode === linkNode) { + if (compileNode === linkNode) { attrs = templateAttrs; } else { attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); } - element = attrs.$$element; + $element = attrs.$$element; + + if (newIsolateScopeDirective) { + var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/; + + var parentScope = scope.$parent || scope; - if (newScopeDirective && isObject(newScopeDirective.scope)) { - forEach(newScopeDirective.scope, function(mode, name) { - (LOCAL_MODE[mode] || wrongMode)(name, mode, - scope.$parent || scope, scope, attrs); + 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 '@': { + attrs.$observe(attrName, function(value) { + scope[scopeName] = value; + }); + attrs.$$observers[attrName].$$scope = parentScope; + break; + } + + case '=': { + parentGet = $parse(attrs[attrName]); + parentSet = parentGet.assign || function() { + // 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: ' + newIsolateScopeDirective.name + ')'); + }; + lastValue = scope[scopeName] = parentGet(parentScope); + scope.$watch(function parentValueWatch() { + var parentValue = parentGet(parentScope); + + if (parentValue !== scope[scopeName]) { + // we are out of sync and need to copy + if (parentValue !== lastValue) { + // parent changed and it has precedence + lastValue = scope[scopeName] = parentValue; + } else { + // if the parent can be assigned then do so + parentSet(parentScope, parentValue = lastValue = scope[scopeName]); + } + } + return parentValue; + }); + break; + } + + case '&': { + parentGet = $parse(attrs[attrName]); + scope[scopeName] = function(locals) { + return parentGet(parentScope, locals); + } + break; + } + + default: { + throw Error('Invalid isolate scope definition for directive ' + + newIsolateScopeDirective.name + ': ' + definiton); + } + } }); } @@ -3965,50 +4323,44 @@ function $CompileProvider($provide) { forEach(controllerDirectives, function(directive) { var locals = { $scope: scope, - $element: element, + $element: $element, $attrs: attrs, $transclude: boundTranscludeFn }; - - forEach(directive.inject || {}, function(mode, name) { - (LOCAL_MODE[mode] || wrongMode)(name, mode, - newScopeDirective ? scope.$parent || scope : scope, locals, attrs); - }); - controller = directive.controller; if (controller == '@') { controller = attrs[directive.name]; } - element.data( + $element.data( '$' + directive.name + 'Controller', $controller(controller, locals)); }); } // PRELINKING - for(i = 0, ii = preLinkingFns.length; i < ii; i++) { + for(i = 0, ii = preLinkFns.length; i < ii; i++) { try { - linkingFn = preLinkingFns[i]; - linkingFn(scope, element, attrs, - linkingFn.require && getControllers(linkingFn.require, element)); + linkFn = preLinkFns[i]; + linkFn(scope, $element, attrs, + linkFn.require && getControllers(linkFn.require, $element)); } catch (e) { - $exceptionHandler(e, startingTag(element)); + $exceptionHandler(e, startingTag($element)); } } // RECURSION - childLinkingFn && childLinkingFn(scope, linkNode.childNodes, undefined, boundTranscludeFn); + childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn); // POSTLINKING - for(i = 0, ii = postLinkingFns.length; i < ii; i++) { + for(i = 0, ii = postLinkFns.length; i < ii; i++) { try { - linkingFn = postLinkingFns[i]; - linkingFn(scope, element, attrs, - linkingFn.require && getControllers(linkingFn.require, element)); + linkFn = postLinkFns[i]; + linkFn(scope, $element, attrs, + linkFn.require && getControllers(linkFn.require, $element)); } catch (e) { - $exceptionHandler(e, startingTag(element)); + $exceptionHandler(e, startingTag($element)); } } } @@ -4033,7 +4385,7 @@ function $CompileProvider($provide) { var match = false; if (hasDirectives.hasOwnProperty(name)) { for(var directive, directives = $injector.get(name + Suffix), - i=0, ii = directives.length; i directive.priority) && @@ -4059,7 +4411,8 @@ function $CompileProvider($provide) { function mergeTemplateAttributes(dst, src) { var srcAttr = src.$attr, dstAttr = dst.$attr, - element = dst.$$element; + $element = dst.$$element; + // reapply the old attributes to the new element forEach(dst, function(value, key) { if (key.charAt(0) != '$') { @@ -4069,12 +4422,14 @@ function $CompileProvider($provide) { dst.$set(key, value, true, srcAttr[key]); } }); + // copy the new attributes on the old attrs object forEach(src, function(value, key) { if (key == 'class') { - safeAddClass(element, value); + safeAddClass($element, value); + dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; } else if (key == 'style') { - element.attr('style', element.attr('style') + ';' + value); + $element.attr('style', $element.attr('style') + ';' + value); } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { dst[key] = value; dstAttr[key] = srcAttr[key]; @@ -4083,59 +4438,64 @@ function $CompileProvider($provide) { } - function compileTemplateUrl(directives, /* directiveLinkingFn */ beforeWidgetLinkFn, - tElement, tAttrs, rootElement, replace, transcludeFn) { + function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs, + $rootElement, replace, childTranscludeFn) { var linkQueue = [], - afterWidgetLinkFn, - afterWidgetChildrenLinkFn, - originalWidgetNode = tElement[0], - asyncWidgetDirective = directives.shift(), + afterTemplateNodeLinkFn, + afterTemplateChildLinkFn, + beforeTemplateCompileNode = $compileNode[0], + origAsyncDirective = directives.shift(), // The fact that we have to copy and patch the directive seems wrong! - syncWidgetDirective = extend({}, asyncWidgetDirective, {templateUrl:null, transclude:null}), - html = tElement.html(); + derivedSyncDirective = extend({}, origAsyncDirective, { + controller: null, templateUrl: null, transclude: null, scope: null + }); - tElement.html(''); + $compileNode.html(''); - $http.get(asyncWidgetDirective.templateUrl, {cache: $templateCache}). + $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}). success(function(content) { - content = trim(content).replace(CONTENT_REGEXP, html); - if (replace && !content.match(HAS_ROOT_ELEMENT)) { - throw Error('Template must have exactly one root element: ' + content); - } + var compileNode, tempTemplateAttrs, $template; - var templateNode, tempTemplateAttrs; + content = denormalizeTemplate(content); if (replace) { + $template = jqLite('
' + trim(content) + '
').contents(); + compileNode = $template[0]; + + if ($template.length != 1 || compileNode.nodeType !== 1) { + throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content); + } + tempTemplateAttrs = {$attr: {}}; - templateNode = jqLite(content)[0]; - replaceWith(rootElement, tElement, templateNode); - collectDirectives(tElement[0], directives, tempTemplateAttrs); + replaceWith($rootElement, $compileNode, compileNode); + collectDirectives(compileNode, directives, tempTemplateAttrs); mergeTemplateAttributes(tAttrs, tempTemplateAttrs); } else { - templateNode = tElement[0]; - tElement.html(content); + compileNode = beforeTemplateCompileNode; + $compileNode.html(content); } - directives.unshift(syncWidgetDirective); - afterWidgetLinkFn = /* directiveLinkingFn */ applyDirectivesToNode(directives, tElement, tAttrs, transcludeFn); - afterWidgetChildrenLinkFn = /* nodesetLinkingFn */ compileNodes(tElement.contents(), transcludeFn); + directives.unshift(derivedSyncDirective); + afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn); + afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); while(linkQueue.length) { var controller = linkQueue.pop(), linkRootElement = linkQueue.pop(), - cLinkNode = linkQueue.pop(), + beforeTemplateLinkNode = linkQueue.pop(), scope = linkQueue.pop(), - node = templateNode; + linkNode = compileNode; - if (cLinkNode !== originalWidgetNode) { + if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { // it was cloned therefore we have to clone as well. - node = JQLiteClone(templateNode); - replaceWith(linkRootElement, jqLite(cLinkNode), node); + linkNode = JQLiteClone(compileNode); + replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); } - afterWidgetLinkFn(function() { - beforeWidgetLinkFn(afterWidgetChildrenLinkFn, scope, node, rootElement, controller); - }, scope, node, rootElement, controller); + + afterTemplateNodeLinkFn(function() { + beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller); + }, scope, linkNode, $rootElement, controller); } linkQueue = null; }). @@ -4143,16 +4503,15 @@ function $CompileProvider($provide) { throw Error('Failed to load template: ' + config.url); }); - return /* directiveLinkingFn */ function(ignoreChildLinkingFn, scope, node, rootElement, - controller) { + return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) { if (linkQueue) { linkQueue.push(scope); linkQueue.push(node); linkQueue.push(rootElement); linkQueue.push(controller); } else { - afterWidgetLinkFn(function() { - beforeWidgetLinkFn(afterWidgetChildrenLinkFn, scope, node, rootElement, controller); + afterTemplateNodeLinkFn(function() { + beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller); }, scope, node, rootElement, controller); } }; @@ -4180,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; }); }) @@ -4197,26 +4556,27 @@ 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') { // we need to interpolate classes again, in the case the element was replaced // and therefore the two class attrs got merged - we want to interpolate the result interpolateFn = $interpolate(attr[name], true); } - // we define observers array only for interpolated attrs - // and ignore observers for non interpolated attrs to save some memory - attr.$$observers[name] = []; attr[name] = undefined; - scope.$watch(interpolateFn, function(value) { - attr.$set(name, value); - }); + ($$observers[name] || ($$observers[name] = [])).$$inter = true; + (attr.$$observers && attr.$$observers[name].$$scope || scope). + $watch(interpolateFn, function interpolateFnWatchAction(value) { + attr.$set(name, value); + }); }) }); } @@ -4226,28 +4586,32 @@ function $CompileProvider($provide) { * This is a special jqLite.replaceWith, which can replace items which * have no parents, provided that the containing jqLite collection is provided. * - * @param {JqLite=} rootElement The root of the compile tree. Used so that we can replace nodes + * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes * in the root of the tree. - * @param {JqLite} element The jqLite element which we are going to replace. We keep the shell, + * @param {JqLite} $element The jqLite element which we are going to replace. We keep the shell, * but replace its DOM node reference. * @param {Node} newNode The new DOM node. */ - function replaceWith(rootElement, element, newNode) { - var oldNode = element[0], + function replaceWith($rootElement, $element, newNode) { + var oldNode = $element[0], parent = oldNode.parentNode, i, ii; - if (rootElement) { - for(i = 0, ii = rootElement.length; i + */ + +/** + * @ngdoc property + * @name ng.$compile.directive.Attributes#$attr + * @propertyOf ng.$compile.directive.Attributes + * @returns {object} A map of DOM element attribute names to the normalized name. This is + * needed to do reverse lookup from normalized name back to actual name. + */ + + +/** + * @ngdoc function + * @name ng.$compile.directive.Attributes#$set + * @methodOf ng.$compile.directive.Attributes + * @function + * + * @description + * Set DOM element attribute value. + * + * + * @param {string} name Normalized element attribute name of the property to modify. The name is + * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr} + * property to the original name. + * @param {string} value Value to set the attribute to. + */ + /** @@ -4291,13 +4692,13 @@ function directiveLinkingFn( /** * @ngdoc object - * @name angular.module.ng.$controllerProvider + * @name ng.$controllerProvider * @description - * The {@link angular.module.ng.$controller $controller service} is used by Angular to create new + * The {@link ng.$controller $controller service} is used by Angular to create new * controllers. * * This provider allows controller registration via the - * {@link angular.module.ng.$controllerProvider#register register} method. + * {@link ng.$controllerProvider#register register} method. */ function $ControllerProvider() { var controllers = {}; @@ -4305,14 +4706,18 @@ function $ControllerProvider() { /** * @ngdoc function - * @name angular.module.ng.$controllerProvider#register - * @methodOf angular.module.ng.$controllerProvider + * @name ng.$controllerProvider#register + * @methodOf ng.$controllerProvider * @param {string} name Controller name * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI * annotations in the array notation). */ this.register = function(name, constructor) { - controllers[name] = constructor; + if (isObject(name)) { + extend(controllers, name) + } else { + controllers[name] = constructor; + } }; @@ -4320,7 +4725,7 @@ function $ControllerProvider() { /** * @ngdoc function - * @name angular.module.ng.$controller + * @name ng.$controller * @requires $injector * * @param {Function|string} constructor If called with a function then it's considered to be the @@ -4337,7 +4742,7 @@ function $ControllerProvider() { * @description * `$controller` service is responsible for instantiating controllers. * - * It's just simple call to {@link angular.module.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}. */ @@ -4356,53 +4761,9 @@ function $ControllerProvider() { }]; } -/** - * @ngdoc function - * @name angular.module.ng.$defer - * @requires $browser - * - * @description - * Delegates to {@link angular.module.ng.$browser#defer $browser.defer}, but wraps the `fn` function - * into a try/catch block and delegates any exceptions to - * {@link angular.module.ng.$exceptionHandler $exceptionHandler} service. - * - * In tests you can use `$browser.defer.flush()` to flush the queue of deferred functions. - * - * @param {function()} fn A function, who's execution should be deferred. - * @param {number=} [delay=0] of milliseconds to defer the function execution. - * @returns {*} DeferId that can be used to cancel the task via `$defer.cancel()`. - */ - -/** - * @ngdoc function - * @name angular.module.ng.$defer#cancel - * @methodOf angular.module.ng.$defer - * - * @description - * Cancels a defered task identified with `deferId`. - * - * @param {*} deferId Token returned by the `$defer` function. - * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled. - */ -function $DeferProvider(){ - this.$get = ['$rootScope', '$browser', function($rootScope, $browser) { - function defer(fn, delay) { - return $browser.defer(function() { - $rootScope.$apply(fn); - }, delay); - } - - defer.cancel = function(deferId) { - return $browser.defer.cancel(deferId); - }; - - return defer; - }]; -} - /** * @ngdoc object - * @name angular.module.ng.$document + * @name ng.$document * @requires $window * * @description @@ -4417,7 +4778,7 @@ function $DocumentProvider(){ /** * @ngdoc function - * @name angular.module.ng.$exceptionHandler + * @name ng.$exceptionHandler * @requires $log * * @description @@ -4426,11 +4787,12 @@ function $DocumentProvider(){ * the browser console. * * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by - * {@link angular.module.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){ @@ -4441,13 +4803,13 @@ function $ExceptionHandlerProvider() { } /** - * @ngdoc function - * @name angular.module.ng.$interpolateProvider + * @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 = '{{'; @@ -4455,12 +4817,13 @@ function $InterpolateProvider() { /** * @ngdoc method - * @name angular.module.ng.$interpolateProvider#startSymbol - * @methodOf angular.module.ng.$interpolateProvider + * @name ng.$interpolateProvider#startSymbol + * @methodOf ng.$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) { @@ -4473,19 +4836,20 @@ function $InterpolateProvider() { /** * @ngdoc method - * @name angular.module.ng.$interpolateProvider#endSymbol - * @methodOf angular.module.ng.$interpolateProvider + * @name ng.$interpolateProvider#endSymbol + * @methodOf ng.$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; } }; @@ -4496,7 +4860,7 @@ function $InterpolateProvider() { /** * @ngdoc function - * @name angular.module.ng.$interpolate + * @name ng.$interpolate * @function * * @requires $parse @@ -4504,8 +4868,8 @@ function $InterpolateProvider() { * @description * * Compiles a string with markup into an interpolation function. This service is used by the - * HTML {@link angular.module.ng.$compile $compile} service for data binding. See - * {@link angular.module.ng.$interpolateProvider $interpolateProvider} for configuring the + * HTML {@link ng.$compile $compile} service for data binding. See + * {@link ng.$interpolateProvider $interpolateProvider} for configuring the * interpolation markup. * * @@ -4527,7 +4891,7 @@ function $InterpolateProvider() { * against. * */ - return function(text, mustHaveExpression) { + function $interpolate(text, mustHaveExpression) { var startIndex, endIndex, index = 0, @@ -4579,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 = /^(file|ftp|http|https):\/\/(\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}; @@ -4606,6 +5006,10 @@ function encodePath(path) { return segments.join('/'); } +function stripHash(url) { + return url.split('#')[0]; +} + function matchUrl(url, obj) { var match = URL_MATCH.exec(url); @@ -4658,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 { @@ -4668,7 +5073,7 @@ function convertToHashbangUrl(url, basePath, hashPrefix) { path = match.path.substr(pathPrefix.length); if (match.path.indexOf(pathPrefix) !== 0) { - throw 'Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !'; + throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !'); } return composeProtocolHostPort(match.protocol, match.host, match.port) + basePath + @@ -4685,19 +5090,19 @@ function convertToHashbangUrl(url, basePath, hashPrefix) { * @param {string} url HTML5 url * @param {string} pathPrefix */ -function LocationUrl(url, pathPrefix) { +function LocationUrl(url, pathPrefix, appBaseUrl) { pathPrefix = pathPrefix || ''; /** * Parse given html5 (regular) url string into properties - * @param {string} url HTML5 url + * @param {string} newAbsoluteUrl HTML5 url * @private */ - this.$$parse = function(url) { - var match = matchUrl(url, this); + this.$$parse = function(newAbsoluteUrl) { + var match = matchUrl(newAbsoluteUrl, this); if (match.path.indexOf(pathPrefix) !== 0) { - throw 'Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !'; + throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !'); } this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length)); @@ -4720,6 +5125,14 @@ function LocationUrl(url, pathPrefix) { pathPrefix + this.$$url; }; + + this.$$rewriteAppUrl = function(absoluteLinkUrl) { + if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) { + return absoluteLinkUrl; + } + } + + this.$$parse(url); } @@ -4732,7 +5145,7 @@ function LocationUrl(url, pathPrefix) { * @param {string} url Legacy url * @param {string} hashPrefix Prefix for hash part (containing path and search) */ -function LocationHashbangUrl(url, hashPrefix) { +function LocationHashbangUrl(url, hashPrefix, appBaseUrl) { var basePath; /** @@ -4743,8 +5156,9 @@ function LocationHashbangUrl(url, hashPrefix) { this.$$parse = function(url) { var match = matchUrl(url, this); + if (match.hash && match.hash.indexOf(hashPrefix) !== 0) { - throw 'Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !'; + throw Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !'); } basePath = match.path + (match.search ? '?' + match.search : ''); @@ -4774,6 +5188,13 @@ function LocationHashbangUrl(url, hashPrefix) { basePath + (this.$$url ? '#' + hashPrefix + this.$$url : ''); }; + this.$$rewriteAppUrl = function(absoluteLinkUrl) { + if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) { + return absoluteLinkUrl; + } + } + + this.$$parse(url); } @@ -4788,8 +5209,8 @@ LocationUrl.prototype = { /** * @ngdoc method - * @name angular.module.ng.$location#absUrl - * @methodOf angular.module.ng.$location + * @name ng.$location#absUrl + * @methodOf ng.$location * * @description * This method is getter only. @@ -4797,14 +5218,14 @@ LocationUrl.prototype = { * Return full url representation with all segments encoded according to rules specified in * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. * - * @return {string} + * @return {string} full url */ absUrl: locationGetter('$$absUrl'), /** * @ngdoc method - * @name angular.module.ng.$location#url - * @methodOf angular.module.ng.$location + * @name ng.$location#url + * @methodOf ng.$location * * @description * This method is getter / setter. @@ -4814,7 +5235,7 @@ LocationUrl.prototype = { * Change path, search and hash, when called with parameter and return `$location`. * * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) - * @return {string} + * @return {string} url */ url: function(url, replace) { if (isUndefined(url)) @@ -4830,50 +5251,50 @@ LocationUrl.prototype = { /** * @ngdoc method - * @name angular.module.ng.$location#protocol - * @methodOf angular.module.ng.$location + * @name ng.$location#protocol + * @methodOf ng.$location * * @description * This method is getter only. * * Return protocol of current url. * - * @return {string} + * @return {string} protocol of current url */ protocol: locationGetter('$$protocol'), /** * @ngdoc method - * @name angular.module.ng.$location#host - * @methodOf angular.module.ng.$location + * @name ng.$location#host + * @methodOf ng.$location * * @description * This method is getter only. * * Return host of current url. * - * @return {string} + * @return {string} host of current url. */ host: locationGetter('$$host'), /** * @ngdoc method - * @name angular.module.ng.$location#port - * @methodOf angular.module.ng.$location + * @name ng.$location#port + * @methodOf ng.$location * * @description * This method is getter only. * * Return port of current url. * - * @return {Number} + * @return {Number} port */ port: locationGetter('$$port'), /** * @ngdoc method - * @name angular.module.ng.$location#path - * @methodOf angular.module.ng.$location + * @name ng.$location#path + * @methodOf ng.$location * * @description * This method is getter / setter. @@ -4886,7 +5307,7 @@ LocationUrl.prototype = { * if it is missing. * * @param {string=} path New path - * @return {string} + * @return {string} path */ path: locationGetterSetter('$$path', function(path) { return path.charAt(0) == '/' ? path : '/' + path; @@ -4894,8 +5315,8 @@ LocationUrl.prototype = { /** * @ngdoc method - * @name angular.module.ng.$location#search - * @methodOf angular.module.ng.$location + * @name ng.$location#search + * @methodOf ng.$location * * @description * This method is getter / setter. @@ -4908,7 +5329,7 @@ LocationUrl.prototype = { * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a * single search parameter. If the value is `null`, the parameter will be deleted. * - * @return {string} + * @return {string} search */ search: function(search, paramValue) { if (isUndefined(search)) @@ -4930,8 +5351,8 @@ LocationUrl.prototype = { /** * @ngdoc method - * @name angular.module.ng.$location#hash - * @methodOf angular.module.ng.$location + * @name ng.$location#hash + * @methodOf ng.$location * * @description * This method is getter / setter. @@ -4941,14 +5362,14 @@ LocationUrl.prototype = { * Change hash fragment when called with parameter and return `$location`. * * @param {string=} hash New hash fragment - * @return {string} + * @return {string} hash */ hash: locationGetterSetter('$$hash', identity), /** * @ngdoc method - * @name angular.module.ng.$location#replace - * @methodOf angular.module.ng.$location + * @name ng.$location#replace + * @methodOf ng.$location * * @description * If called, all changes to $location during current `$digest` will be replacing current history @@ -4962,6 +5383,19 @@ LocationUrl.prototype = { LocationHashbangUrl.prototype = inherit(LocationUrl.prototype); +function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) { + LocationHashbangUrl.apply(this, arguments); + + + this.$$rewriteAppUrl = function(absoluteLinkUrl) { + if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) { + return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length); + } + } +} + +LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype); + function locationGetter(property) { return function() { return this[property]; @@ -4984,14 +5418,17 @@ function locationGetterSetter(property, preprocess) { /** * @ngdoc object - * @name angular.module.ng.$location + * @name ng.$location * * @requires $browser * @requires $sniffer - * @requires $document + * @requires $rootElement * * @description - * The $location service parses the URL in the browser address bar (based on the {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL available to your application. Changes to the URL in the address bar are reflected into $location service and changes to $location are reflected into the browser address bar. + * The $location service parses the URL in the browser address bar (based on the + * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL + * available to your application. Changes to the URL in the address bar are reflected into + * $location service and changes to $location are reflected into the browser address bar. * * **The $location service:** * @@ -5004,12 +5441,13 @@ function locationGetterSetter(property, preprocess) { * - Clicks on a link. * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). * - * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular Services: Using $location} + * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular + * Services: Using $location} */ /** * @ngdoc object - * @name angular.module.ng.$locationProvider + * @name ng.$locationProvider * @description * Use the `$locationProvider` to configure how the application deep linking paths are stored. */ @@ -5019,8 +5457,8 @@ function $LocationProvider(){ /** * @ngdoc property - * @name angular.module.ng.$locationProvider#hashPrefix - * @methodOf angular.module.ng.$locationProvider + * @name ng.$locationProvider#hashPrefix + * @methodOf ng.$locationProvider * @description * @param {string=} prefix Prefix for hash part (containing path and search) * @returns {*} current value if used as getter or itself (chaining) if used as setter @@ -5036,8 +5474,8 @@ function $LocationProvider(){ /** * @ngdoc property - * @name angular.module.ng.$locationProvider#html5Mode - * @methodOf angular.module.ng.$locationProvider + * @name ng.$locationProvider#html5Mode + * @methodOf ng.$locationProvider * @description * @param {string=} mode Use HTML5 strategy if available. * @returns {*} current value if used as getter or itself (chaining) if used as setter @@ -5051,67 +5489,82 @@ function $LocationProvider(){ } }; - this.$get = ['$rootScope', '$browser', '$sniffer', '$document', - function( $rootScope, $browser, $sniffer, $document) { - var currentUrl, - basePath = $browser.baseHref() || '/', - pathPrefix = pathPrefixFromBase(basePath), - initUrl = $browser.url(); + this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', + function( $rootScope, $browser, $sniffer, $rootElement) { + var $location, + basePath, + pathPrefix, + initUrl = $browser.url(), + initUrlParts = matchUrl(initUrl), + appBaseUrl; if (html5Mode) { + basePath = $browser.baseHref() || '/'; + pathPrefix = pathPrefixFromBase(basePath); + appBaseUrl = + composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) + + pathPrefix + '/'; + if ($sniffer.history) { - currentUrl = new LocationUrl(convertToHtml5Url(initUrl, basePath, hashPrefix), pathPrefix); + $location = new LocationUrl( + convertToHtml5Url(initUrl, basePath, hashPrefix), + pathPrefix, appBaseUrl); } else { - currentUrl = new LocationHashbangUrl(convertToHashbangUrl(initUrl, basePath, hashPrefix), - hashPrefix); + $location = new LocationHashbangInHtml5Url( + convertToHashbangUrl(initUrl, basePath, hashPrefix), + hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1)); } + } else { + appBaseUrl = + composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) + + (initUrlParts.path || '') + + (initUrlParts.search ? ('?' + initUrlParts.search) : '') + + '#' + hashPrefix + '/'; - // link rewriting - var u = currentUrl, - absUrlPrefix = composeProtocolHostPort(u.protocol(), u.host(), u.port()) + pathPrefix; - - $document.bind('click', function(event) { - // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) - // currently we open nice url link and redirect then + $location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl); + } - if (event.ctrlKey || event.metaKey || event.which == 2) return; + $rootElement.bind('click', function(event) { + // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) + // currently we open nice url link and redirect then - var elm = jqLite(event.target); + if (event.ctrlKey || event.metaKey || event.which == 2) return; - // traverse the DOM up to find first A tag - while (elm.length && lowercase(elm[0].nodeName) !== 'a') { - elm = elm.parent(); - } + var elm = jqLite(event.target); - var absHref = elm.prop('href'); + // traverse the DOM up to find first A tag + while (lowercase(elm[0].nodeName) !== 'a') { + // ignore rewriting if no A tag (reached root element, or no parent - removed from document) + if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; + } - if (!absHref || - elm.attr('target') || - absHref.indexOf(absUrlPrefix) !== 0) { // link to different domain or base path - return; - } + var absHref = elm.prop('href'), + rewrittenUrl = $location.$$rewriteAppUrl(absHref); - // update location with href without the prefix - currentUrl.url(absHref.substr(absUrlPrefix.length)); + if (absHref && !elm.attr('target') && rewrittenUrl) { + // update location manually + $location.$$parse(rewrittenUrl); $rootScope.$apply(); event.preventDefault(); // hack to work around FF6 bug 684208 when scenario runner clicks on links window.angular['ff-684208-preventDefault'] = true; - }); - } else { - currentUrl = new LocationHashbangUrl(initUrl, hashPrefix); - } + } + }); + // rewrite hashbang url <> html5 url - if (currentUrl.absUrl() != initUrl) { - $browser.url(currentUrl.absUrl(), true); + if ($location.absUrl() != initUrl) { + $browser.url($location.absUrl(), true); } // update $location when $browser url changes $browser.onUrlChange(function(newUrl) { - if (currentUrl.absUrl() != newUrl) { + if ($location.absUrl() != newUrl) { $rootScope.$evalAsync(function() { - currentUrl.$$parse(newUrl); + var oldUrl = $location.absUrl(); + + $location.$$parse(newUrl); + afterLocationChange(oldUrl); }); if (!$rootScope.$$phase) $rootScope.$digest(); } @@ -5120,24 +5573,37 @@ function $LocationProvider(){ // update browser var changeCounter = 0; $rootScope.$watch(function $locationWatch() { - if ($browser.url() != currentUrl.absUrl()) { + var oldUrl = $browser.url(); + var currentReplace = $location.$$replace; + + if (!changeCounter || oldUrl != $location.absUrl()) { changeCounter++; $rootScope.$evalAsync(function() { - $browser.url(currentUrl.absUrl(), currentUrl.$$replace); - currentUrl.$$replace = false; + if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). + defaultPrevented) { + $location.$$parse(oldUrl); + } else { + $browser.url($location.absUrl(), currentReplace); + afterLocationChange(oldUrl); + } }); } + $location.$$replace = false; return changeCounter; }); - return currentUrl; + return $location; + + function afterLocationChange(oldUrl) { + $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); + } }]; } /** * @ngdoc object - * @name angular.module.ng.$log + * @name ng.$log * @requires $window * * @description @@ -5147,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(){ @@ -5175,8 +5639,8 @@ function $LogProvider(){ return { /** * @ngdoc method - * @name angular.module.ng.$log#log - * @methodOf angular.module.ng.$log + * @name ng.$log#log + * @methodOf ng.$log * * @description * Write a log message @@ -5185,8 +5649,8 @@ function $LogProvider(){ /** * @ngdoc method - * @name angular.module.ng.$log#warn - * @methodOf angular.module.ng.$log + * @name ng.$log#warn + * @methodOf ng.$log * * @description * Write a warning message @@ -5195,8 +5659,8 @@ function $LogProvider(){ /** * @ngdoc method - * @name angular.module.ng.$log#info - * @methodOf angular.module.ng.$log + * @name ng.$log#info + * @methodOf ng.$log * * @description * Write an information message @@ -5205,8 +5669,8 @@ function $LogProvider(){ /** * @ngdoc method - * @name angular.module.ng.$log#error - * @methodOf angular.module.ng.$log + * @name ng.$log#error + * @methodOf ng.$log * * @description * Write an error message @@ -5255,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);}, @@ -5277,7 +5749,7 @@ var OPERATORS = { }; var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; -function lex(text){ +function lex(text, csp){ var tokens = [], token, index = 0, @@ -5437,7 +5909,7 @@ function lex(text){ if (OPERATORS.hasOwnProperty(ident)) { token.fn = token.json = OPERATORS[ident]; } else { - var getter = getterFn(ident); + var getter = getterFn(ident, csp); token.fn = extend(function(self, locals) { return (getter(self, locals)); }, { @@ -5511,10 +5983,10 @@ function lex(text){ ///////////////////////////////////////// -function parser(text, json, $filter){ +function parser(text, json, $filter, csp){ var ZERO = valueFn(0), value, - tokens = lex(text), + tokens = lex(text, csp), assignment = _assignment, functionCall = _functionCall, fieldAccess = _fieldAccess, @@ -5782,7 +6254,7 @@ function parser(text, json, $filter){ function _fieldAccess(object) { var field = expect().text; - var getter = getterFn(field); + var getter = getterFn(field, csp); return extend( function(self, locals) { return getter(object(self, locals), locals); @@ -5935,47 +6407,171 @@ function getter(obj, path, bindFnToScope) { var getterFnCache = {}; -function getterFn(path) { +/** + * Implementation of the "Black Hole" variant from: + * - http://jsperf.com/angularjs-parse-getter/4 + * - http://jsperf.com/path-evaluation-simplified/7 + */ +function cspSafeGetterFn(key0, key1, key2, key3, key4) { + return function(scope, locals) { + var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, + promise; + + if (pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key0]; + if (pathVal && pathVal.then) { + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key1 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key1]; + if (pathVal && pathVal.then) { + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key2 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key2]; + if (pathVal && pathVal.then) { + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key3 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key3]; + if (pathVal && pathVal.then) { + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key4 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key4]; + if (pathVal && pathVal.then) { + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + return pathVal; + }; +}; + +function getterFn(path, csp) { if (getterFnCache.hasOwnProperty(path)) { return getterFnCache[path]; } - var fn, code = 'var l, fn, p;\n'; - forEach(path.split('.'), function(key, index) { - code += 'if(!s) return s;\n' + - 'l=s;\n' + - 's='+ (index - // we simply direference 's' on any .dot notation - ? 's' - // but if we are first then we check locals firs, and if so read it first - : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + - 'if (s && s.then) {\n' + - ' if (!("$$v" in s)) {\n' + - ' p=s;\n' + - ' p.$$v = undefined;\n' + - ' p.then(function(v) {p.$$v=v;});\n' + - '}\n' + - ' s=s.$$v\n' + - '}\n'; - }); - code += 'return s;'; - fn = Function('s', 'k', code); - fn.toString = function() { return code; }; + var pathKeys = path.split('.'), + pathKeysLength = pathKeys.length, + fn; + + if (csp) { + fn = (pathKeysLength < 6) + ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4]) + : function(scope, locals) { + var i = 0, val + do { + val = cspSafeGetterFn( + pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++] + )(scope, locals); + + locals = undefined; // clear after first iteration + scope = val; + } while (i < pathKeysLength); + return val; + } + } else { + var code = 'var l, fn, p;\n'; + forEach(pathKeys, function(key, index) { + code += 'if(s === null || s === undefined) return s;\n' + + 'l=s;\n' + + 's='+ (index + // we simply dereference 's' on any .dot notation + ? 's' + // but if we are first then we check locals first, and if so read it first + : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + + 'if (s && s.then) {\n' + + ' if (!("$$v" in s)) {\n' + + ' p=s;\n' + + ' p.$$v = undefined;\n' + + ' p.then(function(v) {p.$$v=v;});\n' + + '}\n' + + ' s=s.$$v\n' + + '}\n'; + }); + code += 'return s;'; + fn = Function('s', 'k', code); // s=scope, k=locals + fn.toString = function() { return code; }; + } return getterFnCache[path] = fn; } /////////////////////////////////// +/** + * @ngdoc function + * @name ng.$parse + * @function + * + * @description + * + * Converts Angular {@link guide/expression expression} into a function. + * + *
+ *   var getter = $parse('user.name');
+ *   var setter = getter.assign;
+ *   var context = {user:{name:'angular'}};
+ *   var locals = {user:{name:'local'}};
+ *
+ *   expect(getter(context)).toEqual('angular');
+ *   setter(context, 'newValue');
+ *   expect(context.user.name).toEqual('newValue');
+ *   expect(getter(context, locals)).toEqual('local');
+ * 
+ * + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * 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. + * + */ function $ParseProvider() { var cache = {}; - this.$get = ['$filter', function($filter) { + this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { return function(exp) { switch(typeof exp) { case 'string': return cache.hasOwnProperty(exp) ? cache[exp] - : cache[exp] = parser(exp, false, $filter); + : cache[exp] = parser(exp, false, $filter, $sniffer.csp); case 'function': return exp; default: @@ -5987,7 +6583,7 @@ function $ParseProvider() { /** * @ngdoc service - * @name angular.module.ng.$q + * @name ng.$q * @requires $rootScope * * @description @@ -5997,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
@@ -6027,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. @@ -6044,7 +6640,7 @@ 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** @@ -6087,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 *
* @@ -6101,13 +6697,37 @@ function $ParseProvider() { * * There are three main differences: * - * - $q is integrated with the {@link angular.module.ng.$rootScope.Scope} Scope model observation + * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation * mechanism in angular, which means faster propagation of resolution or rejection into your * models and avoiding unnecessary browser repaints, which would result in flickering UI. * - $q promises are recognized by the templating engine in angular, which means that in templates * you can treat promises attached to a scope as if they were the resulting values. * - Q has many more features 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() { @@ -6131,8 +6751,8 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc - * @name angular.module.ng.$q#defer - * @methodOf angular.module.ng.$q + * @name ng.$q#defer + * @methodOf ng.$q * @description * Creates a `Deferred` object which represents a task which will finish in the future. * @@ -6221,8 +6841,8 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc - * @name angular.module.ng.$q#reject - * @methodOf angular.module.ng.$q + * @name ng.$q#reject + * @methodOf ng.$q * @description * Creates a promise that is resolved as rejected with the specified `reason`. This api should be * used to forward rejection in a chain of promises. If you are dealing with the last promise in @@ -6269,16 +6889,16 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc - * @name angular.module.ng.$q#when - * @methodOf angular.module.ng.$q + * @name ng.$q#when + * @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. */ @@ -6332,15 +6952,15 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc - * @name angular.module.ng.$q#all - * @methodOf angular.module.ng.$q + * @name ng.$q#all + * @methodOf ng.$q * @description * Combines multiple promises into a single promise that is resolved when all of the input * promises are resolved. * * @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. */ @@ -6377,42 +6997,62 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc object - * @name angular.module.ng.$routeProvider + * @name ng.$routeProvider * @function * * @description * - * Used for configuring routes. See {@link angular.module.ng.$route $route} for an example. + * Used for configuring routes. See {@link ng.$route $route} for an example. */ function $RouteProvider(){ var routes = {}; /** * @ngdoc method - * @name angular.module.ng.$routeProvider#when - * @methodOf angular.module.ng.$routeProvider + * @name ng.$routeProvider#when + * @methodOf ng.$routeProvider * * @param {string} path Route path (matched against `$location.path`). If `$location.path` - * contains redudant 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 + * 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 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=}` – path to an html template that should be used by - * {@link angular.module.ng.$compileProvider.directive.ngView ngView} or - * {@link angular.module.ng.$compileProvider.directive.ngInclude ngInclude} directives. + * - `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 + * {@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 + * `$routeChangeSuccess` event is fired. The map object is: + * + * - `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 - * {@link angular.module.ng.$location $location} path with and trigger route redirection. + * {@link ng.$location $location} path with and trigger route redirection. * * If `redirectTo` is a function, it will be called with the following parameters: * * - `{Object.}` - route parameters extracted from the current - * `$location.path()` by applying the current route template. + * `$location.path()` by applying the current route templateUrl. * - `{string}` - current `$location.path()` * - `{Object}` - current `$location.search()` * @@ -6447,8 +7087,8 @@ function $RouteProvider(){ /** * @ngdoc method - * @name angular.module.ng.$routeProvider#otherwise - * @methodOf angular.module.ng.$routeProvider + * @name ng.$routeProvider#otherwise + * @methodOf ng.$routeProvider * * @description * Sets route definition that will be used on route change when no other route definition @@ -6463,115 +7103,145 @@ function $RouteProvider(){ }; - this.$get = ['$rootScope', '$location', '$routeParams', - function( $rootScope, $location, $routeParams) { + this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache', + function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) { /** * @ngdoc object - * @name angular.module.ng.$route + * @name ng.$route * @requires $location * @requires $routeParams * * @property {Object} current Reference to the current route definition. + * The route definition contains: + * + * - `controller`: The controller constructor as define in route definition. + * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for + * controller instantiation. The `locals` contain + * the resolved values of the `resolve` map. Additionally the `locals` also contain: + * + * - `$scope` - The current route scope. + * - `$template` - The current route template HTML. + * * @property {Array.} routes Array of all configured routes. * * @description * Is used for deep-linking URLs to controllers and views (HTML partials). * It watches `$location.url()` and tries to map the path to an existing route definition. * - * You can define routes through {@link angular.module.ng.$routeProvider $routeProvider}'s API. + * You can define routes through {@link ng.$routeProvider $routeProvider}'s API. * - * The `$route` service is typically used in conjunction with {@link angular.module.ng.$compileProvider.directive.ngView ngView} - * directive and the {@link angular.module.ng.$routeParams $routeParams} service. + * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView} + * directive and the {@link ng.$routeParams $routeParams} service. * * @example This example shows how changing the URL hash causes the `$route` to match a route against the URL, and the `ngView` pulls in the partial. - Note that this example is using {@link angular.module.ng.$compileProvider.directive.script inlined templates} + Note that this example is using {@link ng.directive:script inlined templates} to get it working on jsfiddle as well. - - - - - - - - -
- Choose: - Moby | - Moby: Ch1 | - Gatsby | - Gatsby: Ch4 | - Scarlet Letter
- -
-
- -
$location.path() = {{$location.path()}}
-
$route.current.template = {{$route.current.template}}
-
$route.current.params = {{$route.current.params}}
-
$route.current.scope.name = {{$route.current.scope.name}}
-
$routeParams = {{$routeParams}}
-
-
- - it('should load and compile correct template', function() { - element('a:contains("Moby: Ch1")').click(); - var content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: ChapterCntl/); - expect(content).toMatch(/Book Id\: Moby/); - expect(content).toMatch(/Chapter Id\: 1/); - - element('a:contains("Scarlet")').click(); - content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: BookCntl/); - expect(content).toMatch(/Book Id\: Scarlet/); - }); - -
+ function BookCntl($scope, $routeParams) { + $scope.name = "BookCntl"; + $scope.params = $routeParams; + } + + function ChapterCntl($scope, $routeParams) { + $scope.name = "ChapterCntl"; + $scope.params = $routeParams; + } + + + + it('should load and compile correct template', function() { + element('a:contains("Moby: Ch1")').click(); + var content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: ChapterCntl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element('a:contains("Scarlet")').click(); + sleep(2); // promises are not part of scenario waiting + content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: BookCntl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + + */ /** * @ngdoc event - * @name angular.module.ng.$route#$beforeRouteChange - * @eventOf angular.module.ng.$route + * @name ng.$route#$routeChangeStart + * @eventOf ng.$route * @eventType broadcast on root scope * @description - * Broadcasted before a route change. + * Broadcasted before a route change. At this point the route services starts + * resolving all of the dependencies needed for the route change to occurs. + * Typically this involves fetching the view template as well as any dependencies + * defined in `resolve` route property. Once all of the dependencies are resolved + * `$routeChangeSuccess` is fired. * * @param {Route} next Future route information. * @param {Route} current Current route information. @@ -6579,20 +7249,36 @@ function $RouteProvider(){ /** * @ngdoc event - * @name angular.module.ng.$route#$afterRouteChange - * @eventOf angular.module.ng.$route + * @name ng.$route#$routeChangeSuccess + * @eventOf ng.$route + * @eventType broadcast on root scope + * @description + * Broadcasted after a route dependencies are resolved. + * {@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|Undefined} previous Previous route information, or undefined if current is first route entered. + */ + + /** + * @ngdoc event + * @name ng.$route#$routeChangeError + * @eventOf ng.$route * @eventType broadcast on root scope * @description - * Broadcasted after a route change. + * Broadcasted if any of the resolve promises are rejected. * * @param {Route} current Current route information. * @param {Route} previous Previous route information. + * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. */ /** * @ngdoc event - * @name angular.module.ng.$route#$routeUpdate - * @eventOf angular.module.ng.$route + * @name ng.$route#$routeUpdate + * @eventOf ng.$route * @eventType broadcast on root scope * @description * @@ -6600,51 +7286,64 @@ function $RouteProvider(){ * instance of the Controller. */ - var matcher = switchRouteMatcher, - dirty = 0, - forceReload = false, + var forceReload = false, $route = { routes: routes, /** * @ngdoc method - * @name angular.module.ng.$route#reload - * @methodOf angular.module.ng.$route + * @name ng.$route#reload + * @methodOf ng.$route * * @description * Causes `$route` service to reload the current route even if - * {@link angular.module.ng.$location $location} hasn't changed. + * {@link ng.$location $location} hasn't changed. * - * As a result of that, {@link angular.module.ng.$compileProvider.directive.ngView ngView} + * As a result of that, {@link ng.directive:ngView ngView} * creates new scope, reinstantiates the controller. */ reload: function() { - dirty++; forceReload = true; + $rootScope.$evalAsync(updateRoute); } }; - $rootScope.$watch(function() { return dirty + $location.url(); }, updateRoute); + $rootScope.$on('$locationChangeSuccess', updateRoute); return $route; ///////////////////////////////////////////////////// + /** + * @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) { @@ -6658,14 +7357,14 @@ 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); $rootScope.$broadcast('$routeUpdate', last); } else if (next || last) { forceReload = false; - $rootScope.$broadcast('$beforeRouteChange', next, last); + $rootScope.$broadcast('$routeChangeStart', next, last); $route.current = next; if (next) { if (next.redirectTo) { @@ -6676,11 +7375,52 @@ function $RouteProvider(){ $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) .replace(); } - } else { - copy(next.params, $routeParams); } } - $rootScope.$broadcast('$afterRouteChange', next, last); + + $q.when(next). + then(function() { + if (next) { + var keys = [], + values = [], + template; + + forEach(next.resolve || {}, function(value, key) { + keys.push(key); + values.push(isString(value) ? $injector.get(value) : $injector.invoke(value)); + }); + if (isDefined(template = next.template)) { + } else if (isDefined(template = next.templateUrl)) { + template = $http.get(template, {cache: $templateCache}). + then(function(response) { return response.data; }); + } + if (isDefined(template)) { + keys.push('$template'); + values.push(template); + } + return $q.all(values).then(function(values) { + var locals = {}; + forEach(values, function(value, index) { + locals[keys[index]] = value; + }); + return locals; + }); + } + }). + // after route change + then(function(locals) { + if (next == $route.current) { + if (next) { + next.locals = locals; + copy(next.params, $routeParams); + } + $rootScope.$broadcast('$routeChangeSuccess', next, last); + } + }, function(error) { + if (next == $route.current) { + $rootScope.$broadcast('$routeChangeError', next, last, error); + } + }); } } @@ -6692,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 @@ -6726,13 +7466,13 @@ function $RouteProvider(){ /** * @ngdoc object - * @name angular.module.ng.$routeParams + * @name ng.$routeParams * @requires $route * * @description * Current set of route parameters. The route parameters are a combination of the - * {@link angular.module.ng.$location $location} `search()`, and `path()`. The `path` parameters - * are extracted when the {@link angular.module.ng.$route $route} path is matched. + * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters + * are extracted when the {@link ng.$route $route} path is matched. * * In case of parameter name collision, `path` params take precedence over `search` params. * @@ -6781,7 +7521,7 @@ function $RouteParamsProvider() { /** * @ngdoc object - * @name angular.module.ng.$rootScopeProvider + * @name ng.$rootScopeProvider * @description * * Provider for the $rootScope service. @@ -6789,8 +7529,8 @@ function $RouteParamsProvider() { /** * @ngdoc function - * @name angular.module.ng.$rootScopeProvider#digestTtl - * @methodOf angular.module.ng.$rootScopeProvider + * @name ng.$rootScopeProvider#digestTtl + * @methodOf ng.$rootScopeProvider * @description * * Sets the number of digest iteration the scope should attempt to execute before giving up and @@ -6804,12 +7544,12 @@ function $RouteParamsProvider() { /** * @ngdoc object - * @name angular.module.ng.$rootScope + * @name ng.$rootScope * @description * - * Every application has a single root {@link angular.module.ng.$rootScope.Scope scope}. + * Every application has a single root {@link ng.$rootScope.Scope scope}. * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide - * event processing life-cycle. See {@link guide/dev_guide.scopes developer guide on scopes}. + * event processing life-cycle. See {@link guide/scope developer guide on scopes}. */ function $RootScopeProvider(){ var TTL = 10; @@ -6826,12 +7566,12 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope * * @description - * A root scope can be retrieved using the {@link angular.module.ng.$rootScope $rootScope} key from the - * {@link angular.module.AUTO.$injector $injector}. Child scopes are created using the - * {@link angular.module.ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when + * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the + * {@link AUTO.$injector $injector}. Child scopes are created using the + * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when * compiled HTML template is executed.) * * Here is a simple scope snippet to show how you can interact with the scope. @@ -6844,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); @@ -6872,12 +7612,9 @@ function $RootScopeProvider(){ expect(parent.salutation).toEqual('Hello'); * * - * # Dependency Injection - * See {@link guide/dev_guide.di dependency injection}. - * * * @param {Object.=} providers Map of service factory which need to be provided - * for the current scope. Defaults to {@link angular.module.ng}. + * for the current scope. Defaults to {@link ng}. * @param {Object.=} instanceCache Provides pre-instantiated services which should * append/override services provided by `providers`. This is handy when unit-testing and having * the need to override a default service. @@ -6890,14 +7627,16 @@ 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 = {}; } /** * @ngdoc property - * @name angular.module.ng.$rootScope.Scope#$id - * @propertyOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$id + * @propertyOf ng.$rootScope.Scope * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for * debugging. */ @@ -6906,24 +7645,24 @@ function $RootScopeProvider(){ Scope.prototype = { /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$new - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$new + * @methodOf ng.$rootScope.Scope * @function * * @description - * Creates a new child {@link angular.module.ng.$rootScope.Scope scope}. + * Creates a new child {@link ng.$rootScope.Scope scope}. * - * The parent scope will propagate the {@link angular.module.ng.$rootScope.Scope#$digest $digest()} and - * {@link angular.module.ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope - * hierarchy using {@link angular.module.ng.$rootScope.Scope#$destroy $destroy()}. + * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and + * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope + * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. * - * {@link angular.module.ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for + * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for * the scope and its child scopes to be permanently detached from the parent and thus stop * participating in model change detection and listener notification by invoking. * - * @params {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. @@ -6965,35 +7704,35 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$watch - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$watch + * @methodOf ng.$rootScope.Scope * @function * * @description * Registers a `listener` callback to be executed whenever the `watchExpression` changes. * - * - The `watchExpression` is called on every call to {@link angular.module.ng.$rootScope.Scope#$digest $digest()} and - * should return the value which will be watched. (Since {@link angular.module.ng.$rootScope.Scope#$digest $digest()} + * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and + * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()} * reruns when it detects changes the `watchExpression` can execute multiple times per - * {@link angular.module.ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) + * {@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 angular.module.ng.$rootScope.Scope#$digest $digest} is called, - * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`, - * can execute multiple times per {@link angular.module.ng.$rootScope.Scope#$digest $digest} cycle when a change is + * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, + * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` + * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is * detected, be prepared for multiple calls to your listener.) * * After a watcher is registered with the scope, the `listener` fn is called asynchronously - * (via {@link angular.module.ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the + * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the * watcher. In rare cases, this is undesirable because the listener is called when the result * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the @@ -7001,14 +7740,14 @@ function $RootScopeProvider(){ * * * # Example -
+       * 
            // let's assume that scope was dependency injected as the $rootScope
            var scope = $rootScope;
            scope.name = 'misko';
            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();
@@ -7018,23 +7757,23 @@ function $RootScopeProvider(){
            scope.name = 'adam';
            scope.$digest();
            expect(scope.counter).toEqual(1);
-         
+ *
* * * * @param {(function()|string)} watchExpression Expression that is evaluated on each - * {@link angular.module.ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a + * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a * call to the `listener`. * - * - `string`: Evaluated as {@link guide/dev_guide.expressions expression} + * - `string`: Evaluated as {@link guide/expression expression} * - `function(scope)`: called with current `scope` as a parameter. * @param {(function()|string)=} listener Callback called whenever the return value of * the `watchExpression` changes. * - * - `string`: Evaluated as {@link guide/dev_guide.expressions expression} + * - `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) { @@ -7069,39 +7808,39 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$digest - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$digest + * @methodOf ng.$rootScope.Scope * @function * * @description - * Process all of the {@link angular.module.ng.$rootScope.Scope#$watch watchers} of the current scope and its children. - * Because a {@link angular.module.ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the - * `$digest()` keeps calling the {@link angular.module.ng.$rootScope.Scope#$watch watchers} until no more listeners are + * Process all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children. + * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the + * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are * firing. This means that it is possible to get into an infinite loop. This function will throw - * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 100. + * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10. * * Usually you don't call `$digest()` directly in - * {@link angular.module.ng.$compileProvider.directive.ngController controllers} or in - * {@link angular.module.ng.$compileProvider.directive directives}. - * Instead a call to {@link angular.module.ng.$rootScope.Scope#$apply $apply()} (typically from within a - * {@link angular.module.ng.$compileProvider.directive directives}) will force a `$digest()`. + * {@link ng.directive:ngController controllers} or in + * {@link ng.$compileProvider#directive directives}. + * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a + * {@link ng.$compileProvider#directive directives}) will force a `$digest()`. * * If you want to be notified whenever `$digest()` is called, - * you can register a `watchExpression` function with {@link angular.module.ng.$rootScope.Scope#$watch $watch()} + * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()} * with no `listener`. * * You may have a need to call `$digest()` from within unit-tests, to simulate the scope * life-cycle. * * # Example -
+       * 
            var scope = ...;
            scope.name = 'misko';
            scope.counter = 0;
 
            expect(scope.counter).toEqual(0);
-           scope.$watch('name', function(scope, newValue, oldValue) {
-             counter = counter + 1;
+           scope.$watch('name', function(newValue, oldValue) {
+             scope.counter = scope.counter + 1;
            });
            expect(scope.counter).toEqual(0);
 
@@ -7112,7 +7851,7 @@ function $RootScopeProvider(){
            scope.name = 'adam';
            scope.$digest();
            expect(scope.counter).toEqual(1);
-         
+ *
* */ $digest: function() { @@ -7125,7 +7864,7 @@ function $RootScopeProvider(){ watchLog = [], logIdx, logMsg; - flagPhase(target, '$digest'); + beginPhase('$digest'); do { dirty = false; @@ -7182,19 +7921,20 @@ function $RootScopeProvider(){ } while ((current = next)); if(dirty && !(ttl--)) { + clearPhase(); throw Error(TTL + ' $digest() iterations reached. Aborting!\n' + 'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); } } while (dirty || asyncQueue.length); - this.$root.$$phase = null; + clearPhase(); }, /** * @ngdoc event - * @name angular.module.$rootScope.Scope#$destroy - * @eventOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$destroy + * @eventOf ng.$rootScope.Scope * @eventType broadcast on scope being destroyed * * @description @@ -7203,18 +7943,18 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$destroy - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$destroy + * @methodOf ng.$rootScope.Scope * @function * * @description - * Remove the current scope (and all of its children) from the parent scope. Removal implies - * that calls to {@link angular.module.ng.$rootScope.Scope#$digest $digest()} will no longer + * 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. * * The `$destroy()` is usually used by directives such as - * {@link angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} for managing the + * {@link ng.directive:ngRepeat ngRepeat} for managing the * unrolling of the loop. * * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope. @@ -7222,42 +7962,48 @@ function $RootScopeProvider(){ * perform any necessary cleanup. */ $destroy: function() { - if (this.$root == 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; }, /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$eval - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$eval + * @methodOf ng.$rootScope.Scope * @function * * @description * Executes the `expression` on the current scope returning the result. Any exceptions in the - * expression are propagated (uncaught). This is useful when evaluating engular expressions. + * expression are propagated (uncaught). This is useful when evaluating Angular expressions. * * # Example -
-           var scope = angular.module.ng.$rootScope.Scope();
+       * 
+           var scope = ng.$rootScope.Scope();
            scope.a = 1;
            scope.b = 2;
 
            expect(scope.$eval('a+b')).toEqual(3);
            expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
-         
+ *
* * @param {(string|function())=} expression An angular expression to be executed. * - * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. - * - `function(scope, locals)`: execute the function with the current `scope` parameter. - * @param {Object=} locals Hash object of local variables for the expression. + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. * * @returns {*} The result of evaluating the expression. */ @@ -7267,8 +8013,8 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$evalAsync - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$evalAsync + * @methodOf ng.$rootScope.Scope * @function * * @description @@ -7277,15 +8023,15 @@ function $RootScopeProvider(){ * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that: * * - it will execute in the current script execution context (before any DOM rendering). - * - at least one {@link angular.module.ng.$rootScope.Scope#$digest $digest cycle} will be performed after + * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after * `expression` execution. * * Any exceptions from the execution of the expression are forwarded to the - * {@link angular.module.ng.$exceptionHandler $exceptionHandler} service. + * {@link ng.$exceptionHandler $exceptionHandler} service. * * @param {(string|function())=} expression An angular expression to be executed. * - * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. + * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. * */ @@ -7295,83 +8041,91 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$apply - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$apply + * @methodOf ng.$rootScope.Scope * @function * * @description * `$apply()` is used to execute an expression in angular from outside of the angular framework. * (For example from browser DOM events, setTimeout, XHR or third party libraries). * Because we are calling into the angular framework we need to perform proper scope life-cycle - * of {@link angular.module.ng.$exceptionHandler exception handling}, - * {@link angular.module.ng.$rootScope.Scope#$digest executing watches}. + * of {@link ng.$exceptionHandler exception handling}, + * {@link ng.$rootScope.Scope#$digest executing watches}. * * ## Life cycle * * # Pseudo-Code of `$apply()` - function $apply(expr) { - try { - return $eval(expr); - } catch (e) { - $exceptionHandler(e); - } finally { - $root.$digest(); - } - } + *
+           function $apply(expr) {
+             try {
+               return $eval(expr);
+             } catch (e) {
+               $exceptionHandler(e);
+             } finally {
+               $root.$digest();
+             }
+           }
+       * 
* * * Scope's `$apply()` method transitions through the following stages: * - * 1. The {@link guide/dev_guide.expressions expression} is executed using the - * {@link angular.module.ng.$rootScope.Scope#$eval $eval()} method. + * 1. The {@link guide/expression expression} is executed using the + * {@link ng.$rootScope.Scope#$eval $eval()} method. * 2. Any exceptions from the execution of the expression are forwarded to the - * {@link angular.module.ng.$exceptionHandler $exceptionHandler} service. - * 3. The {@link angular.module.ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression - * was executed using the {@link angular.module.ng.$rootScope.Scope#$digest $digest()} method. + * {@link ng.$exceptionHandler $exceptionHandler} service. + * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression + * was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. * * * @param {(string|function())=} exp An angular expression to be executed. * - * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. + * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with current `scope` parameter. * * @returns {*} The result of evaluating the expression. */ $apply: function(expr) { try { - flagPhase(this, '$apply'); + beginPhase('$apply'); return this.$eval(expr); } catch (e) { $exceptionHandler(e); } finally { - this.$root.$$phase = null; - this.$root.$digest(); + clearPhase(); + try { + $rootScope.$digest(); + } catch (e) { + $exceptionHandler(e); + throw e; + } } }, /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$on - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$on + * @methodOf ng.$rootScope.Scope * @function * * @description - * Listen on events of a given type. See {@link angular.module.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. - * - `cancel` - {function=}: calling `cancel` function will cancel further event propagation - * (available only for events that were `$emit`-ed). - * - `cancelled` - {boolean}: Whether the event was cancelled. + * @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]; @@ -7381,42 +8135,46 @@ function $RootScopeProvider(){ namedListeners.push(listener); return function() { - arrayRemove(namedListeners, listener); + namedListeners[indexOf(namedListeners, listener)] = null; }; }, /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$emit - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$emit + * @methodOf ng.$rootScope.Scope * @function * * @description * Dispatches an event `name` upwards through the scope hierarchy notifying the - * registered {@link angular.module.ng.$rootScope.Scope#$on} listeners. + * registered {@link ng.$rootScope.Scope#$on} listeners. * * The event life cycle starts at the scope on which `$emit` was called. All - * {@link angular.module.ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified. + * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified. * Afterwards, the event traverses upwards toward the root scope and calls all registered * listeners along the way. The event will stop propagating if one of the listeners cancels it. * - * Any exception emmited from the {@link angular.module.ng.$rootScope.Scope#$on listeners} will be passed - * onto the {@link angular.module.ng.$exceptionHandler $exceptionHandler} service. + * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed + * onto the {@link ng.$exceptionHandler $exceptionHandler} service. * * @param {string} name Event name to emit. * @param {...*} args Optional set of arguments which will be passed onto the event listeners. - * @return {Object} Event object, see {@link angular.module.ng.$rootScope.Scope#$on} + * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} */ $emit: function(name, args) { var empty = [], namedListeners, scope = this, + stopPropagation = false, event = { name: name, targetScope: scope, - cancel: function() {event.cancelled = true;}, - cancelled: false + stopPropagation: function() {stopPropagation = true;}, + preventDefault: function() { + event.defaultPrevented = true; + }, + defaultPrevented: false }, listenerArgs = concat([event], arguments, 1), i, length; @@ -7425,9 +8183,17 @@ function $RootScopeProvider(){ namedListeners = scope.$$listeners[name] || empty; event.currentScope = scope; for (i=0, length=namedListeners.length; i 7), hasEvent: function(event) { + // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have + // it. In particular the event is not fired when backspace or delete key are pressed or + // when cut operation is performed. + if (event == 'input' && msie == 9) return false; + if (isUndefined(eventSupport[event])) { var divElm = $window.document.createElement('div'); eventSupport[event] = 'on' + event in divElm; } return eventSupport[event]; - } + }, + // TODO(i): currently there is no way to feature detect CSP without triggering alerts + csp: false }; }]; } /** * @ngdoc object - * @name angular.module.ng.$window + * @name ng.$window * * @description * A reference to the browser's `window` object. While `window` @@ -7694,8 +8492,8 @@ function $HttpProvider() { 'Accept': 'application/json, text/plain, */*', 'X-Requested-With': 'XMLHttpRequest' }, - post: {'Content-Type': 'application/json'}, - put: {'Content-Type': 'application/json'} + post: {'Content-Type': 'application/json;charset=utf-8'}, + put: {'Content-Type': 'application/json;charset=utf-8'} } }; @@ -7718,8 +8516,8 @@ function $HttpProvider() { /** * @ngdoc function - * @name angular.module.ng.$http - * @requires $httpBacked + * @name ng.$http + * @requires $httpBackend * @requires $browser * @requires $cacheFactory * @requires $rootScope @@ -7732,19 +8530,19 @@ function $HttpProvider() { * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}. * * For unit testing applications that use `$http` service, see - * {@link angular.module.ngMock.$httpBackend $httpBackend mock}. + * {@link ngMock.$httpBackend $httpBackend mock}. * - * For a higher level of abstraction, please check out the {@link angular.module.ngResource.$resource + * For a higher level of abstraction, please check out the {@link ngResource.$resource * $resource} service. * - * The $http API is based on the {@link angular.module.ng.$q deferred/promise APIs} exposed by + * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by * the $q service. While for simple usage patters this doesn't matter much, for advanced usage, * it is important to familiarize yourself with these apis and guarantees they provide. * * * # General usage * The `$http` service is a function which takes a single argument — a configuration object — - * that is used to generate an http request and returns a {@link angular.module.ng.$q promise} + * that is used to generate an http request and returns a {@link ng.$q promise} * with two $http specific methods: `success` and `error`. * *
@@ -7755,8 +8553,7 @@ function $HttpProvider() {
      *     }).
      *     error(function(data, status, headers, config) {
      *       // called asynchronously if an error occurs
-     *       // or server returns response with status
-     *       // code outside of the <200, 400) range
+     *       // or server returns response with an error status.
      *     });
      * 
* @@ -7765,6 +8562,10 @@ function $HttpProvider() { * 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 * @@ -7779,12 +8580,12 @@ function $HttpProvider() { * * Complete list of shortcut methods: * - * - {@link angular.module.ng.$http#get $http.get} - * - {@link angular.module.ng.$http#head $http.head} - * - {@link angular.module.ng.$http#post $http.post} - * - {@link angular.module.ng.$http#put $http.put} - * - {@link angular.module.ng.$http#delete $http.delete} - * - {@link angular.module.ng.$http#jsonp $http.jsonp} + * - {@link ng.$http#get $http.get} + * - {@link ng.$http#head $http.head} + * - {@link ng.$http#post $http.post} + * - {@link ng.$http#put $http.put} + * - {@link ng.$http#delete $http.delete} + * - {@link ng.$http#jsonp $http.jsonp} * * * # Setting HTTP Headers @@ -7825,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 @@ -7848,18 +8653,18 @@ function $HttpProvider() { * # Response interceptors * * Before you start creating interceptors, be sure to understand the - * {@link angular.module.ng.$q $q and deferred/promise APIs}. + * {@link ng.$q $q and deferred/promise APIs}. * * For purposes of global error handling, authentication or any kind of synchronous or * asynchronous preprocessing of received responses, it is desirable to be able to intercept * responses for http requests before they are handed over to the application code that - * initiated these requests. The response interceptors leverage the {@link angular.module.ng.$q + * initiated these requests. The response interceptors leverage the {@link ng.$q * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. * * The interceptors are service factories that are registered with the $httpProvider by * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and * injected with dependencies (if specified) and returns the interceptor — a function that - * takes a {@link angular.module.ng.$q promise} and returns the original or a new promise. + * takes a {@link ng.$q promise} and returns the original or a new promise. * *
      *   // register the interceptor as a service
@@ -7958,14 +8763,14 @@ function $HttpProvider() {
      *      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
      *      GET request, otherwise if a cache instance built with
-     *      {@link angular.module.ng.$cacheFactory $cacheFactory}, this cache will be used for
+     *      {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
      *      caching.
      *    - **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.
      *
-     * @returns {HttpPromise} Returns a {@link angular.module.ng.$q promise} object with the
+     * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
      *   standard `then` method and two http specific methods: `success` and `error`. The `then`
      *   method takes two arguments a success and an error callback which will be called with a
      *   response object. The `success` and `error` methods take a single argument - a function that
@@ -7983,72 +8788,75 @@ function $HttpProvider() {
      *
      *
      * @example
-        
-          
-            
-            
- - -
- - - -
http status code: {{status}}
-
http response data: {{data}}
-
-
- - it('should make an xhr GET request', function() { - element(':button:contains("Sample GET")').click(); - element(':button:contains("fetch")').click(); - expect(binding('status')).toBe('200'); - expect(binding('data')).toBe('Hello, $http!\n'); - }); + $scope.updateModel = function(method, url) { + $scope.method = method; + $scope.url = url; + }; + } + + + Hello, $http! + + + it('should make an xhr GET request', function() { + element(':button:contains("Sample GET")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('200'); + expect(binding('data')).toMatch(/Hello, \$http!/); + }); - it('should make a JSONP request to angularjs.org', function() { - element(':button:contains("Sample JSONP")').click(); - element(':button:contains("fetch")').click(); - expect(binding('status')).toBe('200'); - expect(binding('data')).toMatch(/Super Hero!/); - }); + it('should make a JSONP request to angularjs.org', function() { + element(':button:contains("Sample JSONP")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('200'); + expect(binding('data')).toMatch(/Super Hero!/); + }); - it('should make JSONP request to invalid URL and invoke the error handler', - function() { - element(':button:contains("Invalid JSONP")').click(); - element(':button:contains("fetch")').click(); - expect(binding('status')).toBe('0'); - expect(binding('data')).toBe('Request failed'); - }); - -
+ it('should make JSONP request to invalid URL and invoke the error handler', + function() { + element(':button:contains("Invalid JSONP")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('0'); + expect(binding('data')).toBe('Request failed'); + }); + + */ function $http(config) { config.method = uppercase(config.method); @@ -8109,8 +8917,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#get - * @methodOf angular.module.ng.$http + * @name ng.$http#get + * @methodOf ng.$http * * @description * Shortcut method to perform `GET` request @@ -8122,8 +8930,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#delete - * @methodOf angular.module.ng.$http + * @name ng.$http#delete + * @methodOf ng.$http * * @description * Shortcut method to perform `DELETE` request @@ -8135,8 +8943,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#head - * @methodOf angular.module.ng.$http + * @name ng.$http#head + * @methodOf ng.$http * * @description * Shortcut method to perform `HEAD` request @@ -8148,8 +8956,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#jsonp - * @methodOf angular.module.ng.$http + * @name ng.$http#jsonp + * @methodOf ng.$http * * @description * Shortcut method to perform `JSONP` request @@ -8163,8 +8971,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#post - * @methodOf angular.module.ng.$http + * @name ng.$http#post + * @methodOf ng.$http * * @description * Shortcut method to perform `POST` request @@ -8177,8 +8985,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#put - * @methodOf angular.module.ng.$http + * @name ng.$http#put + * @methodOf ng.$http * * @description * Shortcut method to perform `PUT` request @@ -8192,8 +9000,8 @@ function $HttpProvider() { /** * @ngdoc property - * @name angular.module.ng.$http#defaults - * @propertyOf angular.module.ng.$http + * @name ng.$http#defaults + * @propertyOf ng.$http * * @description * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of @@ -8343,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) {} @@ -8353,19 +9162,19 @@ var XHR = window.XMLHttpRequest || function() { /** * @ngdoc object - * @name angular.module.ng.$httpBackend + * @name ng.$httpBackend * @requires $browser * @requires $window * @requires $document * * @description - * HTTP backend used by the {@link angular.module.ng.$http service} that delegates to + * HTTP backend used by the {@link ng.$http service} that delegates to * XMLHttpRequest object or JSONP and deals with browser incompatibilities. * * You should never need to use this service directly, instead use the higher-level abstractions: - * {@link angular.module.ng.$http $http} or {@link angular.module.ngResource.$resource $resource}. + * {@link ng.$http $http} or {@link ngResource.$resource $resource}. * - * During testing this implementation is swapped with {@link angular.module.ngMock.$httpBackend mock + * During testing this implementation is swapped with {@link ngMock.$httpBackend mock * $httpBackend} which can be trained with responses. */ function $HttpBackendProvider() { @@ -8410,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); } }; @@ -8472,7 +9303,7 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, /** * @ngdoc object - * @name angular.module.ng.$locale + * @name ng.$locale * * @description * $locale service provides localization rules for various Angular components. As of right now the @@ -8541,21 +9372,106 @@ function $LocaleProvider(){ }; } +function $TimeoutProvider() { + this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', + function($rootScope, $browser, $q, $exceptionHandler) { + var deferreds = {}; + + + /** + * @ngdoc function + * @name ng.$timeout + * @requires $browser + * + * @description + * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch + * block and delegates any exceptions to + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * The return value of registering a timeout function is a promise which will be resolved when + * the timeout is reached and the timeout function is executed. + * + * To cancel a the timeout request, call `$timeout.cancel(promise)`. + * + * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to + * synchronously flush the queue of deferred functions. + * + * @param {function()} fn A function, who's execution should be delayed. + * @param {number=} [delay=0] Delay in milliseconds. + * @param {boolean=} [invokeApply=true] If set to false skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @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) { + var deferred = $q.defer(), + promise = deferred.promise, + skipApply = (isDefined(invokeApply) && !invokeApply), + timeoutId, cleanup; + + timeoutId = $browser.defer(function() { + try { + deferred.resolve(fn()); + } catch(e) { + deferred.reject(e); + $exceptionHandler(e); + } + + if (!skipApply) $rootScope.$apply(); + }, delay); + + cleanup = function() { + delete deferreds[promise.$$timeoutId]; + }; + + promise.$$timeoutId = timeoutId; + deferreds[timeoutId] = deferred; + promise.then(cleanup, cleanup); + + return promise; + } + + + /** + * @ngdoc function + * @name ng.$timeout#cancel + * @methodOf ng.$timeout + * + * @description + * Cancels a task associated with the `promise`. As a result of this the promise will be + * resolved with a rejection. + * + * @param {Promise=} promise Promise returned by the `$timeout` function. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. + */ + timeout.cancel = function(promise) { + if (promise && promise.$$timeoutId in deferreds) { + deferreds[promise.$$timeoutId].reject('canceled'); + return $browser.defer.cancel(promise.$$timeoutId); + } + return false; + }; + + return timeout; + }]; +} + /** * @ngdoc object - * @name angular.module.ng.$filterProvider + * @name ng.$filterProvider * @description * * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To * achieve this a filter definition consists of a factory function which is annotated with dependencies and is - * responsible for creating a the filter function. + * responsible for creating a filter function. * *
  *   // Filter registration
  *   function MyModule($provide, $filterProvider) {
  *     // create a service to demonstrate injection (not always needed)
  *     $provide.value('greet', function(name){
- *       return 'Hello ' + name + '!':
+ *       return 'Hello ' + name + '!';
  *     });
  *
  *     // register a filter factory which uses the
@@ -8567,7 +9483,7 @@ function $LocaleProvider(){
  *         // filters need to be forgiving so check input validity
  *         return text && greet(text) || text;
  *       };
- *     };
+ *     });
  *   }
  * 
* @@ -8591,8 +9507,8 @@ function $LocaleProvider(){ */ /** * @ngdoc method - * @name angular.module.ng.$filterProvider#register - * @methodOf angular.module.ng.$filterProvider + * @name ng.$filterProvider#register + * @methodOf ng.$filterProvider * @description * Register filter factory function. * @@ -8603,7 +9519,7 @@ function $LocaleProvider(){ /** * @ngdoc function - * @name angular.module.ng.$filter + * @name ng.$filter * @function * @description * Filters are used for formatting data displayed to the user. @@ -8645,14 +9561,14 @@ function $FilterProvider($provide) { /** * @ngdoc filter - * @name angular.module.ng.$filter.filter + * @name ng.filter:filter * @function * * @description * Selects a subset of items from `array` and returns it as a new array. * * Note: This function is used to augment the `Array` type in Angular expressions. See - * {@link angular.module.ng.$filter} for more information about Angular arrays. + * {@link ng.$filter} for more information about Angular arrays. * * @param {Array} array The source array. * @param {string|Object|function()} expression The predicate to be used for selecting items from @@ -8686,22 +9602,22 @@ function $FilterProvider($provide) { Search: - + - +
NamePhone
NamePhone
{{friend.name}} {{friend.phone}}

Any:
Name only
Phone only
- + - +
NamePhone
NamePhone
{{friend.name}} {{friend.phone}}
@@ -8725,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++) { @@ -8808,7 +9724,7 @@ function filterFilter() { /** * @ngdoc filter - * @name angular.module.ng.$filter.currency + * @name ng.filter:currency * @function * * @description @@ -8859,7 +9775,7 @@ function currencyFilter($locale) { /** * @ngdoc filter - * @name angular.module.ng.$filter.number + * @name ng.filter:number * @function * * @description @@ -8923,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 @@ -8965,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); @@ -9008,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) { @@ -9045,7 +9975,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+ /** * @ngdoc filter - * @name angular.module.ng.$filter.date + * @name ng.filter:date * @function * * @description @@ -9073,10 +10003,10 @@ 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/dev_guide.i18n localizable formats}: + * {@link guide/i18n localizable formats}: * * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale * (e.g. Sep 3, 2010 12:05:08 pm) @@ -9095,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. @@ -9115,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)/); }); @@ -9126,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)) { @@ -9192,7 +10123,7 @@ function dateFilter($locale) { /** * @ngdoc filter - * @name angular.module.ng.$filter.json + * @name ng.filter:json * @function * * @description @@ -9227,7 +10158,7 @@ function jsonFilter() { /** * @ngdoc filter - * @name angular.module.ng.$filter.lowercase + * @name ng.filter:lowercase * @function * @description * Converts string to lowercase. @@ -9238,7 +10169,7 @@ var lowercaseFilter = valueFn(lowercase); /** * @ngdoc filter - * @name angular.module.ng.$filter.uppercase + * @name ng.filter:uppercase * @function * @description * Converts string to uppercase. @@ -9248,7 +10179,7 @@ var uppercaseFilter = valueFn(uppercase); /** * @ngdoc function - * @name angular.module.ng.$filter.limitTo + * @name ng.filter:limitTo * @function * * @description @@ -9257,7 +10188,7 @@ var uppercaseFilter = valueFn(uppercase); * value and sign (positive or negative) of `limit`. * * Note: This function is used to augment the `Array` type in Angular expressions. See - * {@link angular.module.ng.$filter} for more information about Angular arrays. + * {@link ng.$filter} for more information about Angular arrays. * * @param {Array} array Source array to be limited. * @param {string|Number} limit The length of the returned array. If the `limit` number is @@ -9334,14 +10265,14 @@ function limitToFilter(){ /** * @ngdoc function - * @name angular.module.ng.$filter.orderBy + * @name ng.filter:orderBy * @function * * @description * Orders a specified `array` by the `expression` predicate. * * Note: this function is used to augment the `Array` type in Angular expressions. See - * {@link angular.module.ng.$filter} for more informaton about Angular arrays. + * {@link ng.$filter} for more informaton about Angular arrays. * * @param {Array} array The array to sort. * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be @@ -9384,12 +10315,12 @@ function limitToFilter(){ (^) Phone Number Age - + {{friend.name}} {{friend.phone}} {{friend.age}} - + @@ -9421,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){ @@ -9478,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) { @@ -9508,7 +10454,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngHref + * @name ng.directive:ngHref * @restrict A * * @description @@ -9572,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() { @@ -9588,7 +10534,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngSrc + * @name ng.directive:ngSrc * @restrict A * * @description @@ -9613,7 +10559,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngDisabled + * @name ng.directive:ngDisabled * @restrict A * * @description @@ -9652,7 +10598,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngChecked + * @name ng.directive:ngChecked * @restrict A * * @description @@ -9682,7 +10628,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngMultiple + * @name ng.directive:ngMultiple * @restrict A * * @description @@ -9718,7 +10664,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngReadonly + * @name ng.directive:ngReadonly * @restrict A * * @description @@ -9748,7 +10694,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngSelected + * @name ng.directive:ngSelected * @restrict A * * @description @@ -9790,8 +10736,7 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) { priority: 100, compile: function() { return function(scope, element, attr) { - attr.$$observers[attrName] = []; - scope.$watch(attr[normalized], function(value) { + scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { attr.$set(attrName, !!value); }); }; @@ -9807,21 +10752,19 @@ forEach(['src', 'href'], function(attrName) { ngAttributeAliasDirectives[normalized] = function() { return { priority: 99, // it needs to run after the attributes are interpolated - compile: function() { - return function(scope, element, attr) { - var value = attr[normalized]; - if (value == undefined) { - // undefined value means that the directive is being interpolated - // so just register observer - attr.$$observers[attrName] = []; - attr.$observe(normalized, function(value) { - attr.$set(attrName, value); - }); - } else { - // value present means that no interpolation, so copy to native attribute. - attr.$set(attrName, value); - } - }; + 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. + // we use attr[attrName] value since $set can sanitize the url. + if (msie) element.prop(attrName, attr[attrName]); + }); } }; }; @@ -9836,24 +10779,24 @@ var nullFormCtrl = { /** * @ngdoc object - * @name angular.module.ng.$compileProvider.directive.form.FormController + * @name ng.directive:form.FormController * * @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 * `FormController` keeps track of all its controls and nested forms as well as state of them, * such as being valid/invalid or dirty/pristine. * - * Each {@link angular.module.ng.$compileProvider.directive.form form} directive creates an instance + * Each {@link ng.directive:form form} directive creates an instance * of `FormController`. * */ @@ -9943,6 +10886,7 @@ function FormController(element, attrs) { element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); form.$dirty = true; form.$pristine = false; + parentForm.$setDirty(); }; } @@ -9950,36 +10894,36 @@ function FormController(element, attrs) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngForm + * @name ng.directive:ngForm * @restrict EAC * * @description - * Nestable alias of {@link angular.module.ng.$compileProvider.directive.form `form`} directive. HTML + * Nestable alias of {@link ng.directive:form `form`} directive. HTML * 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. * */ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.form + * @name ng.directive:form * @restrict E * * @description * Directive that instantiates - * {@link angular.module.ng.$compileProvider.directive.form.FormController FormController}. + * {@link ng.directive:form.FormController FormController}. * * If `name` attribute is specified, the form controller is published onto the current scope under * this name. * - * # Alias: {@link angular.module.ng.$compileProvider.directive.ngForm `ngForm`} + * # Alias: {@link ng.directive:ngForm `ngForm`} * * In angular forms can be nested. This means that the outer form is valid when all of the child * forms are valid as well. However browsers do not allow nesting of `
` elements, for this - * reason angular provides {@link angular.module.ng.$compileProvider.directive.ngForm `ngForm`} alias + * reason angular provides {@link ng.directive:ngForm `ngForm`} alias * which behaves identical to `` but allows form nesting. * * @@ -10003,8 +10947,8 @@ function FormController(element, attrs) { * You can use one of the following two ways to specify what javascript method should be called when * a form is submitted: * - * - {@link angular.module.ng.$compileProvider.directive.ngSubmit ngSubmit} directive on the form element - * - {@link angular.module.ng.$compileProvider.directive.ngClick ngClick} directive on the first + * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element + * - {@link ng.directive:ngClick ngClick} directive on the first * button or input field of type submit (input[type=submit]) * * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This @@ -10031,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}}
@@ -10053,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}$/; @@ -10097,14 +11065,17 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.text + * @name ng.directive:input.text * * @description * Standard HTML text input with angular data binding. * * @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 @@ -10163,7 +11134,7 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.number + * @name ng.directive:input.number * * @description * Text input with number validation and transformation. Sets the `number` validation @@ -10174,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 @@ -10231,7 +11205,7 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.url + * @name ng.directive:input.url * * @description * Text input with URL validation. Sets the `url` validation error key if the content is not a @@ -10240,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 @@ -10296,7 +11273,7 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.email + * @name ng.directive:input.email * * @description * Text input with email validation. Sets the `email` validation error key if not a valid email @@ -10305,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 @@ -10359,7 +11339,7 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.radio + * @name ng.directive:input.radio * * @description * HTML radio button. @@ -10400,7 +11380,7 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.checkbox + * @name ng.directive:input.checkbox * * @description * HTML checkbox. @@ -10468,7 +11448,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { } }; - // if the browser does support "input" event, we are fine + // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the + // input event on backspace, delete or cut if ($sniffer.hasEvent('input')) { element.bind('input', listener); } else { @@ -10715,16 +11696,20 @@ function checkboxInputType(scope, element, attr, ctrl) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.textarea + * @name ng.directive:textarea + * @restrict E * * @description * HTML textarea element control with angular data-binding. The data-binding and validation * properties of this element are exactly the same as those of the - * {@link angular.module.ng.$compileProvider.directive.input input element}. + * {@link ng.directive:input input element}. * * @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 @@ -10739,7 +11724,7 @@ function checkboxInputType(scope, element, attr, ctrl) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.input + * @name ng.directive:input * @restrict E * * @description @@ -10749,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 @@ -10851,7 +11837,7 @@ var VALID_CLASS = 'ng-valid', /** * @ngdoc object - * @name angular.module.ng.$compileProvider.directive.ngModel.NgModelController + * @name ng.directive:ngModel.NgModelController * * @property {string} $viewValue Actual string value in the view. * @property {*} $modelValue The value in the model, that the control is bound to. @@ -10870,9 +11856,82 @@ var VALID_CLASS = 'ng-valid', * * @description * + * `NgModelController` provides API for the `ng-model` directive. The controller contains + * services for data-binding, validation, CSS update, value formatting and parsing. It + * specifically does not contain any logic which deals with DOM rendering or listening to + * DOM events. The `NgModelController` is meant to be extended by other directives where, the + * directive provides DOM manipulation and the `NgModelController` provides the data-binding. + * + * This example shows how to use `NgModelController` with a custom control to achieve + * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) + * collaborate together to achieve the desired result. + * + * + + [contenteditable] { + border: 1px solid black; + background-color: white; + min-height: 20px; + } + + .ng-invalid { + border: 1px solid red; + } + + + + angular.module('customControl', []). + directive('contenteditable', function() { + return { + restrict: 'A', // only activate on element attribute + require: '?ngModel', // get a hold of NgModelController + link: function(scope, element, attrs, ngModel) { + if(!ngModel) return; // do nothing if no ng-model + + // Specify how UI should be updated + ngModel.$render = function() { + element.html(ngModel.$viewValue || ''); + }; + + // Listen for change events to enable binding + element.bind('blur keyup change', function() { + scope.$apply(read); + }); + read(); // initialize + + // Write data to the model + function read() { + ngModel.$setViewValue(element.html()); + } + } + }; + }); + + +
+
Change me!
+ Required! +
+ +
+
+ + it('should data-bind and become invalid', function() { + var contentEditable = element('[contenteditable]'); + + expect(contentEditable.text()).toEqual('Change me!'); + input('userContent').enter(''); + expect(contentEditable.text()).toEqual(''); + expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/); + }); + + *
+ * */ -var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$element', - function($scope, $exceptionHandler, $attr, ngModel, $element) { +var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', + function($scope, $exceptionHandler, $attr, $element, $parse) { this.$viewValue = Number.NaN; this.$modelValue = Number.NaN; this.$parsers = []; @@ -10882,9 +11941,27 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e this.$dirty = false; this.$valid = true; this.$invalid = false; - this.$render = noop; this.$name = $attr.name; + var ngModelGet = $parse($attr.ngModel), + ngModelSet = ngModelGet.assign; + + if (!ngModelSet) { + throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + $attr.ngModel + + ' (' + startingTag($element) + ')'); + } + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$render + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Called when the view needs to be updated. It is expected that the user of the ng-model + * directive will implement this method. + */ + this.$render = noop; + var parentForm = $element.inheritedData('$formController') || nullFormCtrl, invalidCount = 0, // used to easily determine if we are valid $error = this.$error = {}; // keep invalid keys here @@ -10904,8 +11981,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e /** * @ngdoc function - * @name angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setValidity - * @methodOf angular.module.ng.$compileProvider.directive.ngModel.NgModelController + * @name ng.directive:ngModel.NgModelController#$setValidity + * @methodOf ng.directive:ngModel.NgModelController * * @description * Change the validity state, and notifies the form when the control changes validity. (i.e. it @@ -10946,15 +12023,15 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e /** * @ngdoc function - * @name angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setViewValue - * @methodOf angular.module.ng.$compileProvider.directive.ngModel.NgModelController + * @name ng.directive:ngModel.NgModelController#$setViewValue + * @methodOf ng.directive:ngModel.NgModelController * * @description * Read a value from view. * * This method should be called from within a DOM event handler. - * For example {@link angular.module.ng.$compileProvider.directive.input input} or - * {@link angular.module.ng.$compileProvider.directive.select select} directives call it. + * For example {@link ng.directive:input input} or + * {@link ng.directive:select select} directives call it. * * It internally calls all `formatters` and if resulted value is valid, updates the model and * calls all registered change listeners. @@ -10978,7 +12055,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e if (this.$modelValue !== value) { this.$modelValue = value; - ngModel(value); + ngModelSet($scope, value); forEach(this.$viewChangeListeners, function(listener) { try { listener(); @@ -10991,24 +12068,25 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e // model -> value var ctrl = this; - $scope.$watch(function() { - return ngModel(); - }, 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(); + } } }); }]; @@ -11016,7 +12094,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngModel + * @name ng.directive:ngModel * * @element input * @@ -11031,26 +12109,23 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e * - providing validation behavior (i.e. required, number, email, url), * - keeping state of the control (valid/invalid, dirty/pristine, validation errors), * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`), - * - register the control with parent {@link angular.module.ng.$compileProvider.directive.form form}. + * - register the control with parent {@link ng.directive:form form}. * * For basic examples, how to use `ngModel`, see: * - * - {@link angular.module.ng.$compileProvider.directive.input input} - * - {@link angular.module.ng.$compileProvider.directive.input.text text} - * - {@link angular.module.ng.$compileProvider.directive.input.checkbox checkbox} - * - {@link angular.module.ng.$compileProvider.directive.input.radio radio} - * - {@link angular.module.ng.$compileProvider.directive.input.number number} - * - {@link angular.module.ng.$compileProvider.directive.input.email email} - * - {@link angular.module.ng.$compileProvider.directive.input.url url} - * - {@link angular.module.ng.$compileProvider.directive.select select} - * - {@link angular.module.ng.$compileProvider.directive.textarea textarea} + * - {@link ng.directive:input input} + * - {@link ng.directive:input.text text} + * - {@link ng.directive:input.checkbox checkbox} + * - {@link ng.directive:input.radio radio} + * - {@link ng.directive:input.number number} + * - {@link ng.directive:input.email email} + * - {@link ng.directive:input.url url} + * - {@link ng.directive:select select} + * - {@link ng.directive:textarea textarea} * */ -var ngModelDirective = [function() { +var ngModelDirective = function() { return { - inject: { - ngModel: 'accessor' - }, require: ['ngModel', '^?form'], controller: NgModelController, link: function(scope, element, attr, ctrls) { @@ -11066,12 +12141,12 @@ var ngModelDirective = [function() { }); } }; -}]; +}; /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngChange + * @name ng.directive:ngChange * @restrict E * * @description @@ -11127,11 +12202,12 @@ var ngChangeDirective = valueFn({ }); -var requiredDirective = [function() { +var requiredDirective = function() { return { require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; + attr.required = true; // force truthy in case we are on non input element var validator = function(value) { if (attr.required && (isEmpty(value) || value === false)) { @@ -11151,15 +12227,15 @@ var requiredDirective = [function() { }); } }; -}]; +}; /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngList + * @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 @@ -11219,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(', '); } @@ -11232,7 +12308,7 @@ var ngListDirective = function() { var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; -var ngValueDirective = [function() { +var ngValueDirective = function() { return { priority: 100, compile: function(tpl, tplAttr) { @@ -11242,19 +12318,18 @@ var ngValueDirective = [function() { }; } else { return function(scope, elm, attr) { - attr.$$observers.value = []; - scope.$watch(attr.ngValue, function(value) { + scope.$watch(attr.ngValue, function valueWatchAction(value) { attr.$set('value', value, false); }); }; } } }; -}]; +}; /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngBind + * @name ng.directive:ngBind * * @description * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element @@ -11270,11 +12345,11 @@ var ngValueDirective = [function() { * bindings invisible to the user while the page is loading. * * An alternative solution to this problem would be using the - * {@link angular.module.ng.$compileProvider.directive.ngCloak ngCloak} directive. + * {@link ng.directive:ngCloak ngCloak} directive. * * * @element ANY - * @param {expression} ngBind {@link guide/dev_guide.expressions Expression} to evaluate. + * @param {expression} ngBind {@link guide/expression Expression} to evaluate. * * @example * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. @@ -11301,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); }); }); @@ -11309,7 +12384,7 @@ var ngBindDirective = ngDirective(function(scope, element, attr) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngBindTemplate + * @name ng.directive:ngBindTemplate * * @description * The `ngBindTemplate` directive specifies that the element @@ -11368,23 +12443,23 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngBindHtmlUnsafe + * @name ng.directive:ngBindHtmlUnsafe * * @description * Creates a binding that will innerHTML the result of evaluating the `expression` into the current * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if - * {@link angular.module.ng.$compileProvider.directive.ngBindHtml ngBindHtml} directive is too + * {@link ngSanitize.directive:ngBindHtml ngBindHtml} directive is too * restrictive and when you absolutely trust the source of the content you are binding to. * - * See {@link angular.module.ngSanitize.$sanitize $sanitize} docs for examples. + * See {@link ngSanitize.$sanitize $sanitize} docs for examples. * * @element ANY - * @param {expression} ngBindHtmlUnsafe {@link guide/dev_guide.expressions Expression} to evaluate. + * @param {expression} ngBindHtmlUnsafe {@link guide/expression Expression} to evaluate. */ 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 || ''); }); }; @@ -11393,23 +12468,63 @@ 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); + } + } }); } /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngClass + * @name ng.directive:ngClass * * @description * The `ngClass` allows you to set CSS class on HTML element dynamically by databinding an @@ -11417,101 +12532,113 @@ 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 - * @param {expression} ngClass {@link guide/dev_guide.expressions Expression} to eval. The result + * @param {expression} ngClass {@link guide/expression Expression} to eval. The result * of the evaluation can be a string representing space delimited class * names, an array, or a map of class names to boolean values. * * @example - - - + + +
- Sample Text      -
- + Sample Text + + + .my-class { + color: red; + } + + it('should check ng-class', function() { expect(element('.doc-example-live span').prop('className')).not(). - toMatch(/ng-invalid/); + toMatch(/my-class/); using('.doc-example-live').element(':button:first').click(); expect(element('.doc-example-live span').prop('className')). - toMatch(/ng-invalid/); + toMatch(/my-class/); using('.doc-example-live').element(':button:last').click(); expect(element('.doc-example-live span').prop('className')).not(). - toMatch(/ng-invalid/); + toMatch(/my-class/); }); - -
+ + */ var ngClassDirective = classDirective('', true); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngClassOdd + * @name ng.directive:ngClassOdd * * @description * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link angular.module.ng.$compileProvider.directive.ngClass ngClass}, except it works in + * {@link ng.directive:ngClass ngClass}, except it works in * conjunction with `ngRepeat` and takes affect only on odd (even) rows. * * This directive can be applied only within a scope of an - * {@link angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat}. + * {@link ng.directive:ngRepeat ngRepeat}. * * @element ANY - * @param {expression} ngClassOdd {@link guide/dev_guide.expressions Expression} to eval. The result + * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result * of the evaluation can be a string representing space delimited class names or an array. * * @example - - + +
  1. - - {{name}}       + + {{name}}
-
- + + + .odd { + color: red; + } + .even { + color: blue; + } + + it('should check ng-class-odd and ng-class-even', function() { expect(element('.doc-example-live li:first span').prop('className')). - toMatch(/ng-format-negative/); + toMatch(/odd/); expect(element('.doc-example-live li:last span').prop('className')). - toMatch(/ng-invalid/); + toMatch(/even/); }); - -
+ + */ var ngClassOddDirective = classDirective('Odd', 0); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngClassEven + * @name ng.directive:ngClassEven * * @description * The `ngClassOdd` and `ngClassEven` works exactly as - * {@link angular.module.ng.$compileProvider.directive.ngClass ngClass}, except it works in + * {@link ng.directive:ngClass ngClass}, except it works in * conjunction with `ngRepeat` and takes affect only on odd (even) rows. * * This directive can be applied only within a scope of an - * {@link angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat}. + * {@link ng.directive:ngRepeat ngRepeat}. * * @element ANY - * @param {expression} ngClassEven {@link guide/dev_guide.expressions Expression} to eval. The + * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The * result of the evaluation can be a string representing space delimited class names or an array. * * @example - - + +
  1. @@ -11519,22 +12646,30 @@ var ngClassOddDirective = classDirective('Odd', 0);
-
- + + + .odd { + color: red; + } + .even { + color: blue; + } + + it('should check ng-class-odd and ng-class-even', function() { expect(element('.doc-example-live li:first span').prop('className')). toMatch(/odd/); expect(element('.doc-example-live li:last span').prop('className')). toMatch(/even/); }); - -
+ + */ var ngClassEvenDirective = classDirective('Even', 1); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngCloak + * @name ng.directive:ngCloak * * @description * The `ngCloak` directive is used to prevent the Angular html template from being briefly @@ -11548,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;
  * }
  * 
@@ -11594,7 +12729,7 @@ var ngCloakDirective = ngDirective({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngController + * @name ng.directive:ngController * * @description * The `ngController` directive assigns behavior to a scope. This is a key aspect of how angular @@ -11607,13 +12742,13 @@ var ngCloakDirective = ngDirective({ * * 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 angular.module.ng.$route}` + * Note that an alternative way to define controllers is via the `{@link ng.$route}` * service. * * @element ANY * @scope * @param {expression} ngController Name of a globally accessible constructor function or an - * {@link guide/dev_guide.expressions expression} that on the current scope evaluates to a + * {@link guide/expression expression} that on the current scope evaluates to a * constructor function. * * @example @@ -11625,7 +12760,7 @@ var ngCloakDirective = ngDirective({ * for a manual update. - -
- - url of the template: {{template.url}} -
-
-
-
- - it('should load template1.html', function() { - expect(element('.doc-example-live [ng-include]').text()). - toBe('Content of template1.html\n'); - }); - it('should load template2.html', function() { - select('template').option('1'); - expect(element('.doc-example-live [ng-include]').text()). - toBe('Content of template2.html\n'); - }); - it('should change to blank', function() { - select('template').option(''); - expect(element('.doc-example-live [ng-include]').text()).toEqual(''); - }); - -
+ + +
+ + url of the template: {{template.url}} +
+
+
+
+ + function Ctrl($scope) { + $scope.templates = + [ { name: 'template1.html', url: 'template1.html'} + , { name: 'template2.html', url: 'template2.html'} ]; + $scope.template = $scope.templates[0]; + } + + + Content of template1.html + + + Content of template2.html + + + it('should load template1.html', function() { + expect(element('.doc-example-live [ng-include]').text()). + toMatch(/Content of template1.html/); + }); + it('should load template2.html', function() { + select('template').option('1'); + expect(element('.doc-example-live [ng-include]').text()). + toMatch(/Content of template2.html/); + }); + it('should change to blank', function() { + select('template').option(''); + expect(element('.doc-example-live [ng-include]').text()).toEqual(''); + }); + +
*/ /** * @ngdoc event - * @name angular.module.ng.$compileProvider.directive.ngInclude#$includeContentLoaded - * @eventOf angular.module.ng.$compileProvider.directive.ngInclude + * @name ng.directive:ngInclude#$includeContentLoaded + * @eventOf ng.directive:ngInclude * @eventType emit on the current ngInclude scope * @description * Emitted every time the ngInclude content is reloaded. @@ -12010,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) { @@ -12041,14 +13208,14 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile' /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngInit + * @name ng.directive:ngInit * * @description * The `ngInit` directive specifies initialization tasks to be executed * before the template enters execution mode during bootstrap. * * @element ANY - * @param {expression} ngInit {@link guide/dev_guide.expressions Expression} to eval. + * @param {expression} ngInit {@link guide/expression Expression} to eval. * * @example @@ -12077,7 +13244,7 @@ var ngInitDirective = ngDirective({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngNonBindable + * @name ng.directive:ngNonBindable * @priority 1000 * * @description @@ -12109,14 +13276,14 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngPluralize + * @name ng.directive:ngPluralize * @restrict EA * * @description * # Overview * `ngPluralize` is a directive that displays messages according to en-US localization rules. * These rules are bundled with angular.js and the rules can be overridden - * (see {@link guide/dev_guide.i18n Angular i18n} dev guide). You configure ngPluralize directive + * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive * by specifying the mappings between * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html * plural categories} and the strings to be displayed. @@ -12135,8 +13302,8 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); * You configure ngPluralize by providing 2 attributes: `count` and `when`. * You can also provide an optional attribute, `offset`. * - * The value of the `count` attribute can be either a string or an {@link guide/dev_guide.expressions - * Angular expression}; these are evaluated on the current scope for its binded value. + * The value of the `count` attribute can be either a string or an {@link guide/expression + * Angular expression}; these are evaluated on the current scope for its bound value. * * The `when` attribute specifies the mappings between plural categories and the actual * string to be displayed. The value of the attribute should be a JSON object so that Angular @@ -12285,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)) { @@ -12303,7 +13473,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp } else { return ''; } - }, function(newVal) { + }, function ngPluralizeWatchAction(newVal) { element.text(newVal); }); } @@ -12312,7 +13482,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngRepeat + * @name ng.directive:ngRepeat * * @description * The `ngRepeat` directive instantiates a template once per item from a collection. Each template @@ -12322,10 +13492,9 @@ 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) - * * `$position` – `{string}` – position of the repeated element in the iterator. One of: - * * `'first'`, - * * `'middle'` - * * `'last'` + * * `$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 @@ -12399,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 @@ -12424,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 @@ -12455,9 +13632,10 @@ var ngRepeatDirective = ngDirective({ childScope[valueIdent] = value; if (keyIdent) childScope[keyIdent] = key; childScope.$index = index; - childScope.$position = index === 0 ? - 'first' : - (index == collectionLength - 1 ? 'last' : 'middle'); + + childScope.$first = (index === 0); + childScope.$last = (index === arrayBound); + childScope.$middle = !(childScope.$first || childScope.$last); if (!last) { linker(childScope, function(clone){ @@ -12492,14 +13670,14 @@ var ngRepeatDirective = ngDirective({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngShow + * @name ng.directive:ngShow * * @description * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML) * conditionally. * * @element ANY - * @param {expression} ngShow If the {@link guide/dev_guide.expressions expression} is truthy + * @param {expression} ngShow If the {@link guide/expression expression} is truthy * then the element is shown or hidden respectively. * * @example @@ -12524,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'); }); }); @@ -12532,14 +13710,14 @@ var ngShowDirective = ngDirective(function(scope, element, attr){ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngHide + * @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/dev_guide.expressions expression} truthy then + * @param {expression} ngHide If the {@link guide/expression expression} is truthy then * the element is shown or hidden respectively. * * @example @@ -12564,33 +13742,38 @@ 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' : ''); }); }); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngStyle + * @name ng.directive:ngStyle * * @description * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. * * @element ANY - * @param {expression} ngStyle {@link guide/dev_guide.expressions Expression} which evals to an + * @param {expression} ngStyle {@link guide/expression Expression} which evals to an * object whose keys are CSS style names and values are corresponding values for those CSS * keys. * * @example - - + +
Sample Text
myStyle={{myStyle}}
-
- + + + span { + color: black; + } + + it('should check ng-style', function() { expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); element('.doc-example-live :button[value=set]').click(); @@ -12598,11 +13781,11 @@ var ngHideDirective = ngDirective(function(scope, element, attr){ element('.doc-example-live :button[value=clear]').click(); expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); }); - -
+ + */ 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, '');}); } @@ -12612,17 +13795,19 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngSwitch + * @name ng.directive:ngSwitch * @restrict EA * * @description * Conditionally change the DOM structure. * - * @usageContent - * ... + * @usage + * + * ... * ... * ... * ... + * * * @scope * @param {*} ngSwitch|on expression to match against ng-switch-when. @@ -12672,58 +13857,60 @@ 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; + }; } }); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngTransclude + * @name ng.directive:ngTransclude * * @description * Insert the transcluded DOM here. @@ -12780,63 +13967,20 @@ var ngTranscludeDirective = ngDirective({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngView + * @name ng.directive:ngView * @restrict ECA * * @description * # Overview - * `ngView` is a directive that complements the {@link angular.module.ng.$route $route} service by + * `ngView` is a directive that complements the {@link ng.$route $route} service by * including the rendered template of the current route into the main layout (`index.html`) file. * Every time the current route changes, the included view changes with it according to the * configuration of the `$route` service. * * @scope * @example - - - - - - - - + +
Choose: Moby | @@ -12849,13 +13993,57 @@ 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}}
-
- + + + + controller: {{name}}
+ Book Id: {{params.bookId}}
+
+ + + controller: {{name}}
+ Book Id: {{params.bookId}}
+ Chapter Id: {{params.chapterId}} +
+ + + angular.module('ngView', [], function($routeProvider, $locationProvider) { + $routeProvider.when('/Book/:bookId', { + templateUrl: 'book.html', + controller: BookCntl + }); + $routeProvider.when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: ChapterCntl + }); + + // configure html5 to get links working on jsfiddle + $locationProvider.html5Mode(true); + }); + + function MainCntl($scope, $route, $routeParams, $location) { + $scope.$route = $route; + $scope.$location = $location; + $scope.$routeParams = $routeParams; + } + + function BookCntl($scope, $routeParams) { + $scope.name = "BookCntl"; + $scope.params = $routeParams; + } + + function ChapterCntl($scope, $routeParams) { + $scope.name = "ChapterCntl"; + $scope.params = $routeParams; + } + + + it('should load and compile correct template', function() { element('a:contains("Moby: Ch1")').click(); var content = element('.doc-example-live [ng-view]').text(); @@ -12868,15 +14056,15 @@ var ngTranscludeDirective = ngDirective({ expect(content).toMatch(/controller\: BookCntl/); expect(content).toMatch(/Book Id\: Scarlet/); }); -
-
+ + */ /** * @ngdoc event - * @name angular.module.ng.$compileProvider.directive.ngView#$viewContentLoaded - * @eventOf angular.module.ng.$compileProvider.directive.ngView + * @name ng.directive:ngView#$viewContentLoaded + * @eventOf ng.directive:ngView * @eventType emit on the current ngView scope * @description * Emitted every time the ngView content is reloaded. @@ -12889,11 +14077,10 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c restrict: 'ECA', terminal: true, link: function(scope, element, attr) { - var changeCounter = 0, - lastScope, + var lastScope, onloadExp = attr.onload || ''; - scope.$on('$afterRouteChange', update); + scope.$on('$routeChangeSuccess', update); update(); @@ -12904,43 +14091,36 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c } } + function clearContent() { + element.html(''); + destroyLastScope(); + } + function update() { - var template = $route.current && $route.current.template, - thisChangeId = ++changeCounter; - - function clearContent() { - // ignore callback if another route change occured since - if (thisChangeId === changeCounter) { - element.html(''); - destroyLastScope(); - } - } + var locals = $route.current && $route.current.locals, + template = locals && locals.$template; if (template) { - $http.get(template, {cache: $templateCache}).success(function(response) { - // ignore callback if another route change occured since - if (thisChangeId === changeCounter) { - element.html(response); - destroyLastScope(); - - var link = $compile(element.contents()), - current = $route.current, - controller; - - lastScope = current.scope = scope.$new(); - if (current.controller) { - controller = $controller(current.controller, {$scope: lastScope}); - element.contents().data('$ngControllerController', controller); - } + element.html(template); + destroyLastScope(); + + var link = $compile(element.contents()), + current = $route.current, + controller; + + lastScope = current.scope = scope.$new(); + if (current.controller) { + locals.$scope = lastScope; + controller = $controller(current.controller, locals); + element.children().data('$ngControllerController', controller); + } - link(lastScope); - lastScope.$emit('$viewContentLoaded'); - lastScope.$eval(onloadExp); + link(lastScope); + lastScope.$emit('$viewContentLoaded'); + lastScope.$eval(onloadExp); - // $anchorScroll might listen on event... - $anchorScroll(); - } - }).error(clearContent); + // $anchorScroll might listen on event... + $anchorScroll(); } else { clearContent(); } @@ -12951,7 +14131,7 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.script + * @name ng.directive:script * * @description * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the @@ -12996,7 +14176,7 @@ var scriptDirective = ['$templateCache', function($templateCache) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.select + * @name ng.directive:select * @restrict E * * @description @@ -13017,12 +14197,15 @@ var scriptDirective = ['$templateCache', function($templateCache) { * option. See example below for demonstration. * * Note: `ngOptions` provides iterator facility for `
* @@ -15506,13 +16103,37 @@ function $ParseProvider() { * * There are three main differences: * - * - $q is integrated with the {@link angular.module.ng.$rootScope.Scope} Scope model observation + * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation * mechanism in angular, which means faster propagation of resolution or rejection into your * models and avoiding unnecessary browser repaints, which would result in flickering UI. * - $q promises are recognized by the templating engine in angular, which means that in templates * you can treat promises attached to a scope as if they were the resulting values. * - Q has many more features 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() { @@ -15536,8 +16157,8 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc - * @name angular.module.ng.$q#defer - * @methodOf angular.module.ng.$q + * @name ng.$q#defer + * @methodOf ng.$q * @description * Creates a `Deferred` object which represents a task which will finish in the future. * @@ -15626,8 +16247,8 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc - * @name angular.module.ng.$q#reject - * @methodOf angular.module.ng.$q + * @name ng.$q#reject + * @methodOf ng.$q * @description * Creates a promise that is resolved as rejected with the specified `reason`. This api should be * used to forward rejection in a chain of promises. If you are dealing with the last promise in @@ -15674,16 +16295,16 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc - * @name angular.module.ng.$q#when - * @methodOf angular.module.ng.$q + * @name ng.$q#when + * @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. */ @@ -15737,15 +16358,15 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc - * @name angular.module.ng.$q#all - * @methodOf angular.module.ng.$q + * @name ng.$q#all + * @methodOf ng.$q * @description * Combines multiple promises into a single promise that is resolved when all of the input * promises are resolved. * * @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. */ @@ -15782,42 +16403,62 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc object - * @name angular.module.ng.$routeProvider + * @name ng.$routeProvider * @function * * @description * - * Used for configuring routes. See {@link angular.module.ng.$route $route} for an example. + * Used for configuring routes. See {@link ng.$route $route} for an example. */ function $RouteProvider(){ var routes = {}; /** * @ngdoc method - * @name angular.module.ng.$routeProvider#when - * @methodOf angular.module.ng.$routeProvider + * @name ng.$routeProvider#when + * @methodOf ng.$routeProvider * * @param {string} path Route path (matched against `$location.path`). If `$location.path` - * contains redudant 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 + * 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 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=}` – path to an html template that should be used by - * {@link angular.module.ng.$compileProvider.directive.ngView ngView} or - * {@link angular.module.ng.$compileProvider.directive.ngInclude ngInclude} directives. + * - `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 + * {@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 + * `$routeChangeSuccess` event is fired. The map object is: + * + * - `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 - * {@link angular.module.ng.$location $location} path with and trigger route redirection. + * {@link ng.$location $location} path with and trigger route redirection. * * If `redirectTo` is a function, it will be called with the following parameters: * * - `{Object.}` - route parameters extracted from the current - * `$location.path()` by applying the current route template. + * `$location.path()` by applying the current route templateUrl. * - `{string}` - current `$location.path()` * - `{Object}` - current `$location.search()` * @@ -15852,8 +16493,8 @@ function $RouteProvider(){ /** * @ngdoc method - * @name angular.module.ng.$routeProvider#otherwise - * @methodOf angular.module.ng.$routeProvider + * @name ng.$routeProvider#otherwise + * @methodOf ng.$routeProvider * * @description * Sets route definition that will be used on route change when no other route definition @@ -15868,115 +16509,145 @@ function $RouteProvider(){ }; - this.$get = ['$rootScope', '$location', '$routeParams', - function( $rootScope, $location, $routeParams) { + this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache', + function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) { /** * @ngdoc object - * @name angular.module.ng.$route + * @name ng.$route * @requires $location * @requires $routeParams * * @property {Object} current Reference to the current route definition. + * The route definition contains: + * + * - `controller`: The controller constructor as define in route definition. + * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for + * controller instantiation. The `locals` contain + * the resolved values of the `resolve` map. Additionally the `locals` also contain: + * + * - `$scope` - The current route scope. + * - `$template` - The current route template HTML. + * * @property {Array.} routes Array of all configured routes. * * @description * Is used for deep-linking URLs to controllers and views (HTML partials). * It watches `$location.url()` and tries to map the path to an existing route definition. * - * You can define routes through {@link angular.module.ng.$routeProvider $routeProvider}'s API. + * You can define routes through {@link ng.$routeProvider $routeProvider}'s API. * - * The `$route` service is typically used in conjunction with {@link angular.module.ng.$compileProvider.directive.ngView ngView} - * directive and the {@link angular.module.ng.$routeParams $routeParams} service. + * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView} + * directive and the {@link ng.$routeParams $routeParams} service. * * @example This example shows how changing the URL hash causes the `$route` to match a route against the URL, and the `ngView` pulls in the partial. - Note that this example is using {@link angular.module.ng.$compileProvider.directive.script inlined templates} + Note that this example is using {@link ng.directive:script inlined templates} to get it working on jsfiddle as well. - - - - - - - - -
- Choose: - Moby | - Moby: Ch1 | - Gatsby | - Gatsby: Ch4 | - Scarlet Letter
- -
-
- -
$location.path() = {{$location.path()}}
-
$route.current.template = {{$route.current.template}}
-
$route.current.params = {{$route.current.params}}
-
$route.current.scope.name = {{$route.current.scope.name}}
-
$routeParams = {{$routeParams}}
-
-
- - it('should load and compile correct template', function() { - element('a:contains("Moby: Ch1")').click(); - var content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: ChapterCntl/); - expect(content).toMatch(/Book Id\: Moby/); - expect(content).toMatch(/Chapter Id\: 1/); - - element('a:contains("Scarlet")').click(); - content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: BookCntl/); - expect(content).toMatch(/Book Id\: Scarlet/); - }); - -
+ function BookCntl($scope, $routeParams) { + $scope.name = "BookCntl"; + $scope.params = $routeParams; + } + + function ChapterCntl($scope, $routeParams) { + $scope.name = "ChapterCntl"; + $scope.params = $routeParams; + } + + + + it('should load and compile correct template', function() { + element('a:contains("Moby: Ch1")').click(); + var content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: ChapterCntl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element('a:contains("Scarlet")').click(); + sleep(2); // promises are not part of scenario waiting + content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: BookCntl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + + */ /** * @ngdoc event - * @name angular.module.ng.$route#$beforeRouteChange - * @eventOf angular.module.ng.$route + * @name ng.$route#$routeChangeStart + * @eventOf ng.$route * @eventType broadcast on root scope * @description - * Broadcasted before a route change. + * Broadcasted before a route change. At this point the route services starts + * resolving all of the dependencies needed for the route change to occurs. + * Typically this involves fetching the view template as well as any dependencies + * defined in `resolve` route property. Once all of the dependencies are resolved + * `$routeChangeSuccess` is fired. * * @param {Route} next Future route information. * @param {Route} current Current route information. @@ -15984,20 +16655,36 @@ function $RouteProvider(){ /** * @ngdoc event - * @name angular.module.ng.$route#$afterRouteChange - * @eventOf angular.module.ng.$route + * @name ng.$route#$routeChangeSuccess + * @eventOf ng.$route * @eventType broadcast on root scope * @description - * Broadcasted after a route change. + * Broadcasted after a route dependencies are resolved. + * {@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|Undefined} previous Previous route information, or undefined if current is first route entered. + */ + + /** + * @ngdoc event + * @name ng.$route#$routeChangeError + * @eventOf ng.$route + * @eventType broadcast on root scope + * @description + * Broadcasted if any of the resolve promises are rejected. * * @param {Route} current Current route information. * @param {Route} previous Previous route information. + * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. */ /** * @ngdoc event - * @name angular.module.ng.$route#$routeUpdate - * @eventOf angular.module.ng.$route + * @name ng.$route#$routeUpdate + * @eventOf ng.$route * @eventType broadcast on root scope * @description * @@ -16005,51 +16692,64 @@ function $RouteProvider(){ * instance of the Controller. */ - var matcher = switchRouteMatcher, - dirty = 0, - forceReload = false, + var forceReload = false, $route = { routes: routes, /** * @ngdoc method - * @name angular.module.ng.$route#reload - * @methodOf angular.module.ng.$route + * @name ng.$route#reload + * @methodOf ng.$route * * @description * Causes `$route` service to reload the current route even if - * {@link angular.module.ng.$location $location} hasn't changed. + * {@link ng.$location $location} hasn't changed. * - * As a result of that, {@link angular.module.ng.$compileProvider.directive.ngView ngView} + * As a result of that, {@link ng.directive:ngView ngView} * creates new scope, reinstantiates the controller. */ reload: function() { - dirty++; forceReload = true; + $rootScope.$evalAsync(updateRoute); } }; - $rootScope.$watch(function() { return dirty + $location.url(); }, updateRoute); + $rootScope.$on('$locationChangeSuccess', updateRoute); return $route; ///////////////////////////////////////////////////// + /** + * @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) { @@ -16063,14 +16763,14 @@ 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); $rootScope.$broadcast('$routeUpdate', last); } else if (next || last) { forceReload = false; - $rootScope.$broadcast('$beforeRouteChange', next, last); + $rootScope.$broadcast('$routeChangeStart', next, last); $route.current = next; if (next) { if (next.redirectTo) { @@ -16081,11 +16781,52 @@ function $RouteProvider(){ $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) .replace(); } - } else { - copy(next.params, $routeParams); } } - $rootScope.$broadcast('$afterRouteChange', next, last); + + $q.when(next). + then(function() { + if (next) { + var keys = [], + values = [], + template; + + forEach(next.resolve || {}, function(value, key) { + keys.push(key); + values.push(isString(value) ? $injector.get(value) : $injector.invoke(value)); + }); + if (isDefined(template = next.template)) { + } else if (isDefined(template = next.templateUrl)) { + template = $http.get(template, {cache: $templateCache}). + then(function(response) { return response.data; }); + } + if (isDefined(template)) { + keys.push('$template'); + values.push(template); + } + return $q.all(values).then(function(values) { + var locals = {}; + forEach(values, function(value, index) { + locals[keys[index]] = value; + }); + return locals; + }); + } + }). + // after route change + then(function(locals) { + if (next == $route.current) { + if (next) { + next.locals = locals; + copy(next.params, $routeParams); + } + $rootScope.$broadcast('$routeChangeSuccess', next, last); + } + }, function(error) { + if (next == $route.current) { + $rootScope.$broadcast('$routeChangeError', next, last, error); + } + }); } } @@ -16097,11 +16838,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 @@ -16131,13 +16872,13 @@ function $RouteProvider(){ /** * @ngdoc object - * @name angular.module.ng.$routeParams + * @name ng.$routeParams * @requires $route * * @description * Current set of route parameters. The route parameters are a combination of the - * {@link angular.module.ng.$location $location} `search()`, and `path()`. The `path` parameters - * are extracted when the {@link angular.module.ng.$route $route} path is matched. + * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters + * are extracted when the {@link ng.$route $route} path is matched. * * In case of parameter name collision, `path` params take precedence over `search` params. * @@ -16186,7 +16927,7 @@ function $RouteParamsProvider() { /** * @ngdoc object - * @name angular.module.ng.$rootScopeProvider + * @name ng.$rootScopeProvider * @description * * Provider for the $rootScope service. @@ -16194,8 +16935,8 @@ function $RouteParamsProvider() { /** * @ngdoc function - * @name angular.module.ng.$rootScopeProvider#digestTtl - * @methodOf angular.module.ng.$rootScopeProvider + * @name ng.$rootScopeProvider#digestTtl + * @methodOf ng.$rootScopeProvider * @description * * Sets the number of digest iteration the scope should attempt to execute before giving up and @@ -16209,12 +16950,12 @@ function $RouteParamsProvider() { /** * @ngdoc object - * @name angular.module.ng.$rootScope + * @name ng.$rootScope * @description * - * Every application has a single root {@link angular.module.ng.$rootScope.Scope scope}. + * Every application has a single root {@link ng.$rootScope.Scope scope}. * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide - * event processing life-cycle. See {@link guide/dev_guide.scopes developer guide on scopes}. + * event processing life-cycle. See {@link guide/scope developer guide on scopes}. */ function $RootScopeProvider(){ var TTL = 10; @@ -16231,12 +16972,12 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope * * @description - * A root scope can be retrieved using the {@link angular.module.ng.$rootScope $rootScope} key from the - * {@link angular.module.AUTO.$injector $injector}. Child scopes are created using the - * {@link angular.module.ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when + * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the + * {@link AUTO.$injector $injector}. Child scopes are created using the + * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when * compiled HTML template is executed.) * * Here is a simple scope snippet to show how you can interact with the scope. @@ -16249,7 +16990,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); @@ -16277,12 +17018,9 @@ function $RootScopeProvider(){ expect(parent.salutation).toEqual('Hello'); * * - * # Dependency Injection - * See {@link guide/dev_guide.di dependency injection}. - * * * @param {Object.=} providers Map of service factory which need to be provided - * for the current scope. Defaults to {@link angular.module.ng}. + * for the current scope. Defaults to {@link ng}. * @param {Object.=} instanceCache Provides pre-instantiated services which should * append/override services provided by `providers`. This is handy when unit-testing and having * the need to override a default service. @@ -16295,14 +17033,16 @@ 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 = {}; } /** * @ngdoc property - * @name angular.module.ng.$rootScope.Scope#$id - * @propertyOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$id + * @propertyOf ng.$rootScope.Scope * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for * debugging. */ @@ -16311,24 +17051,24 @@ function $RootScopeProvider(){ Scope.prototype = { /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$new - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$new + * @methodOf ng.$rootScope.Scope * @function * * @description - * Creates a new child {@link angular.module.ng.$rootScope.Scope scope}. + * Creates a new child {@link ng.$rootScope.Scope scope}. * - * The parent scope will propagate the {@link angular.module.ng.$rootScope.Scope#$digest $digest()} and - * {@link angular.module.ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope - * hierarchy using {@link angular.module.ng.$rootScope.Scope#$destroy $destroy()}. + * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and + * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope + * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. * - * {@link angular.module.ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for + * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for * the scope and its child scopes to be permanently detached from the parent and thus stop * participating in model change detection and listener notification by invoking. * - * @params {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. @@ -16370,35 +17110,35 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$watch - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$watch + * @methodOf ng.$rootScope.Scope * @function * * @description * Registers a `listener` callback to be executed whenever the `watchExpression` changes. * - * - The `watchExpression` is called on every call to {@link angular.module.ng.$rootScope.Scope#$digest $digest()} and - * should return the value which will be watched. (Since {@link angular.module.ng.$rootScope.Scope#$digest $digest()} + * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and + * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()} * reruns when it detects changes the `watchExpression` can execute multiple times per - * {@link angular.module.ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) + * {@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 angular.module.ng.$rootScope.Scope#$digest $digest} is called, - * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`, - * can execute multiple times per {@link angular.module.ng.$rootScope.Scope#$digest $digest} cycle when a change is + * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, + * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` + * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is * detected, be prepared for multiple calls to your listener.) * * After a watcher is registered with the scope, the `listener` fn is called asynchronously - * (via {@link angular.module.ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the + * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the * watcher. In rare cases, this is undesirable because the listener is called when the result * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the @@ -16406,14 +17146,14 @@ function $RootScopeProvider(){ * * * # Example -
+       * 
            // let's assume that scope was dependency injected as the $rootScope
            var scope = $rootScope;
            scope.name = 'misko';
            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();
@@ -16423,23 +17163,23 @@ function $RootScopeProvider(){
            scope.name = 'adam';
            scope.$digest();
            expect(scope.counter).toEqual(1);
-         
+ *
* * * * @param {(function()|string)} watchExpression Expression that is evaluated on each - * {@link angular.module.ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a + * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a * call to the `listener`. * - * - `string`: Evaluated as {@link guide/dev_guide.expressions expression} + * - `string`: Evaluated as {@link guide/expression expression} * - `function(scope)`: called with current `scope` as a parameter. * @param {(function()|string)=} listener Callback called whenever the return value of * the `watchExpression` changes. * - * - `string`: Evaluated as {@link guide/dev_guide.expressions expression} + * - `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) { @@ -16474,39 +17214,39 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$digest - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$digest + * @methodOf ng.$rootScope.Scope * @function * * @description - * Process all of the {@link angular.module.ng.$rootScope.Scope#$watch watchers} of the current scope and its children. - * Because a {@link angular.module.ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the - * `$digest()` keeps calling the {@link angular.module.ng.$rootScope.Scope#$watch watchers} until no more listeners are + * Process all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children. + * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the + * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are * firing. This means that it is possible to get into an infinite loop. This function will throw - * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 100. + * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10. * * Usually you don't call `$digest()` directly in - * {@link angular.module.ng.$compileProvider.directive.ngController controllers} or in - * {@link angular.module.ng.$compileProvider.directive directives}. - * Instead a call to {@link angular.module.ng.$rootScope.Scope#$apply $apply()} (typically from within a - * {@link angular.module.ng.$compileProvider.directive directives}) will force a `$digest()`. + * {@link ng.directive:ngController controllers} or in + * {@link ng.$compileProvider#directive directives}. + * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a + * {@link ng.$compileProvider#directive directives}) will force a `$digest()`. * * If you want to be notified whenever `$digest()` is called, - * you can register a `watchExpression` function with {@link angular.module.ng.$rootScope.Scope#$watch $watch()} + * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()} * with no `listener`. * * You may have a need to call `$digest()` from within unit-tests, to simulate the scope * life-cycle. * * # Example -
+       * 
            var scope = ...;
            scope.name = 'misko';
            scope.counter = 0;
 
            expect(scope.counter).toEqual(0);
-           scope.$watch('name', function(scope, newValue, oldValue) {
-             counter = counter + 1;
+           scope.$watch('name', function(newValue, oldValue) {
+             scope.counter = scope.counter + 1;
            });
            expect(scope.counter).toEqual(0);
 
@@ -16517,7 +17257,7 @@ function $RootScopeProvider(){
            scope.name = 'adam';
            scope.$digest();
            expect(scope.counter).toEqual(1);
-         
+ *
* */ $digest: function() { @@ -16530,7 +17270,7 @@ function $RootScopeProvider(){ watchLog = [], logIdx, logMsg; - flagPhase(target, '$digest'); + beginPhase('$digest'); do { dirty = false; @@ -16587,19 +17327,20 @@ function $RootScopeProvider(){ } while ((current = next)); if(dirty && !(ttl--)) { + clearPhase(); throw Error(TTL + ' $digest() iterations reached. Aborting!\n' + 'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); } } while (dirty || asyncQueue.length); - this.$root.$$phase = null; + clearPhase(); }, /** * @ngdoc event - * @name angular.module.$rootScope.Scope#$destroy - * @eventOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$destroy + * @eventOf ng.$rootScope.Scope * @eventType broadcast on scope being destroyed * * @description @@ -16608,18 +17349,18 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$destroy - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$destroy + * @methodOf ng.$rootScope.Scope * @function * * @description - * Remove the current scope (and all of its children) from the parent scope. Removal implies - * that calls to {@link angular.module.ng.$rootScope.Scope#$digest $digest()} will no longer + * 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. * * The `$destroy()` is usually used by directives such as - * {@link angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} for managing the + * {@link ng.directive:ngRepeat ngRepeat} for managing the * unrolling of the loop. * * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope. @@ -16627,42 +17368,48 @@ function $RootScopeProvider(){ * perform any necessary cleanup. */ $destroy: function() { - if (this.$root == 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; }, /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$eval - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$eval + * @methodOf ng.$rootScope.Scope * @function * * @description * Executes the `expression` on the current scope returning the result. Any exceptions in the - * expression are propagated (uncaught). This is useful when evaluating engular expressions. + * expression are propagated (uncaught). This is useful when evaluating Angular expressions. * * # Example -
-           var scope = angular.module.ng.$rootScope.Scope();
+       * 
+           var scope = ng.$rootScope.Scope();
            scope.a = 1;
            scope.b = 2;
 
            expect(scope.$eval('a+b')).toEqual(3);
            expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
-         
+ *
* * @param {(string|function())=} expression An angular expression to be executed. * - * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. - * - `function(scope, locals)`: execute the function with the current `scope` parameter. - * @param {Object=} locals Hash object of local variables for the expression. + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. * * @returns {*} The result of evaluating the expression. */ @@ -16672,8 +17419,8 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$evalAsync - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$evalAsync + * @methodOf ng.$rootScope.Scope * @function * * @description @@ -16682,15 +17429,15 @@ function $RootScopeProvider(){ * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that: * * - it will execute in the current script execution context (before any DOM rendering). - * - at least one {@link angular.module.ng.$rootScope.Scope#$digest $digest cycle} will be performed after + * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after * `expression` execution. * * Any exceptions from the execution of the expression are forwarded to the - * {@link angular.module.ng.$exceptionHandler $exceptionHandler} service. + * {@link ng.$exceptionHandler $exceptionHandler} service. * * @param {(string|function())=} expression An angular expression to be executed. * - * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. + * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. * */ @@ -16700,83 +17447,91 @@ function $RootScopeProvider(){ /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$apply - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$apply + * @methodOf ng.$rootScope.Scope * @function * * @description * `$apply()` is used to execute an expression in angular from outside of the angular framework. * (For example from browser DOM events, setTimeout, XHR or third party libraries). * Because we are calling into the angular framework we need to perform proper scope life-cycle - * of {@link angular.module.ng.$exceptionHandler exception handling}, - * {@link angular.module.ng.$rootScope.Scope#$digest executing watches}. + * of {@link ng.$exceptionHandler exception handling}, + * {@link ng.$rootScope.Scope#$digest executing watches}. * * ## Life cycle * * # Pseudo-Code of `$apply()` - function $apply(expr) { - try { - return $eval(expr); - } catch (e) { - $exceptionHandler(e); - } finally { - $root.$digest(); - } - } + *
+           function $apply(expr) {
+             try {
+               return $eval(expr);
+             } catch (e) {
+               $exceptionHandler(e);
+             } finally {
+               $root.$digest();
+             }
+           }
+       * 
* * * Scope's `$apply()` method transitions through the following stages: * - * 1. The {@link guide/dev_guide.expressions expression} is executed using the - * {@link angular.module.ng.$rootScope.Scope#$eval $eval()} method. + * 1. The {@link guide/expression expression} is executed using the + * {@link ng.$rootScope.Scope#$eval $eval()} method. * 2. Any exceptions from the execution of the expression are forwarded to the - * {@link angular.module.ng.$exceptionHandler $exceptionHandler} service. - * 3. The {@link angular.module.ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression - * was executed using the {@link angular.module.ng.$rootScope.Scope#$digest $digest()} method. + * {@link ng.$exceptionHandler $exceptionHandler} service. + * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression + * was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. * * * @param {(string|function())=} exp An angular expression to be executed. * - * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. + * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with current `scope` parameter. * * @returns {*} The result of evaluating the expression. */ $apply: function(expr) { try { - flagPhase(this, '$apply'); + beginPhase('$apply'); return this.$eval(expr); } catch (e) { $exceptionHandler(e); } finally { - this.$root.$$phase = null; - this.$root.$digest(); + clearPhase(); + try { + $rootScope.$digest(); + } catch (e) { + $exceptionHandler(e); + throw e; + } } }, /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$on - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$on + * @methodOf ng.$rootScope.Scope * @function * * @description - * Listen on events of a given type. See {@link angular.module.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. - * - `cancel` - {function=}: calling `cancel` function will cancel further event propagation - * (available only for events that were `$emit`-ed). - * - `cancelled` - {boolean}: Whether the event was cancelled. + * @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]; @@ -16786,42 +17541,46 @@ function $RootScopeProvider(){ namedListeners.push(listener); return function() { - arrayRemove(namedListeners, listener); + namedListeners[indexOf(namedListeners, listener)] = null; }; }, /** * @ngdoc function - * @name angular.module.ng.$rootScope.Scope#$emit - * @methodOf angular.module.ng.$rootScope.Scope + * @name ng.$rootScope.Scope#$emit + * @methodOf ng.$rootScope.Scope * @function * * @description * Dispatches an event `name` upwards through the scope hierarchy notifying the - * registered {@link angular.module.ng.$rootScope.Scope#$on} listeners. + * registered {@link ng.$rootScope.Scope#$on} listeners. * * The event life cycle starts at the scope on which `$emit` was called. All - * {@link angular.module.ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified. + * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified. * Afterwards, the event traverses upwards toward the root scope and calls all registered * listeners along the way. The event will stop propagating if one of the listeners cancels it. * - * Any exception emmited from the {@link angular.module.ng.$rootScope.Scope#$on listeners} will be passed - * onto the {@link angular.module.ng.$exceptionHandler $exceptionHandler} service. + * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed + * onto the {@link ng.$exceptionHandler $exceptionHandler} service. * * @param {string} name Event name to emit. * @param {...*} args Optional set of arguments which will be passed onto the event listeners. - * @return {Object} Event object, see {@link angular.module.ng.$rootScope.Scope#$on} + * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} */ $emit: function(name, args) { var empty = [], namedListeners, scope = this, + stopPropagation = false, event = { name: name, targetScope: scope, - cancel: function() {event.cancelled = true;}, - cancelled: false + stopPropagation: function() {stopPropagation = true;}, + preventDefault: function() { + event.defaultPrevented = true; + }, + defaultPrevented: false }, listenerArgs = concat([event], arguments, 1), i, length; @@ -16830,9 +17589,17 @@ function $RootScopeProvider(){ namedListeners = scope.$$listeners[name] || empty; event.currentScope = scope; for (i=0, length=namedListeners.length; i 7), hasEvent: function(event) { + // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have + // it. In particular the event is not fired when backspace or delete key are pressed or + // when cut operation is performed. + if (event == 'input' && msie == 9) return false; + if (isUndefined(eventSupport[event])) { var divElm = $window.document.createElement('div'); eventSupport[event] = 'on' + event in divElm; } return eventSupport[event]; - } + }, + // TODO(i): currently there is no way to feature detect CSP without triggering alerts + csp: false }; }]; } /** * @ngdoc object - * @name angular.module.ng.$window + * @name ng.$window * * @description * A reference to the browser's `window` object. While `window` @@ -17099,8 +17898,8 @@ function $HttpProvider() { 'Accept': 'application/json, text/plain, */*', 'X-Requested-With': 'XMLHttpRequest' }, - post: {'Content-Type': 'application/json'}, - put: {'Content-Type': 'application/json'} + post: {'Content-Type': 'application/json;charset=utf-8'}, + put: {'Content-Type': 'application/json;charset=utf-8'} } }; @@ -17123,8 +17922,8 @@ function $HttpProvider() { /** * @ngdoc function - * @name angular.module.ng.$http - * @requires $httpBacked + * @name ng.$http + * @requires $httpBackend * @requires $browser * @requires $cacheFactory * @requires $rootScope @@ -17137,19 +17936,19 @@ function $HttpProvider() { * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}. * * For unit testing applications that use `$http` service, see - * {@link angular.module.ngMock.$httpBackend $httpBackend mock}. + * {@link ngMock.$httpBackend $httpBackend mock}. * - * For a higher level of abstraction, please check out the {@link angular.module.ngResource.$resource + * For a higher level of abstraction, please check out the {@link ngResource.$resource * $resource} service. * - * The $http API is based on the {@link angular.module.ng.$q deferred/promise APIs} exposed by + * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by * the $q service. While for simple usage patters this doesn't matter much, for advanced usage, * it is important to familiarize yourself with these apis and guarantees they provide. * * * # General usage * The `$http` service is a function which takes a single argument — a configuration object — - * that is used to generate an http request and returns a {@link angular.module.ng.$q promise} + * that is used to generate an http request and returns a {@link ng.$q promise} * with two $http specific methods: `success` and `error`. * *
@@ -17160,8 +17959,7 @@ function $HttpProvider() {
      *     }).
      *     error(function(data, status, headers, config) {
      *       // called asynchronously if an error occurs
-     *       // or server returns response with status
-     *       // code outside of the <200, 400) range
+     *       // or server returns response with an error status.
      *     });
      * 
* @@ -17170,6 +17968,10 @@ function $HttpProvider() { * 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 * @@ -17184,12 +17986,12 @@ function $HttpProvider() { * * Complete list of shortcut methods: * - * - {@link angular.module.ng.$http#get $http.get} - * - {@link angular.module.ng.$http#head $http.head} - * - {@link angular.module.ng.$http#post $http.post} - * - {@link angular.module.ng.$http#put $http.put} - * - {@link angular.module.ng.$http#delete $http.delete} - * - {@link angular.module.ng.$http#jsonp $http.jsonp} + * - {@link ng.$http#get $http.get} + * - {@link ng.$http#head $http.head} + * - {@link ng.$http#post $http.post} + * - {@link ng.$http#put $http.put} + * - {@link ng.$http#delete $http.delete} + * - {@link ng.$http#jsonp $http.jsonp} * * * # Setting HTTP Headers @@ -17230,10 +18032,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 @@ -17253,18 +18059,18 @@ function $HttpProvider() { * # Response interceptors * * Before you start creating interceptors, be sure to understand the - * {@link angular.module.ng.$q $q and deferred/promise APIs}. + * {@link ng.$q $q and deferred/promise APIs}. * * For purposes of global error handling, authentication or any kind of synchronous or * asynchronous preprocessing of received responses, it is desirable to be able to intercept * responses for http requests before they are handed over to the application code that - * initiated these requests. The response interceptors leverage the {@link angular.module.ng.$q + * initiated these requests. The response interceptors leverage the {@link ng.$q * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. * * The interceptors are service factories that are registered with the $httpProvider by * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and * injected with dependencies (if specified) and returns the interceptor — a function that - * takes a {@link angular.module.ng.$q promise} and returns the original or a new promise. + * takes a {@link ng.$q promise} and returns the original or a new promise. * *
      *   // register the interceptor as a service
@@ -17363,14 +18169,14 @@ function $HttpProvider() {
      *      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
      *      GET request, otherwise if a cache instance built with
-     *      {@link angular.module.ng.$cacheFactory $cacheFactory}, this cache will be used for
+     *      {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
      *      caching.
      *    - **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.
      *
-     * @returns {HttpPromise} Returns a {@link angular.module.ng.$q promise} object with the
+     * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
      *   standard `then` method and two http specific methods: `success` and `error`. The `then`
      *   method takes two arguments a success and an error callback which will be called with a
      *   response object. The `success` and `error` methods take a single argument - a function that
@@ -17388,72 +18194,75 @@ function $HttpProvider() {
      *
      *
      * @example
-        
-          
-            
-            
- - -
- - - -
http status code: {{status}}
-
http response data: {{data}}
-
-
- - it('should make an xhr GET request', function() { - element(':button:contains("Sample GET")').click(); - element(':button:contains("fetch")').click(); - expect(binding('status')).toBe('200'); - expect(binding('data')).toBe('Hello, $http!\n'); - }); + $scope.updateModel = function(method, url) { + $scope.method = method; + $scope.url = url; + }; + } + + + Hello, $http! + + + it('should make an xhr GET request', function() { + element(':button:contains("Sample GET")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('200'); + expect(binding('data')).toMatch(/Hello, \$http!/); + }); - it('should make a JSONP request to angularjs.org', function() { - element(':button:contains("Sample JSONP")').click(); - element(':button:contains("fetch")').click(); - expect(binding('status')).toBe('200'); - expect(binding('data')).toMatch(/Super Hero!/); - }); + it('should make a JSONP request to angularjs.org', function() { + element(':button:contains("Sample JSONP")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('200'); + expect(binding('data')).toMatch(/Super Hero!/); + }); - it('should make JSONP request to invalid URL and invoke the error handler', - function() { - element(':button:contains("Invalid JSONP")').click(); - element(':button:contains("fetch")').click(); - expect(binding('status')).toBe('0'); - expect(binding('data')).toBe('Request failed'); - }); - -
+ it('should make JSONP request to invalid URL and invoke the error handler', + function() { + element(':button:contains("Invalid JSONP")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('0'); + expect(binding('data')).toBe('Request failed'); + }); + + */ function $http(config) { config.method = uppercase(config.method); @@ -17514,8 +18323,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#get - * @methodOf angular.module.ng.$http + * @name ng.$http#get + * @methodOf ng.$http * * @description * Shortcut method to perform `GET` request @@ -17527,8 +18336,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#delete - * @methodOf angular.module.ng.$http + * @name ng.$http#delete + * @methodOf ng.$http * * @description * Shortcut method to perform `DELETE` request @@ -17540,8 +18349,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#head - * @methodOf angular.module.ng.$http + * @name ng.$http#head + * @methodOf ng.$http * * @description * Shortcut method to perform `HEAD` request @@ -17553,8 +18362,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#jsonp - * @methodOf angular.module.ng.$http + * @name ng.$http#jsonp + * @methodOf ng.$http * * @description * Shortcut method to perform `JSONP` request @@ -17568,8 +18377,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#post - * @methodOf angular.module.ng.$http + * @name ng.$http#post + * @methodOf ng.$http * * @description * Shortcut method to perform `POST` request @@ -17582,8 +18391,8 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#put - * @methodOf angular.module.ng.$http + * @name ng.$http#put + * @methodOf ng.$http * * @description * Shortcut method to perform `PUT` request @@ -17597,8 +18406,8 @@ function $HttpProvider() { /** * @ngdoc property - * @name angular.module.ng.$http#defaults - * @propertyOf angular.module.ng.$http + * @name ng.$http#defaults + * @propertyOf ng.$http * * @description * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of @@ -17748,6 +18557,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) {} @@ -17758,19 +18568,19 @@ var XHR = window.XMLHttpRequest || function() { /** * @ngdoc object - * @name angular.module.ng.$httpBackend + * @name ng.$httpBackend * @requires $browser * @requires $window * @requires $document * * @description - * HTTP backend used by the {@link angular.module.ng.$http service} that delegates to + * HTTP backend used by the {@link ng.$http service} that delegates to * XMLHttpRequest object or JSONP and deals with browser incompatibilities. * * You should never need to use this service directly, instead use the higher-level abstractions: - * {@link angular.module.ng.$http $http} or {@link angular.module.ngResource.$resource $resource}. + * {@link ng.$http $http} or {@link ngResource.$resource $resource}. * - * During testing this implementation is swapped with {@link angular.module.ngMock.$httpBackend mock + * During testing this implementation is swapped with {@link ngMock.$httpBackend mock * $httpBackend} which can be trained with responses. */ function $HttpBackendProvider() { @@ -17815,8 +18625,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); } }; @@ -17877,7 +18709,7 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, /** * @ngdoc object - * @name angular.module.ng.$locale + * @name ng.$locale * * @description * $locale service provides localization rules for various Angular components. As of right now the @@ -17946,21 +18778,106 @@ function $LocaleProvider(){ }; } +function $TimeoutProvider() { + this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', + function($rootScope, $browser, $q, $exceptionHandler) { + var deferreds = {}; + + + /** + * @ngdoc function + * @name ng.$timeout + * @requires $browser + * + * @description + * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch + * block and delegates any exceptions to + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * The return value of registering a timeout function is a promise which will be resolved when + * the timeout is reached and the timeout function is executed. + * + * To cancel a the timeout request, call `$timeout.cancel(promise)`. + * + * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to + * synchronously flush the queue of deferred functions. + * + * @param {function()} fn A function, who's execution should be delayed. + * @param {number=} [delay=0] Delay in milliseconds. + * @param {boolean=} [invokeApply=true] If set to false skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @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) { + var deferred = $q.defer(), + promise = deferred.promise, + skipApply = (isDefined(invokeApply) && !invokeApply), + timeoutId, cleanup; + + timeoutId = $browser.defer(function() { + try { + deferred.resolve(fn()); + } catch(e) { + deferred.reject(e); + $exceptionHandler(e); + } + + if (!skipApply) $rootScope.$apply(); + }, delay); + + cleanup = function() { + delete deferreds[promise.$$timeoutId]; + }; + + promise.$$timeoutId = timeoutId; + deferreds[timeoutId] = deferred; + promise.then(cleanup, cleanup); + + return promise; + } + + + /** + * @ngdoc function + * @name ng.$timeout#cancel + * @methodOf ng.$timeout + * + * @description + * Cancels a task associated with the `promise`. As a result of this the promise will be + * resolved with a rejection. + * + * @param {Promise=} promise Promise returned by the `$timeout` function. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. + */ + timeout.cancel = function(promise) { + if (promise && promise.$$timeoutId in deferreds) { + deferreds[promise.$$timeoutId].reject('canceled'); + return $browser.defer.cancel(promise.$$timeoutId); + } + return false; + }; + + return timeout; + }]; +} + /** * @ngdoc object - * @name angular.module.ng.$filterProvider + * @name ng.$filterProvider * @description * * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To * achieve this a filter definition consists of a factory function which is annotated with dependencies and is - * responsible for creating a the filter function. + * responsible for creating a filter function. * *
  *   // Filter registration
  *   function MyModule($provide, $filterProvider) {
  *     // create a service to demonstrate injection (not always needed)
  *     $provide.value('greet', function(name){
- *       return 'Hello ' + name + '!':
+ *       return 'Hello ' + name + '!';
  *     });
  *
  *     // register a filter factory which uses the
@@ -17972,7 +18889,7 @@ function $LocaleProvider(){
  *         // filters need to be forgiving so check input validity
  *         return text && greet(text) || text;
  *       };
- *     };
+ *     });
  *   }
  * 
* @@ -17996,8 +18913,8 @@ function $LocaleProvider(){ */ /** * @ngdoc method - * @name angular.module.ng.$filterProvider#register - * @methodOf angular.module.ng.$filterProvider + * @name ng.$filterProvider#register + * @methodOf ng.$filterProvider * @description * Register filter factory function. * @@ -18008,7 +18925,7 @@ function $LocaleProvider(){ /** * @ngdoc function - * @name angular.module.ng.$filter + * @name ng.$filter * @function * @description * Filters are used for formatting data displayed to the user. @@ -18050,14 +18967,14 @@ function $FilterProvider($provide) { /** * @ngdoc filter - * @name angular.module.ng.$filter.filter + * @name ng.filter:filter * @function * * @description * Selects a subset of items from `array` and returns it as a new array. * * Note: This function is used to augment the `Array` type in Angular expressions. See - * {@link angular.module.ng.$filter} for more information about Angular arrays. + * {@link ng.$filter} for more information about Angular arrays. * * @param {Array} array The source array. * @param {string|Object|function()} expression The predicate to be used for selecting items from @@ -18091,22 +19008,22 @@ function $FilterProvider($provide) { Search: - + - +
NamePhone
NamePhone
{{friend.name}} {{friend.phone}}

Any:
Name only
Phone only
- + - +
NamePhone
NamePhone
{{friend.name}} {{friend.phone}}
@@ -18130,7 +19047,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++) { @@ -18213,7 +19130,7 @@ function filterFilter() { /** * @ngdoc filter - * @name angular.module.ng.$filter.currency + * @name ng.filter:currency * @function * * @description @@ -18264,7 +19181,7 @@ function currencyFilter($locale) { /** * @ngdoc filter - * @name angular.module.ng.$filter.number + * @name ng.filter:number * @function * * @description @@ -18328,9 +19245,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 @@ -18370,7 +19296,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); @@ -18413,8 +19339,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) { @@ -18450,7 +19381,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+ /** * @ngdoc filter - * @name angular.module.ng.$filter.date + * @name ng.filter:date * @function * * @description @@ -18478,10 +19409,10 @@ 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/dev_guide.i18n localizable formats}: + * {@link guide/i18n localizable formats}: * * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale * (e.g. Sep 3, 2010 12:05:08 pm) @@ -18500,7 +19431,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. @@ -18520,7 +19452,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)/); }); @@ -18531,7 +19463,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)) { @@ -18597,7 +19529,7 @@ function dateFilter($locale) { /** * @ngdoc filter - * @name angular.module.ng.$filter.json + * @name ng.filter:json * @function * * @description @@ -18632,7 +19564,7 @@ function jsonFilter() { /** * @ngdoc filter - * @name angular.module.ng.$filter.lowercase + * @name ng.filter:lowercase * @function * @description * Converts string to lowercase. @@ -18643,7 +19575,7 @@ var lowercaseFilter = valueFn(lowercase); /** * @ngdoc filter - * @name angular.module.ng.$filter.uppercase + * @name ng.filter:uppercase * @function * @description * Converts string to uppercase. @@ -18653,7 +19585,7 @@ var uppercaseFilter = valueFn(uppercase); /** * @ngdoc function - * @name angular.module.ng.$filter.limitTo + * @name ng.filter:limitTo * @function * * @description @@ -18662,7 +19594,7 @@ var uppercaseFilter = valueFn(uppercase); * value and sign (positive or negative) of `limit`. * * Note: This function is used to augment the `Array` type in Angular expressions. See - * {@link angular.module.ng.$filter} for more information about Angular arrays. + * {@link ng.$filter} for more information about Angular arrays. * * @param {Array} array Source array to be limited. * @param {string|Number} limit The length of the returned array. If the `limit` number is @@ -18739,14 +19671,14 @@ function limitToFilter(){ /** * @ngdoc function - * @name angular.module.ng.$filter.orderBy + * @name ng.filter:orderBy * @function * * @description * Orders a specified `array` by the `expression` predicate. * * Note: this function is used to augment the `Array` type in Angular expressions. See - * {@link angular.module.ng.$filter} for more informaton about Angular arrays. + * {@link ng.$filter} for more informaton about Angular arrays. * * @param {Array} array The array to sort. * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be @@ -18789,12 +19721,12 @@ function limitToFilter(){ (^) Phone Number Age - + {{friend.name}} {{friend.phone}} {{friend.age}} - + @@ -18826,7 +19758,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){ @@ -18883,21 +19815,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) { @@ -18913,7 +19860,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngHref + * @name ng.directive:ngHref * @restrict A * * @description @@ -18977,7 +19924,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() { @@ -18993,7 +19940,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngSrc + * @name ng.directive:ngSrc * @restrict A * * @description @@ -19018,7 +19965,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngDisabled + * @name ng.directive:ngDisabled * @restrict A * * @description @@ -19057,7 +20004,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngChecked + * @name ng.directive:ngChecked * @restrict A * * @description @@ -19087,7 +20034,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngMultiple + * @name ng.directive:ngMultiple * @restrict A * * @description @@ -19123,7 +20070,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngReadonly + * @name ng.directive:ngReadonly * @restrict A * * @description @@ -19153,7 +20100,7 @@ var htmlAnchorDirective = valueFn({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngSelected + * @name ng.directive:ngSelected * @restrict A * * @description @@ -19195,8 +20142,7 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) { priority: 100, compile: function() { return function(scope, element, attr) { - attr.$$observers[attrName] = []; - scope.$watch(attr[normalized], function(value) { + scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { attr.$set(attrName, !!value); }); }; @@ -19212,21 +20158,19 @@ forEach(['src', 'href'], function(attrName) { ngAttributeAliasDirectives[normalized] = function() { return { priority: 99, // it needs to run after the attributes are interpolated - compile: function() { - return function(scope, element, attr) { - var value = attr[normalized]; - if (value == undefined) { - // undefined value means that the directive is being interpolated - // so just register observer - attr.$$observers[attrName] = []; - attr.$observe(normalized, function(value) { - attr.$set(attrName, value); - }); - } else { - // value present means that no interpolation, so copy to native attribute. - attr.$set(attrName, value); - } - }; + 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. + // we use attr[attrName] value since $set can sanitize the url. + if (msie) element.prop(attrName, attr[attrName]); + }); } }; }; @@ -19241,24 +20185,24 @@ var nullFormCtrl = { /** * @ngdoc object - * @name angular.module.ng.$compileProvider.directive.form.FormController + * @name ng.directive:form.FormController * * @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 * `FormController` keeps track of all its controls and nested forms as well as state of them, * such as being valid/invalid or dirty/pristine. * - * Each {@link angular.module.ng.$compileProvider.directive.form form} directive creates an instance + * Each {@link ng.directive:form form} directive creates an instance * of `FormController`. * */ @@ -19348,6 +20292,7 @@ function FormController(element, attrs) { element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); form.$dirty = true; form.$pristine = false; + parentForm.$setDirty(); }; } @@ -19355,36 +20300,36 @@ function FormController(element, attrs) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngForm + * @name ng.directive:ngForm * @restrict EAC * * @description - * Nestable alias of {@link angular.module.ng.$compileProvider.directive.form `form`} directive. HTML + * Nestable alias of {@link ng.directive:form `form`} directive. HTML * 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. * */ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.form + * @name ng.directive:form * @restrict E * * @description * Directive that instantiates - * {@link angular.module.ng.$compileProvider.directive.form.FormController FormController}. + * {@link ng.directive:form.FormController FormController}. * * If `name` attribute is specified, the form controller is published onto the current scope under * this name. * - * # Alias: {@link angular.module.ng.$compileProvider.directive.ngForm `ngForm`} + * # Alias: {@link ng.directive:ngForm `ngForm`} * * In angular forms can be nested. This means that the outer form is valid when all of the child * forms are valid as well. However browsers do not allow nesting of `
` elements, for this - * reason angular provides {@link angular.module.ng.$compileProvider.directive.ngForm `ngForm`} alias + * reason angular provides {@link ng.directive:ngForm `ngForm`} alias * which behaves identical to `` but allows form nesting. * * @@ -19408,8 +20353,8 @@ function FormController(element, attrs) { * You can use one of the following two ways to specify what javascript method should be called when * a form is submitted: * - * - {@link angular.module.ng.$compileProvider.directive.ngSubmit ngSubmit} directive on the form element - * - {@link angular.module.ng.$compileProvider.directive.ngClick ngClick} directive on the first + * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element + * - {@link ng.directive:ngClick ngClick} directive on the first * button or input field of type submit (input[type=submit]) * * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This @@ -19436,12 +20381,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}}
@@ -19458,41 +20403,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}$/; @@ -19502,14 +20471,17 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.text + * @name ng.directive:input.text * * @description * Standard HTML text input with angular data binding. * * @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 @@ -19568,7 +20540,7 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.number + * @name ng.directive:input.number * * @description * Text input with number validation and transformation. Sets the `number` validation @@ -19579,6 +20551,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 @@ -19636,7 +20611,7 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.url + * @name ng.directive:input.url * * @description * Text input with URL validation. Sets the `url` validation error key if the content is not a @@ -19645,6 +20620,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 @@ -19701,7 +20679,7 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.email + * @name ng.directive:input.email * * @description * Text input with email validation. Sets the `email` validation error key if not a valid email @@ -19710,6 +20688,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 @@ -19764,7 +20745,7 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.radio + * @name ng.directive:input.radio * * @description * HTML radio button. @@ -19805,7 +20786,7 @@ var inputType = { /** * @ngdoc inputType - * @name angular.module.ng.$compileProvider.directive.input.checkbox + * @name ng.directive:input.checkbox * * @description * HTML checkbox. @@ -19873,7 +20854,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { } }; - // if the browser does support "input" event, we are fine + // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the + // input event on backspace, delete or cut if ($sniffer.hasEvent('input')) { element.bind('input', listener); } else { @@ -20120,16 +21102,20 @@ function checkboxInputType(scope, element, attr, ctrl) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.textarea + * @name ng.directive:textarea + * @restrict E * * @description * HTML textarea element control with angular data-binding. The data-binding and validation * properties of this element are exactly the same as those of the - * {@link angular.module.ng.$compileProvider.directive.input input element}. + * {@link ng.directive:input input element}. * * @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 @@ -20144,7 +21130,7 @@ function checkboxInputType(scope, element, attr, ctrl) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.input + * @name ng.directive:input * @restrict E * * @description @@ -20154,6 +21140,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 @@ -20256,7 +21243,7 @@ var VALID_CLASS = 'ng-valid', /** * @ngdoc object - * @name angular.module.ng.$compileProvider.directive.ngModel.NgModelController + * @name ng.directive:ngModel.NgModelController * * @property {string} $viewValue Actual string value in the view. * @property {*} $modelValue The value in the model, that the control is bound to. @@ -20275,9 +21262,82 @@ var VALID_CLASS = 'ng-valid', * * @description * + * `NgModelController` provides API for the `ng-model` directive. The controller contains + * services for data-binding, validation, CSS update, value formatting and parsing. It + * specifically does not contain any logic which deals with DOM rendering or listening to + * DOM events. The `NgModelController` is meant to be extended by other directives where, the + * directive provides DOM manipulation and the `NgModelController` provides the data-binding. + * + * This example shows how to use `NgModelController` with a custom control to achieve + * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) + * collaborate together to achieve the desired result. + * + * + + [contenteditable] { + border: 1px solid black; + background-color: white; + min-height: 20px; + } + + .ng-invalid { + border: 1px solid red; + } + + + + angular.module('customControl', []). + directive('contenteditable', function() { + return { + restrict: 'A', // only activate on element attribute + require: '?ngModel', // get a hold of NgModelController + link: function(scope, element, attrs, ngModel) { + if(!ngModel) return; // do nothing if no ng-model + + // Specify how UI should be updated + ngModel.$render = function() { + element.html(ngModel.$viewValue || ''); + }; + + // Listen for change events to enable binding + element.bind('blur keyup change', function() { + scope.$apply(read); + }); + read(); // initialize + + // Write data to the model + function read() { + ngModel.$setViewValue(element.html()); + } + } + }; + }); + + +
+
Change me!
+ Required! +
+ +
+
+ + it('should data-bind and become invalid', function() { + var contentEditable = element('[contenteditable]'); + + expect(contentEditable.text()).toEqual('Change me!'); + input('userContent').enter(''); + expect(contentEditable.text()).toEqual(''); + expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/); + }); + + *
+ * */ -var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$element', - function($scope, $exceptionHandler, $attr, ngModel, $element) { +var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', + function($scope, $exceptionHandler, $attr, $element, $parse) { this.$viewValue = Number.NaN; this.$modelValue = Number.NaN; this.$parsers = []; @@ -20287,9 +21347,27 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e this.$dirty = false; this.$valid = true; this.$invalid = false; - this.$render = noop; this.$name = $attr.name; + var ngModelGet = $parse($attr.ngModel), + ngModelSet = ngModelGet.assign; + + if (!ngModelSet) { + throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + $attr.ngModel + + ' (' + startingTag($element) + ')'); + } + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$render + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Called when the view needs to be updated. It is expected that the user of the ng-model + * directive will implement this method. + */ + this.$render = noop; + var parentForm = $element.inheritedData('$formController') || nullFormCtrl, invalidCount = 0, // used to easily determine if we are valid $error = this.$error = {}; // keep invalid keys here @@ -20309,8 +21387,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e /** * @ngdoc function - * @name angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setValidity - * @methodOf angular.module.ng.$compileProvider.directive.ngModel.NgModelController + * @name ng.directive:ngModel.NgModelController#$setValidity + * @methodOf ng.directive:ngModel.NgModelController * * @description * Change the validity state, and notifies the form when the control changes validity. (i.e. it @@ -20351,15 +21429,15 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e /** * @ngdoc function - * @name angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setViewValue - * @methodOf angular.module.ng.$compileProvider.directive.ngModel.NgModelController + * @name ng.directive:ngModel.NgModelController#$setViewValue + * @methodOf ng.directive:ngModel.NgModelController * * @description * Read a value from view. * * This method should be called from within a DOM event handler. - * For example {@link angular.module.ng.$compileProvider.directive.input input} or - * {@link angular.module.ng.$compileProvider.directive.select select} directives call it. + * For example {@link ng.directive:input input} or + * {@link ng.directive:select select} directives call it. * * It internally calls all `formatters` and if resulted value is valid, updates the model and * calls all registered change listeners. @@ -20383,7 +21461,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e if (this.$modelValue !== value) { this.$modelValue = value; - ngModel(value); + ngModelSet($scope, value); forEach(this.$viewChangeListeners, function(listener) { try { listener(); @@ -20396,24 +21474,25 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e // model -> value var ctrl = this; - $scope.$watch(function() { - return ngModel(); - }, 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(); + } } }); }]; @@ -20421,7 +21500,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngModel + * @name ng.directive:ngModel * * @element input * @@ -20436,26 +21515,23 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e * - providing validation behavior (i.e. required, number, email, url), * - keeping state of the control (valid/invalid, dirty/pristine, validation errors), * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`), - * - register the control with parent {@link angular.module.ng.$compileProvider.directive.form form}. + * - register the control with parent {@link ng.directive:form form}. * * For basic examples, how to use `ngModel`, see: * - * - {@link angular.module.ng.$compileProvider.directive.input input} - * - {@link angular.module.ng.$compileProvider.directive.input.text text} - * - {@link angular.module.ng.$compileProvider.directive.input.checkbox checkbox} - * - {@link angular.module.ng.$compileProvider.directive.input.radio radio} - * - {@link angular.module.ng.$compileProvider.directive.input.number number} - * - {@link angular.module.ng.$compileProvider.directive.input.email email} - * - {@link angular.module.ng.$compileProvider.directive.input.url url} - * - {@link angular.module.ng.$compileProvider.directive.select select} - * - {@link angular.module.ng.$compileProvider.directive.textarea textarea} + * - {@link ng.directive:input input} + * - {@link ng.directive:input.text text} + * - {@link ng.directive:input.checkbox checkbox} + * - {@link ng.directive:input.radio radio} + * - {@link ng.directive:input.number number} + * - {@link ng.directive:input.email email} + * - {@link ng.directive:input.url url} + * - {@link ng.directive:select select} + * - {@link ng.directive:textarea textarea} * */ -var ngModelDirective = [function() { +var ngModelDirective = function() { return { - inject: { - ngModel: 'accessor' - }, require: ['ngModel', '^?form'], controller: NgModelController, link: function(scope, element, attr, ctrls) { @@ -20471,12 +21547,12 @@ var ngModelDirective = [function() { }); } }; -}]; +}; /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngChange + * @name ng.directive:ngChange * @restrict E * * @description @@ -20532,11 +21608,12 @@ var ngChangeDirective = valueFn({ }); -var requiredDirective = [function() { +var requiredDirective = function() { return { require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; + attr.required = true; // force truthy in case we are on non input element var validator = function(value) { if (attr.required && (isEmpty(value) || value === false)) { @@ -20556,15 +21633,15 @@ var requiredDirective = [function() { }); } }; -}]; +}; /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngList + * @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 @@ -20624,7 +21701,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(', '); } @@ -20637,7 +21714,7 @@ var ngListDirective = function() { var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; -var ngValueDirective = [function() { +var ngValueDirective = function() { return { priority: 100, compile: function(tpl, tplAttr) { @@ -20647,19 +21724,18 @@ var ngValueDirective = [function() { }; } else { return function(scope, elm, attr) { - attr.$$observers.value = []; - scope.$watch(attr.ngValue, function(value) { + scope.$watch(attr.ngValue, function valueWatchAction(value) { attr.$set('value', value, false); }); }; } } }; -}]; +}; /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngBind + * @name ng.directive:ngBind * * @description * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element @@ -20675,11 +21751,11 @@ var ngValueDirective = [function() { * bindings invisible to the user while the page is loading. * * An alternative solution to this problem would be using the - * {@link angular.module.ng.$compileProvider.directive.ngCloak ngCloak} directive. + * {@link ng.directive:ngCloak ngCloak} directive. * * * @element ANY - * @param {expression} ngBind {@link guide/dev_guide.expressions Expression} to evaluate. + * @param {expression} ngBind {@link guide/expression Expression} to evaluate. * * @example * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. @@ -20706,7 +21782,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); }); }); @@ -20714,7 +21790,7 @@ var ngBindDirective = ngDirective(function(scope, element, attr) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngBindTemplate + * @name ng.directive:ngBindTemplate * * @description * The `ngBindTemplate` directive specifies that the element @@ -20773,23 +21849,23 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngBindHtmlUnsafe + * @name ng.directive:ngBindHtmlUnsafe * * @description * Creates a binding that will innerHTML the result of evaluating the `expression` into the current * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if - * {@link angular.module.ng.$compileProvider.directive.ngBindHtml ngBindHtml} directive is too + * {@link ngSanitize.directive:ngBindHtml ngBindHtml} directive is too * restrictive and when you absolutely trust the source of the content you are binding to. * - * See {@link angular.module.ngSanitize.$sanitize $sanitize} docs for examples. + * See {@link ngSanitize.$sanitize $sanitize} docs for examples. * * @element ANY - * @param {expression} ngBindHtmlUnsafe {@link guide/dev_guide.expressions Expression} to evaluate. + * @param {expression} ngBindHtmlUnsafe {@link guide/expression Expression} to evaluate. */ 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 || ''); }); }; @@ -20798,23 +21874,63 @@ 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); + } + } }); } /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngClass + * @name ng.directive:ngClass * * @description * The `ngClass` allows you to set CSS class on HTML element dynamically by databinding an @@ -20822,101 +21938,113 @@ 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 - * @param {expression} ngClass {@link guide/dev_guide.expressions Expression} to eval. The result + * @param {expression} ngClass {@link guide/expression Expression} to eval. The result * of the evaluation can be a string representing space delimited class * names, an array, or a map of class names to boolean values. * * @example - - - + + +
- Sample Text      -
- + Sample Text + + + .my-class { + color: red; + } + + it('should check ng-class', function() { expect(element('.doc-example-live span').prop('className')).not(). - toMatch(/ng-invalid/); + toMatch(/my-class/); using('.doc-example-live').element(':button:first').click(); expect(element('.doc-example-live span').prop('className')). - toMatch(/ng-invalid/); + toMatch(/my-class/); using('.doc-example-live').element(':button:last').click(); expect(element('.doc-example-live span').prop('className')).not(). - toMatch(/ng-invalid/); + toMatch(/my-class/); }); - -
+ + */ var ngClassDirective = classDirective('', true); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngClassOdd + * @name ng.directive:ngClassOdd * * @description * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link angular.module.ng.$compileProvider.directive.ngClass ngClass}, except it works in + * {@link ng.directive:ngClass ngClass}, except it works in * conjunction with `ngRepeat` and takes affect only on odd (even) rows. * * This directive can be applied only within a scope of an - * {@link angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat}. + * {@link ng.directive:ngRepeat ngRepeat}. * * @element ANY - * @param {expression} ngClassOdd {@link guide/dev_guide.expressions Expression} to eval. The result + * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result * of the evaluation can be a string representing space delimited class names or an array. * * @example - - + +
  1. - - {{name}}       + + {{name}}
-
- + + + .odd { + color: red; + } + .even { + color: blue; + } + + it('should check ng-class-odd and ng-class-even', function() { expect(element('.doc-example-live li:first span').prop('className')). - toMatch(/ng-format-negative/); + toMatch(/odd/); expect(element('.doc-example-live li:last span').prop('className')). - toMatch(/ng-invalid/); + toMatch(/even/); }); - -
+ + */ var ngClassOddDirective = classDirective('Odd', 0); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngClassEven + * @name ng.directive:ngClassEven * * @description * The `ngClassOdd` and `ngClassEven` works exactly as - * {@link angular.module.ng.$compileProvider.directive.ngClass ngClass}, except it works in + * {@link ng.directive:ngClass ngClass}, except it works in * conjunction with `ngRepeat` and takes affect only on odd (even) rows. * * This directive can be applied only within a scope of an - * {@link angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat}. + * {@link ng.directive:ngRepeat ngRepeat}. * * @element ANY - * @param {expression} ngClassEven {@link guide/dev_guide.expressions Expression} to eval. The + * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The * result of the evaluation can be a string representing space delimited class names or an array. * * @example - - + +
  1. @@ -20924,22 +22052,30 @@ var ngClassOddDirective = classDirective('Odd', 0);
-
- + + + .odd { + color: red; + } + .even { + color: blue; + } + + it('should check ng-class-odd and ng-class-even', function() { expect(element('.doc-example-live li:first span').prop('className')). toMatch(/odd/); expect(element('.doc-example-live li:last span').prop('className')). toMatch(/even/); }); - -
+ + */ var ngClassEvenDirective = classDirective('Even', 1); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngCloak + * @name ng.directive:ngCloak * * @description * The `ngCloak` directive is used to prevent the Angular html template from being briefly @@ -20953,7 +22089,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;
  * }
  * 
@@ -20999,7 +22135,7 @@ var ngCloakDirective = ngDirective({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngController + * @name ng.directive:ngController * * @description * The `ngController` directive assigns behavior to a scope. This is a key aspect of how angular @@ -21012,13 +22148,13 @@ var ngCloakDirective = ngDirective({ * * 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 angular.module.ng.$route}` + * Note that an alternative way to define controllers is via the `{@link ng.$route}` * service. * * @element ANY * @scope * @param {expression} ngController Name of a globally accessible constructor function or an - * {@link guide/dev_guide.expressions expression} that on the current scope evaluates to a + * {@link guide/expression expression} that on the current scope evaluates to a * constructor function. * * @example @@ -21030,7 +22166,7 @@ var ngCloakDirective = ngDirective({ * for a manual update. - -
- - url of the template: {{template.url}} -
-
-
-
- - it('should load template1.html', function() { - expect(element('.doc-example-live [ng-include]').text()). - toBe('Content of template1.html\n'); - }); - it('should load template2.html', function() { - select('template').option('1'); - expect(element('.doc-example-live [ng-include]').text()). - toBe('Content of template2.html\n'); - }); - it('should change to blank', function() { - select('template').option(''); - expect(element('.doc-example-live [ng-include]').text()).toEqual(''); - }); - -
+ * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`. + * @param {string=} onload Expression to evaluate when a new partial is loaded. + * + * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll + * $anchorScroll} to scroll the viewport after the content is loaded. + * + * - If the attribute is not set, disable scrolling. + * - If the attribute is set without value, enable scrolling. + * - Otherwise enable scrolling only if the expression evaluates to truthy value. + * + * @example + + +
+ + url of the template: {{template.url}} +
+
+
+
+ + function Ctrl($scope) { + $scope.templates = + [ { name: 'template1.html', url: 'template1.html'} + , { name: 'template2.html', url: 'template2.html'} ]; + $scope.template = $scope.templates[0]; + } + + + Content of template1.html + + + Content of template2.html + + + it('should load template1.html', function() { + expect(element('.doc-example-live [ng-include]').text()). + toMatch(/Content of template1.html/); + }); + it('should load template2.html', function() { + select('template').option('1'); + expect(element('.doc-example-live [ng-include]').text()). + toMatch(/Content of template2.html/); + }); + it('should change to blank', function() { + select('template').option(''); + expect(element('.doc-example-live [ng-include]').text()).toEqual(''); + }); + +
*/ /** * @ngdoc event - * @name angular.module.ng.$compileProvider.directive.ngInclude#$includeContentLoaded - * @eventOf angular.module.ng.$compileProvider.directive.ngInclude + * @name ng.directive:ngInclude#$includeContentLoaded + * @eventOf ng.directive:ngInclude * @eventType emit on the current ngInclude scope * @description * Emitted every time the ngInclude content is reloaded. @@ -21415,7 +22583,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) { @@ -21446,14 +22614,14 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile' /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngInit + * @name ng.directive:ngInit * * @description * The `ngInit` directive specifies initialization tasks to be executed * before the template enters execution mode during bootstrap. * * @element ANY - * @param {expression} ngInit {@link guide/dev_guide.expressions Expression} to eval. + * @param {expression} ngInit {@link guide/expression Expression} to eval. * * @example @@ -21482,7 +22650,7 @@ var ngInitDirective = ngDirective({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngNonBindable + * @name ng.directive:ngNonBindable * @priority 1000 * * @description @@ -21514,14 +22682,14 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngPluralize + * @name ng.directive:ngPluralize * @restrict EA * * @description * # Overview * `ngPluralize` is a directive that displays messages according to en-US localization rules. * These rules are bundled with angular.js and the rules can be overridden - * (see {@link guide/dev_guide.i18n Angular i18n} dev guide). You configure ngPluralize directive + * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive * by specifying the mappings between * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html * plural categories} and the strings to be displayed. @@ -21540,8 +22708,8 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); * You configure ngPluralize by providing 2 attributes: `count` and `when`. * You can also provide an optional attribute, `offset`. * - * The value of the `count` attribute can be either a string or an {@link guide/dev_guide.expressions - * Angular expression}; these are evaluated on the current scope for its binded value. + * The value of the `count` attribute can be either a string or an {@link guide/expression + * Angular expression}; these are evaluated on the current scope for its bound value. * * The `when` attribute specifies the mappings between plural categories and the actual * string to be displayed. The value of the attribute should be a JSON object so that Angular @@ -21690,14 +22858,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)) { @@ -21708,7 +22879,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp } else { return ''; } - }, function(newVal) { + }, function ngPluralizeWatchAction(newVal) { element.text(newVal); }); } @@ -21717,7 +22888,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngRepeat + * @name ng.directive:ngRepeat * * @description * The `ngRepeat` directive instantiates a template once per item from a collection. Each template @@ -21727,10 +22898,9 @@ 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) - * * `$position` – `{string}` – position of the repeated element in the iterator. One of: - * * `'first'`, - * * `'middle'` - * * `'last'` + * * `$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 @@ -21804,17 +22974,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 @@ -21829,11 +23003,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 @@ -21860,9 +23038,10 @@ var ngRepeatDirective = ngDirective({ childScope[valueIdent] = value; if (keyIdent) childScope[keyIdent] = key; childScope.$index = index; - childScope.$position = index === 0 ? - 'first' : - (index == collectionLength - 1 ? 'last' : 'middle'); + + childScope.$first = (index === 0); + childScope.$last = (index === arrayBound); + childScope.$middle = !(childScope.$first || childScope.$last); if (!last) { linker(childScope, function(clone){ @@ -21897,14 +23076,14 @@ var ngRepeatDirective = ngDirective({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngShow + * @name ng.directive:ngShow * * @description * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML) * conditionally. * * @element ANY - * @param {expression} ngShow If the {@link guide/dev_guide.expressions expression} is truthy + * @param {expression} ngShow If the {@link guide/expression expression} is truthy * then the element is shown or hidden respectively. * * @example @@ -21929,7 +23108,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'); }); }); @@ -21937,14 +23116,14 @@ var ngShowDirective = ngDirective(function(scope, element, attr){ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngHide + * @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/dev_guide.expressions expression} truthy then + * @param {expression} ngHide If the {@link guide/expression expression} is truthy then * the element is shown or hidden respectively. * * @example @@ -21969,33 +23148,38 @@ 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' : ''); }); }); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngStyle + * @name ng.directive:ngStyle * * @description * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. * * @element ANY - * @param {expression} ngStyle {@link guide/dev_guide.expressions Expression} which evals to an + * @param {expression} ngStyle {@link guide/expression Expression} which evals to an * object whose keys are CSS style names and values are corresponding values for those CSS * keys. * * @example - - + +
Sample Text
myStyle={{myStyle}}
-
- + + + span { + color: black; + } + + it('should check ng-style', function() { expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); element('.doc-example-live :button[value=set]').click(); @@ -22003,11 +23187,11 @@ var ngHideDirective = ngDirective(function(scope, element, attr){ element('.doc-example-live :button[value=clear]').click(); expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); }); - -
+ + */ 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, '');}); } @@ -22017,17 +23201,19 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngSwitch + * @name ng.directive:ngSwitch * @restrict EA * * @description * Conditionally change the DOM structure. * - * @usageContent - * ... + * @usage + * + * ... * ... * ... * ... + * * * @scope * @param {*} ngSwitch|on expression to match against ng-switch-when. @@ -22077,58 +23263,60 @@ 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; + }; } }); /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngTransclude + * @name ng.directive:ngTransclude * * @description * Insert the transcluded DOM here. @@ -22185,63 +23373,20 @@ var ngTranscludeDirective = ngDirective({ /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ngView + * @name ng.directive:ngView * @restrict ECA * * @description * # Overview - * `ngView` is a directive that complements the {@link angular.module.ng.$route $route} service by + * `ngView` is a directive that complements the {@link ng.$route $route} service by * including the rendered template of the current route into the main layout (`index.html`) file. * Every time the current route changes, the included view changes with it according to the * configuration of the `$route` service. * * @scope * @example - - - - - - - - + +
Choose: Moby | @@ -22254,13 +23399,57 @@ 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}}
-
- + + + + controller: {{name}}
+ Book Id: {{params.bookId}}
+
+ + + controller: {{name}}
+ Book Id: {{params.bookId}}
+ Chapter Id: {{params.chapterId}} +
+ + + angular.module('ngView', [], function($routeProvider, $locationProvider) { + $routeProvider.when('/Book/:bookId', { + templateUrl: 'book.html', + controller: BookCntl + }); + $routeProvider.when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: ChapterCntl + }); + + // configure html5 to get links working on jsfiddle + $locationProvider.html5Mode(true); + }); + + function MainCntl($scope, $route, $routeParams, $location) { + $scope.$route = $route; + $scope.$location = $location; + $scope.$routeParams = $routeParams; + } + + function BookCntl($scope, $routeParams) { + $scope.name = "BookCntl"; + $scope.params = $routeParams; + } + + function ChapterCntl($scope, $routeParams) { + $scope.name = "ChapterCntl"; + $scope.params = $routeParams; + } + + + it('should load and compile correct template', function() { element('a:contains("Moby: Ch1")').click(); var content = element('.doc-example-live [ng-view]').text(); @@ -22273,15 +23462,15 @@ var ngTranscludeDirective = ngDirective({ expect(content).toMatch(/controller\: BookCntl/); expect(content).toMatch(/Book Id\: Scarlet/); }); -
-
+ + */ /** * @ngdoc event - * @name angular.module.ng.$compileProvider.directive.ngView#$viewContentLoaded - * @eventOf angular.module.ng.$compileProvider.directive.ngView + * @name ng.directive:ngView#$viewContentLoaded + * @eventOf ng.directive:ngView * @eventType emit on the current ngView scope * @description * Emitted every time the ngView content is reloaded. @@ -22294,11 +23483,10 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c restrict: 'ECA', terminal: true, link: function(scope, element, attr) { - var changeCounter = 0, - lastScope, + var lastScope, onloadExp = attr.onload || ''; - scope.$on('$afterRouteChange', update); + scope.$on('$routeChangeSuccess', update); update(); @@ -22309,43 +23497,36 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c } } + function clearContent() { + element.html(''); + destroyLastScope(); + } + function update() { - var template = $route.current && $route.current.template, - thisChangeId = ++changeCounter; - - function clearContent() { - // ignore callback if another route change occured since - if (thisChangeId === changeCounter) { - element.html(''); - destroyLastScope(); - } - } + var locals = $route.current && $route.current.locals, + template = locals && locals.$template; if (template) { - $http.get(template, {cache: $templateCache}).success(function(response) { - // ignore callback if another route change occured since - if (thisChangeId === changeCounter) { - element.html(response); - destroyLastScope(); - - var link = $compile(element.contents()), - current = $route.current, - controller; - - lastScope = current.scope = scope.$new(); - if (current.controller) { - controller = $controller(current.controller, {$scope: lastScope}); - element.contents().data('$ngControllerController', controller); - } + element.html(template); + destroyLastScope(); + + var link = $compile(element.contents()), + current = $route.current, + controller; + + lastScope = current.scope = scope.$new(); + if (current.controller) { + locals.$scope = lastScope; + controller = $controller(current.controller, locals); + element.children().data('$ngControllerController', controller); + } - link(lastScope); - lastScope.$emit('$viewContentLoaded'); - lastScope.$eval(onloadExp); + link(lastScope); + lastScope.$emit('$viewContentLoaded'); + lastScope.$eval(onloadExp); - // $anchorScroll might listen on event... - $anchorScroll(); - } - }).error(clearContent); + // $anchorScroll might listen on event... + $anchorScroll(); } else { clearContent(); } @@ -22356,7 +23537,7 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.script + * @name ng.directive:script * * @description * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the @@ -22401,7 +23582,7 @@ var scriptDirective = ['$templateCache', function($templateCache) { /** * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.select + * @name ng.directive:select * @restrict E * * @description @@ -22422,12 +23603,15 @@ var scriptDirective = ['$templateCache', function($templateCache) { * option. See example below for demonstration. * * Note: `ngOptions` provides iterator facility for `