angular-rails-engine 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data.tar.gz.sig ADDED
Binary file
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in bootstrap-rails-engine.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  Make [Angular.js](http://angularjs.org) into Rails Engine.
3
3
 
4
4
  ## Version
5
- Angular.js 1.0.2
5
+ Angular.js 1.0.6
6
6
 
7
7
  ## Rails 3.1 or later
8
8
  Include Gemfile,
@@ -14,6 +14,9 @@ Gem::Specification.new do |gem|
14
14
  gem.files = `git ls-files`.split("\n")
15
15
  gem.require_paths = ["lib"]
16
16
 
17
+ gem.signing_key = File.join(Dir.home,'/.gem/trust/gem-private_key.pem')
18
+ gem.cert_chain = ['gem-public_cert.pem']
19
+
17
20
  gem.add_dependency "railties", ">= 3.0"
18
21
  gem.add_development_dependency "bundler", ">= 1.0"
19
22
  gem.add_development_dependency "rake"
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.0.2
2
+ * @license AngularJS v1.0.6
3
3
  * (c) 2010-2012 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -25,6 +25,18 @@ angular.module('ngCookies', ['ng']).
25
25
  * this object, new cookies are created/deleted at the end of current $eval.
26
26
  *
27
27
  * @example
28
+ <doc:example>
29
+ <doc:source>
30
+ <script>
31
+ function ExampleController($cookies) {
32
+ // Retrieving a cookie
33
+ var favoriteCookie = $cookies.myFavorite;
34
+ // Setting a cookie
35
+ $cookies.myFavorite = 'oatmeal';
36
+ }
37
+ </script>
38
+ </doc:source>
39
+ </doc:example>
28
40
  */
29
41
  factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
30
42
  var cookies = {},
@@ -168,4 +180,5 @@ angular.module('ngCookies', ['ng']).
168
180
 
169
181
  }]);
170
182
 
183
+
171
184
  })(window, window.angular);
@@ -1,5 +1,5 @@
1
1
  /*
2
- AngularJS v1.0.2
2
+ AngularJS v1.0.6
3
3
  (c) 2010-2012 Google, Inc. http://angularjs.org
4
4
  License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.0.2
2
+ * @license AngularJS v1.0.6
3
3
  * (c) 2010-2012 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -12,7 +12,7 @@
12
12
  * @description
13
13
  */
14
14
 
15
- /**
15
+ /**
16
16
  * @ngdoc object
17
17
  * @name ngResource.$resource
18
18
  * @requires $http
@@ -24,8 +24,21 @@
24
24
  * The returned resource object has action methods which provide high-level behaviors without
25
25
  * the need to interact with the low level {@link ng.$http $http} service.
26
26
  *
27
+ * # Installation
28
+ * To use $resource make sure you have included the `angular-resource.js` that comes in Angular
29
+ * package. You can also find this file on Google CDN, bower as well as at
30
+ * {@link http://code.angularjs.org/ code.angularjs.org}.
31
+ *
32
+ * Finally load the module in your application:
33
+ *
34
+ * angular.module('app', ['ngResource']);
35
+ *
36
+ * and you are ready to get started!
37
+ *
27
38
  * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
28
- * `/user/:username`.
39
+ * `/user/:username`. If you are using a URL with a port number (e.g.
40
+ * `http://example.com:8080/api`), you'll need to escape the colon character before the port
41
+ * number, like this: `$resource('http://example.com\\:8080/api')`.
29
42
  *
30
43
  * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
31
44
  * `actions` methods.
@@ -67,9 +80,9 @@
67
80
  *
68
81
  * Calling these methods invoke an {@link ng.$http} with the specified http method,
69
82
  * destination and parameters. When the data is returned from the server then the object is an
70
- * instance of the resource class `save`, `remove` and `delete` actions are available on it as
71
- * methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read,
72
- * update, delete) on server-side data like this:
83
+ * instance of the resource class. The actions `save`, `remove` and `delete` are available on it
84
+ * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
85
+ * read, update, delete) on server-side data like this:
73
86
  * <pre>
74
87
  var User = $resource('/user/:userId', {userId:'@id'});
75
88
  var user = User.get({userId:123}, function() {
@@ -149,9 +162,9 @@
149
162
  });
150
163
  </pre>
151
164
  *
152
- * It's worth noting that the success callback for `get`, `query` and other method gets passed
153
- * in the response that came from the server as well as $http header getter function, so one
154
- * could rewrite the above example and get access to http headers as:
165
+ * It's worth noting that the success callback for `get`, `query` and other method gets passed
166
+ * in the response that came from the server as well as $http header getter function, so one
167
+ * could rewrite the above example and get access to http headers as:
155
168
  *
156
169
  <pre>
157
170
  var User = $resource('/user/:userId', {userId:'@id'});
@@ -230,51 +243,51 @@ angular.module('ngResource', ['ng']).
230
243
  return $parse(path)(obj);
231
244
  };
232
245
 
233
- /**
234
- * We need our custom mehtod because encodeURIComponent is too agressive and doesn't follow
235
- * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
236
- * segments:
237
- * segment = *pchar
238
- * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
239
- * pct-encoded = "%" HEXDIG HEXDIG
240
- * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
241
- * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
242
- * / "*" / "+" / "," / ";" / "="
243
- */
244
- function encodeUriSegment(val) {
245
- return encodeUriQuery(val, true).
246
- replace(/%26/gi, '&').
247
- replace(/%3D/gi, '=').
248
- replace(/%2B/gi, '+');
249
- }
250
-
251
-
252
- /**
253
- * This method is intended for encoding *key* or *value* parts of query component. We need a custom
254
- * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
255
- * encoded per http://tools.ietf.org/html/rfc3986:
256
- * query = *( pchar / "/" / "?" )
257
- * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
258
- * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
259
- * pct-encoded = "%" HEXDIG HEXDIG
260
- * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
261
- * / "*" / "+" / "," / ";" / "="
262
- */
263
- function encodeUriQuery(val, pctEncodeSpaces) {
264
- return encodeURIComponent(val).
265
- replace(/%40/gi, '@').
266
- replace(/%3A/gi, ':').
267
- replace(/%24/g, '$').
268
- replace(/%2C/gi, ',').
269
- replace((pctEncodeSpaces ? null : /%20/g), '+');
270
- }
271
-
272
- function Route(template, defaults) {
246
+ /**
247
+ * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
248
+ * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
249
+ * segments:
250
+ * segment = *pchar
251
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
252
+ * pct-encoded = "%" HEXDIG HEXDIG
253
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
254
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
255
+ * / "*" / "+" / "," / ";" / "="
256
+ */
257
+ function encodeUriSegment(val) {
258
+ return encodeUriQuery(val, true).
259
+ replace(/%26/gi, '&').
260
+ replace(/%3D/gi, '=').
261
+ replace(/%2B/gi, '+');
262
+ }
263
+
264
+
265
+ /**
266
+ * This method is intended for encoding *key* or *value* parts of query component. We need a custom
267
+ * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
268
+ * encoded per http://tools.ietf.org/html/rfc3986:
269
+ * query = *( pchar / "/" / "?" )
270
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
271
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
272
+ * pct-encoded = "%" HEXDIG HEXDIG
273
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
274
+ * / "*" / "+" / "," / ";" / "="
275
+ */
276
+ function encodeUriQuery(val, pctEncodeSpaces) {
277
+ return encodeURIComponent(val).
278
+ replace(/%40/gi, '@').
279
+ replace(/%3A/gi, ':').
280
+ replace(/%24/g, '$').
281
+ replace(/%2C/gi, ',').
282
+ replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
283
+ }
284
+
285
+ function Route(template, defaults) {
273
286
  this.template = template = template + '#';
274
287
  this.defaults = defaults || {};
275
288
  var urlParams = this.urlParams = {};
276
289
  forEach(template.split(/\W/), function(param){
277
- if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) {
290
+ if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) {
278
291
  urlParams[param] = true;
279
292
  }
280
293
  });
@@ -285,12 +298,25 @@ angular.module('ngResource', ['ng']).
285
298
  url: function(params) {
286
299
  var self = this,
287
300
  url = this.template,
301
+ val,
288
302
  encodedVal;
289
303
 
290
304
  params = params || {};
291
305
  forEach(this.urlParams, function(_, urlParam){
292
- encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || "");
293
- url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1");
306
+ val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
307
+ if (angular.isDefined(val) && val !== null) {
308
+ encodedVal = encodeUriSegment(val);
309
+ url = url.replace(new RegExp(":" + urlParam + "(\\W)", "g"), encodedVal + "$1");
310
+ } else {
311
+ url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W)", "g"), function(match,
312
+ leadingSlashes, tail) {
313
+ if (tail.charAt(0) == '/') {
314
+ return tail;
315
+ } else {
316
+ return leadingSlashes + tail;
317
+ }
318
+ });
319
+ }
294
320
  });
295
321
  url = url.replace(/\/?#$/, '');
296
322
  var query = [];
@@ -311,9 +337,10 @@ angular.module('ngResource', ['ng']).
311
337
 
312
338
  actions = extend({}, DEFAULT_ACTIONS, actions);
313
339
 
314
- function extractParams(data){
340
+ function extractParams(data, actionParams){
315
341
  var ids = {};
316
- forEach(paramDefaults || {}, function(value, key){
342
+ actionParams = extend({}, paramDefaults, actionParams);
343
+ forEach(actionParams, function(value, key){
317
344
  ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
318
345
  });
319
346
  return ids;
@@ -324,6 +351,7 @@ angular.module('ngResource', ['ng']).
324
351
  }
325
352
 
326
353
  forEach(actions, function(action, name) {
354
+ action.method = angular.uppercase(action.method);
327
355
  var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH';
328
356
  Resource[name] = function(a1, a2, a3, a4) {
329
357
  var params = {};
@@ -367,7 +395,7 @@ angular.module('ngResource', ['ng']).
367
395
  var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
368
396
  $http({
369
397
  method: action.method,
370
- url: route.url(extend({}, extractParams(data), action.params || {}, params)),
398
+ url: route.url(extend({}, extractParams(data, action.params || {}), params)),
371
399
  data: data
372
400
  }).then(function(response) {
373
401
  var data = response.data;
@@ -389,11 +417,6 @@ angular.module('ngResource', ['ng']).
389
417
  };
390
418
 
391
419
 
392
- Resource.bind = function(additionalParamDefaults){
393
- return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
394
- };
395
-
396
-
397
420
  Resource.prototype['$' + name] = function(a1, a2, a3) {
398
421
  var params = extractParams(this),
399
422
  success = noop,
@@ -419,10 +442,16 @@ angular.module('ngResource', ['ng']).
419
442
  Resource[name].call(this, params, data, success, error);
420
443
  };
421
444
  });
445
+
446
+ Resource.bind = function(additionalParamDefaults){
447
+ return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
448
+ };
449
+
422
450
  return Resource;
423
451
  }
424
452
 
425
453
  return ResourceFactory;
426
454
  }]);
427
455
 
456
+
428
457
  })(window, window.angular);
@@ -0,0 +1,10 @@
1
+ /*
2
+ AngularJS v1.0.6
3
+ (c) 2010-2012 Google, Inc. http://angularjs.org
4
+ License: MIT
5
+ */
6
+ (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=
7
+ {},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 "+
8
+ 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 "+
9
+ 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,
10
+ "=").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);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.0.2
2
+ * @license AngularJS v1.0.6
3
3
  * (c) 2010-2012 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -416,12 +416,13 @@ angular.module('ngSanitize', []).value('$sanitize', $sanitize);
416
416
  angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {
417
417
  return function(scope, element, attr) {
418
418
  element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
419
- scope.$watch(attr.ngBindHtml, function(value) {
419
+ scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) {
420
420
  value = $sanitize(value);
421
421
  element.html(value || '');
422
422
  });
423
423
  };
424
424
  }]);
425
+
425
426
  /**
426
427
  * @ngdoc filter
427
428
  * @name ngSanitize.filter:linky
@@ -532,4 +533,5 @@ angular.module('ngSanitize').filter('linky', function() {
532
533
  };
533
534
  });
534
535
 
536
+
535
537
  })(window, window.angular);
@@ -1,5 +1,5 @@
1
1
  /*
2
- AngularJS v1.0.2
2
+ AngularJS v1.0.6
3
3
  (c) 2010-2012 Google, Inc. http://angularjs.org
4
4
  License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.0.2
2
+ * @license AngularJS v1.0.6
3
3
  * (c) 2010-2012 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -34,12 +34,12 @@ var uppercase = function(string){return isString(string) ? string.toUpperCase()
34
34
 
35
35
  var manualLowercase = function(s) {
36
36
  return isString(s)
37
- ? s.replace(/[A-Z]/g, function(ch) {return fromCharCode(ch.charCodeAt(0) | 32);})
37
+ ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
38
38
  : s;
39
39
  };
40
40
  var manualUppercase = function(s) {
41
41
  return isString(s)
42
- ? s.replace(/[a-z]/g, function(ch) {return fromCharCode(ch.charCodeAt(0) & ~32);})
42
+ ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
43
43
  : s;
44
44
  };
45
45
 
@@ -52,11 +52,8 @@ if ('i' !== 'I'.toLowerCase()) {
52
52
  uppercase = manualUppercase;
53
53
  }
54
54
 
55
- function fromCharCode(code) {return String.fromCharCode(code);}
56
55
 
57
-
58
- var Error = window.Error,
59
- /** holds major version number for IE or NaN for real browsers */
56
+ var /** holds major version number for IE or NaN for real browsers */
60
57
  msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]),
61
58
  jqLite, // delay binding since jQuery could be loaded after us.
62
59
  jQuery, // delay binding
@@ -97,6 +94,30 @@ var Error = window.Error,
97
94
  * @param {Object=} context Object to become context (`this`) for the iterator function.
98
95
  * @returns {Object|Array} Reference to `obj`.
99
96
  */
97
+
98
+
99
+ /**
100
+ * @private
101
+ * @param {*} obj
102
+ * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...)
103
+ */
104
+ function isArrayLike(obj) {
105
+ if (!obj || (typeof obj.length !== 'number')) return false;
106
+
107
+ // We have on object which has length property. Should we treat it as array?
108
+ if (typeof obj.hasOwnProperty != 'function' &&
109
+ typeof obj.constructor != 'function') {
110
+ // This is here for IE8: it is a bogus object treat it as array;
111
+ return true;
112
+ } else {
113
+ return obj instanceof JQLite || // JQLite
114
+ (jQuery && obj instanceof jQuery) || // jQuery
115
+ toString.call(obj) !== '[object Object]' || // some browser native object
116
+ typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj)
117
+ }
118
+ }
119
+
120
+
100
121
  function forEach(obj, iterator, context) {
101
122
  var key;
102
123
  if (obj) {
@@ -108,7 +129,7 @@ function forEach(obj, iterator, context) {
108
129
  }
109
130
  } else if (obj.forEach && obj.forEach !== forEach) {
110
131
  obj.forEach(iterator, context);
111
- } else if (isObject(obj) && isNumber(obj.length)) {
132
+ } else if (isArrayLike(obj)) {
112
133
  for (key = 0; key < obj.length; key++)
113
134
  iterator.call(context, obj[key], key);
114
135
  } else {
@@ -153,7 +174,7 @@ function reverseParams(iteratorFn) {
153
174
  /**
154
175
  * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
155
176
  * characters such as '012ABC'. The reason why we are not using simply a number counter is that
156
- * the number string gets longer over time, and it can also overflow, where as the the nextId
177
+ * the number string gets longer over time, and it can also overflow, where as the nextId
157
178
  * will grow much slower, it is a string, and it will never overflow.
158
179
  *
159
180
  * @returns an unique alpha-numeric string
@@ -549,9 +570,7 @@ function copy(source, destination){
549
570
  } else {
550
571
  if (source === destination) throw Error("Can't copy equivalent objects or arrays");
551
572
  if (isArray(source)) {
552
- while(destination.length) {
553
- destination.pop();
554
- }
573
+ destination.length = 0;
555
574
  for ( var i = 0; i < source.length; i++) {
556
575
  destination.push(copy(source[i]));
557
576
  }
@@ -627,13 +646,15 @@ function equals(o1, o2) {
627
646
  if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2)) return false;
628
647
  keySet = {};
629
648
  for(key in o1) {
630
- if (key.charAt(0) !== '$' && !isFunction(o1[key]) && !equals(o1[key], o2[key])) {
631
- return false;
632
- }
649
+ if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
650
+ if (!equals(o1[key], o2[key])) return false;
633
651
  keySet[key] = true;
634
652
  }
635
653
  for(key in o2) {
636
- if (!keySet[key] && key.charAt(0) !== '$' && !isFunction(o2[key])) return false;
654
+ if (!keySet[key] &&
655
+ key.charAt(0) !== '$' &&
656
+ o2[key] !== undefined &&
657
+ !isFunction(o2[key])) return false;
637
658
  }
638
659
  return true;
639
660
  }
@@ -760,9 +781,18 @@ function startingTag(element) {
760
781
  // are not allowed to have children. So we just ignore it.
761
782
  element.html('');
762
783
  } catch(e) {}
763
- return jqLite('<div>').append(element).html().
764
- match(/^(<[^>]+>)/)[1].
765
- replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
784
+ // As Per DOM Standards
785
+ var TEXT_NODE = 3;
786
+ var elemHtml = jqLite('<div>').append(element).html();
787
+ try {
788
+ return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
789
+ elemHtml.
790
+ match(/^(<[^>]+>)/)[1].
791
+ replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
792
+ } catch(e) {
793
+ return lowercase(elemHtml);
794
+ }
795
+
766
796
  }
767
797
 
768
798
 
@@ -794,7 +824,7 @@ function toKeyValue(obj) {
794
824
 
795
825
 
796
826
  /**
797
- * We need our custom mehtod because encodeURIComponent is too agressive and doesn't follow
827
+ * We need our custom method because encodeURIComponent is too agressive and doesn't follow
798
828
  * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
799
829
  * segments:
800
830
  * segment = *pchar
@@ -829,7 +859,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
829
859
  replace(/%3A/gi, ':').
830
860
  replace(/%24/g, '$').
831
861
  replace(/%2C/gi, ',').
832
- replace((pctEncodeSpaces ? null : /%20/g), '+');
862
+ replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
833
863
  }
834
864
 
835
865
 
@@ -838,7 +868,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
838
868
  * @name ng.directive:ngApp
839
869
  *
840
870
  * @element ANY
841
- * @param {angular.Module} ngApp on optional application
871
+ * @param {angular.Module} ngApp an optional application
842
872
  * {@link angular.module module} name to load.
843
873
  *
844
874
  * @description
@@ -846,7 +876,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
846
876
  * Use this directive to auto-bootstrap on application. Only
847
877
  * one directive can be used per HTML document. The directive
848
878
  * designates the root of the application and is typically placed
849
- * ot the root of the page.
879
+ * at the root of the page.
850
880
  *
851
881
  * In the example below if the `ngApp` directive would not be placed
852
882
  * on the `html` element then the document would not be compiled
@@ -918,22 +948,38 @@ function angularInit(element, bootstrap) {
918
948
  * @returns {AUTO.$injector} Returns the newly created injector for this app.
919
949
  */
920
950
  function bootstrap(element, modules) {
921
- element = jqLite(element);
922
- modules = modules || [];
923
- modules.unshift(['$provide', function($provide) {
924
- $provide.value('$rootElement', element);
925
- }]);
926
- modules.unshift('ng');
927
- var injector = createInjector(modules);
928
- injector.invoke(
929
- ['$rootScope', '$rootElement', '$compile', '$injector', function(scope, element, compile, injector){
930
- scope.$apply(function() {
931
- element.data('$injector', injector);
932
- compile(element)(scope);
933
- });
934
- }]
935
- );
936
- return injector;
951
+ var resumeBootstrapInternal = function() {
952
+ element = jqLite(element);
953
+ modules = modules || [];
954
+ modules.unshift(['$provide', function($provide) {
955
+ $provide.value('$rootElement', element);
956
+ }]);
957
+ modules.unshift('ng');
958
+ var injector = createInjector(modules);
959
+ injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
960
+ function(scope, element, compile, injector) {
961
+ scope.$apply(function() {
962
+ element.data('$injector', injector);
963
+ compile(element)(scope);
964
+ });
965
+ }]
966
+ );
967
+ return injector;
968
+ };
969
+
970
+ var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
971
+
972
+ if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
973
+ return resumeBootstrapInternal();
974
+ }
975
+
976
+ window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
977
+ angular.resumeBootstrap = function(extraModules) {
978
+ forEach(extraModules, function(module) {
979
+ modules.push(module);
980
+ });
981
+ resumeBootstrapInternal();
982
+ };
937
983
  }
938
984
 
939
985
  var SNAKE_CASE_REGEXP = /[A-Z]/g;
@@ -1015,7 +1061,7 @@ function setupModuleLoader(window) {
1015
1061
  *
1016
1062
  * # Module
1017
1063
  *
1018
- * A module is a collocation of services, directives, filters, and configure information. Module
1064
+ * A module is a collocation of services, directives, filters, and configuration information. Module
1019
1065
  * is used to configure the {@link AUTO.$injector $injector}.
1020
1066
  *
1021
1067
  * <pre>
@@ -1045,7 +1091,7 @@ function setupModuleLoader(window) {
1045
1091
  * @param {!string} name The name of the module to create or retrieve.
1046
1092
  * @param {Array.<string>=} requires If specified then new module is being created. If unspecified then the
1047
1093
  * the module is being retrieved for further configuration.
1048
- * @param {Function} configFn Option configuration function for the module. Same as
1094
+ * @param {Function} configFn Optional configuration function for the module. Same as
1049
1095
  * {@link angular.Module#config Module#config()}.
1050
1096
  * @returns {module} new module with the {@link angular.Module} api.
1051
1097
  */
@@ -1200,8 +1246,8 @@ function setupModuleLoader(window) {
1200
1246
  * @param {Function} initializationFn Execute this function after injector creation.
1201
1247
  * Useful for application initialization.
1202
1248
  * @description
1203
- * Use this method to register work which needs to be performed when the injector with
1204
- * with the current module is finished loading.
1249
+ * Use this method to register work which should be performed when the injector is done
1250
+ * loading all modules.
1205
1251
  */
1206
1252
  run: function(block) {
1207
1253
  runBlocks.push(block);
@@ -1247,11 +1293,11 @@ function setupModuleLoader(window) {
1247
1293
  * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
1248
1294
  */
1249
1295
  var version = {
1250
- full: '1.0.2', // all of these placeholder strings will be replaced by rake's
1251
- major: 1, // compile task
1296
+ full: '1.0.6', // all of these placeholder strings will be replaced by grunt's
1297
+ major: 1, // package task
1252
1298
  minor: 0,
1253
- dot: 2,
1254
- codeName: 'debilitating-awesomeness'
1299
+ dot: 6,
1300
+ codeName: 'universal-irreversibility'
1255
1301
  };
1256
1302
 
1257
1303
 
@@ -1418,11 +1464,12 @@ function publishExternalAPI(angular){
1418
1464
  * - [replaceWith()](http://api.jquery.com/replaceWith/)
1419
1465
  * - [text()](http://api.jquery.com/text/)
1420
1466
  * - [toggleClass()](http://api.jquery.com/toggleClass/)
1467
+ * - [triggerHandler()](http://api.jquery.com/triggerHandler/) - Doesn't pass native event objects to handlers.
1421
1468
  * - [unbind()](http://api.jquery.com/unbind/)
1422
1469
  * - [val()](http://api.jquery.com/val/)
1423
1470
  * - [wrap()](http://api.jquery.com/wrap/)
1424
1471
  *
1425
- * ## In addtion to the above, Angular privides an additional method to both jQuery and jQuery lite:
1472
+ * ## In addtion to the above, Angular provides additional methods to both jQuery and jQuery lite:
1426
1473
  *
1427
1474
  * - `controller(name)` - retrieves the controller of the current element or its parent. By default
1428
1475
  * retrieves controller associated with the `ngController` directive. If `name` is provided as
@@ -1493,12 +1540,7 @@ function JQLitePatchJQueryRemove(name, dispatchThis) {
1493
1540
  for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
1494
1541
  element = jqLite(set[setIndex]);
1495
1542
  if (fireEvent) {
1496
- events = element.data('events');
1497
- if ( (fns = events && events.$destroy) ) {
1498
- forEach(fns, function(fn){
1499
- fn.handler();
1500
- });
1501
- }
1543
+ element.triggerHandler('$destroy');
1502
1544
  } else {
1503
1545
  fireEvent = !fireEvent;
1504
1546
  }
@@ -1630,9 +1672,9 @@ function JQLiteHasClass(element, selector) {
1630
1672
  indexOf( " " + selector + " " ) > -1);
1631
1673
  }
1632
1674
 
1633
- function JQLiteRemoveClass(element, selector) {
1634
- if (selector) {
1635
- forEach(selector.split(' '), function(cssClass) {
1675
+ function JQLiteRemoveClass(element, cssClasses) {
1676
+ if (cssClasses) {
1677
+ forEach(cssClasses.split(' '), function(cssClass) {
1636
1678
  element.className = trim(
1637
1679
  (" " + element.className + " ")
1638
1680
  .replace(/[\n\t]/g, " ")
@@ -1642,9 +1684,9 @@ function JQLiteRemoveClass(element, selector) {
1642
1684
  }
1643
1685
  }
1644
1686
 
1645
- function JQLiteAddClass(element, selector) {
1646
- if (selector) {
1647
- forEach(selector.split(' '), function(cssClass) {
1687
+ function JQLiteAddClass(element, cssClasses) {
1688
+ if (cssClasses) {
1689
+ forEach(cssClasses.split(' '), function(cssClass) {
1648
1690
  if (!JQLiteHasClass(element, cssClass)) {
1649
1691
  element.className = trim(element.className + ' ' + trim(cssClass));
1650
1692
  }
@@ -2015,14 +2057,14 @@ forEach({
2015
2057
  children: function(element) {
2016
2058
  var children = [];
2017
2059
  forEach(element.childNodes, function(element){
2018
- if (element.nodeName != '#text')
2060
+ if (element.nodeType === 1)
2019
2061
  children.push(element);
2020
2062
  });
2021
2063
  return children;
2022
2064
  },
2023
2065
 
2024
2066
  contents: function(element) {
2025
- return element.childNodes;
2067
+ return element.childNodes || [];
2026
2068
  },
2027
2069
 
2028
2070
  append: function(element, node) {
@@ -2085,14 +2127,31 @@ forEach({
2085
2127
  },
2086
2128
 
2087
2129
  next: function(element) {
2088
- return element.nextSibling;
2130
+ if (element.nextElementSibling) {
2131
+ return element.nextElementSibling;
2132
+ }
2133
+
2134
+ // IE8 doesn't have nextElementSibling
2135
+ var elm = element.nextSibling;
2136
+ while (elm != null && elm.nodeType !== 1) {
2137
+ elm = elm.nextSibling;
2138
+ }
2139
+ return elm;
2089
2140
  },
2090
2141
 
2091
2142
  find: function(element, selector) {
2092
2143
  return element.getElementsByTagName(selector);
2093
2144
  },
2094
2145
 
2095
- clone: JQLiteClone
2146
+ clone: JQLiteClone,
2147
+
2148
+ triggerHandler: function(element, eventName) {
2149
+ var eventFns = (JQLiteExpandoStore(element, 'events') || {})[eventName];
2150
+
2151
+ forEach(eventFns, function(fn) {
2152
+ fn.call(element, null);
2153
+ });
2154
+ }
2096
2155
  }, function(fn, name){
2097
2156
  /**
2098
2157
  * chaining functions
@@ -2210,6 +2269,16 @@ HashQueueMap.prototype = {
2210
2269
  return array.shift();
2211
2270
  }
2212
2271
  }
2272
+ },
2273
+
2274
+ /**
2275
+ * return the first item without deleting it
2276
+ */
2277
+ peek: function(key) {
2278
+ var array = this[hashKey(key)];
2279
+ if (array) {
2280
+ return array[0];
2281
+ }
2213
2282
  }
2214
2283
  };
2215
2284
 
@@ -2233,7 +2302,7 @@ HashQueueMap.prototype = {
2233
2302
  * // create an injector
2234
2303
  * var $injector = angular.injector(['ng']);
2235
2304
  *
2236
- * // use the injector to kick of your application
2305
+ * // use the injector to kick off your application
2237
2306
  * // use the type inference to auto inject arguments, or use implicit injection
2238
2307
  * $injector.invoke(function($rootScope, $compile, $document){
2239
2308
  * $compile($document)($rootScope);
@@ -2253,7 +2322,7 @@ HashQueueMap.prototype = {
2253
2322
 
2254
2323
  var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
2255
2324
  var FN_ARG_SPLIT = /,/;
2256
- var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
2325
+ var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
2257
2326
  var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
2258
2327
  function annotate(fn) {
2259
2328
  var $inject,
@@ -2313,15 +2382,15 @@ function annotate(fn) {
2313
2382
  *
2314
2383
  * <pre>
2315
2384
  * // inferred (only works if code not minified/obfuscated)
2316
- * $inject.invoke(function(serviceA){});
2385
+ * $injector.invoke(function(serviceA){});
2317
2386
  *
2318
2387
  * // annotated
2319
2388
  * function explicit(serviceA) {};
2320
2389
  * explicit.$inject = ['serviceA'];
2321
- * $inject.invoke(explicit);
2390
+ * $injector.invoke(explicit);
2322
2391
  *
2323
2392
  * // inline
2324
- * $inject.invoke(['serviceA', function(serviceA){}]);
2393
+ * $injector.invoke(['serviceA', function(serviceA){}]);
2325
2394
  * </pre>
2326
2395
  *
2327
2396
  * ## Inference
@@ -2405,7 +2474,7 @@ function annotate(fn) {
2405
2474
  * This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
2406
2475
  * are supported.
2407
2476
  *
2408
- * # The `$injector` property
2477
+ * # The `$inject` property
2409
2478
  *
2410
2479
  * If a function has an `$inject` property and its value is an array of strings, then the strings represent names of
2411
2480
  * services to be injected into the function.
@@ -2467,7 +2536,7 @@ function annotate(fn) {
2467
2536
  * @description
2468
2537
  *
2469
2538
  * Use `$provide` to register new providers with the `$injector`. The providers are the factories for the instance.
2470
- * The providers share the same name as the instance they create with the `Provider` suffixed to them.
2539
+ * The providers share the same name as the instance they create with `Provider` suffixed to them.
2471
2540
  *
2472
2541
  * A provider is an object with a `$get()` method. The injector calls the `$get` method to create a new instance of
2473
2542
  * a service. The Provider can have additional methods which would allow for configuration of the provider.
@@ -2653,7 +2722,7 @@ function createInjector(modulesToLoad) {
2653
2722
  }
2654
2723
 
2655
2724
  function provider(name, provider_) {
2656
- if (isFunction(provider_)) {
2725
+ if (isFunction(provider_) || isArray(provider_)) {
2657
2726
  provider_ = providerInjector.instantiate(provider_);
2658
2727
  }
2659
2728
  if (!provider_.$get) {
@@ -2770,7 +2839,7 @@ function createInjector(modulesToLoad) {
2770
2839
  args.push(
2771
2840
  locals && locals.hasOwnProperty(key)
2772
2841
  ? locals[key]
2773
- : getService(key, path)
2842
+ : getService(key)
2774
2843
  );
2775
2844
  }
2776
2845
  if (!fn.$inject) {
@@ -2815,6 +2884,7 @@ function createInjector(modulesToLoad) {
2815
2884
  };
2816
2885
  }
2817
2886
  }
2887
+
2818
2888
  /**
2819
2889
  * @ngdoc function
2820
2890
  * @name ng.$anchorScroll
@@ -2870,11 +2940,12 @@ function $AnchorScrollProvider() {
2870
2940
  }
2871
2941
 
2872
2942
  // does not scroll when user clicks on anchor link that is currently on
2873
- // (no url change, no $locaiton.hash() change), browser native does scroll
2943
+ // (no url change, no $location.hash() change), browser native does scroll
2874
2944
  if (autoScrollingEnabled) {
2875
- $rootScope.$watch(function() {return $location.hash();}, function() {
2876
- $rootScope.$evalAsync(scroll);
2877
- });
2945
+ $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
2946
+ function autoScrollWatchAction() {
2947
+ $rootScope.$evalAsync(scroll);
2948
+ });
2878
2949
  }
2879
2950
 
2880
2951
  return scroll;
@@ -3118,7 +3189,7 @@ function Browser(window, document, $log, $sniffer) {
3118
3189
  */
3119
3190
  self.baseHref = function() {
3120
3191
  var href = baseElement.attr('href');
3121
- return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href;
3192
+ return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : '';
3122
3193
  };
3123
3194
 
3124
3195
  //////////////////////////////////////////////////////////////
@@ -3157,14 +3228,15 @@ function Browser(window, document, $log, $sniffer) {
3157
3228
  } else {
3158
3229
  if (isString(value)) {
3159
3230
  cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1;
3231
+
3232
+ // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
3233
+ // - 300 cookies
3234
+ // - 20 cookies per unique domain
3235
+ // - 4096 bytes per cookie
3160
3236
  if (cookieLength > 4096) {
3161
3237
  $log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+
3162
3238
  cookieLength + " > 4096 bytes)!");
3163
3239
  }
3164
- if (lastCookies.length > 20) {
3165
- $log.warn("Cookie '"+ name +"' possibly not set or overflowed because too many cookies " +
3166
- "were already set (" + lastCookies.length + " > 20 )");
3167
- }
3168
3240
  }
3169
3241
  }
3170
3242
  } else {
@@ -3241,6 +3313,7 @@ function $BrowserProvider(){
3241
3313
  return new Browser($window, $document, $log, $sniffer);
3242
3314
  }];
3243
3315
  }
3316
+
3244
3317
  /**
3245
3318
  * @ngdoc object
3246
3319
  * @name ng.$cacheFactory
@@ -3258,10 +3331,10 @@ function $BrowserProvider(){
3258
3331
  *
3259
3332
  * - `{object}` `info()` — Returns id, size, and options of cache.
3260
3333
  * - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache.
3261
- * - `{{*}} `get({string} key) — Returns cached value for `key` or undefined for cache miss.
3262
- * - `{void}` `remove({string} key) — Removes a key-value pair from the cache.
3263
- * - `{void}` `removeAll() — Removes all cached values.
3264
- * - `{void}` `destroy() — Removes references to this cache from $cacheFactory.
3334
+ * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
3335
+ * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
3336
+ * - `{void}` `removeAll()` — Removes all cached values.
3337
+ * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
3265
3338
  *
3266
3339
  */
3267
3340
  function $CacheFactoryProvider() {
@@ -3313,6 +3386,8 @@ function $CacheFactoryProvider() {
3313
3386
  remove: function(key) {
3314
3387
  var lruEntry = lruHash[key];
3315
3388
 
3389
+ if (!lruEntry) return;
3390
+
3316
3391
  if (lruEntry == freshEnd) freshEnd = lruEntry.p;
3317
3392
  if (lruEntry == staleEnd) staleEnd = lruEntry.n;
3318
3393
  link(lruEntry.n,lruEntry.p);
@@ -3565,7 +3640,8 @@ function $CompileProvider($provide) {
3565
3640
  Suffix = 'Directive',
3566
3641
  COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
3567
3642
  CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
3568
- MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ';
3643
+ MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ',
3644
+ urlSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/;
3569
3645
 
3570
3646
 
3571
3647
  /**
@@ -3619,11 +3695,41 @@ function $CompileProvider($provide) {
3619
3695
  };
3620
3696
 
3621
3697
 
3698
+ /**
3699
+ * @ngdoc function
3700
+ * @name ng.$compileProvider#urlSanitizationWhitelist
3701
+ * @methodOf ng.$compileProvider
3702
+ * @function
3703
+ *
3704
+ * @description
3705
+ * Retrieves or overrides the default regular expression that is used for whitelisting of safe
3706
+ * urls during a[href] sanitization.
3707
+ *
3708
+ * The sanitization is a security measure aimed at prevent XSS attacks via html links.
3709
+ *
3710
+ * Any url about to be assigned to a[href] via data-binding is first normalized and turned into an
3711
+ * absolute url. Afterwards the url is matched against the `urlSanitizationWhitelist` regular
3712
+ * expression. If a match is found the original url is written into the dom. Otherwise the
3713
+ * absolute url is prefixed with `'unsafe:'` string and only then it is written into the DOM.
3714
+ *
3715
+ * @param {RegExp=} regexp New regexp to whitelist urls with.
3716
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
3717
+ * chaining otherwise.
3718
+ */
3719
+ this.urlSanitizationWhitelist = function(regexp) {
3720
+ if (isDefined(regexp)) {
3721
+ urlSanitizationWhitelist = regexp;
3722
+ return this;
3723
+ }
3724
+ return urlSanitizationWhitelist;
3725
+ };
3726
+
3727
+
3622
3728
  this.$get = [
3623
3729
  '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
3624
- '$controller', '$rootScope',
3730
+ '$controller', '$rootScope', '$document',
3625
3731
  function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
3626
- $controller, $rootScope) {
3732
+ $controller, $rootScope, $document) {
3627
3733
 
3628
3734
  var Attributes = function(element, attr) {
3629
3735
  this.$$element = element;
@@ -3645,7 +3751,8 @@ function $CompileProvider($provide) {
3645
3751
  */
3646
3752
  $set: function(key, value, writeAttr, attrName) {
3647
3753
  var booleanKey = getBooleanAttrName(this.$$element[0], key),
3648
- $$observers = this.$$observers;
3754
+ $$observers = this.$$observers,
3755
+ normalizedVal;
3649
3756
 
3650
3757
  if (booleanKey) {
3651
3758
  this.$$element.prop(key, value);
@@ -3664,6 +3771,19 @@ function $CompileProvider($provide) {
3664
3771
  }
3665
3772
  }
3666
3773
 
3774
+
3775
+ // sanitize a[href] values
3776
+ if (nodeName_(this.$$element[0]) === 'A' && key === 'href') {
3777
+ urlSanitizationNode.setAttribute('href', value);
3778
+
3779
+ // href property always returns normalized absolute url, so we can match against that
3780
+ normalizedVal = urlSanitizationNode.href;
3781
+ if (!normalizedVal.match(urlSanitizationWhitelist)) {
3782
+ this[key] = value = 'unsafe:' + normalizedVal;
3783
+ }
3784
+ }
3785
+
3786
+
3667
3787
  if (writeAttr !== false) {
3668
3788
  if (value === null || value === undefined) {
3669
3789
  this.$$element.removeAttr(attrName);
@@ -3707,7 +3827,8 @@ function $CompileProvider($provide) {
3707
3827
  }
3708
3828
  };
3709
3829
 
3710
- var startSymbol = $interpolate.startSymbol(),
3830
+ var urlSanitizationNode = $document[0].createElement('a'),
3831
+ startSymbol = $interpolate.startSymbol(),
3711
3832
  endSymbol = $interpolate.endSymbol(),
3712
3833
  denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
3713
3834
  ? identity
@@ -3720,27 +3841,34 @@ function $CompileProvider($provide) {
3720
3841
 
3721
3842
  //================================
3722
3843
 
3723
- function compile($compileNode, transcludeFn, maxPriority) {
3724
- if (!($compileNode instanceof jqLite)) {
3725
- // jquery always rewraps, where as we need to preserve the original selector so that we can modify it.
3726
- $compileNode = jqLite($compileNode);
3844
+ function compile($compileNodes, transcludeFn, maxPriority) {
3845
+ if (!($compileNodes instanceof jqLite)) {
3846
+ // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it.
3847
+ $compileNodes = jqLite($compileNodes);
3727
3848
  }
3728
3849
  // We can not compile top level text elements since text nodes can be merged and we will
3729
3850
  // not be able to attach scope data to them, so we will wrap them in <span>
3730
- forEach($compileNode, function(node, index){
3731
- if (node.nodeType == 3 /* text node */) {
3732
- $compileNode[index] = jqLite(node).wrap('<span></span>').parent()[0];
3851
+ forEach($compileNodes, function(node, index){
3852
+ if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
3853
+ $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
3733
3854
  }
3734
3855
  });
3735
- var compositeLinkFn = compileNodes($compileNode, transcludeFn, $compileNode, maxPriority);
3736
- return function(scope, cloneConnectFn){
3856
+ var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority);
3857
+ return function publicLinkFn(scope, cloneConnectFn){
3737
3858
  assertArg(scope, 'scope');
3738
3859
  // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
3739
3860
  // and sometimes changes the structure of the DOM.
3740
3861
  var $linkNode = cloneConnectFn
3741
- ? JQLitePrototype.clone.call($compileNode) // IMPORTANT!!!
3742
- : $compileNode;
3743
- $linkNode.data('$scope', scope);
3862
+ ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
3863
+ : $compileNodes;
3864
+
3865
+ // Attach scope only to non-text nodes.
3866
+ for(var i = 0, ii = $linkNode.length; i<ii; i++) {
3867
+ var node = $linkNode[i];
3868
+ if (node.nodeType == 1 /* element */ || node.nodeType == 9 /* document */) {
3869
+ $linkNode.eq(i).data('$scope', scope);
3870
+ }
3871
+ }
3744
3872
  safeAddClass($linkNode, 'ng-scope');
3745
3873
  if (cloneConnectFn) cloneConnectFn($linkNode, scope);
3746
3874
  if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
@@ -3767,7 +3895,7 @@ function $CompileProvider($provide) {
3767
3895
  * functions return values - the linking functions - are combined into a composite linking
3768
3896
  * function, which is the a linking function for the node.
3769
3897
  *
3770
- * @param {NodeList} nodeList an array of nodes to compile
3898
+ * @param {NodeList} nodeList an array of nodes or NodeList to compile
3771
3899
  * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
3772
3900
  * scope argument is auto-generated to the new child of the transcluded parent scope.
3773
3901
  * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then the
@@ -3777,78 +3905,86 @@ function $CompileProvider($provide) {
3777
3905
  * @returns {?function} A composite linking function of all of the matched directives or null.
3778
3906
  */
3779
3907
  function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority) {
3780
- var linkFns = [],
3781
- nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
3908
+ var linkFns = [],
3909
+ nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
3782
3910
 
3783
- for(var i = 0; i < nodeList.length; i++) {
3784
- attrs = new Attributes();
3911
+ for(var i = 0; i < nodeList.length; i++) {
3912
+ attrs = new Attributes();
3785
3913
 
3786
- // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
3787
- directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
3914
+ // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
3915
+ directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
3788
3916
 
3789
- nodeLinkFn = (directives.length)
3790
- ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
3791
- : null;
3917
+ nodeLinkFn = (directives.length)
3918
+ ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
3919
+ : null;
3792
3920
 
3793
- childLinkFn = (nodeLinkFn && nodeLinkFn.terminal)
3794
- ? null
3795
- : compileNodes(nodeList[i].childNodes,
3796
- nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
3921
+ childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes || !nodeList[i].childNodes.length)
3922
+ ? null
3923
+ : compileNodes(nodeList[i].childNodes,
3924
+ nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
3797
3925
 
3798
- linkFns.push(nodeLinkFn);
3799
- linkFns.push(childLinkFn);
3800
- linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
3801
- }
3926
+ linkFns.push(nodeLinkFn);
3927
+ linkFns.push(childLinkFn);
3928
+ linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
3929
+ }
3802
3930
 
3803
- // return a linking function if we have found anything, null otherwise
3804
- return linkFnFound ? compositeLinkFn : null;
3931
+ // return a linking function if we have found anything, null otherwise
3932
+ return linkFnFound ? compositeLinkFn : null;
3805
3933
 
3806
- function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
3807
- var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn;
3934
+ function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
3935
+ var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn, i, ii, n;
3808
3936
 
3809
- for(var i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
3810
- node = nodeList[n];
3811
- nodeLinkFn = linkFns[i++];
3812
- childLinkFn = linkFns[i++];
3937
+ // copy nodeList so that linking doesn't break due to live list updates.
3938
+ var stableNodeList = [];
3939
+ for (i = 0, ii = nodeList.length; i < ii; i++) {
3940
+ stableNodeList.push(nodeList[i]);
3941
+ }
3813
3942
 
3814
- if (nodeLinkFn) {
3815
- if (nodeLinkFn.scope) {
3816
- childScope = scope.$new(isObject(nodeLinkFn.scope));
3817
- jqLite(node).data('$scope', childScope);
3818
- } else {
3819
- childScope = scope;
3820
- }
3821
- childTranscludeFn = nodeLinkFn.transclude;
3822
- if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
3823
- nodeLinkFn(childLinkFn, childScope, node, $rootElement,
3824
- (function(transcludeFn) {
3825
- return function(cloneFn) {
3826
- var transcludeScope = scope.$new();
3827
-
3828
- return transcludeFn(transcludeScope, cloneFn).
3829
- bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
3943
+ for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
3944
+ node = stableNodeList[n];
3945
+ nodeLinkFn = linkFns[i++];
3946
+ childLinkFn = linkFns[i++];
3947
+
3948
+ if (nodeLinkFn) {
3949
+ if (nodeLinkFn.scope) {
3950
+ childScope = scope.$new(isObject(nodeLinkFn.scope));
3951
+ jqLite(node).data('$scope', childScope);
3952
+ } else {
3953
+ childScope = scope;
3954
+ }
3955
+ childTranscludeFn = nodeLinkFn.transclude;
3956
+ if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
3957
+ nodeLinkFn(childLinkFn, childScope, node, $rootElement,
3958
+ (function(transcludeFn) {
3959
+ return function(cloneFn) {
3960
+ var transcludeScope = scope.$new();
3961
+ transcludeScope.$$transcluded = true;
3962
+
3963
+ return transcludeFn(transcludeScope, cloneFn).
3964
+ bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
3830
3965
  };
3831
3966
  })(childTranscludeFn || transcludeFn)
3832
- );
3833
- } else {
3834
- nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
3835
- }
3836
- } else if (childLinkFn) {
3837
- childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
3838
- }
3839
- }
3840
- }
3841
- }
3967
+ );
3968
+ } else {
3969
+ nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
3970
+ }
3971
+ } else if (childLinkFn) {
3972
+ childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
3973
+ }
3974
+ }
3975
+ }
3976
+ }
3842
3977
 
3843
3978
 
3844
3979
  /**
3845
- * Looks for directives on the given node ands them to the directive collection which is sorted.
3980
+ * Looks for directives on the given node and adds them to the directive collection which is
3981
+ * sorted.
3846
3982
  *
3847
- * @param node node to search
3848
- * @param directives an array to which the directives are added to. This array is sorted before
3983
+ * @param node Node to search.
3984
+ * @param directives An array to which the directives are added to. This array is sorted before
3849
3985
  * the function returns.
3850
- * @param attrs the shared attrs object which is used to populate the normalized attributes.
3851
- * @param {number=} max directive priority
3986
+ * @param attrs The shared attrs object which is used to populate the normalized attributes.
3987
+ * @param {number=} maxPriority Max directive priority.
3852
3988
  */
3853
3989
  function collectDirectives(node, directives, attrs, maxPriority) {
3854
3990
  var nodeType = node.nodeType,
@@ -3883,7 +4019,7 @@ function $CompileProvider($provide) {
3883
4019
 
3884
4020
  // use class as directive
3885
4021
  className = node.className;
3886
- if (isString(className)) {
4022
+ if (isString(className) && className !== '') {
3887
4023
  while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
3888
4024
  nName = directiveNormalize(match[2]);
3889
4025
  if (addDirective(directives, nName, 'C', maxPriority)) {
@@ -3937,7 +4073,7 @@ function $CompileProvider($provide) {
3937
4073
  preLinkFns = [],
3938
4074
  postLinkFns = [],
3939
4075
  newScopeDirective = null,
3940
- newIsolatedScopeDirective = null,
4076
+ newIsolateScopeDirective = null,
3941
4077
  templateDirective = null,
3942
4078
  $compileNode = templateAttrs.$$element = jqLite(compileNode),
3943
4079
  directive,
@@ -3959,10 +4095,10 @@ function $CompileProvider($provide) {
3959
4095
  }
3960
4096
 
3961
4097
  if (directiveValue = directive.scope) {
3962
- assertNoDuplicate('isolated scope', newIsolatedScopeDirective, directive, $compileNode);
4098
+ assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode);
3963
4099
  if (isObject(directiveValue)) {
3964
4100
  safeAddClass($compileNode, 'ng-isolate-scope');
3965
- newIsolatedScopeDirective = directive;
4101
+ newIsolateScopeDirective = directive;
3966
4102
  }
3967
4103
  safeAddClass($compileNode, 'ng-scope');
3968
4104
  newScopeDirective = newScopeDirective || directive;
@@ -3984,7 +4120,7 @@ function $CompileProvider($provide) {
3984
4120
  if (directiveValue == 'element') {
3985
4121
  $template = jqLite(compileNode);
3986
4122
  $compileNode = templateAttrs.$$element =
3987
- jqLite('<!-- ' + directiveName + ': ' + templateAttrs[directiveName] + ' -->');
4123
+ jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' '));
3988
4124
  compileNode = $compileNode[0];
3989
4125
  replaceWith($rootElement, jqLite($template[0]), compileNode);
3990
4126
  childTranscludeFn = compile($template, transcludeFn, terminalPriority);
@@ -4116,18 +4252,20 @@ function $CompileProvider($provide) {
4116
4252
  }
4117
4253
  $element = attrs.$$element;
4118
4254
 
4119
- if (newScopeDirective && isObject(newScopeDirective.scope)) {
4255
+ if (newIsolateScopeDirective) {
4120
4256
  var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/;
4121
4257
 
4122
4258
  var parentScope = scope.$parent || scope;
4123
4259
 
4124
- forEach(newScopeDirective.scope, function(definiton, scopeName) {
4260
+ forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
4125
4261
  var match = definiton.match(LOCAL_REGEXP) || [],
4126
4262
  attrName = match[2]|| scopeName,
4127
4263
  mode = match[1], // @, =, or &
4128
4264
  lastValue,
4129
4265
  parentGet, parentSet;
4130
4266
 
4267
+ scope.$$isolateBindings[scopeName] = mode + attrName;
4268
+
4131
4269
  switch (mode) {
4132
4270
 
4133
4271
  case '@': {
@@ -4144,10 +4282,10 @@ function $CompileProvider($provide) {
4144
4282
  // reset the change, or we will throw this exception on every $digest
4145
4283
  lastValue = scope[scopeName] = parentGet(parentScope);
4146
4284
  throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] +
4147
- ' (directive: ' + newScopeDirective.name + ')');
4285
+ ' (directive: ' + newIsolateScopeDirective.name + ')');
4148
4286
  };
4149
4287
  lastValue = scope[scopeName] = parentGet(parentScope);
4150
- scope.$watch(function() {
4288
+ scope.$watch(function parentValueWatch() {
4151
4289
  var parentValue = parentGet(parentScope);
4152
4290
 
4153
4291
  if (parentValue !== scope[scopeName]) {
@@ -4157,7 +4295,7 @@ function $CompileProvider($provide) {
4157
4295
  lastValue = scope[scopeName] = parentValue;
4158
4296
  } else {
4159
4297
  // if the parent can be assigned then do so
4160
- parentSet(parentScope, lastValue = scope[scopeName]);
4298
+ parentSet(parentScope, parentValue = lastValue = scope[scopeName]);
4161
4299
  }
4162
4300
  }
4163
4301
  return parentValue;
@@ -4175,7 +4313,7 @@ function $CompileProvider($provide) {
4175
4313
 
4176
4314
  default: {
4177
4315
  throw Error('Invalid isolate scope definition for directive ' +
4178
- newScopeDirective.name + ': ' + definiton);
4316
+ newIsolateScopeDirective.name + ': ' + definiton);
4179
4317
  }
4180
4318
  }
4181
4319
  });
@@ -4309,7 +4447,7 @@ function $CompileProvider($provide) {
4309
4447
  origAsyncDirective = directives.shift(),
4310
4448
  // The fact that we have to copy and patch the directive seems wrong!
4311
4449
  derivedSyncDirective = extend({}, origAsyncDirective, {
4312
- controller: null, templateUrl: null, transclude: null
4450
+ controller: null, templateUrl: null, transclude: null, scope: null
4313
4451
  });
4314
4452
 
4315
4453
  $compileNode.html('');
@@ -4338,8 +4476,8 @@ function $CompileProvider($provide) {
4338
4476
  }
4339
4477
 
4340
4478
  directives.unshift(derivedSyncDirective);
4341
- afterTemplateNodeLinkFn = applyDirectivesToNode(directives, $compileNode, tAttrs, childTranscludeFn);
4342
- afterTemplateChildLinkFn = compileNodes($compileNode.contents(), childTranscludeFn);
4479
+ afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn);
4480
+ afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
4343
4481
 
4344
4482
 
4345
4483
  while(linkQueue.length) {
@@ -4401,12 +4539,12 @@ function $CompileProvider($provide) {
4401
4539
  if (interpolateFn) {
4402
4540
  directives.push({
4403
4541
  priority: 0,
4404
- compile: valueFn(function(scope, node) {
4542
+ compile: valueFn(function textInterpolateLinkFn(scope, node) {
4405
4543
  var parent = node.parent(),
4406
4544
  bindings = parent.data('$binding') || [];
4407
4545
  bindings.push(interpolateFn);
4408
4546
  safeAddClass(parent.data('$binding', bindings), 'ng-binding');
4409
- scope.$watch(interpolateFn, function(value) {
4547
+ scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
4410
4548
  node[0].nodeValue = value;
4411
4549
  });
4412
4550
  })
@@ -4418,13 +4556,13 @@ function $CompileProvider($provide) {
4418
4556
  function addAttrInterpolateDirective(node, directives, value, name) {
4419
4557
  var interpolateFn = $interpolate(value, true);
4420
4558
 
4421
-
4422
4559
  // no interpolation found -> ignore
4423
4560
  if (!interpolateFn) return;
4424
4561
 
4562
+
4425
4563
  directives.push({
4426
4564
  priority: 100,
4427
- compile: valueFn(function(scope, element, attr) {
4565
+ compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
4428
4566
  var $$observers = (attr.$$observers || (attr.$$observers = {}));
4429
4567
 
4430
4568
  if (name === 'class') {
@@ -4436,7 +4574,7 @@ function $CompileProvider($provide) {
4436
4574
  attr[name] = undefined;
4437
4575
  ($$observers[name] || ($$observers[name] = [])).$$inter = true;
4438
4576
  (attr.$$observers && attr.$$observers[name].$$scope || scope).
4439
- $watch(interpolateFn, function(value) {
4577
+ $watch(interpolateFn, function interpolateFnWatchAction(value) {
4440
4578
  attr.$set(name, value);
4441
4579
  });
4442
4580
  })
@@ -4604,7 +4742,7 @@ function $ControllerProvider() {
4604
4742
  * @description
4605
4743
  * `$controller` service is responsible for instantiating controllers.
4606
4744
  *
4607
- * It's just simple call to {@link AUTO.$injector $injector}, but extracted into
4745
+ * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into
4608
4746
  * a service, so that one can override this service with {@link https://gist.github.com/1649788
4609
4747
  * BC version}.
4610
4748
  */
@@ -4649,11 +4787,12 @@ function $DocumentProvider(){
4649
4787
  * the browser console.
4650
4788
  *
4651
4789
  * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
4652
- * {@link ngMock.$exceptionHandler mock $exceptionHandler}
4790
+ * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
4653
4791
  *
4654
4792
  * @param {Error} exception Exception associated with the error.
4655
4793
  * @param {string=} cause optional information about the context in which
4656
4794
  * the error was thrown.
4795
+ *
4657
4796
  */
4658
4797
  function $ExceptionHandlerProvider() {
4659
4798
  this.$get = ['$log', function($log){
@@ -4844,7 +4983,7 @@ function $InterpolateProvider() {
4844
4983
  }];
4845
4984
  }
4846
4985
 
4847
- var URL_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
4986
+ var URL_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
4848
4987
  PATH_MATCH = /^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,
4849
4988
  HASH_MATCH = PATH_MATCH,
4850
4989
  DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
@@ -4923,7 +5062,8 @@ function convertToHashbangUrl(url, basePath, hashPrefix) {
4923
5062
  var match = matchUrl(url);
4924
5063
 
4925
5064
  // already hashbang url
4926
- if (decodeURIComponent(match.path) == basePath) {
5065
+ if (decodeURIComponent(match.path) == basePath && !isUndefined(match.hash) &&
5066
+ match.hash.indexOf(hashPrefix) === 0) {
4927
5067
  return url;
4928
5068
  // convert html5 url -> hashbang url
4929
5069
  } else {
@@ -5434,6 +5574,7 @@ function $LocationProvider(){
5434
5574
  var changeCounter = 0;
5435
5575
  $rootScope.$watch(function $locationWatch() {
5436
5576
  var oldUrl = $browser.url();
5577
+ var currentReplace = $location.$$replace;
5437
5578
 
5438
5579
  if (!changeCounter || oldUrl != $location.absUrl()) {
5439
5580
  changeCounter++;
@@ -5442,12 +5583,12 @@ function $LocationProvider(){
5442
5583
  defaultPrevented) {
5443
5584
  $location.$$parse(oldUrl);
5444
5585
  } else {
5445
- $browser.url($location.absUrl(), $location.$$replace);
5446
- $location.$$replace = false;
5586
+ $browser.url($location.absUrl(), currentReplace);
5447
5587
  afterLocationChange(oldUrl);
5448
5588
  }
5449
5589
  });
5450
5590
  }
5591
+ $location.$$replace = false;
5451
5592
 
5452
5593
  return changeCounter;
5453
5594
  });
@@ -5578,7 +5719,15 @@ var OPERATORS = {
5578
5719
  'true':function(){return true;},
5579
5720
  'false':function(){return false;},
5580
5721
  undefined:noop,
5581
- '+':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
5722
+ '+':function(self, locals, a,b){
5723
+ a=a(self, locals); b=b(self, locals);
5724
+ if (isDefined(a)) {
5725
+ if (isDefined(b)) {
5726
+ return a + b;
5727
+ }
5728
+ return a;
5729
+ }
5730
+ return isDefined(b)?b:undefined;},
5582
5731
  '-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
5583
5732
  '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
5584
5733
  '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
@@ -6405,9 +6554,10 @@ function getterFn(path, csp) {
6405
6554
  * @param {string} expression String expression to compile.
6406
6555
  * @returns {function(context, locals)} a function which represents the compiled expression:
6407
6556
  *
6408
- * * `context`: an object against which any expressions embedded in the strings are evaluated
6409
- * against (Topically a scope object).
6410
- * * `locals`: local variables context object, useful for overriding values in `context`.
6557
+ * * `context` – `{object}` – an object against which any expressions embedded in the strings
6558
+ * are evaluated against (tipically a scope object).
6559
+ * * `locals` – `{object=}` – local variables context object, useful for overriding values in
6560
+ * `context`.
6411
6561
  *
6412
6562
  * The return function also has an `assign` property, if the expression is assignable, which
6413
6563
  * allows one to set values to expressions.
@@ -6443,8 +6593,8 @@ function $ParseProvider() {
6443
6593
  * interface for interacting with an object that represents the result of an action that is
6444
6594
  * performed asynchronously, and may or may not be finished at any given point in time.
6445
6595
  *
6446
- * From the perspective of dealing with error handling, deferred and promise apis are to
6447
- * asynchronous programing what `try`, `catch` and `throw` keywords are to synchronous programing.
6596
+ * From the perspective of dealing with error handling, deferred and promise APIs are to
6597
+ * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
6448
6598
  *
6449
6599
  * <pre>
6450
6600
  * // for the purpose of this example let's assume that variables `$q` and `scope` are
@@ -6473,12 +6623,12 @@ function $ParseProvider() {
6473
6623
  * alert('Success: ' + greeting);
6474
6624
  * }, function(reason) {
6475
6625
  * alert('Failed: ' + reason);
6476
- * );
6626
+ * });
6477
6627
  * </pre>
6478
6628
  *
6479
6629
  * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
6480
6630
  * comes in the way of
6481
- * [guarantees that promise and deferred apis make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
6631
+ * [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
6482
6632
  *
6483
6633
  * Additionally the promise api allows for composition that is very hard to do with the
6484
6634
  * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
@@ -6490,7 +6640,7 @@ function $ParseProvider() {
6490
6640
  *
6491
6641
  * A new instance of deferred is constructed by calling `$q.defer()`.
6492
6642
  *
6493
- * The purpose of the deferred object is to expose the associated Promise instance as well as apis
6643
+ * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
6494
6644
  * that can be used for signaling the successful or unsuccessful completion of the task.
6495
6645
  *
6496
6646
  * **Methods**
@@ -6533,7 +6683,7 @@ function $ParseProvider() {
6533
6683
  * return result + 1;
6534
6684
  * });
6535
6685
  *
6536
- * // promiseB will be resolved immediately after promiseA is resolved and it's value will be
6686
+ * // promiseB will be resolved immediately after promiseA is resolved and its value will be
6537
6687
  * // the result of promiseA incremented by 1
6538
6688
  * </pre>
6539
6689
  *
@@ -6554,6 +6704,30 @@ function $ParseProvider() {
6554
6704
  * you can treat promises attached to a scope as if they were the resulting values.
6555
6705
  * - Q has many more features that $q, but that comes at a cost of bytes. $q is tiny, but contains
6556
6706
  * all the important functionality needed for common async tasks.
6707
+ *
6708
+ * # Testing
6709
+ *
6710
+ * <pre>
6711
+ * it('should simulate promise', inject(function($q, $rootScope) {
6712
+ * var deferred = $q.defer();
6713
+ * var promise = deferred.promise;
6714
+ * var resolvedValue;
6715
+ *
6716
+ * promise.then(function(value) { resolvedValue = value; });
6717
+ * expect(resolvedValue).toBeUndefined();
6718
+ *
6719
+ * // Simulate resolving of promise
6720
+ * deferred.resolve(123);
6721
+ * // Note that the 'then' function does not get called synchronously.
6722
+ * // This is because we want the promise API to always be async, whether or not
6723
+ * // it got called synchronously or asynchronously.
6724
+ * expect(resolvedValue).toBeUndefined();
6725
+ *
6726
+ * // Propagate promise resolution to 'then' functions using $apply().
6727
+ * $rootScope.$apply();
6728
+ * expect(resolvedValue).toEqual(123);
6729
+ * });
6730
+ * </pre>
6557
6731
  */
6558
6732
  function $QProvider() {
6559
6733
 
@@ -6719,12 +6893,12 @@ function qFactory(nextTick, exceptionHandler) {
6719
6893
  * @methodOf ng.$q
6720
6894
  * @description
6721
6895
  * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
6722
- * This is useful when you are dealing with on object that might or might not be a promise, or if
6896
+ * This is useful when you are dealing with an object that might or might not be a promise, or if
6723
6897
  * the promise comes from a source that can't be trusted.
6724
6898
  *
6725
6899
  * @param {*} value Value or a promise
6726
6900
  * @returns {Promise} Returns a single promise that will be resolved with an array of values,
6727
- * each value coresponding to the promise at the same index in the `promises` array. If any of
6901
+ * each value corresponding to the promise at the same index in the `promises` array. If any of
6728
6902
  * the promises is resolved with a rejection, this resulting promise will be resolved with the
6729
6903
  * same rejection.
6730
6904
  */
@@ -6786,7 +6960,7 @@ function qFactory(nextTick, exceptionHandler) {
6786
6960
  *
6787
6961
  * @param {Array.<Promise>} promises An array of promises.
6788
6962
  * @returns {Promise} Returns a single promise that will be resolved with an array of values,
6789
- * each value coresponding to the promise at the same index in the `promises` array. If any of
6963
+ * each value corresponding to the promise at the same index in the `promises` array. If any of
6790
6964
  * the promises is resolved with a rejection, this resulting promise will be resolved with the
6791
6965
  * same rejection.
6792
6966
  */
@@ -6840,15 +7014,20 @@ function $RouteProvider(){
6840
7014
  *
6841
7015
  * @param {string} path Route path (matched against `$location.path`). If `$location.path`
6842
7016
  * contains redundant trailing slash or is missing one, the route will still match and the
6843
- * `$location.path` will be updated to add or drop the trailing slash to exacly match the
7017
+ * `$location.path` will be updated to add or drop the trailing slash to exactly match the
6844
7018
  * route definition.
7019
+ *
7020
+ * `path` can contain named groups starting with a colon (`:name`). All characters up to the
7021
+ * next slash are matched and stored in `$routeParams` under the given `name` when the route
7022
+ * matches.
7023
+ *
6845
7024
  * @param {Object} route Mapping information to be assigned to `$route.current` on route
6846
7025
  * match.
6847
7026
  *
6848
7027
  * Object properties:
6849
7028
  *
6850
7029
  * - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly
6851
- * created scope or the name of a {@link angular.Module#controller registered controller}
7030
+ * created scope or the name of a {@link angular.Module#controller registered controller}
6852
7031
  * if passed as a string.
6853
7032
  * - `template` – `{string=}` – html template as a string that should be used by
6854
7033
  * {@link ng.directive:ngView ngView} or
@@ -6859,7 +7038,7 @@ function $RouteProvider(){
6859
7038
  * - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
6860
7039
  * be injected into the controller. If any of these dependencies are promises, they will be
6861
7040
  * resolved and converted to a value before the controller is instantiated and the
6862
- * `$afterRouteChange` event is fired. The map object is:
7041
+ * `$routeChangeSuccess` event is fired. The map object is:
6863
7042
  *
6864
7043
  * - `key` – `{string}`: a name of a dependency to be injected into the controller.
6865
7044
  * - `factory` - `{string|function}`: If `string` then it is an alias for a service.
@@ -7078,8 +7257,9 @@ function $RouteProvider(){
7078
7257
  * {@link ng.directive:ngView ngView} listens for the directive
7079
7258
  * to instantiate the controller and render the view.
7080
7259
  *
7260
+ * @param {Object} angularEvent Synthetic event object.
7081
7261
  * @param {Route} current Current route information.
7082
- * @param {Route} previous Previous route information.
7262
+ * @param {Route|Undefined} previous Previous route information, or undefined if current is first route entered.
7083
7263
  */
7084
7264
 
7085
7265
  /**
@@ -7106,8 +7286,7 @@ function $RouteProvider(){
7106
7286
  * instance of the Controller.
7107
7287
  */
7108
7288
 
7109
- var matcher = switchRouteMatcher,
7110
- forceReload = false,
7289
+ var forceReload = false,
7111
7290
  $route = {
7112
7291
  routes: routes,
7113
7292
 
@@ -7135,21 +7314,36 @@ function $RouteProvider(){
7135
7314
 
7136
7315
  /////////////////////////////////////////////////////
7137
7316
 
7317
+ /**
7318
+ * @param on {string} current url
7319
+ * @param when {string} route when template to match the url against
7320
+ * @return {?Object}
7321
+ */
7138
7322
  function switchRouteMatcher(on, when) {
7139
7323
  // TODO(i): this code is convoluted and inefficient, we should construct the route matching
7140
7324
  // regex only once and then reuse it
7141
- var regex = '^' + when.replace(/([\.\\\(\)\^\$])/g, "\\$1") + '$',
7325
+
7326
+ // Escape regexp special characters.
7327
+ when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$';
7328
+ var regex = '',
7142
7329
  params = [],
7143
7330
  dst = {};
7144
- forEach(when.split(/\W/), function(param) {
7145
- if (param) {
7146
- var paramRegExp = new RegExp(":" + param + "([\\W])");
7147
- if (regex.match(paramRegExp)) {
7148
- regex = regex.replace(paramRegExp, "([^\\/]*)$1");
7149
- params.push(param);
7150
- }
7151
- }
7152
- });
7331
+
7332
+ var re = /:(\w+)/g,
7333
+ paramMatch,
7334
+ lastMatchedIndex = 0;
7335
+
7336
+ while ((paramMatch = re.exec(when)) !== null) {
7337
+ // Find each :param in `when` and replace it with a capturing group.
7338
+ // Append all other sections of when unchanged.
7339
+ regex += when.slice(lastMatchedIndex, paramMatch.index);
7340
+ regex += '([^\\/]*)';
7341
+ params.push(paramMatch[1]);
7342
+ lastMatchedIndex = re.lastIndex;
7343
+ }
7344
+ // Append trailing path part.
7345
+ regex += when.substr(lastMatchedIndex);
7346
+
7153
7347
  var match = on.match(new RegExp(regex));
7154
7348
  if (match) {
7155
7349
  forEach(params, function(name, index) {
@@ -7163,7 +7357,7 @@ function $RouteProvider(){
7163
7357
  var next = parseRoute(),
7164
7358
  last = $route.current;
7165
7359
 
7166
- if (next && last && next.$route === last.$route
7360
+ if (next && last && next.$$route === last.$$route
7167
7361
  && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) {
7168
7362
  last.params = next.params;
7169
7363
  copy(last.params, $routeParams);
@@ -7193,7 +7387,7 @@ function $RouteProvider(){
7193
7387
 
7194
7388
  forEach(next.resolve || {}, function(value, key) {
7195
7389
  keys.push(key);
7196
- values.push(isFunction(value) ? $injector.invoke(value) : $injector.get(value));
7390
+ values.push(isString(value) ? $injector.get(value) : $injector.invoke(value));
7197
7391
  });
7198
7392
  if (isDefined(template = next.template)) {
7199
7393
  } else if (isDefined(template = next.templateUrl)) {
@@ -7238,11 +7432,11 @@ function $RouteProvider(){
7238
7432
  // Match a route
7239
7433
  var params, match;
7240
7434
  forEach(routes, function(route, path) {
7241
- if (!match && (params = matcher($location.path(), path))) {
7435
+ if (!match && (params = switchRouteMatcher($location.path(), path))) {
7242
7436
  match = inherit(route, {
7243
7437
  params: extend({}, $location.search(), params),
7244
7438
  pathParams: params});
7245
- match.$route = route;
7439
+ match.$$route = route;
7246
7440
  }
7247
7441
  });
7248
7442
  // No route matched; fallback to "otherwise" route
@@ -7390,7 +7584,7 @@ function $RootScopeProvider(){
7390
7584
  expect(scope.greeting).toEqual(undefined);
7391
7585
 
7392
7586
  scope.$watch('name', function() {
7393
- this.greeting = this.salutation + ' ' + this.name + '!';
7587
+ scope.greeting = scope.salutation + ' ' + scope.name + '!';
7394
7588
  }); // initialize the watch
7395
7589
 
7396
7590
  expect(scope.greeting).toEqual(undefined);
@@ -7433,8 +7627,10 @@ function $RootScopeProvider(){
7433
7627
  this.$$nextSibling = this.$$prevSibling =
7434
7628
  this.$$childHead = this.$$childTail = null;
7435
7629
  this['this'] = this.$root = this;
7630
+ this.$$destroyed = false;
7436
7631
  this.$$asyncQueue = [];
7437
7632
  this.$$listeners = {};
7633
+ this.$$isolateBindings = {};
7438
7634
  }
7439
7635
 
7440
7636
  /**
@@ -7464,9 +7660,9 @@ function $RootScopeProvider(){
7464
7660
  * the scope and its child scopes to be permanently detached from the parent and thus stop
7465
7661
  * participating in model change detection and listener notification by invoking.
7466
7662
  *
7467
- * @param {boolean} isolate if true then the scoped does not prototypically inherit from the
7468
- * parent scope. The scope is isolated, as it can not se parent scope properties.
7469
- * When creating widgets it is useful for the widget to not accidently read parent
7663
+ * @param {boolean} isolate if true then the scope does not prototypically inherit from the
7664
+ * parent scope. The scope is isolated, as it can not see parent scope properties.
7665
+ * When creating widgets it is useful for the widget to not accidentally read parent
7470
7666
  * state.
7471
7667
  *
7472
7668
  * @returns {Object} The newly created child scope.
@@ -7520,18 +7716,18 @@ function $RootScopeProvider(){
7520
7716
  * reruns when it detects changes the `watchExpression` can execute multiple times per
7521
7717
  * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
7522
7718
  * - The `listener` is called only when the value from the current `watchExpression` and the
7523
- * previous call to `watchExpression' are not equal (with the exception of the initial run
7719
+ * previous call to `watchExpression` are not equal (with the exception of the initial run,
7524
7720
  * see below). The inequality is determined according to
7525
- * {@link angular.equals} function. To save the value of the object for later comparison
7721
+ * {@link angular.equals} function. To save the value of the object for later comparison, the
7526
7722
  * {@link angular.copy} function is used. It also means that watching complex options will
7527
7723
  * have adverse memory and performance implications.
7528
7724
  * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This
7529
7725
  * is achieved by rerunning the watchers until no changes are detected. The rerun iteration
7530
- * limit is 100 to prevent infinity loop deadlock.
7726
+ * limit is 10 to prevent an infinite loop deadlock.
7531
7727
  *
7532
7728
  *
7533
7729
  * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
7534
- * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`,
7730
+ * you can register a `watchExpression` function with no `listener`. (Since `watchExpression`
7535
7731
  * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is
7536
7732
  * detected, be prepared for multiple calls to your listener.)
7537
7733
  *
@@ -7551,7 +7747,7 @@ function $RootScopeProvider(){
7551
7747
  scope.counter = 0;
7552
7748
 
7553
7749
  expect(scope.counter).toEqual(0);
7554
- scope.$watch('name', function(newValue, oldValue) { counter = counter + 1; });
7750
+ scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; });
7555
7751
  expect(scope.counter).toEqual(0);
7556
7752
 
7557
7753
  scope.$digest();
@@ -7577,7 +7773,7 @@ function $RootScopeProvider(){
7577
7773
  * - `string`: Evaluated as {@link guide/expression expression}
7578
7774
  * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters.
7579
7775
  *
7580
- * @param {boolean=} objectEquality Compare object for equality rather then for refference.
7776
+ * @param {boolean=} objectEquality Compare object for equality rather than for reference.
7581
7777
  * @returns {function()} Returns a deregistration function for this listener.
7582
7778
  */
7583
7779
  $watch: function(watchExp, listener, objectEquality) {
@@ -7644,7 +7840,7 @@ function $RootScopeProvider(){
7644
7840
 
7645
7841
  expect(scope.counter).toEqual(0);
7646
7842
  scope.$watch('name', function(newValue, oldValue) {
7647
- counter = counter + 1;
7843
+ scope.counter = scope.counter + 1;
7648
7844
  });
7649
7845
  expect(scope.counter).toEqual(0);
7650
7846
 
@@ -7752,7 +7948,7 @@ function $RootScopeProvider(){
7752
7948
  * @function
7753
7949
  *
7754
7950
  * @description
7755
- * Remove the current scope (and all of its children) from the parent scope. Removal implies
7951
+ * Removes the current scope (and all of its children) from the parent scope. Removal implies
7756
7952
  * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
7757
7953
  * propagate to the current scope and its children. Removal also implies that the current
7758
7954
  * scope is eligible for garbage collection.
@@ -7766,15 +7962,22 @@ function $RootScopeProvider(){
7766
7962
  * perform any necessary cleanup.
7767
7963
  */
7768
7964
  $destroy: function() {
7769
- if ($rootScope == this) return; // we can't remove the root node;
7965
+ // we can't destroy the root scope or a scope that has been already destroyed
7966
+ if ($rootScope == this || this.$$destroyed) return;
7770
7967
  var parent = this.$parent;
7771
7968
 
7772
7969
  this.$broadcast('$destroy');
7970
+ this.$$destroyed = true;
7773
7971
 
7774
7972
  if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
7775
7973
  if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
7776
7974
  if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
7777
7975
  if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
7976
+
7977
+ // This is bogus code that works around Chrome's GC leak
7978
+ // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
7979
+ this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
7980
+ this.$$childTail = null;
7778
7981
  },
7779
7982
 
7780
7983
  /**
@@ -7785,7 +7988,7 @@ function $RootScopeProvider(){
7785
7988
  *
7786
7989
  * @description
7787
7990
  * Executes the `expression` on the current scope returning the result. Any exceptions in the
7788
- * expression are propagated (uncaught). This is useful when evaluating engular expressions.
7991
+ * expression are propagated (uncaught). This is useful when evaluating Angular expressions.
7789
7992
  *
7790
7993
  * # Example
7791
7994
  * <pre>
@@ -7906,23 +8109,23 @@ function $RootScopeProvider(){
7906
8109
  * @function
7907
8110
  *
7908
8111
  * @description
7909
- * Listen on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
8112
+ * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
7910
8113
  * event life cycle.
7911
8114
  *
7912
- * @param {string} name Event name to listen on.
7913
- * @param {function(event)} listener Function to call when the event is emitted.
7914
- * @returns {function()} Returns a deregistration function for this listener.
7915
- *
7916
8115
  * The event listener function format is: `function(event, args...)`. The `event` object
7917
8116
  * passed into the listener has the following attributes:
7918
8117
  *
7919
- * - `targetScope` - {Scope}: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
7920
- * - `currentScope` - {Scope}: the current scope which is handling the event.
7921
- * - `name` - {string}: Name of the event.
7922
- * - `stopPropagation` - {function=}: calling `stopPropagation` function will cancel further event propagation
7923
- * (available only for events that were `$emit`-ed).
7924
- * - `preventDefault` - {function}: calling `preventDefault` sets `defaultPrevented` flag to true.
7925
- * - `defaultPrevented` - {boolean}: true if `preventDefault` was called.
8118
+ * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
8119
+ * - `currentScope` - `{Scope}`: the current scope which is handling the event.
8120
+ * - `name` - `{string}`: Name of the event.
8121
+ * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel further event
8122
+ * propagation (available only for events that were `$emit`-ed).
8123
+ * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true.
8124
+ * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
8125
+ *
8126
+ * @param {string} name Event name to listen on.
8127
+ * @param {function(event, args...)} listener Function to call when the event is emitted.
8128
+ * @returns {function()} Returns a deregistration function for this listener.
7926
8129
  */
7927
8130
  $on: function(name, listener) {
7928
8131
  var namedListeners = this.$$listeners[name];
@@ -7932,7 +8135,7 @@ function $RootScopeProvider(){
7932
8135
  namedListeners.push(listener);
7933
8136
 
7934
8137
  return function() {
7935
- arrayRemove(namedListeners, listener);
8138
+ namedListeners[indexOf(namedListeners, listener)] = null;
7936
8139
  };
7937
8140
  },
7938
8141
 
@@ -7980,6 +8183,14 @@ function $RootScopeProvider(){
7980
8183
  namedListeners = scope.$$listeners[name] || empty;
7981
8184
  event.currentScope = scope;
7982
8185
  for (i=0, length=namedListeners.length; i<length; i++) {
8186
+
8187
+ // if listeners were deregistered, defragment the array
8188
+ if (!namedListeners[i]) {
8189
+ namedListeners.splice(i, 1);
8190
+ i--;
8191
+ length--;
8192
+ continue;
8193
+ }
7983
8194
  try {
7984
8195
  namedListeners[i].apply(null, listenerArgs);
7985
8196
  if (stopPropagation) return event;
@@ -8029,19 +8240,29 @@ function $RootScopeProvider(){
8029
8240
  },
8030
8241
  defaultPrevented: false
8031
8242
  },
8032
- listenerArgs = concat([event], arguments, 1);
8243
+ listenerArgs = concat([event], arguments, 1),
8244
+ listeners, i, length;
8033
8245
 
8034
8246
  //down while you can, then up and next sibling or up and next sibling until back at root
8035
8247
  do {
8036
8248
  current = next;
8037
8249
  event.currentScope = current;
8038
- forEach(current.$$listeners[name], function(listener) {
8250
+ listeners = current.$$listeners[name] || [];
8251
+ for (i=0, length = listeners.length; i<length; i++) {
8252
+ // if listeners were deregistered, defragment the array
8253
+ if (!listeners[i]) {
8254
+ listeners.splice(i, 1);
8255
+ i--;
8256
+ length--;
8257
+ continue;
8258
+ }
8259
+
8039
8260
  try {
8040
- listener.apply(null, listenerArgs);
8261
+ listeners[i].apply(null, listenerArgs);
8041
8262
  } catch(e) {
8042
8263
  $exceptionHandler(e);
8043
8264
  }
8044
- });
8265
+ }
8045
8266
 
8046
8267
  // Insanity Warning: scope depth-first traversal
8047
8268
  // yes, this code is a bit crazy, but it works and we have tests to prove it!
@@ -8082,7 +8303,7 @@ function $RootScopeProvider(){
8082
8303
 
8083
8304
  /**
8084
8305
  * function used as an initial value for watchers.
8085
- * because it's uniqueue we can easily tell it apart from other values
8306
+ * because it's unique we can easily tell it apart from other values
8086
8307
  */
8087
8308
  function initWatchVal() {}
8088
8309
  }];
@@ -8296,7 +8517,7 @@ function $HttpProvider() {
8296
8517
  /**
8297
8518
  * @ngdoc function
8298
8519
  * @name ng.$http
8299
- * @requires $httpBacked
8520
+ * @requires $httpBackend
8300
8521
  * @requires $browser
8301
8522
  * @requires $cacheFactory
8302
8523
  * @requires $rootScope
@@ -8332,8 +8553,7 @@ function $HttpProvider() {
8332
8553
  * }).
8333
8554
  * error(function(data, status, headers, config) {
8334
8555
  * // called asynchronously if an error occurs
8335
- * // or server returns response with status
8336
- * // code outside of the <200, 400) range
8556
+ * // or server returns response with an error status.
8337
8557
  * });
8338
8558
  * </pre>
8339
8559
  *
@@ -8342,6 +8562,10 @@ function $HttpProvider() {
8342
8562
  * an object representing the response. See the api signature and type info below for more
8343
8563
  * details.
8344
8564
  *
8565
+ * A response status code that falls in the [200, 300) range is considered a success status and
8566
+ * will result in the success callback being called. Note that if the response is a redirect,
8567
+ * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
8568
+ * called for such responses.
8345
8569
  *
8346
8570
  * # Shortcut methods
8347
8571
  *
@@ -8402,10 +8626,14 @@ function $HttpProvider() {
8402
8626
  * - if XSRF prefix is detected, strip it (see Security Considerations section below)
8403
8627
  * - if json response is detected, deserialize it using a JSON parser
8404
8628
  *
8405
- * To override these transformation locally, specify transform functions as `transformRequest`
8406
- * and/or `transformResponse` properties of the config object. To globally override the default
8407
- * transforms, override the `$httpProvider.defaults.transformRequest` and
8408
- * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`.
8629
+ * To globally augment or override the default transforms, modify the `$httpProvider.defaults.transformRequest` and
8630
+ * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`. These properties are by default an
8631
+ * array of transform functions, which allows you to `push` or `unshift` a new transformation function into the
8632
+ * transformation chain. You can also decide to completely override any default transformations by assigning your
8633
+ * transformation functions to these properties directly without the array wrapper.
8634
+ *
8635
+ * Similarly, to locally override the request/response transforms, augment the `transformRequest` and/or
8636
+ * `transformResponse` properties of the config object passed into `$http`.
8409
8637
  *
8410
8638
  *
8411
8639
  * # Caching
@@ -8923,6 +9151,7 @@ function $HttpProvider() {
8923
9151
 
8924
9152
  }];
8925
9153
  }
9154
+
8926
9155
  var XHR = window.XMLHttpRequest || function() {
8927
9156
  try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
8928
9157
  try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
@@ -8990,8 +9219,30 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
8990
9219
  // always async
8991
9220
  xhr.onreadystatechange = function() {
8992
9221
  if (xhr.readyState == 4) {
8993
- completeRequest(
8994
- callback, status || xhr.status, xhr.responseText, xhr.getAllResponseHeaders());
9222
+ var responseHeaders = xhr.getAllResponseHeaders();
9223
+
9224
+ // TODO(vojta): remove once Firefox 21 gets released.
9225
+ // begin: workaround to overcome Firefox CORS http response headers bug
9226
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=608735
9227
+ // Firefox already patched in nightly. Should land in Firefox 21.
9228
+
9229
+ // CORS "simple response headers" http://www.w3.org/TR/cors/
9230
+ var value,
9231
+ simpleHeaders = ["Cache-Control", "Content-Language", "Content-Type",
9232
+ "Expires", "Last-Modified", "Pragma"];
9233
+ if (!responseHeaders) {
9234
+ responseHeaders = "";
9235
+ forEach(simpleHeaders, function (header) {
9236
+ var value = xhr.getResponseHeader(header);
9237
+ if (value) {
9238
+ responseHeaders += header + ": " + value + "\n";
9239
+ }
9240
+ });
9241
+ }
9242
+ // end of the workaround.
9243
+
9244
+ completeRequest(callback, status || xhr.status, xhr.responseText,
9245
+ responseHeaders);
8995
9246
  }
8996
9247
  };
8997
9248
 
@@ -9149,7 +9400,7 @@ function $TimeoutProvider() {
9149
9400
  * @param {number=} [delay=0] Delay in milliseconds.
9150
9401
  * @param {boolean=} [invokeApply=true] If set to false skips model dirty checking, otherwise
9151
9402
  * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
9152
- * @returns {*} Promise that will be resolved when the timeout is reached. The value this
9403
+ * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
9153
9404
  * promise will be resolved with is the return value of the `fn` function.
9154
9405
  */
9155
9406
  function timeout(fn, delay, invokeApply) {
@@ -9213,7 +9464,7 @@ function $TimeoutProvider() {
9213
9464
  *
9214
9465
  * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To
9215
9466
  * achieve this a filter definition consists of a factory function which is annotated with dependencies and is
9216
- * responsible for creating a the filter function.
9467
+ * responsible for creating a filter function.
9217
9468
  *
9218
9469
  * <pre>
9219
9470
  * // Filter registration
@@ -9351,22 +9602,22 @@ function $FilterProvider($provide) {
9351
9602
 
9352
9603
  Search: <input ng-model="searchText">
9353
9604
  <table id="searchTextResults">
9354
- <tr><th>Name</th><th>Phone</th><tr>
9605
+ <tr><th>Name</th><th>Phone</th></tr>
9355
9606
  <tr ng-repeat="friend in friends | filter:searchText">
9356
9607
  <td>{{friend.name}}</td>
9357
9608
  <td>{{friend.phone}}</td>
9358
- <tr>
9609
+ </tr>
9359
9610
  </table>
9360
9611
  <hr>
9361
9612
  Any: <input ng-model="search.$"> <br>
9362
9613
  Name only <input ng-model="search.name"><br>
9363
9614
  Phone only <input ng-model="search.phone"å><br>
9364
9615
  <table id="searchObjResults">
9365
- <tr><th>Name</th><th>Phone</th><tr>
9616
+ <tr><th>Name</th><th>Phone</th></tr>
9366
9617
  <tr ng-repeat="friend in friends | filter:search">
9367
9618
  <td>{{friend.name}}</td>
9368
9619
  <td>{{friend.phone}}</td>
9369
- <tr>
9620
+ </tr>
9370
9621
  </table>
9371
9622
  </doc:source>
9372
9623
  <doc:scenario>
@@ -9390,7 +9641,7 @@ function $FilterProvider($provide) {
9390
9641
  */
9391
9642
  function filterFilter() {
9392
9643
  return function(array, expression) {
9393
- if (!(array instanceof Array)) return array;
9644
+ if (!isArray(array)) return array;
9394
9645
  var predicates = [];
9395
9646
  predicates.check = function(value) {
9396
9647
  for (var j = 0; j < predicates.length; j++) {
@@ -9588,9 +9839,18 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
9588
9839
  formatedText = '',
9589
9840
  parts = [];
9590
9841
 
9842
+ var hasExponent = false;
9591
9843
  if (numStr.indexOf('e') !== -1) {
9592
- formatedText = numStr;
9593
- } else {
9844
+ var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
9845
+ if (match && match[2] == '-' && match[3] > fractionSize + 1) {
9846
+ numStr = '0';
9847
+ } else {
9848
+ formatedText = numStr;
9849
+ hasExponent = true;
9850
+ }
9851
+ }
9852
+
9853
+ if (!hasExponent) {
9594
9854
  var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
9595
9855
 
9596
9856
  // determine fractionSize if it is not specified
@@ -9630,7 +9890,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
9630
9890
  fraction += '0';
9631
9891
  }
9632
9892
 
9633
- if (fractionSize) formatedText += decimalSep + fraction.substr(0, fractionSize);
9893
+ if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
9634
9894
  }
9635
9895
 
9636
9896
  parts.push(isNegative ? pattern.negPre : pattern.posPre);
@@ -9673,8 +9933,13 @@ function dateStrGetter(name, shortForm) {
9673
9933
  }
9674
9934
 
9675
9935
  function timeZoneGetter(date) {
9676
- var offset = date.getTimezoneOffset();
9677
- return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
9936
+ var zone = -1 * date.getTimezoneOffset();
9937
+ var paddedZone = (zone >= 0) ? "+" : "";
9938
+
9939
+ paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
9940
+ padNumber(Math.abs(zone % 60), 2);
9941
+
9942
+ return paddedZone;
9678
9943
  }
9679
9944
 
9680
9945
  function ampmGetter(date, formats) {
@@ -9738,7 +10003,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
9738
10003
  * * `'ss'`: Second in minute, padded (00-59)
9739
10004
  * * `'s'`: Second in minute (0-59)
9740
10005
  * * `'a'`: am/pm marker
9741
- * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200)
10006
+ * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
9742
10007
  *
9743
10008
  * `format` string can also be one of the following predefined
9744
10009
  * {@link guide/i18n localizable formats}:
@@ -9760,7 +10025,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
9760
10025
  *
9761
10026
  * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
9762
10027
  * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's
9763
- * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ).
10028
+ * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
10029
+ * specified in the string input, the time is considered to be in the local timezone.
9764
10030
  * @param {string=} format Formatting rules (see Description). If not specified,
9765
10031
  * `mediumDate` is used.
9766
10032
  * @returns {string} Formatted string or the input if input is not recognized as date/millis.
@@ -9780,7 +10046,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
9780
10046
  expect(binding("1288323623006 | date:'medium'")).
9781
10047
  toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
9782
10048
  expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
9783
- toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
10049
+ toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
9784
10050
  expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
9785
10051
  toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
9786
10052
  });
@@ -9791,7 +10057,7 @@ dateFilter.$inject = ['$locale'];
9791
10057
  function dateFilter($locale) {
9792
10058
 
9793
10059
 
9794
- var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
10060
+ var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
9795
10061
  function jsonStringToDate(string){
9796
10062
  var match;
9797
10063
  if (match = string.match(R_ISO8601_STR)) {
@@ -10049,12 +10315,12 @@ function limitToFilter(){
10049
10315
  (<a href ng-click="predicate = '-name'; reverse=false">^</a>)</th>
10050
10316
  <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th>
10051
10317
  <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th>
10052
- <tr>
10318
+ </tr>
10053
10319
  <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
10054
10320
  <td>{{friend.name}}</td>
10055
10321
  <td>{{friend.phone}}</td>
10056
10322
  <td>{{friend.age}}</td>
10057
- <tr>
10323
+ </tr>
10058
10324
  </table>
10059
10325
  </div>
10060
10326
  </doc:source>
@@ -10086,7 +10352,7 @@ function limitToFilter(){
10086
10352
  orderByFilter.$inject = ['$parse'];
10087
10353
  function orderByFilter($parse){
10088
10354
  return function(array, sortPredicate, reverseOrder) {
10089
- if (!(array instanceof Array)) return array;
10355
+ if (!isArray(array)) return array;
10090
10356
  if (!sortPredicate) return array;
10091
10357
  sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
10092
10358
  sortPredicate = map(sortPredicate, function(predicate){
@@ -10154,15 +10420,25 @@ function ngDirective(directive) {
10154
10420
  *
10155
10421
  * The reasoning for this change is to allow easy creation of action links with `ngClick` directive
10156
10422
  * without changing the location or causing page reloads, e.g.:
10157
- * <a href="" ng-click="model.$save()">Save</a>
10423
+ * `<a href="" ng-click="model.$save()">Save</a>`
10158
10424
  */
10159
10425
  var htmlAnchorDirective = valueFn({
10160
10426
  restrict: 'E',
10161
10427
  compile: function(element, attr) {
10162
- // turn <a href ng-click="..">link</a> into a link in IE
10163
- // but only if it doesn't have name attribute, in which case it's an anchor
10164
- if (!attr.href) {
10165
- attr.$set('href', '');
10428
+
10429
+ if (msie <= 8) {
10430
+
10431
+ // turn <a href ng-click="..">link</a> into a stylable link in IE
10432
+ // but only if it doesn't have name attribute, in which case it's an anchor
10433
+ if (!attr.href && !attr.name) {
10434
+ attr.$set('href', '');
10435
+ }
10436
+
10437
+ // add a comment node to anchors to workaround IE bug that causes element content to be reset
10438
+ // to new attribute content if attribute is updated with value containing @ and element also
10439
+ // contains value with @
10440
+ // see issue #1949
10441
+ element.append(document.createComment('IE fix'));
10166
10442
  }
10167
10443
 
10168
10444
  return function(scope, element) {
@@ -10242,7 +10518,7 @@ var htmlAnchorDirective = valueFn({
10242
10518
  it('should execute ng-click but not reload when no href but name specified', function() {
10243
10519
  element('#link-5').click();
10244
10520
  expect(input('value').val()).toEqual('5');
10245
- expect(element('#link-5').attr('href')).toBe('');
10521
+ expect(element('#link-5').attr('href')).toBe(undefined);
10246
10522
  });
10247
10523
 
10248
10524
  it('should only change url when only ng-href', function() {
@@ -10460,7 +10736,7 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) {
10460
10736
  priority: 100,
10461
10737
  compile: function() {
10462
10738
  return function(scope, element, attr) {
10463
- scope.$watch(attr[normalized], function(value) {
10739
+ scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
10464
10740
  attr.$set(attrName, !!value);
10465
10741
  });
10466
10742
  };
@@ -10478,12 +10754,16 @@ forEach(['src', 'href'], function(attrName) {
10478
10754
  priority: 99, // it needs to run after the attributes are interpolated
10479
10755
  link: function(scope, element, attr) {
10480
10756
  attr.$observe(normalized, function(value) {
10757
+ if (!value)
10758
+ return;
10759
+
10481
10760
  attr.$set(attrName, value);
10482
10761
 
10483
10762
  // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
10484
10763
  // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
10485
- // to set the property as well to achieve the desired effect
10486
- if (msie) element.prop(attrName, value);
10764
+ // to set the property as well to achieve the desired effect.
10765
+ // we use attr[attrName] value since $set can sanitize the url.
10766
+ if (msie) element.prop(attrName, attr[attrName]);
10487
10767
  });
10488
10768
  }
10489
10769
  };
@@ -10503,13 +10783,13 @@ var nullFormCtrl = {
10503
10783
  *
10504
10784
  * @property {boolean} $pristine True if user has not interacted with the form yet.
10505
10785
  * @property {boolean} $dirty True if user has already interacted with the form.
10506
- * @property {boolean} $valid True if all of the containg forms and controls are valid.
10786
+ * @property {boolean} $valid True if all of the containing forms and controls are valid.
10507
10787
  * @property {boolean} $invalid True if at least one containing control or form is invalid.
10508
10788
  *
10509
10789
  * @property {Object} $error Is an object hash, containing references to all invalid controls or
10510
10790
  * forms, where:
10511
10791
  *
10512
- * - keys are validation tokens (error names) — such as `REQUIRED`, `URL` or `EMAIL`),
10792
+ * - keys are validation tokens (error names) — such as `required`, `url` or `email`),
10513
10793
  * - values are arrays of controls or forms that are invalid with given error.
10514
10794
  *
10515
10795
  * @description
@@ -10606,6 +10886,7 @@ function FormController(element, attrs) {
10606
10886
  element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
10607
10887
  form.$dirty = true;
10608
10888
  form.$pristine = false;
10889
+ parentForm.$setDirty();
10609
10890
  };
10610
10891
 
10611
10892
  }
@@ -10621,7 +10902,7 @@ function FormController(element, attrs) {
10621
10902
  * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
10622
10903
  * sub-group of controls needs to be determined.
10623
10904
  *
10624
- * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
10905
+ * @param {string=} name|ngForm Name of the form. If specified, the form controller will be published into
10625
10906
  * related scope, under this name.
10626
10907
  *
10627
10908
  */
@@ -10694,12 +10975,12 @@ function FormController(element, attrs) {
10694
10975
  </script>
10695
10976
  <form name="myForm" ng-controller="Ctrl">
10696
10977
  userType: <input name="input" ng-model="userType" required>
10697
- <span class="error" ng-show="myForm.input.$error.REQUIRED">Required!</span><br>
10978
+ <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
10698
10979
  <tt>userType = {{userType}}</tt><br>
10699
10980
  <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br>
10700
10981
  <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br>
10701
10982
  <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
10702
- <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br>
10983
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
10703
10984
  </form>
10704
10985
  </doc:source>
10705
10986
  <doc:scenario>
@@ -10791,7 +11072,10 @@ var inputType = {
10791
11072
  *
10792
11073
  * @param {string} ngModel Assignable angular expression to data-bind to.
10793
11074
  * @param {string=} name Property name of the form under which the control is published.
10794
- * @param {string=} required Sets `required` validation error key if the value is not entered.
11075
+ * @param {string=} required Adds `required` validation error key if the value is not entered.
11076
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
11077
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
11078
+ * `required` when you want to data-bind to the `required` attribute.
10795
11079
  * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
10796
11080
  * minlength.
10797
11081
  * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
@@ -10861,6 +11145,9 @@ var inputType = {
10861
11145
  * @param {string=} min Sets the `min` validation error key if the value entered is less then `min`.
10862
11146
  * @param {string=} max Sets the `max` validation error key if the value entered is greater then `min`.
10863
11147
  * @param {string=} required Sets `required` validation error key if the value is not entered.
11148
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
11149
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
11150
+ * `required` when you want to data-bind to the `required` attribute.
10864
11151
  * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
10865
11152
  * minlength.
10866
11153
  * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
@@ -10927,6 +11214,9 @@ var inputType = {
10927
11214
  * @param {string} ngModel Assignable angular expression to data-bind to.
10928
11215
  * @param {string=} name Property name of the form under which the control is published.
10929
11216
  * @param {string=} required Sets `required` validation error key if the value is not entered.
11217
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
11218
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
11219
+ * `required` when you want to data-bind to the `required` attribute.
10930
11220
  * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
10931
11221
  * minlength.
10932
11222
  * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
@@ -10992,6 +11282,9 @@ var inputType = {
10992
11282
  * @param {string} ngModel Assignable angular expression to data-bind to.
10993
11283
  * @param {string=} name Property name of the form under which the control is published.
10994
11284
  * @param {string=} required Sets `required` validation error key if the value is not entered.
11285
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
11286
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
11287
+ * `required` when you want to data-bind to the `required` attribute.
10995
11288
  * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
10996
11289
  * minlength.
10997
11290
  * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
@@ -11414,6 +11707,9 @@ function checkboxInputType(scope, element, attr, ctrl) {
11414
11707
  * @param {string} ngModel Assignable angular expression to data-bind to.
11415
11708
  * @param {string=} name Property name of the form under which the control is published.
11416
11709
  * @param {string=} required Sets `required` validation error key if the value is not entered.
11710
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
11711
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
11712
+ * `required` when you want to data-bind to the `required` attribute.
11417
11713
  * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
11418
11714
  * minlength.
11419
11715
  * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
@@ -11438,6 +11734,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
11438
11734
  * @param {string} ngModel Assignable angular expression to data-bind to.
11439
11735
  * @param {string=} name Property name of the form under which the control is published.
11440
11736
  * @param {string=} required Sets `required` validation error key if the value is not entered.
11737
+ * @param {boolean=} ngRequired Sets `required` attribute if set to true
11441
11738
  * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
11442
11739
  * minlength.
11443
11740
  * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
@@ -11771,22 +12068,25 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
11771
12068
 
11772
12069
  // model -> value
11773
12070
  var ctrl = this;
11774
- $scope.$watch(ngModelGet, function(value) {
11775
12071
 
11776
- // ignore change from view
11777
- if (ctrl.$modelValue === value) return;
12072
+ $scope.$watch(function ngModelWatch() {
12073
+ var value = ngModelGet($scope);
11778
12074
 
11779
- var formatters = ctrl.$formatters,
11780
- idx = formatters.length;
12075
+ // if scope model value and ngModel value are out of sync
12076
+ if (ctrl.$modelValue !== value) {
11781
12077
 
11782
- ctrl.$modelValue = value;
11783
- while(idx--) {
11784
- value = formatters[idx](value);
11785
- }
12078
+ var formatters = ctrl.$formatters,
12079
+ idx = formatters.length;
11786
12080
 
11787
- if (ctrl.$viewValue !== value) {
11788
- ctrl.$viewValue = value;
11789
- ctrl.$render();
12081
+ ctrl.$modelValue = value;
12082
+ while(idx--) {
12083
+ value = formatters[idx](value);
12084
+ }
12085
+
12086
+ if (ctrl.$viewValue !== value) {
12087
+ ctrl.$viewValue = value;
12088
+ ctrl.$render();
12089
+ }
11790
12090
  }
11791
12091
  });
11792
12092
  }];
@@ -11935,7 +12235,7 @@ var requiredDirective = function() {
11935
12235
  * @name ng.directive:ngList
11936
12236
  *
11937
12237
  * @description
11938
- * Text input that converts between comma-seperated string into an array of strings.
12238
+ * Text input that converts between comma-separated string into an array of strings.
11939
12239
  *
11940
12240
  * @element input
11941
12241
  * @param {string=} ngList optional delimiter that should be used to split the value. If
@@ -12018,7 +12318,7 @@ var ngValueDirective = function() {
12018
12318
  };
12019
12319
  } else {
12020
12320
  return function(scope, elm, attr) {
12021
- scope.$watch(attr.ngValue, function(value) {
12321
+ scope.$watch(attr.ngValue, function valueWatchAction(value) {
12022
12322
  attr.$set('value', value, false);
12023
12323
  });
12024
12324
  };
@@ -12076,7 +12376,7 @@ var ngValueDirective = function() {
12076
12376
  */
12077
12377
  var ngBindDirective = ngDirective(function(scope, element, attr) {
12078
12378
  element.addClass('ng-binding').data('$binding', attr.ngBind);
12079
- scope.$watch(attr.ngBind, function(value) {
12379
+ scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
12080
12380
  element.text(value == undefined ? '' : value);
12081
12381
  });
12082
12382
  });
@@ -12159,7 +12459,7 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
12159
12459
  var ngBindHtmlUnsafeDirective = [function() {
12160
12460
  return function(scope, element, attr) {
12161
12461
  element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe);
12162
- scope.$watch(attr.ngBindHtmlUnsafe, function(value) {
12462
+ scope.$watch(attr.ngBindHtmlUnsafe, function ngBindHtmlUnsafeWatchAction(value) {
12163
12463
  element.html(value || '');
12164
12464
  });
12165
12465
  };
@@ -12168,17 +12468,57 @@ var ngBindHtmlUnsafeDirective = [function() {
12168
12468
  function classDirective(name, selector) {
12169
12469
  name = 'ngClass' + name;
12170
12470
  return ngDirective(function(scope, element, attr) {
12171
- scope.$watch(attr[name], function(newVal, oldVal) {
12471
+ var oldVal = undefined;
12472
+
12473
+ scope.$watch(attr[name], ngClassWatchAction, true);
12474
+
12475
+ attr.$observe('class', function(value) {
12476
+ var ngClass = scope.$eval(attr[name]);
12477
+ ngClassWatchAction(ngClass, ngClass);
12478
+ });
12479
+
12480
+
12481
+ if (name !== 'ngClass') {
12482
+ scope.$watch('$index', function($index, old$index) {
12483
+ var mod = $index % 2;
12484
+ if (mod !== old$index % 2) {
12485
+ if (mod == selector) {
12486
+ addClass(scope.$eval(attr[name]));
12487
+ } else {
12488
+ removeClass(scope.$eval(attr[name]));
12489
+ }
12490
+ }
12491
+ });
12492
+ }
12493
+
12494
+
12495
+ function ngClassWatchAction(newVal) {
12172
12496
  if (selector === true || scope.$index % 2 === selector) {
12173
12497
  if (oldVal && (newVal !== oldVal)) {
12174
- if (isObject(oldVal) && !isArray(oldVal))
12175
- oldVal = map(oldVal, function(v, k) { if (v) return k });
12176
- element.removeClass(isArray(oldVal) ? oldVal.join(' ') : oldVal);
12177
- }
12178
- if (isObject(newVal) && !isArray(newVal))
12179
- newVal = map(newVal, function(v, k) { if (v) return k });
12180
- if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal); }
12181
- }, true);
12498
+ removeClass(oldVal);
12499
+ }
12500
+ addClass(newVal);
12501
+ }
12502
+ oldVal = newVal;
12503
+ }
12504
+
12505
+
12506
+ function removeClass(classVal) {
12507
+ if (isObject(classVal) && !isArray(classVal)) {
12508
+ classVal = map(classVal, function(v, k) { if (v) return k });
12509
+ }
12510
+ element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal);
12511
+ }
12512
+
12513
+
12514
+ function addClass(classVal) {
12515
+ if (isObject(classVal) && !isArray(classVal)) {
12516
+ classVal = map(classVal, function(v, k) { if (v) return k });
12517
+ }
12518
+ if (classVal) {
12519
+ element.addClass(isArray(classVal) ? classVal.join(' ') : classVal);
12520
+ }
12521
+ }
12182
12522
  });
12183
12523
  }
12184
12524
 
@@ -12192,7 +12532,7 @@ function classDirective(name, selector) {
12192
12532
  *
12193
12533
  * The directive won't add duplicate classes if a particular class was already set.
12194
12534
  *
12195
- * When the expression changes, the previously added classes are removed and only then the classes
12535
+ * When the expression changes, the previously added classes are removed and only then the
12196
12536
  * new classes are added.
12197
12537
  *
12198
12538
  * @element ANY
@@ -12343,7 +12683,7 @@ var ngClassEvenDirective = classDirective('Even', 1);
12343
12683
  * `angular.min.js` files. Following is the css rule:
12344
12684
  *
12345
12685
  * <pre>
12346
- * [ng\:cloak], [ng-cloak], .ng-cloak {
12686
+ * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
12347
12687
  * display: none;
12348
12688
  * }
12349
12689
  * </pre>
@@ -12837,7 +13177,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
12837
13177
  element.html('');
12838
13178
  };
12839
13179
 
12840
- scope.$watch(srcExp, function(src) {
13180
+ scope.$watch(srcExp, function ngIncludeWatchAction(src) {
12841
13181
  var thisChangeId = ++changeCounter;
12842
13182
 
12843
13183
  if (src) {
@@ -13122,7 +13462,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
13122
13462
  offset + endSymbol));
13123
13463
  });
13124
13464
 
13125
- scope.$watch(function() {
13465
+ scope.$watch(function ngPluralizeWatch() {
13126
13466
  var value = parseFloat(scope.$eval(numberExp));
13127
13467
 
13128
13468
  if (!isNaN(value)) {
@@ -13133,7 +13473,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
13133
13473
  } else {
13134
13474
  return '';
13135
13475
  }
13136
- }, function(newVal) {
13476
+ }, function ngPluralizeWatchAction(newVal) {
13137
13477
  element.text(newVal);
13138
13478
  });
13139
13479
  }
@@ -13228,17 +13568,21 @@ var ngRepeatDirective = ngDirective({
13228
13568
  // We need an array of these objects since the same object can be returned from the iterator.
13229
13569
  // We expect this to be a rare case.
13230
13570
  var lastOrder = new HashQueueMap();
13231
- scope.$watch(function(scope){
13571
+
13572
+ scope.$watch(function ngRepeatWatch(scope){
13232
13573
  var index, length,
13233
13574
  collection = scope.$eval(rhs),
13234
- collectionLength = size(collection, true),
13235
- childScope,
13575
+ cursor = iterStartElement, // current position of the node
13236
13576
  // Same as lastOrder but it has the current state. It will become the
13237
13577
  // lastOrder on the next iteration.
13238
13578
  nextOrder = new HashQueueMap(),
13579
+ arrayBound,
13580
+ childScope,
13239
13581
  key, value, // key/value of iteration
13240
- array, last, // last object information {scope, element, index}
13241
- cursor = iterStartElement; // current position of the node
13582
+ array,
13583
+ last; // last object information {scope, element, index}
13584
+
13585
+
13242
13586
 
13243
13587
  if (!isArray(collection)) {
13244
13588
  // if object, extract keys, sort them and use to determine order of iteration over obj props
@@ -13253,11 +13597,15 @@ var ngRepeatDirective = ngDirective({
13253
13597
  array = collection || [];
13254
13598
  }
13255
13599
 
13600
+ arrayBound = array.length-1;
13601
+
13256
13602
  // we are not using forEach for perf reasons (trying to avoid #call)
13257
13603
  for (index = 0, length = array.length; index < length; index++) {
13258
13604
  key = (collection === array) ? index : array[index];
13259
13605
  value = collection[key];
13606
+
13260
13607
  last = lastOrder.shift(value);
13608
+
13261
13609
  if (last) {
13262
13610
  // if we have already seen this object, then we need to reuse the
13263
13611
  // associated scope/element
@@ -13286,7 +13634,7 @@ var ngRepeatDirective = ngDirective({
13286
13634
  childScope.$index = index;
13287
13635
 
13288
13636
  childScope.$first = (index === 0);
13289
- childScope.$last = (index === (collectionLength - 1));
13637
+ childScope.$last = (index === arrayBound);
13290
13638
  childScope.$middle = !(childScope.$first || childScope.$last);
13291
13639
 
13292
13640
  if (!last) {
@@ -13354,7 +13702,7 @@ var ngRepeatDirective = ngDirective({
13354
13702
  */
13355
13703
  //TODO(misko): refactor to remove element from the DOM
13356
13704
  var ngShowDirective = ngDirective(function(scope, element, attr){
13357
- scope.$watch(attr.ngShow, function(value){
13705
+ scope.$watch(attr.ngShow, function ngShowWatchAction(value){
13358
13706
  element.css('display', toBoolean(value) ? '' : 'none');
13359
13707
  });
13360
13708
  });
@@ -13365,11 +13713,11 @@ var ngShowDirective = ngDirective(function(scope, element, attr){
13365
13713
  * @name ng.directive:ngHide
13366
13714
  *
13367
13715
  * @description
13368
- * The `ngHide` and `ngShow` directives hide or show a portion
13369
- * of the HTML conditionally.
13716
+ * The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML)
13717
+ * conditionally.
13370
13718
  *
13371
13719
  * @element ANY
13372
- * @param {expression} ngHide If the {@link guide/expression expression} truthy then
13720
+ * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
13373
13721
  * the element is shown or hidden respectively.
13374
13722
  *
13375
13723
  * @example
@@ -13394,7 +13742,7 @@ var ngShowDirective = ngDirective(function(scope, element, attr){
13394
13742
  */
13395
13743
  //TODO(misko): refactor to remove element from the DOM
13396
13744
  var ngHideDirective = ngDirective(function(scope, element, attr){
13397
- scope.$watch(attr.ngHide, function(value){
13745
+ scope.$watch(attr.ngHide, function ngHideWatchAction(value){
13398
13746
  element.css('display', toBoolean(value) ? 'none' : '');
13399
13747
  });
13400
13748
  });
@@ -13437,7 +13785,7 @@ var ngHideDirective = ngDirective(function(scope, element, attr){
13437
13785
  </example>
13438
13786
  */
13439
13787
  var ngStyleDirective = ngDirective(function(scope, element, attr) {
13440
- scope.$watch(attr.ngStyle, function(newStyles, oldStyles) {
13788
+ scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
13441
13789
  if (oldStyles && (newStyles !== oldStyles)) {
13442
13790
  forEach(oldStyles, function(val, style) { element.css(style, '');});
13443
13791
  }
@@ -13453,11 +13801,13 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
13453
13801
  * @description
13454
13802
  * Conditionally change the DOM structure.
13455
13803
  *
13456
- * @usageContent
13457
- * <ANY ng-switch-when="matchValue1">...</ANY>
13804
+ * @usage
13805
+ * <ANY ng-switch="expression">
13806
+ * <ANY ng-switch-when="matchValue1">...</ANY>
13458
13807
  * <ANY ng-switch-when="matchValue2">...</ANY>
13459
13808
  * ...
13460
13809
  * <ANY ng-switch-default>...</ANY>
13810
+ * </ANY>
13461
13811
  *
13462
13812
  * @scope
13463
13813
  * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
@@ -13507,52 +13857,54 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
13507
13857
  var NG_SWITCH = 'ng-switch';
13508
13858
  var ngSwitchDirective = valueFn({
13509
13859
  restrict: 'EA',
13510
- compile: function(element, attr) {
13860
+ require: 'ngSwitch',
13861
+ // asks for $scope to fool the BC controller module
13862
+ controller: ['$scope', function ngSwitchController() {
13863
+ this.cases = {};
13864
+ }],
13865
+ link: function(scope, element, attr, ctrl) {
13511
13866
  var watchExpr = attr.ngSwitch || attr.on,
13512
- cases = {};
13513
-
13514
- element.data(NG_SWITCH, cases);
13515
- return function(scope, element){
13516
- var selectedTransclude,
13517
- selectedElement,
13518
- selectedScope;
13519
-
13520
- scope.$watch(watchExpr, function(value) {
13521
- if (selectedElement) {
13522
- selectedScope.$destroy();
13523
- selectedElement.remove();
13524
- selectedElement = selectedScope = null;
13525
- }
13526
- if ((selectedTransclude = cases['!' + value] || cases['?'])) {
13527
- scope.$eval(attr.change);
13528
- selectedScope = scope.$new();
13529
- selectedTransclude(selectedScope, function(caseElement) {
13530
- selectedElement = caseElement;
13531
- element.append(caseElement);
13532
- });
13533
- }
13534
- });
13535
- };
13867
+ selectedTransclude,
13868
+ selectedElement,
13869
+ selectedScope;
13870
+
13871
+ scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
13872
+ if (selectedElement) {
13873
+ selectedScope.$destroy();
13874
+ selectedElement.remove();
13875
+ selectedElement = selectedScope = null;
13876
+ }
13877
+ if ((selectedTransclude = ctrl.cases['!' + value] || ctrl.cases['?'])) {
13878
+ scope.$eval(attr.change);
13879
+ selectedScope = scope.$new();
13880
+ selectedTransclude(selectedScope, function(caseElement) {
13881
+ selectedElement = caseElement;
13882
+ element.append(caseElement);
13883
+ });
13884
+ }
13885
+ });
13536
13886
  }
13537
13887
  });
13538
13888
 
13539
13889
  var ngSwitchWhenDirective = ngDirective({
13540
13890
  transclude: 'element',
13541
13891
  priority: 500,
13892
+ require: '^ngSwitch',
13542
13893
  compile: function(element, attrs, transclude) {
13543
- var cases = element.inheritedData(NG_SWITCH);
13544
- assertArg(cases);
13545
- cases['!' + attrs.ngSwitchWhen] = transclude;
13894
+ return function(scope, element, attr, ctrl) {
13895
+ ctrl.cases['!' + attrs.ngSwitchWhen] = transclude;
13896
+ };
13546
13897
  }
13547
13898
  });
13548
13899
 
13549
13900
  var ngSwitchDefaultDirective = ngDirective({
13550
13901
  transclude: 'element',
13551
13902
  priority: 500,
13903
+ require: '^ngSwitch',
13552
13904
  compile: function(element, attrs, transclude) {
13553
- var cases = element.inheritedData(NG_SWITCH);
13554
- assertArg(cases);
13555
- cases['?'] = transclude;
13905
+ return function(scope, element, attr, ctrl) {
13906
+ ctrl.cases['?'] = transclude;
13907
+ };
13556
13908
  }
13557
13909
  });
13558
13910
 
@@ -13641,7 +13993,7 @@ var ngTranscludeDirective = ngDirective({
13641
13993
  <hr />
13642
13994
 
13643
13995
  <pre>$location.path() = {{$location.path()}}</pre>
13644
- <pre>$route.current.template = {{$route.current.template}}</pre>
13996
+ <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
13645
13997
  <pre>$route.current.params = {{$route.current.params}}</pre>
13646
13998
  <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
13647
13999
  <pre>$routeParams = {{$routeParams}}</pre>
@@ -13760,7 +14112,7 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
13760
14112
  if (current.controller) {
13761
14113
  locals.$scope = lastScope;
13762
14114
  controller = $controller(current.controller, locals);
13763
- element.contents().data('$ngControllerController', controller);
14115
+ element.children().data('$ngControllerController', controller);
13764
14116
  }
13765
14117
 
13766
14118
  link(lastScope);
@@ -13851,6 +14203,9 @@ var scriptDirective = ['$templateCache', function($templateCache) {
13851
14203
  *
13852
14204
  * @param {string} name assignable expression to data-bind to.
13853
14205
  * @param {string=} required The control is considered valid only if value is entered.
14206
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
14207
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
14208
+ * `required` when you want to data-bind to the `required` attribute.
13854
14209
  * @param {comprehension_expression=} ngOptions in one of the following forms:
13855
14210
  *
13856
14211
  * * for array data sources:
@@ -13941,7 +14296,7 @@ var scriptDirective = ['$templateCache', function($templateCache) {
13941
14296
 
13942
14297
  var ngOptionsDirective = valueFn({ terminal: true });
13943
14298
  var selectDirective = ['$compile', '$parse', function($compile, $parse) {
13944
- //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777
14299
+ //0000111110000000000022220000000000000000000000333300000000000000444444444444444440000000005555555555555555500000006666666666666666600000000000000077770
13945
14300
  var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/,
13946
14301
  nullModelCtrl = {$setViewValue: noop};
13947
14302
 
@@ -14084,14 +14439,14 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
14084
14439
  var lastView;
14085
14440
  ctrl.$render = function() {
14086
14441
  var items = new HashMap(ctrl.$viewValue);
14087
- forEach(selectElement.children(), function(option) {
14442
+ forEach(selectElement.find('option'), function(option) {
14088
14443
  option.selected = isDefined(items.get(option.value));
14089
14444
  });
14090
14445
  };
14091
14446
 
14092
14447
  // we have to do it on each watch since ngModel watches reference, but
14093
14448
  // we need to work of an array, so we need to see if anything was inserted/removed
14094
- scope.$watch(function() {
14449
+ scope.$watch(function selectMultipleWatch() {
14095
14450
  if (!equals(lastView, ctrl.$viewValue)) {
14096
14451
  lastView = copy(ctrl.$viewValue);
14097
14452
  ctrl.$render();
@@ -14101,7 +14456,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
14101
14456
  selectElement.bind('change', function() {
14102
14457
  scope.$apply(function() {
14103
14458
  var array = [];
14104
- forEach(selectElement.children(), function(option) {
14459
+ forEach(selectElement.find('option'), function(option) {
14105
14460
  if (option.selected) {
14106
14461
  array.push(option.value);
14107
14462
  }
@@ -14208,7 +14563,8 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
14208
14563
  selected,
14209
14564
  selectedSet = false, // nothing is selected yet
14210
14565
  lastElement,
14211
- element;
14566
+ element,
14567
+ label;
14212
14568
 
14213
14569
  if (multiple) {
14214
14570
  selectedSet = new HashMap(modelValue);
@@ -14232,9 +14588,11 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
14232
14588
  selected = modelValue === valueFn(scope, locals);
14233
14589
  selectedSet = selectedSet || selected; // see if at least one item is selected
14234
14590
  }
14591
+ label = displayFn(scope, locals); // what will be seen by the user
14592
+ label = label === undefined ? '' : label; // doing displayFn(scope, locals) || '' overwrites zero values
14235
14593
  optionGroup.push({
14236
14594
  id: keyName ? keys[index] : index, // either the index into array or key from object
14237
- label: displayFn(scope, locals) || '', // what will be seen by the user
14595
+ label: label,
14238
14596
  selected: selected // determine if we should be selected
14239
14597
  });
14240
14598
  }
@@ -14366,7 +14724,7 @@ var optionDirective = ['$interpolate', function($interpolate) {
14366
14724
  }
14367
14725
 
14368
14726
  if (interpolateFn) {
14369
- scope.$watch(interpolateFn, function(newVal, oldVal) {
14727
+ scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
14370
14728
  attr.$set('value', newVal);
14371
14729
  if (newVal !== oldVal) selectCtrl.removeOption(oldVal);
14372
14730
  selectCtrl.addOption(newVal);
@@ -14387,6 +14745,7 @@ var styleDirective = valueFn({
14387
14745
  restrict: 'E',
14388
14746
  terminal: true
14389
14747
  });
14748
+
14390
14749
  //try to bind to jquery now so that one can write angular.element().read()
14391
14750
  //but we will rebind on bootstrap again.
14392
14751
  bindJQuery();