angular-gem 1.1.3 → 1.1.4

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.1.3
2
+ * @license AngularJS v1.1.4
3
3
  * (c) 2010-2012 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -24,14 +24,23 @@
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
- * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
27
+ * # Installation
28
+ * To use $resource make sure you have included the `angular-resource.js` that comes in Angular
29
+ * package. You also can find this stuff in {@link http://code.angularjs.org/ code.angularjs.org}.
30
+ * Finally load the module in your application:
31
+ *
32
+ * angular.module('app', ['ngResource']);
33
+ *
34
+ * and you ready to get started!
35
+ *
36
+ * @param {string} url A parametrized URL template with parameters prefixed by `:` as in
28
37
  * `/user/:username`. If you are using a URL with a port number (e.g.
29
38
  * `http://example.com:8080/api`), you'll need to escape the colon character before the port
30
39
  * number, like this: `$resource('http://example.com\\:8080/api')`.
31
40
  *
32
41
  * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
33
42
  * `actions` methods. If any of the parameter value is a function, it will be executed every time
34
- * when a param value needs to be obtained for a request (unless the param was overriden).
43
+ * when a param value needs to be obtained for a request (unless the param was overridden).
35
44
  *
36
45
  * Each key value in the parameter object is first bound to url template if present and then any
37
46
  * excess keys are appended to the url search query after the `?`.
@@ -58,7 +67,9 @@
58
67
  * and `JSONP`.
59
68
  * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of the
60
69
  * parameter value is a function, it will be executed every time when a param value needs to be
61
- * obtained for a request (unless the param was overriden).
70
+ * obtained for a request (unless the param was overridden).
71
+ * - **`url`** – {string} – action specific `url` override. The url templating is supported just like
72
+ * for the resource-level urls.
62
73
  * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, see
63
74
  * `returns` section.
64
75
  * - **`transformRequest`** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
@@ -291,7 +302,7 @@ angular.module('ngResource', ['ng']).
291
302
 
292
303
  /**
293
304
  * This method is intended for encoding *key* or *value* parts of query component. We need a custom
294
- * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
305
+ * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
295
306
  * encoded per http://tools.ietf.org/html/rfc3986:
296
307
  * query = *( pchar / "/" / "?" )
297
308
  * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
@@ -306,36 +317,38 @@ angular.module('ngResource', ['ng']).
306
317
  replace(/%3A/gi, ':').
307
318
  replace(/%24/g, '$').
308
319
  replace(/%2C/gi, ',').
309
- replace((pctEncodeSpaces ? null : /%20/g), '+');
320
+ replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
310
321
  }
311
322
 
312
323
  function Route(template, defaults) {
313
324
  this.template = template = template + '#';
314
325
  this.defaults = defaults || {};
315
- var urlParams = this.urlParams = {};
316
- forEach(template.split(/\W/), function(param){
317
- if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) {
318
- urlParams[param] = true;
319
- }
320
- });
321
- this.template = template.replace(/\\:/g, ':');
326
+ this.urlParams = {};
322
327
  }
323
328
 
324
329
  Route.prototype = {
325
- setUrlParams: function(config, params) {
330
+ setUrlParams: function(config, params, actionUrl) {
326
331
  var self = this,
327
- url = this.template,
332
+ url = actionUrl || self.template,
328
333
  val,
329
334
  encodedVal;
330
335
 
336
+ var urlParams = self.urlParams = {};
337
+ forEach(url.split(/\W/), function(param){
338
+ if (param && (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
339
+ urlParams[param] = true;
340
+ }
341
+ });
342
+ url = url.replace(/\\:/g, ':');
343
+
331
344
  params = params || {};
332
- forEach(this.urlParams, function(_, urlParam){
345
+ forEach(self.urlParams, function(_, urlParam){
333
346
  val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
334
347
  if (angular.isDefined(val) && val !== null) {
335
348
  encodedVal = encodeUriSegment(val);
336
- url = url.replace(new RegExp(":" + urlParam + "(\\W)", "g"), encodedVal + "$1");
349
+ url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1");
337
350
  } else {
338
- url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W)", "g"), function(match,
351
+ url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
339
352
  leadingSlashes, tail) {
340
353
  if (tail.charAt(0) == '/') {
341
354
  return tail;
@@ -433,7 +446,7 @@ angular.module('ngResource', ['ng']).
433
446
  }
434
447
  });
435
448
  httpConfig.data = data;
436
- route.setUrlParams(httpConfig, extend({}, extractParams(data, action.params || {}), params));
449
+ route.setUrlParams(httpConfig, extend({}, extractParams(data, action.params || {}), params), action.url);
437
450
 
438
451
  function markResolved() { value.$resolved = true; }
439
452
 
@@ -504,4 +517,5 @@ angular.module('ngResource', ['ng']).
504
517
  return ResourceFactory;
505
518
  }]);
506
519
 
520
+
507
521
  })(window, window.angular);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.0.5
2
+ * @license AngularJS v1.0.6
3
3
  * (c) 2010-2012 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -24,6 +24,17 @@
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
39
  * `/user/:username`. If you are using a URL with a port number (e.g.
29
40
  * `http://example.com:8080/api`), you'll need to escape the colon character before the port
@@ -268,7 +279,7 @@ angular.module('ngResource', ['ng']).
268
279
  replace(/%3A/gi, ':').
269
280
  replace(/%24/g, '$').
270
281
  replace(/%2C/gi, ',').
271
- replace((pctEncodeSpaces ? null : /%20/g), '+');
282
+ replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
272
283
  }
273
284
 
274
285
  function Route(template, defaults) {
@@ -442,4 +453,5 @@ angular.module('ngResource', ['ng']).
442
453
  return ResourceFactory;
443
454
  }]);
444
455
 
456
+
445
457
  })(window, window.angular);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.1.3
2
+ * @license AngularJS v1.1.4
3
3
  * (c) 2010-2012 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -422,6 +422,7 @@ angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($san
422
422
  });
423
423
  };
424
424
  }]);
425
+
425
426
  /**
426
427
  * @ngdoc filter
427
428
  * @name ngSanitize.filter:linky
@@ -553,4 +554,5 @@ angular.module('ngSanitize').filter('linky', function() {
553
554
  };
554
555
  });
555
556
 
557
+
556
558
  })(window, window.angular);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.0.5
2
+ * @license AngularJS v1.0.6
3
3
  * (c) 2010-2012 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -422,6 +422,7 @@ angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($san
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
- * @license AngularJS v1.1.3
2
+ * @license AngularJS v1.1.4
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,8 +52,6 @@ if ('i' !== 'I'.toLowerCase()) {
52
52
  uppercase = manualUppercase;
53
53
  }
54
54
 
55
- function fromCharCode(code) {return String.fromCharCode(code);}
56
-
57
55
 
58
56
  var /** holds major version number for IE or NaN for real browsers */
59
57
  msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]),
@@ -64,7 +62,7 @@ var /** holds major version number for IE or NaN for real browsers */
64
62
  toString = Object.prototype.toString,
65
63
 
66
64
 
67
- _angular = window.angular,
65
+ _angular = window.angular,
68
66
  /** @name angular */
69
67
  angular = window.angular || (window.angular = {}),
70
68
  angularModule,
@@ -255,6 +253,11 @@ function inherit(parent, extra) {
255
253
  return extend(new (extend(function() {}, {prototype:parent}))(), extra);
256
254
  }
257
255
 
256
+ var START_SPACE = /^\s*/;
257
+ var END_SPACE = /\s*$/;
258
+ function stripWhitespace(str) {
259
+ return isString(str) ? str.replace(START_SPACE, '').replace(END_SPACE, '') : str;
260
+ }
258
261
 
259
262
  /**
260
263
  * @ngdoc function
@@ -639,7 +642,7 @@ function shallowCopy(src, dst) {
639
642
  * * Both objects or values are of the same type and all of their properties pass `===` comparison.
640
643
  * * Both values are NaN. (In JavasScript, NaN == NaN => false. But we consider two NaN as equal)
641
644
  *
642
- * During a property comparision, properties of `function` type and properties with names
645
+ * During a property comparison, properties of `function` type and properties with names
643
646
  * that begin with `$` are ignored.
644
647
  *
645
648
  * Scope and DOMWindow objects are being compared only be identify (`===`).
@@ -846,7 +849,7 @@ function toKeyValue(obj) {
846
849
 
847
850
 
848
851
  /**
849
- * We need our custom method because encodeURIComponent is too agressive and doesn't follow
852
+ * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
850
853
  * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
851
854
  * segments:
852
855
  * segment = *pchar
@@ -866,7 +869,7 @@ function encodeUriSegment(val) {
866
869
 
867
870
  /**
868
871
  * This method is intended for encoding *key* or *value* parts of query component. We need a custom
869
- * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
872
+ * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
870
873
  * encoded per http://tools.ietf.org/html/rfc3986:
871
874
  * query = *( pchar / "/" / "?" )
872
875
  * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
@@ -881,7 +884,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
881
884
  replace(/%3A/gi, ':').
882
885
  replace(/%24/g, '$').
883
886
  replace(/%2C/gi, ',').
884
- replace((pctEncodeSpaces ? null : /%20/g), '+');
887
+ replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
885
888
  }
886
889
 
887
890
 
@@ -970,22 +973,38 @@ function angularInit(element, bootstrap) {
970
973
  * @returns {AUTO.$injector} Returns the newly created injector for this app.
971
974
  */
972
975
  function bootstrap(element, modules) {
973
- element = jqLite(element);
974
- modules = modules || [];
975
- modules.unshift(['$provide', function($provide) {
976
- $provide.value('$rootElement', element);
977
- }]);
978
- modules.unshift('ng');
979
- var injector = createInjector(modules);
980
- injector.invoke(
981
- ['$rootScope', '$rootElement', '$compile', '$injector', function(scope, element, compile, injector){
982
- scope.$apply(function() {
983
- element.data('$injector', injector);
984
- compile(element)(scope);
985
- });
986
- }]
987
- );
988
- return injector;
976
+ var resumeBootstrapInternal = function() {
977
+ element = jqLite(element);
978
+ modules = modules || [];
979
+ modules.unshift(['$provide', function($provide) {
980
+ $provide.value('$rootElement', element);
981
+ }]);
982
+ modules.unshift('ng');
983
+ var injector = createInjector(modules);
984
+ injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
985
+ function(scope, element, compile, injector) {
986
+ scope.$apply(function() {
987
+ element.data('$injector', injector);
988
+ compile(element)(scope);
989
+ });
990
+ }]
991
+ );
992
+ return injector;
993
+ };
994
+
995
+ var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
996
+
997
+ if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
998
+ return resumeBootstrapInternal();
999
+ }
1000
+
1001
+ window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1002
+ angular.resumeBootstrap = function(extraModules) {
1003
+ forEach(extraModules, function(module) {
1004
+ modules.push(module);
1005
+ });
1006
+ resumeBootstrapInternal();
1007
+ };
989
1008
  }
990
1009
 
991
1010
  var SNAKE_CASE_REGEXP = /[A-Z]/g;
@@ -1200,6 +1219,33 @@ function setupModuleLoader(window) {
1200
1219
  */
1201
1220
  constant: invokeLater('$provide', 'constant', 'unshift'),
1202
1221
 
1222
+ /**
1223
+ * @ngdoc method
1224
+ * @name angular.Module#animation
1225
+ * @methodOf angular.Module
1226
+ * @param {string} name animation name
1227
+ * @param {Function} animationFactory Factory function for creating new instance of an animation.
1228
+ * @description
1229
+ *
1230
+ * Defines an animation hook that can be later used with {@link ng.directive:ngAnimate ngAnimate}
1231
+ * alongside {@link ng.directive:ngAnimate#Description common ng directives} as well as custom directives.
1232
+ * <pre>
1233
+ * module.animation('animation-name', function($inject1, $inject2) {
1234
+ * return {
1235
+ * //this gets called in preparation to setup an animation
1236
+ * setup : function(element) { ... },
1237
+ *
1238
+ * //this gets called once the animation is run
1239
+ * start : function(element, done, memo) { ... }
1240
+ * }
1241
+ * })
1242
+ * </pre>
1243
+ *
1244
+ * See {@link ng.$animationProvider#register $animationProvider.register()} and
1245
+ * {@link ng.directive:ngAnimate ngAnimate} for more information.
1246
+ */
1247
+ animation: invokeLater('$animationProvider', 'register'),
1248
+
1203
1249
  /**
1204
1250
  * @ngdoc method
1205
1251
  * @name angular.Module#filter
@@ -1299,11 +1345,11 @@ function setupModuleLoader(window) {
1299
1345
  * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
1300
1346
  */
1301
1347
  var version = {
1302
- full: '1.1.3', // all of these placeholder strings will be replaced by rake's
1303
- major: 1, // compile task
1348
+ full: '1.1.4', // all of these placeholder strings will be replaced by grunt's
1349
+ major: 1, // package task
1304
1350
  minor: 1,
1305
- dot: 3,
1306
- codeName: 'radioactive-gargle'
1351
+ dot: 4,
1352
+ codeName: 'quantum-manipulation'
1307
1353
  };
1308
1354
 
1309
1355
 
@@ -1392,6 +1438,8 @@ function publishExternalAPI(angular){
1392
1438
  directive(ngEventDirectives);
1393
1439
  $provide.provider({
1394
1440
  $anchorScroll: $AnchorScrollProvider,
1441
+ $animation: $AnimationProvider,
1442
+ $animator: $AnimatorProvider,
1395
1443
  $browser: $BrowserProvider,
1396
1444
  $cacheFactory: $CacheFactoryProvider,
1397
1445
  $controller: $ControllerProvider,
@@ -1476,7 +1524,7 @@ function publishExternalAPI(angular){
1476
1524
  * - [val()](http://api.jquery.com/val/)
1477
1525
  * - [wrap()](http://api.jquery.com/wrap/)
1478
1526
  *
1479
- * ## In addtion to the above, Angular provides additional methods to both jQuery and jQuery lite:
1527
+ * ## In addition to the above, Angular provides additional methods to both jQuery and jQuery lite:
1480
1528
  *
1481
1529
  * - `controller(name)` - retrieves the controller of the current element or its parent. By default
1482
1530
  * retrieves controller associated with the `ngController` directive. If `name` is provided as
@@ -1744,9 +1792,14 @@ var JQLitePrototype = JQLite.prototype = {
1744
1792
  fn();
1745
1793
  }
1746
1794
 
1747
- this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
1748
- // we can not use jqLite since we are not done loading and jQuery could be loaded later.
1749
- JQLite(window).bind('load', trigger); // fallback to window.onload for others
1795
+ // check if document already is loaded
1796
+ if (document.readyState === 'complete'){
1797
+ setTimeout(trigger);
1798
+ } else {
1799
+ this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
1800
+ // we can not use jqLite since we are not done loading and jQuery could be loaded later.
1801
+ JQLite(window).bind('load', trigger); // fallback to window.onload for others
1802
+ }
1750
1803
  },
1751
1804
  toString: function() {
1752
1805
  var value = [];
@@ -2246,50 +2299,6 @@ HashMap.prototype = {
2246
2299
  }
2247
2300
  };
2248
2301
 
2249
- /**
2250
- * A map where multiple values can be added to the same key such that they form a queue.
2251
- * @returns {HashQueueMap}
2252
- */
2253
- function HashQueueMap() {}
2254
- HashQueueMap.prototype = {
2255
- /**
2256
- * Same as array push, but using an array as the value for the hash
2257
- */
2258
- push: function(key, value) {
2259
- var array = this[key = hashKey(key)];
2260
- if (!array) {
2261
- this[key] = [value];
2262
- } else {
2263
- array.push(value);
2264
- }
2265
- },
2266
-
2267
- /**
2268
- * Same as array shift, but using an array as the value for the hash
2269
- */
2270
- shift: function(key) {
2271
- var array = this[key = hashKey(key)];
2272
- if (array) {
2273
- if (array.length == 1) {
2274
- delete this[key];
2275
- return array[0];
2276
- } else {
2277
- return array.shift();
2278
- }
2279
- }
2280
- },
2281
-
2282
- /**
2283
- * return the first item without deleting it
2284
- */
2285
- peek: function(key) {
2286
- var array = this[hashKey(key)];
2287
- if (array) {
2288
- return array[0];
2289
- }
2290
- }
2291
- };
2292
-
2293
2302
  /**
2294
2303
  * @ngdoc function
2295
2304
  * @name angular.injector
@@ -2390,15 +2399,15 @@ function annotate(fn) {
2390
2399
  *
2391
2400
  * <pre>
2392
2401
  * // inferred (only works if code not minified/obfuscated)
2393
- * $inject.invoke(function(serviceA){});
2402
+ * $injector.invoke(function(serviceA){});
2394
2403
  *
2395
2404
  * // annotated
2396
2405
  * function explicit(serviceA) {};
2397
2406
  * explicit.$inject = ['serviceA'];
2398
- * $inject.invoke(explicit);
2407
+ * $injector.invoke(explicit);
2399
2408
  *
2400
2409
  * // inline
2401
- * $inject.invoke(['serviceA', function(serviceA){}]);
2410
+ * $injector.invoke(['serviceA', function(serviceA){}]);
2402
2411
  * </pre>
2403
2412
  *
2404
2413
  * ## Inference
@@ -2544,7 +2553,7 @@ function annotate(fn) {
2544
2553
  * @description
2545
2554
  *
2546
2555
  * Use `$provide` to register new providers with the `$injector`. The providers are the factories for the instance.
2547
- * The providers share the same name as the instance they create with the `Provider` suffixed to them.
2556
+ * The providers share the same name as the instance they create with `Provider` suffixed to them.
2548
2557
  *
2549
2558
  * A provider is an object with a `$get()` method. The injector calls the `$get` method to create a new instance of
2550
2559
  * a service. The Provider can have additional methods which would allow for configuration of the provider.
@@ -2891,6 +2900,7 @@ function createInjector(modulesToLoad) {
2891
2900
  };
2892
2901
  }
2893
2902
  }
2903
+
2894
2904
  /**
2895
2905
  * @ngdoc function
2896
2906
  * @name ng.$anchorScroll
@@ -2958,6 +2968,383 @@ function $AnchorScrollProvider() {
2958
2968
  }];
2959
2969
  }
2960
2970
 
2971
+
2972
+ /**
2973
+ * @ngdoc object
2974
+ * @name ng.$animationProvider
2975
+ * @description
2976
+ *
2977
+ * The $AnimationProvider provider allows developers to register and access custom JavaScript animations directly inside
2978
+ * of a module.
2979
+ *
2980
+ */
2981
+ $AnimationProvider.$inject = ['$provide'];
2982
+ function $AnimationProvider($provide) {
2983
+ var suffix = 'Animation';
2984
+
2985
+ /**
2986
+ * @ngdoc function
2987
+ * @name ng.$animation#register
2988
+ * @methodOf ng.$animationProvider
2989
+ *
2990
+ * @description
2991
+ * Registers a new injectable animation factory function. The factory function produces the animation object which
2992
+ * has these two properties:
2993
+ *
2994
+ * * `setup`: `function(Element):*` A function which receives the starting state of the element. The purpose
2995
+ * of this function is to get the element ready for animation. Optionally the function returns an memento which
2996
+ * is passed to the `start` function.
2997
+ * * `start`: `function(Element, doneFunction, *)` The element to animate, the `doneFunction` to be called on
2998
+ * element animation completion, and an optional memento from the `setup` function.
2999
+ *
3000
+ * @param {string} name The name of the animation.
3001
+ * @param {function} factory The factory function that will be executed to return the animation object.
3002
+ *
3003
+ */
3004
+ this.register = function(name, factory) {
3005
+ $provide.factory(camelCase(name) + suffix, factory);
3006
+ };
3007
+
3008
+ this.$get = ['$injector', function($injector) {
3009
+ /**
3010
+ * @ngdoc function
3011
+ * @name ng.$animation
3012
+ * @function
3013
+ *
3014
+ * @description
3015
+ * The $animation service is used to retrieve any defined animation functions. When executed, the $animation service
3016
+ * will return a object that contains the setup and start functions that were defined for the animation.
3017
+ *
3018
+ * @param {String} name Name of the animation function to retrieve. Animation functions are registered and stored
3019
+ * inside of the AngularJS DI so a call to $animate('custom') is the same as injecting `customAnimation`
3020
+ * via dependency injection.
3021
+ * @return {Object} the animation object which contains the `setup` and `start` functions that perform the animation.
3022
+ */
3023
+ return function $animation(name) {
3024
+ if (name) {
3025
+ try {
3026
+ return $injector.get(camelCase(name) + suffix);
3027
+ } catch (e) {
3028
+ //TODO(misko): this is a hack! we should have a better way to test if the injector has a given key.
3029
+ // The issue is that the animations are optional, and if not present they should be silently ignored.
3030
+ // The proper way to fix this is to add API onto the injector so that we can ask to see if a given
3031
+ // animation is supported.
3032
+ }
3033
+ }
3034
+ }
3035
+ }];
3036
+ };
3037
+
3038
+ // NOTE: this is a pseudo directive.
3039
+
3040
+ /**
3041
+ * @ngdoc directive
3042
+ * @name ng.directive:ngAnimate
3043
+ *
3044
+ * @description
3045
+ * The `ngAnimate` directive works as an attribute that is attached alongside pre-existing directives.
3046
+ * It effects how the directive will perform DOM manipulation. This allows for complex animations to take place while
3047
+ * without burduning the directive which uses the animation with animation details. The built dn directives
3048
+ * `ngRepeat`, `ngInclude`, `ngSwitch`, `ngShow`, `ngHide` and `ngView` already accept `ngAnimate` directive.
3049
+ * Custom directives can take advantage of animation through {@link ng.$animator $animator service}.
3050
+ *
3051
+ * Below is a more detailed breakdown of the supported callback events provided by pre-exisitng ng directives:
3052
+ *
3053
+ * * {@link ng.directive:ngRepeat#animations ngRepeat} — enter, leave and move
3054
+ * * {@link ng.directive:ngView#animations ngView} — enter and leave
3055
+ * * {@link ng.directive:ngInclude#animations ngInclude} — enter and leave
3056
+ * * {@link ng.directive:ngSwitch#animations ngSwitch} — enter and leave
3057
+ * * {@link ng.directive:ngShow#animations ngShow & ngHide} - show and hide respectively
3058
+ *
3059
+ * You can find out more information about animations upon visiting each directive page.
3060
+ *
3061
+ * Below is an example of a directive that makes use of the ngAnimate attribute:
3062
+ *
3063
+ * <pre>
3064
+ * <!-- you can also use data-ng-animate, ng:animate or x-ng-animate as well -->
3065
+ * <ANY ng-directive ng-animate="{event1: 'animation-name', event2: 'animation-name-2'}"></ANY>
3066
+ *
3067
+ * <!-- you can also use a short hand -->
3068
+ * <ANY ng-directive ng-animate=" 'animation' "></ANY>
3069
+ * <!-- which expands to -->
3070
+ * <ANY ng-directive ng-animate="{ enter: 'animation-enter', leave: 'animation-leave', ...}"></ANY>
3071
+ *
3072
+ * <!-- keep in mind that ng-animate can take expressions -->
3073
+ * <ANY ng-directive ng-animate=" computeCurrentAnimation() "></ANY>
3074
+ * </pre>
3075
+ *
3076
+ * The `event1` and `event2` attributes refer to the animation events specific to the directive that has been assigned.
3077
+ *
3078
+ * <h2>CSS-defined Animations</h2>
3079
+ * By default, ngAnimate attaches two CSS3 classes per animation event to the DOM element to achieve the animation.
3080
+ * This is up to you, the developer, to ensure that the animations take place using cross-browser CSS3 transitions.
3081
+ * All that is required is the following CSS code:
3082
+ *
3083
+ * <pre>
3084
+ * <style type="text/css">
3085
+ * /&#42;
3086
+ * The animate-enter prefix is the event name that you
3087
+ * have provided within the ngAnimate attribute.
3088
+ * &#42;/
3089
+ * .animate-enter-setup {
3090
+ * -webkit-transition: 1s linear all; /&#42; Safari/Chrome &#42;/
3091
+ * -moz-transition: 1s linear all; /&#42; Firefox &#42;/
3092
+ * -ms-transition: 1s linear all; /&#42; IE10 &#42;/
3093
+ * -o-transition: 1s linear all; /&#42; Opera &#42;/
3094
+ * transition: 1s linear all; /&#42; Future Browsers &#42;/
3095
+ *
3096
+ * /&#42; The animation preparation code &#42;/
3097
+ * opacity: 0;
3098
+ * }
3099
+ *
3100
+ * /&#42;
3101
+ * Keep in mind that you want to combine both CSS
3102
+ * classes together to avoid any CSS-specificity
3103
+ * conflicts
3104
+ * &#42;/
3105
+ * .animate-enter-setup.animate-enter-start {
3106
+ * /&#42; The animation code itself &#42;/
3107
+ * opacity: 1;
3108
+ * }
3109
+ * </style>
3110
+ *
3111
+ * <div ng-directive ng-animate="{enter: 'animate-enter'}"></div>
3112
+ * </pre>
3113
+ *
3114
+ * Upon DOM mutation, the setup class is added first, then the browser is allowed to reflow the content and then,
3115
+ * the start class is added to trigger the animation. The ngAnimate directive will automatically extract the duration
3116
+ * of the animation to determine when the animation ends. Once the animation is over then both CSS classes will be
3117
+ * removed from the DOM. If a browser does not support CSS transitions then the animation will start and end
3118
+ * immediately resulting in a DOM element that is at it's final state. This final state is when the DOM element
3119
+ * has no CSS animation classes surrounding it.
3120
+ *
3121
+ * <h2>JavaScript-defined Animations</h2>
3122
+ * In the event that you do not want to use CSS3 animations or if you wish to offer animations to browsers that do not
3123
+ * yet support them, then you can make use of JavaScript animations defined inside ngModule.
3124
+ *
3125
+ * <pre>
3126
+ * var ngModule = angular.module('YourApp', []);
3127
+ * ngModule.animation('animate-enter', function() {
3128
+ * return {
3129
+ * setup : function(element) {
3130
+ * //prepare the element for animation
3131
+ * element.css({ 'opacity': 0 });
3132
+ * var memo = "..."; //this value is passed to the start function
3133
+ * return memo;
3134
+ * },
3135
+ * start : function(element, done, memo) {
3136
+ * //start the animation
3137
+ * element.animate({
3138
+ * 'opacity' : 1
3139
+ * }, function() {
3140
+ * //call when the animation is complete
3141
+ * done()
3142
+ * });
3143
+ * }
3144
+ * }
3145
+ * });
3146
+ * </pre>
3147
+ *
3148
+ * As you can see, the JavaScript code follows a similar template to the CSS3 animations. Once defined, the animation
3149
+ * can be used in the same way with the ngAnimate attribute. Keep in mind that, when using JavaScript-enabled
3150
+ * animations, ngAnimate will also add in the same CSS classes that CSS-enabled animations do (even if you're using
3151
+ * JavaScript animations) to animated the element, but it will not attempt to find any CSS3 transition duration value.
3152
+ * It will instead close off the animation once the provided done function is executed. So it's important that you
3153
+ * make sure your animations remember to fire off the done function once the animations are complete.
3154
+ *
3155
+ * @param {expression} ngAnimate Used to configure the DOM manipulation animations.
3156
+ *
3157
+ */
3158
+
3159
+ /**
3160
+ * @ngdoc function
3161
+ * @name ng.$animator
3162
+ *
3163
+ * @description
3164
+ * The $animator service provides the DOM manipulation API which is decorated with animations.
3165
+ *
3166
+ * @param {Scope} scope the scope for the ng-animate.
3167
+ * @param {Attributes} attr the attributes object which contains the ngAnimate key / value pair. (The attributes are
3168
+ * passed into the linking function of the directive using the `$animator`.)
3169
+ * @return {object} the animator object which contains the enter, leave, move, show, hide and animate methods.
3170
+ */
3171
+ var $AnimatorProvider = function() {
3172
+ this.$get = ['$animation', '$window', '$sniffer', function($animation, $window, $sniffer) {
3173
+ return function(scope, attrs) {
3174
+ var ngAnimateAttr = attrs.ngAnimate;
3175
+ var animator = {};
3176
+
3177
+ /**
3178
+ * @ngdoc function
3179
+ * @name ng.animator#enter
3180
+ * @methodOf ng.$animator
3181
+ * @function
3182
+ *
3183
+ * @description
3184
+ * Injects the element object into the DOM (inside of the parent element) and then runs the enter animation.
3185
+ *
3186
+ * @param {jQuery/jqLite element} element the element that will be the focus of the enter animation
3187
+ * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the enter animation
3188
+ * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation
3189
+ */
3190
+ animator.enter = animateActionFactory('enter', insert, noop);
3191
+
3192
+ /**
3193
+ * @ngdoc function
3194
+ * @name ng.animator#leave
3195
+ * @methodOf ng.$animator
3196
+ * @function
3197
+ *
3198
+ * @description
3199
+ * Runs the leave animation operation and, upon completion, removes the element from the DOM.
3200
+ *
3201
+ * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation
3202
+ * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the leave animation
3203
+ */
3204
+ animator.leave = animateActionFactory('leave', noop, remove);
3205
+
3206
+ /**
3207
+ * @ngdoc function
3208
+ * @name ng.animator#move
3209
+ * @methodOf ng.$animator
3210
+ * @function
3211
+ *
3212
+ * @description
3213
+ * Fires the move DOM operation. Just before the animation starts, the animator will either append it into the parent container or
3214
+ * add the element directly after the after element if present. Then the move animation will be run.
3215
+ *
3216
+ * @param {jQuery/jqLite element} element the element that will be the focus of the move animation
3217
+ * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the move animation
3218
+ * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation
3219
+ */
3220
+ animator.move = animateActionFactory('move', move, noop);
3221
+
3222
+ /**
3223
+ * @ngdoc function
3224
+ * @name ng.animator#show
3225
+ * @methodOf ng.$animator
3226
+ * @function
3227
+ *
3228
+ * @description
3229
+ * Reveals the element by setting the CSS property `display` to `block` and then starts the show animation directly after.
3230
+ *
3231
+ * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
3232
+ */
3233
+ animator.show = animateActionFactory('show', show, noop);
3234
+
3235
+ /**
3236
+ * @ngdoc function
3237
+ * @name ng.animator#hide
3238
+ * @methodOf ng.$animator
3239
+ *
3240
+ * @description
3241
+ * Starts the hide animation first and sets the CSS `display` property to `none` upon completion.
3242
+ *
3243
+ * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
3244
+ */
3245
+ animator.hide = animateActionFactory('hide', noop, hide);
3246
+ return animator;
3247
+
3248
+ function animateActionFactory(type, beforeFn, afterFn) {
3249
+ var ngAnimateValue = ngAnimateAttr && scope.$eval(ngAnimateAttr);
3250
+ var className = ngAnimateAttr
3251
+ ? isObject(ngAnimateValue) ? ngAnimateValue[type] : ngAnimateValue + '-' + type
3252
+ : '';
3253
+ var animationPolyfill = $animation(className);
3254
+
3255
+ var polyfillSetup = animationPolyfill && animationPolyfill.setup;
3256
+ var polyfillStart = animationPolyfill && animationPolyfill.start;
3257
+
3258
+ if (!className) {
3259
+ return function(element, parent, after) {
3260
+ beforeFn(element, parent, after);
3261
+ afterFn(element, parent, after);
3262
+ }
3263
+ } else {
3264
+ var setupClass = className + '-setup';
3265
+ var startClass = className + '-start';
3266
+
3267
+ return function(element, parent, after) {
3268
+ if (!$sniffer.supportsTransitions && !polyfillSetup && !polyfillStart) {
3269
+ beforeFn(element, parent, after);
3270
+ afterFn(element, parent, after);
3271
+ return;
3272
+ }
3273
+
3274
+ element.addClass(setupClass);
3275
+ beforeFn(element, parent, after);
3276
+ if (element.length == 0) return done();
3277
+
3278
+ var memento = (polyfillSetup || noop)(element);
3279
+
3280
+ // $window.setTimeout(beginAnimation, 0); this was causing the element not to animate
3281
+ // keep at 1 for animation dom rerender
3282
+ $window.setTimeout(beginAnimation, 1);
3283
+
3284
+ function beginAnimation() {
3285
+ element.addClass(startClass);
3286
+ if (polyfillStart) {
3287
+ polyfillStart(element, done, memento);
3288
+ } else if (isFunction($window.getComputedStyle)) {
3289
+ var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition';
3290
+ var w3cTransitionProp = 'transition'; //one day all browsers will have this
3291
+
3292
+ var durationKey = 'Duration';
3293
+ var duration = 0;
3294
+ //we want all the styles defined before and after
3295
+ forEach(element, function(element) {
3296
+ var globalStyles = $window.getComputedStyle(element) || {};
3297
+ duration = Math.max(
3298
+ parseFloat(globalStyles[w3cTransitionProp + durationKey]) ||
3299
+ parseFloat(globalStyles[vendorTransitionProp + durationKey]) ||
3300
+ 0,
3301
+ duration);
3302
+ });
3303
+
3304
+ $window.setTimeout(done, duration * 1000);
3305
+ } else {
3306
+ done();
3307
+ }
3308
+ }
3309
+
3310
+ function done() {
3311
+ afterFn(element, parent, after);
3312
+ element.removeClass(setupClass);
3313
+ element.removeClass(startClass);
3314
+ }
3315
+ }
3316
+ }
3317
+ }
3318
+ }
3319
+
3320
+ function show(element) {
3321
+ element.css('display', '');
3322
+ }
3323
+
3324
+ function hide(element) {
3325
+ element.css('display', 'none');
3326
+ }
3327
+
3328
+ function insert(element, parent, after) {
3329
+ if (after) {
3330
+ after.after(element);
3331
+ } else {
3332
+ parent.append(element);
3333
+ }
3334
+ }
3335
+
3336
+ function remove(element) {
3337
+ element.remove();
3338
+ }
3339
+
3340
+ function move(element, parent, after) {
3341
+ // Do not remove element before insert. Removing will cause data associated with the
3342
+ // element to be dropped. Insert will implicitly do the remove.
3343
+ insert(element, parent, after);
3344
+ }
3345
+ }];
3346
+ };
3347
+
2961
3348
  /**
2962
3349
  * ! This is a private undocumented service !
2963
3350
  *
@@ -3210,7 +3597,7 @@ function Browser(window, document, $log, $sniffer) {
3210
3597
  * @methodOf ng.$browser
3211
3598
  *
3212
3599
  * @param {string=} name Cookie name
3213
- * @param {string=} value Cokkie value
3600
+ * @param {string=} value Cookie value
3214
3601
  *
3215
3602
  * @description
3216
3603
  * The cookies method provides a 'private' low level access to browser cookies.
@@ -3272,7 +3659,7 @@ function Browser(window, document, $log, $sniffer) {
3272
3659
  * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
3273
3660
  *
3274
3661
  * @description
3275
- * Executes a fn asynchroniously via `setTimeout(fn, delay)`.
3662
+ * Executes a fn asynchronously via `setTimeout(fn, delay)`.
3276
3663
  *
3277
3664
  * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
3278
3665
  * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
@@ -3299,7 +3686,7 @@ function Browser(window, document, $log, $sniffer) {
3299
3686
  * Cancels a defered task identified with `deferId`.
3300
3687
  *
3301
3688
  * @param {*} deferId Token returned by the `$browser.defer` function.
3302
- * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled.
3689
+ * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully canceled.
3303
3690
  */
3304
3691
  self.defer.cancel = function(deferId) {
3305
3692
  if (pendingDeferIds[deferId]) {
@@ -3319,6 +3706,7 @@ function $BrowserProvider(){
3319
3706
  return new Browser($window, $document, $log, $sniffer);
3320
3707
  }];
3321
3708
  }
3709
+
3322
3710
  /**
3323
3711
  * @ngdoc object
3324
3712
  * @name ng.$cacheFactory
@@ -3648,7 +4036,7 @@ function $CompileProvider($provide) {
3648
4036
  COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
3649
4037
  CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
3650
4038
  MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ',
3651
- urlSanitizationWhitelist = /^\s*(https?|ftp|mailto):/;
4039
+ urlSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/;
3652
4040
 
3653
4041
 
3654
4042
  /**
@@ -3662,7 +4050,7 @@ function $CompileProvider($provide) {
3662
4050
  *
3663
4051
  * @param {string} name Name of the directive in camel-case. (ie <code>ngBind</code> which will match as
3664
4052
  * <code>ng-bind</code>).
3665
- * @param {function} directiveFactory An injectable directive factroy function. See {@link guide/directive} for more
4053
+ * @param {function} directiveFactory An injectable directive factory function. See {@link guide/directive} for more
3666
4054
  * info.
3667
4055
  * @returns {ng.$compileProvider} Self for chaining.
3668
4056
  */
@@ -3841,7 +4229,8 @@ function $CompileProvider($provide) {
3841
4229
  ? identity
3842
4230
  : function denormalizeTemplate(template) {
3843
4231
  return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
3844
- };
4232
+ },
4233
+ NG_ATTR_BINDING = /^ngAttr[A-Z]/;
3845
4234
 
3846
4235
 
3847
4236
  return compile;
@@ -3850,7 +4239,7 @@ function $CompileProvider($provide) {
3850
4239
 
3851
4240
  function compile($compileNodes, transcludeFn, maxPriority) {
3852
4241
  if (!($compileNodes instanceof jqLite)) {
3853
- // jquery always rewraps, where as we need to preserve the original selector so that we can modify it.
4242
+ // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it.
3854
4243
  $compileNodes = jqLite($compileNodes);
3855
4244
  }
3856
4245
  // We can not compile top level text elements since text nodes can be merged and we will
@@ -3902,7 +4291,7 @@ function $CompileProvider($provide) {
3902
4291
  * functions return values - the linking functions - are combined into a composite linking
3903
4292
  * function, which is the a linking function for the node.
3904
4293
  *
3905
- * @param {NodeList} nodeList an array of nodes to compile
4294
+ * @param {NodeList} nodeList an array of nodes or NodeList to compile
3906
4295
  * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
3907
4296
  * scope argument is auto-generated to the new child of the transcluded parent scope.
3908
4297
  * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then the
@@ -3925,7 +4314,7 @@ function $CompileProvider($provide) {
3925
4314
  ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
3926
4315
  : null;
3927
4316
 
3928
- childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes.length)
4317
+ childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes || !nodeList[i].childNodes.length)
3929
4318
  ? null
3930
4319
  : compileNodes(nodeList[i].childNodes,
3931
4320
  nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
@@ -4006,11 +4395,16 @@ function $CompileProvider($provide) {
4006
4395
  directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority);
4007
4396
 
4008
4397
  // iterate over the attributes
4009
- for (var attr, name, nName, value, nAttrs = node.attributes,
4398
+ for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
4010
4399
  j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
4011
4400
  attr = nAttrs[j];
4012
4401
  if (attr.specified) {
4013
4402
  name = attr.name;
4403
+ // support ngAttr attribute binding
4404
+ ngAttrName = directiveNormalize(name);
4405
+ if (NG_ATTR_BINDING.test(ngAttrName)) {
4406
+ name = ngAttrName.substr(6).toLowerCase();
4407
+ }
4014
4408
  nName = directiveNormalize(name.toLowerCase());
4015
4409
  attrsMap[nName] = name;
4016
4410
  attrs[nName] = value = trim((msie && name == 'href')
@@ -4138,9 +4532,14 @@ function $CompileProvider($provide) {
4138
4532
  }
4139
4533
  }
4140
4534
 
4141
- if ((directiveValue = directive.template)) {
4535
+ if (directive.template) {
4142
4536
  assertNoDuplicate('template', templateDirective, directive, $compileNode);
4143
4537
  templateDirective = directive;
4538
+
4539
+ directiveValue = (isFunction(directive.template))
4540
+ ? directive.template($compileNode, templateAttrs)
4541
+ : directive.template;
4542
+
4144
4543
  directiveValue = denormalizeTemplate(directiveValue);
4145
4544
 
4146
4545
  if (directive.replace) {
@@ -4260,13 +4659,14 @@ function $CompileProvider($provide) {
4260
4659
  $element = attrs.$$element;
4261
4660
 
4262
4661
  if (newIsolateScopeDirective) {
4263
- var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/;
4662
+ var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
4264
4663
 
4265
4664
  var parentScope = scope.$parent || scope;
4266
4665
 
4267
4666
  forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
4268
4667
  var match = definiton.match(LOCAL_REGEXP) || [],
4269
- attrName = match[2]|| scopeName,
4668
+ attrName = match[3] || scopeName,
4669
+ optional = (match[2] == '?'),
4270
4670
  mode = match[1], // @, =, or &
4271
4671
  lastValue,
4272
4672
  parentGet, parentSet;
@@ -4288,6 +4688,9 @@ function $CompileProvider($provide) {
4288
4688
  }
4289
4689
 
4290
4690
  case '=': {
4691
+ if (optional && !attrs[attrName]) {
4692
+ return;
4693
+ }
4291
4694
  parentGet = $parse(attrs[attrName]);
4292
4695
  parentSet = parentGet.assign || function() {
4293
4696
  // reset the change, or we will throw this exception on every $digest
@@ -4459,11 +4862,14 @@ function $CompileProvider($provide) {
4459
4862
  // The fact that we have to copy and patch the directive seems wrong!
4460
4863
  derivedSyncDirective = extend({}, origAsyncDirective, {
4461
4864
  controller: null, templateUrl: null, transclude: null, scope: null
4462
- });
4865
+ }),
4866
+ templateUrl = (isFunction(origAsyncDirective.templateUrl))
4867
+ ? origAsyncDirective.templateUrl($compileNode, tAttrs)
4868
+ : origAsyncDirective.templateUrl;
4463
4869
 
4464
4870
  $compileNode.html('');
4465
4871
 
4466
- $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}).
4872
+ $http.get(templateUrl, {cache: $templateCache}).
4467
4873
  success(function(content) {
4468
4874
  var compileNode, tempTemplateAttrs, $template;
4469
4875
 
@@ -4488,14 +4894,14 @@ function $CompileProvider($provide) {
4488
4894
 
4489
4895
  directives.unshift(derivedSyncDirective);
4490
4896
  afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn);
4491
- afterTemplateChildLinkFn = compileNodes($compileNode.contents(), childTranscludeFn);
4897
+ afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
4492
4898
 
4493
4899
 
4494
4900
  while(linkQueue.length) {
4495
- var controller = linkQueue.pop(),
4496
- linkRootElement = linkQueue.pop(),
4497
- beforeTemplateLinkNode = linkQueue.pop(),
4498
- scope = linkQueue.pop(),
4901
+ var scope = linkQueue.shift(),
4902
+ beforeTemplateLinkNode = linkQueue.shift(),
4903
+ linkRootElement = linkQueue.shift(),
4904
+ controller = linkQueue.shift(),
4499
4905
  linkNode = compileNode;
4500
4906
 
4501
4907
  if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
@@ -4576,11 +4982,13 @@ function $CompileProvider($provide) {
4576
4982
  compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
4577
4983
  var $$observers = (attr.$$observers || (attr.$$observers = {}));
4578
4984
 
4579
- if (name === 'class') {
4580
- // we need to interpolate classes again, in the case the element was replaced
4581
- // and therefore the two class attrs got merged - we want to interpolate the result
4582
- interpolateFn = $interpolate(attr[name], true);
4583
- }
4985
+ // we need to interpolate again, in case the attribute value has been updated
4986
+ // (e.g. by another directive's compile function)
4987
+ interpolateFn = $interpolate(attr[name], true);
4988
+
4989
+ // if attribute was updated so that there is no interpolation going on we don't want to
4990
+ // register any observers
4991
+ if (!interpolateFn) return;
4584
4992
 
4585
4993
  attr[name] = interpolateFn(scope);
4586
4994
  ($$observers[name] || ($$observers[name] = [])).$$inter = true;
@@ -4677,7 +5085,7 @@ function directiveNormalize(name) {
4677
5085
  * @param {string} name Normalized element attribute name of the property to modify. The name is
4678
5086
  * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
4679
5087
  * property to the original name.
4680
- * @param {string} value Value to set the attribute to.
5088
+ * @param {string} value Value to set the attribute to. The value can be an interpolated string.
4681
5089
  */
4682
5090
 
4683
5091
 
@@ -4753,7 +5161,7 @@ function $ControllerProvider() {
4753
5161
  * @description
4754
5162
  * `$controller` service is responsible for instantiating controllers.
4755
5163
  *
4756
- * It's just simple call to {@link AUTO.$injector $injector}, but extracted into
5164
+ * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into
4757
5165
  * a service, so that one can override this service with {@link https://gist.github.com/1649788
4758
5166
  * BC version}.
4759
5167
  */
@@ -5000,7 +5408,7 @@ function $InterpolateProvider() {
5000
5408
  }];
5001
5409
  }
5002
5410
 
5003
- var URL_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
5411
+ var URL_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
5004
5412
  PATH_MATCH = /^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,
5005
5413
  HASH_MATCH = PATH_MATCH,
5006
5414
  DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
@@ -5079,7 +5487,8 @@ function convertToHashbangUrl(url, basePath, hashPrefix) {
5079
5487
  var match = matchUrl(url);
5080
5488
 
5081
5489
  // already hashbang url
5082
- if (decodeURIComponent(match.path) == basePath) {
5490
+ if (decodeURIComponent(match.path) == basePath && !isUndefined(match.hash) &&
5491
+ match.hash.indexOf(hashPrefix) === 0) {
5083
5492
  return url;
5084
5493
  // convert html5 url -> hashbang url
5085
5494
  } else {
@@ -6471,11 +6880,11 @@ function setter(obj, path, setValue) {
6471
6880
  }
6472
6881
 
6473
6882
  /**
6474
- * Return the value accesible from the object by path. Any undefined traversals are ignored
6883
+ * Return the value accessible from the object by path. Any undefined traversals are ignored
6475
6884
  * @param {Object} obj starting object
6476
6885
  * @param {string} path path to traverse
6477
6886
  * @param {boolean=true} bindFnToScope
6478
- * @returns value as accesbile by path
6887
+ * @returns value as accessible by path
6479
6888
  */
6480
6889
  //TODO(misko): this function needs to be removed
6481
6890
  function getter(obj, path, bindFnToScope) {
@@ -6647,7 +7056,7 @@ function getterFn(path, csp) {
6647
7056
  * @returns {function(context, locals)} a function which represents the compiled expression:
6648
7057
  *
6649
7058
  * * `context` – `{object}` – an object against which any expressions embedded in the strings
6650
- * are evaluated against (tipically a scope object).
7059
+ * are evaluated against (typically a scope object).
6651
7060
  * * `locals` – `{object=}` – local variables context object, useful for overriding values in
6652
7061
  * `context`.
6653
7062
  *
@@ -7055,29 +7464,30 @@ function qFactory(nextTick, exceptionHandler) {
7055
7464
  * Combines multiple promises into a single promise that is resolved when all of the input
7056
7465
  * promises are resolved.
7057
7466
  *
7058
- * @param {Array.<Promise>} promises An array of promises.
7059
- * @returns {Promise} Returns a single promise that will be resolved with an array of values,
7060
- * each value corresponding to the promise at the same index in the `promises` array. If any of
7467
+ * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
7468
+ * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
7469
+ * each value corresponding to the promise at the same index/key in the `promises` array/hash. If any of
7061
7470
  * the promises is resolved with a rejection, this resulting promise will be resolved with the
7062
7471
  * same rejection.
7063
7472
  */
7064
7473
  function all(promises) {
7065
7474
  var deferred = defer(),
7066
- counter = promises.length,
7067
- results = [];
7068
-
7069
- if (counter) {
7070
- forEach(promises, function(promise, index) {
7071
- ref(promise).then(function(value) {
7072
- if (index in results) return;
7073
- results[index] = value;
7074
- if (!(--counter)) deferred.resolve(results);
7075
- }, function(reason) {
7076
- if (index in results) return;
7077
- deferred.reject(reason);
7078
- });
7475
+ counter = 0,
7476
+ results = isArray(promises) ? [] : {};
7477
+
7478
+ forEach(promises, function(promise, key) {
7479
+ counter++;
7480
+ ref(promise).then(function(value) {
7481
+ if (results.hasOwnProperty(key)) return;
7482
+ results[key] = value;
7483
+ if (!(--counter)) deferred.resolve(results);
7484
+ }, function(reason) {
7485
+ if (results.hasOwnProperty(key)) return;
7486
+ deferred.reject(reason);
7079
7487
  });
7080
- } else {
7488
+ });
7489
+
7490
+ if (counter === 0) {
7081
7491
  deferred.resolve(results);
7082
7492
  }
7083
7493
 
@@ -7183,13 +7593,18 @@ function $RouteProvider(){
7183
7593
  * If the option is set to `false` and url in the browser changes, then
7184
7594
  * `$routeUpdate` event is broadcasted on the root scope.
7185
7595
  *
7596
+ * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
7597
+ *
7598
+ * If the option is set to `true`, then the particular route can be matched without being
7599
+ * case sensitive
7600
+ *
7186
7601
  * @returns {Object} self
7187
7602
  *
7188
7603
  * @description
7189
7604
  * Adds a new route definition to the `$route` service.
7190
7605
  */
7191
7606
  this.when = function(path, route) {
7192
- routes[path] = extend({reloadOnSearch: true}, route);
7607
+ routes[path] = extend({reloadOnSearch: true, caseInsensitiveMatch: false}, route);
7193
7608
 
7194
7609
  // create redirection for trailing slashes
7195
7610
  if (path) {
@@ -7375,8 +7790,9 @@ function $RouteProvider(){
7375
7790
  * {@link ng.directive:ngView ngView} listens for the directive
7376
7791
  * to instantiate the controller and render the view.
7377
7792
  *
7793
+ * @param {Object} angularEvent Synthetic event object.
7378
7794
  * @param {Route} current Current route information.
7379
- * @param {Route} previous Previous route information.
7795
+ * @param {Route|Undefined} previous Previous route information, or undefined if current is first route entered.
7380
7796
  */
7381
7797
 
7382
7798
  /**
@@ -7434,14 +7850,16 @@ function $RouteProvider(){
7434
7850
  /**
7435
7851
  * @param on {string} current url
7436
7852
  * @param when {string} route when template to match the url against
7853
+ * @param whenProperties {Object} properties to define when's matching behavior
7437
7854
  * @return {?Object}
7438
7855
  */
7439
- function switchRouteMatcher(on, when) {
7856
+ function switchRouteMatcher(on, when, whenProperties) {
7440
7857
  // TODO(i): this code is convoluted and inefficient, we should construct the route matching
7441
7858
  // regex only once and then reuse it
7442
7859
 
7443
7860
  // Escape regexp special characters.
7444
7861
  when = '^' + when.replace(/[-\/\\^$:*+?.()|[\]{}]/g, "\\$&") + '$';
7862
+
7445
7863
  var regex = '',
7446
7864
  params = [],
7447
7865
  dst = {};
@@ -7468,7 +7886,7 @@ function $RouteProvider(){
7468
7886
  // Append trailing path part.
7469
7887
  regex += when.substr(lastMatchedIndex);
7470
7888
 
7471
- var match = on.match(new RegExp(regex));
7889
+ var match = on.match(new RegExp(regex, whenProperties.caseInsensitiveMatch ? 'i' : ''));
7472
7890
  if (match) {
7473
7891
  forEach(params, function(name, index) {
7474
7892
  dst[name] = match[index + 1];
@@ -7481,7 +7899,7 @@ function $RouteProvider(){
7481
7899
  var next = parseRoute(),
7482
7900
  last = $route.current;
7483
7901
 
7484
- if (next && last && next.$route === last.$route
7902
+ if (next && last && next.$$route === last.$$route
7485
7903
  && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) {
7486
7904
  last.params = next.params;
7487
7905
  copy(last.params, $routeParams);
@@ -7505,14 +7923,13 @@ function $RouteProvider(){
7505
7923
  $q.when(next).
7506
7924
  then(function() {
7507
7925
  if (next) {
7508
- var keys = [],
7509
- values = [],
7926
+ var locals = extend({}, next.resolve),
7510
7927
  template;
7511
7928
 
7512
- forEach(next.resolve || {}, function(value, key) {
7513
- keys.push(key);
7514
- values.push(isString(value) ? $injector.get(value) : $injector.invoke(value));
7929
+ forEach(locals, function(value, key) {
7930
+ locals[key] = isString(value) ? $injector.get(value) : $injector.invoke(value);
7515
7931
  });
7932
+
7516
7933
  if (isDefined(template = next.template)) {
7517
7934
  if (isFunction(template)) {
7518
7935
  template = template(next.params);
@@ -7528,16 +7945,9 @@ function $RouteProvider(){
7528
7945
  }
7529
7946
  }
7530
7947
  if (isDefined(template)) {
7531
- keys.push('$template');
7532
- values.push(template);
7948
+ locals['$template'] = template;
7533
7949
  }
7534
- return $q.all(values).then(function(values) {
7535
- var locals = {};
7536
- forEach(values, function(value, index) {
7537
- locals[keys[index]] = value;
7538
- });
7539
- return locals;
7540
- });
7950
+ return $q.all(locals);
7541
7951
  }
7542
7952
  }).
7543
7953
  // after route change
@@ -7565,11 +7975,11 @@ function $RouteProvider(){
7565
7975
  // Match a route
7566
7976
  var params, match;
7567
7977
  forEach(routes, function(route, path) {
7568
- if (!match && (params = switchRouteMatcher($location.path(), path))) {
7978
+ if (!match && (params = switchRouteMatcher($location.path(), path, route))) {
7569
7979
  match = inherit(route, {
7570
7980
  params: extend({}, $location.search(), params),
7571
7981
  pathParams: params});
7572
- match.$route = route;
7982
+ match.$$route = route;
7573
7983
  }
7574
7984
  });
7575
7985
  // No route matched; fallback to "otherwise" route
@@ -7577,7 +7987,7 @@ function $RouteProvider(){
7577
7987
  }
7578
7988
 
7579
7989
  /**
7580
- * @returns interpolation of the redirect path with the parametrs
7990
+ * @returns interpolation of the redirect path with the parameters
7581
7991
  */
7582
7992
  function interpolate(string, params) {
7583
7993
  var result = [];
@@ -7709,25 +8119,7 @@ function $RootScopeProvider(){
7709
8119
  *
7710
8120
  * Here is a simple scope snippet to show how you can interact with the scope.
7711
8121
  * <pre>
7712
- angular.injector(['ng']).invoke(function($rootScope) {
7713
- var scope = $rootScope.$new();
7714
- scope.salutation = 'Hello';
7715
- scope.name = 'World';
7716
-
7717
- expect(scope.greeting).toEqual(undefined);
7718
-
7719
- scope.$watch('name', function() {
7720
- scope.greeting = scope.salutation + ' ' + scope.name + '!';
7721
- }); // initialize the watch
7722
-
7723
- expect(scope.greeting).toEqual(undefined);
7724
- scope.name = 'Misko';
7725
- // still old value, since watches have not been called yet
7726
- expect(scope.greeting).toEqual(undefined);
7727
-
7728
- scope.$digest(); // fire all the watches
7729
- expect(scope.greeting).toEqual('Hello Misko!');
7730
- });
8122
+ * <file src="./test/ng/rootScopeSpec.js" tag="docs1" />
7731
8123
  * </pre>
7732
8124
  *
7733
8125
  * # Inheritance
@@ -7946,51 +8338,192 @@ function $RootScopeProvider(){
7946
8338
  };
7947
8339
  },
7948
8340
 
8341
+
7949
8342
  /**
7950
8343
  * @ngdoc function
7951
- * @name ng.$rootScope.Scope#$digest
8344
+ * @name ng.$rootScope.Scope#$watchCollection
7952
8345
  * @methodOf ng.$rootScope.Scope
7953
8346
  * @function
7954
8347
  *
7955
8348
  * @description
7956
- * Process all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children.
7957
- * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the
7958
- * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are
7959
- * firing. This means that it is possible to get into an infinite loop. This function will throw
7960
- * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10.
7961
- *
7962
- * Usually you don't call `$digest()` directly in
7963
- * {@link ng.directive:ngController controllers} or in
7964
- * {@link ng.$compileProvider#directive directives}.
7965
- * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a
7966
- * {@link ng.$compileProvider#directive directives}) will force a `$digest()`.
8349
+ * Shallow watches the properties of an object and fires whenever any of the properties change
8350
+ * (for arrays this implies watching the array items, for object maps this implies watching the properties).
8351
+ * If a change is detected the `listener` callback is fired.
7967
8352
  *
7968
- * If you want to be notified whenever `$digest()` is called,
7969
- * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()}
7970
- * with no `listener`.
8353
+ * - The `obj` collection is observed via standard $watch operation and is examined on every call to $digest() to
8354
+ * see if any items have been added, removed, or moved.
8355
+ * - The `listener` is called whenever anything within the `obj` has changed. Examples include adding new items
8356
+ * into the object or array, removing and moving items around.
7971
8357
  *
7972
- * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
7973
- * life-cycle.
7974
8358
  *
7975
8359
  * # Example
7976
8360
  * <pre>
7977
- var scope = ...;
7978
- scope.name = 'misko';
7979
- scope.counter = 0;
8361
+ $scope.names = ['igor', 'matias', 'misko', 'james'];
8362
+ $scope.dataCount = 4;
7980
8363
 
7981
- expect(scope.counter).toEqual(0);
7982
- scope.$watch('name', function(newValue, oldValue) {
7983
- scope.counter = scope.counter + 1;
7984
- });
7985
- expect(scope.counter).toEqual(0);
8364
+ $scope.$watchCollection('names', function(newNames, oldNames) {
8365
+ $scope.dataCount = newNames.length;
8366
+ });
7986
8367
 
7987
- scope.$digest();
7988
- // no variable change
7989
- expect(scope.counter).toEqual(0);
8368
+ expect($scope.dataCount).toEqual(4);
8369
+ $scope.$digest();
7990
8370
 
7991
- scope.name = 'adam';
7992
- scope.$digest();
7993
- expect(scope.counter).toEqual(1);
8371
+ //still at 4 ... no changes
8372
+ expect($scope.dataCount).toEqual(4);
8373
+
8374
+ $scope.names.pop();
8375
+ $scope.$digest();
8376
+
8377
+ //now there's been a change
8378
+ expect($scope.dataCount).toEqual(3);
8379
+ * </pre>
8380
+ *
8381
+ *
8382
+ * @param {string|Function(scope)} obj Evaluated as {@link guide/expression expression}. The expression value
8383
+ * should evaluate to an object or an array which is observed on each
8384
+ * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the collection will trigger
8385
+ * a call to the `listener`.
8386
+ *
8387
+ * @param {function(newCollection, oldCollection, scope)} listener a callback function that is fired with both
8388
+ * the `newCollection` and `oldCollection` as parameters.
8389
+ * The `newCollection` object is the newly modified data obtained from the `obj` expression and the
8390
+ * `oldCollection` object is a copy of the former collection data.
8391
+ * The `scope` refers to the current scope.
8392
+ *
8393
+ * @returns {function()} Returns a de-registration function for this listener. When the de-registration function is executed
8394
+ * then the internal watch operation is terminated.
8395
+ */
8396
+ $watchCollection: function(obj, listener) {
8397
+ var self = this;
8398
+ var oldValue;
8399
+ var newValue;
8400
+ var changeDetected = 0;
8401
+ var objGetter = $parse(obj);
8402
+ var internalArray = [];
8403
+ var internalObject = {};
8404
+ var oldLength = 0;
8405
+
8406
+ function $watchCollectionWatch() {
8407
+ newValue = objGetter(self);
8408
+ var newLength, key;
8409
+
8410
+ if (!isObject(newValue)) {
8411
+ if (oldValue !== newValue) {
8412
+ oldValue = newValue;
8413
+ changeDetected++;
8414
+ }
8415
+ } else if (isArray(newValue)) {
8416
+ if (oldValue !== internalArray) {
8417
+ // we are transitioning from something which was not an array into array.
8418
+ oldValue = internalArray;
8419
+ oldLength = oldValue.length = 0;
8420
+ changeDetected++;
8421
+ }
8422
+
8423
+ newLength = newValue.length;
8424
+
8425
+ if (oldLength !== newLength) {
8426
+ // if lengths do not match we need to trigger change notification
8427
+ changeDetected++;
8428
+ oldValue.length = oldLength = newLength;
8429
+ }
8430
+ // copy the items to oldValue and look for changes.
8431
+ for (var i = 0; i < newLength; i++) {
8432
+ if (oldValue[i] !== newValue[i]) {
8433
+ changeDetected++;
8434
+ oldValue[i] = newValue[i];
8435
+ }
8436
+ }
8437
+ } else {
8438
+ if (oldValue !== internalObject) {
8439
+ // we are transitioning from something which was not an object into object.
8440
+ oldValue = internalObject = {};
8441
+ oldLength = 0;
8442
+ changeDetected++;
8443
+ }
8444
+ // copy the items to oldValue and look for changes.
8445
+ newLength = 0;
8446
+ for (key in newValue) {
8447
+ if (newValue.hasOwnProperty(key)) {
8448
+ newLength++;
8449
+ if (oldValue.hasOwnProperty(key)) {
8450
+ if (oldValue[key] !== newValue[key]) {
8451
+ changeDetected++;
8452
+ oldValue[key] = newValue[key];
8453
+ }
8454
+ } else {
8455
+ oldLength++;
8456
+ oldValue[key] = newValue[key];
8457
+ changeDetected++;
8458
+ }
8459
+ }
8460
+ }
8461
+ if (oldLength > newLength) {
8462
+ // we used to have more keys, need to find them and destroy them.
8463
+ changeDetected++;
8464
+ for(key in oldValue) {
8465
+ if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) {
8466
+ oldLength--;
8467
+ delete oldValue[key];
8468
+ }
8469
+ }
8470
+ }
8471
+ }
8472
+ return changeDetected;
8473
+ }
8474
+
8475
+ function $watchCollectionAction() {
8476
+ listener(newValue, oldValue, self);
8477
+ }
8478
+
8479
+ return this.$watch($watchCollectionWatch, $watchCollectionAction);
8480
+ },
8481
+
8482
+ /**
8483
+ * @ngdoc function
8484
+ * @name ng.$rootScope.Scope#$digest
8485
+ * @methodOf ng.$rootScope.Scope
8486
+ * @function
8487
+ *
8488
+ * @description
8489
+ * Process all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children.
8490
+ * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the
8491
+ * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are
8492
+ * firing. This means that it is possible to get into an infinite loop. This function will throw
8493
+ * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10.
8494
+ *
8495
+ * Usually you don't call `$digest()` directly in
8496
+ * {@link ng.directive:ngController controllers} or in
8497
+ * {@link ng.$compileProvider#directive directives}.
8498
+ * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a
8499
+ * {@link ng.$compileProvider#directive directives}) will force a `$digest()`.
8500
+ *
8501
+ * If you want to be notified whenever `$digest()` is called,
8502
+ * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()}
8503
+ * with no `listener`.
8504
+ *
8505
+ * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
8506
+ * life-cycle.
8507
+ *
8508
+ * # Example
8509
+ * <pre>
8510
+ var scope = ...;
8511
+ scope.name = 'misko';
8512
+ scope.counter = 0;
8513
+
8514
+ expect(scope.counter).toEqual(0);
8515
+ scope.$watch('name', function(newValue, oldValue) {
8516
+ scope.counter = scope.counter + 1;
8517
+ });
8518
+ expect(scope.counter).toEqual(0);
8519
+
8520
+ scope.$digest();
8521
+ // no variable change
8522
+ expect(scope.counter).toEqual(0);
8523
+
8524
+ scope.name = 'adam';
8525
+ scope.$digest();
8526
+ expect(scope.counter).toEqual(1);
7994
8527
  * </pre>
7995
8528
  *
7996
8529
  */
@@ -8362,7 +8895,7 @@ function $RootScopeProvider(){
8362
8895
  * Afterwards, the event propagates to all direct and indirect scopes of the current scope and
8363
8896
  * calls all registered listeners along the way. The event cannot be canceled.
8364
8897
  *
8365
- * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed
8898
+ * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
8366
8899
  * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
8367
8900
  *
8368
8901
  * @param {string} name Event name to emit.
@@ -8459,6 +8992,7 @@ function $RootScopeProvider(){
8459
8992
  *
8460
8993
  * @property {boolean} history Does the browser support html5 history api ?
8461
8994
  * @property {boolean} hashchange Does the browser support hashchange event ?
8995
+ * @property {boolean} supportsTransitions Does the browser support CSS transition events ?
8462
8996
  *
8463
8997
  * @description
8464
8998
  * This is very simple implementation of testing browser's features.
@@ -8466,8 +9000,25 @@ function $RootScopeProvider(){
8466
9000
  function $SnifferProvider() {
8467
9001
  this.$get = ['$window', '$document', function($window, $document) {
8468
9002
  var eventSupport = {},
8469
- android = int((/android (\d+)/.exec(lowercase($window.navigator.userAgent)) || [])[1]),
8470
- document = $document[0];
9003
+ android = int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
9004
+ document = $document[0] || {},
9005
+ vendorPrefix,
9006
+ vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
9007
+ bodyStyle = document.body && document.body.style,
9008
+ transitions = false,
9009
+ match;
9010
+
9011
+ if (bodyStyle) {
9012
+ for(var prop in bodyStyle) {
9013
+ if(match = vendorRegex.exec(prop)) {
9014
+ vendorPrefix = match[0];
9015
+ vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
9016
+ break;
9017
+ }
9018
+ }
9019
+ transitions = !!(vendorPrefix + 'Transition' in bodyStyle);
9020
+ }
9021
+
8471
9022
 
8472
9023
  return {
8473
9024
  // Android has history.pushState, but it does not update location correctly
@@ -8491,7 +9042,9 @@ function $SnifferProvider() {
8491
9042
 
8492
9043
  return eventSupport[event];
8493
9044
  },
8494
- csp: document.securityPolicy ? document.securityPolicy.isActive : false
9045
+ csp: document.securityPolicy ? document.securityPolicy.isActive : false,
9046
+ vendorPrefix: vendorPrefix,
9047
+ supportsTransitions : transitions
8495
9048
  };
8496
9049
  }];
8497
9050
  }
@@ -8504,7 +9057,7 @@ function $SnifferProvider() {
8504
9057
  * A reference to the browser's `window` object. While `window`
8505
9058
  * is globally available in JavaScript, it causes testability problems, because
8506
9059
  * it is a global variable. In angular we always refer to it through the
8507
- * `$window` service, so it may be overriden, removed or mocked for testing.
9060
+ * `$window` service, so it may be overridden, removed or mocked for testing.
8508
9061
  *
8509
9062
  * All expressions are evaluated with respect to current scope so they don't
8510
9063
  * suffer from window globality.
@@ -8678,20 +9231,52 @@ function $HttpProvider() {
8678
9231
  xsrfHeaderName: 'X-XSRF-TOKEN'
8679
9232
  };
8680
9233
 
8681
- var providerResponseInterceptors = this.responseInterceptors = [];
9234
+ /**
9235
+ * Are order by request. I.E. they are applied in the same order as
9236
+ * array on request, but revers order on response.
9237
+ */
9238
+ var interceptorFactories = this.interceptors = [];
9239
+ /**
9240
+ * For historical reasons, response interceptors ordered by the order in which
9241
+ * they are applied to response. (This is in revers to interceptorFactories)
9242
+ */
9243
+ var responseInterceptorFactories = this.responseInterceptors = [];
8682
9244
 
8683
9245
  this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
8684
9246
  function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
8685
9247
 
8686
- var defaultCache = $cacheFactory('$http'),
8687
- responseInterceptors = [];
9248
+ var defaultCache = $cacheFactory('$http');
8688
9249
 
8689
- forEach(providerResponseInterceptors, function(interceptor) {
8690
- responseInterceptors.push(
8691
- isString(interceptor)
8692
- ? $injector.get(interceptor)
8693
- : $injector.invoke(interceptor)
8694
- );
9250
+ /**
9251
+ * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
9252
+ * The reversal is needed so that we can build up the interception chain around the
9253
+ * server request.
9254
+ */
9255
+ var reversedInterceptors = [];
9256
+
9257
+ forEach(interceptorFactories, function(interceptorFactory) {
9258
+ reversedInterceptors.unshift(isString(interceptorFactory)
9259
+ ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
9260
+ });
9261
+
9262
+ forEach(responseInterceptorFactories, function(interceptorFactory, index) {
9263
+ var responseFn = isString(interceptorFactory)
9264
+ ? $injector.get(interceptorFactory)
9265
+ : $injector.invoke(interceptorFactory);
9266
+
9267
+ /**
9268
+ * Response interceptors go before "around" interceptors (no real reason, just
9269
+ * had to pick one.) But they are already revesed, so we can't use unshift, hence
9270
+ * the splice.
9271
+ */
9272
+ reversedInterceptors.splice(index, 0, {
9273
+ response: function(response) {
9274
+ return responseFn($q.when(response));
9275
+ },
9276
+ responseError: function(response) {
9277
+ return responseFn($q.reject(response));
9278
+ }
9279
+ });
8695
9280
  });
8696
9281
 
8697
9282
 
@@ -8788,7 +9373,7 @@ function $HttpProvider() {
8788
9373
  * `$httpProvider.defaults.headers.get['My-Header']='value'`.
8789
9374
  *
8790
9375
  * Additionally, the defaults can be set at runtime via the `$http.defaults` object in a similar
8791
- * fassion as described above.
9376
+ * fashion as described above.
8792
9377
  *
8793
9378
  *
8794
9379
  * # Transforming Requests and Responses
@@ -8806,10 +9391,14 @@ function $HttpProvider() {
8806
9391
  * - if XSRF prefix is detected, strip it (see Security Considerations section below)
8807
9392
  * - if json response is detected, deserialize it using a JSON parser
8808
9393
  *
8809
- * To override these transformation locally, specify transform functions as `transformRequest`
8810
- * and/or `transformResponse` properties of the config object. To globally override the default
8811
- * transforms, override the `$httpProvider.defaults.transformRequest` and
8812
- * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`.
9394
+ * To globally augment or override the default transforms, modify the `$httpProvider.defaults.transformRequest` and
9395
+ * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`. These properties are by default an
9396
+ * array of transform functions, which allows you to `push` or `unshift` a new transformation function into the
9397
+ * transformation chain. You can also decide to completely override any default transformations by assigning your
9398
+ * transformation functions to these properties directly without the array wrapper.
9399
+ *
9400
+ * Similarly, to locally override the request/response transforms, augment the `transformRequest` and/or
9401
+ * `transformResponse` properties of the config object passed into `$http`.
8813
9402
  *
8814
9403
  *
8815
9404
  * # Caching
@@ -8825,8 +9414,94 @@ function $HttpProvider() {
8825
9414
  * cache, but the cache is not populated yet, only one request to the server will be made and
8826
9415
  * the remaining requests will be fulfilled using the response for the first request.
8827
9416
  *
9417
+ * A custom default cache built with $cacheFactory can be provided in $http.defaults.cache.
9418
+ * To skip it, set configuration property `cache` to `false`.
9419
+ *
8828
9420
  *
8829
- * # Response interceptors
9421
+ * # Interceptors
9422
+ *
9423
+ * Before you start creating interceptors, be sure to understand the
9424
+ * {@link ng.$q $q and deferred/promise APIs}.
9425
+ *
9426
+ * For purposes of global error handling, authentication or any kind of synchronous or
9427
+ * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
9428
+ * able to intercept requests before they are handed to the server and
9429
+ * responses before they are handed over to the application code that
9430
+ * initiated these requests. The interceptors leverage the {@link ng.$q
9431
+ * promise APIs} to fulfil this need for both synchronous and asynchronous pre-processing.
9432
+ *
9433
+ * The interceptors are service factories that are registered with the $httpProvider by
9434
+ * adding them to the `$httpProvider.interceptors` array. The factory is called and
9435
+ * injected with dependencies (if specified) and returns the interceptor.
9436
+ *
9437
+ * There are two kinds of interceptors (and two kinds of rejection interceptors):
9438
+ *
9439
+ * * `request`: interceptors get called with http `config` object. The function is free to modify
9440
+ * the `config` or create a new one. The function needs to return the `config` directly or as a
9441
+ * promise.
9442
+ * * `requestError`: interceptor gets called when a previous interceptor threw an error or resolved
9443
+ * with a rejection.
9444
+ * * `response`: interceptors get called with http `response` object. The function is free to modify
9445
+ * the `response` or create a new one. The function needs to return the `response` directly or as a
9446
+ * promise.
9447
+ * * `responseError`: interceptor gets called when a previous interceptor threw an error or resolved
9448
+ * with a rejection.
9449
+ *
9450
+ *
9451
+ * <pre>
9452
+ * // register the interceptor as a service
9453
+ * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
9454
+ * return {
9455
+ * // optional method
9456
+ * 'request': function(config) {
9457
+ * // do something on success
9458
+ * return config || $q.when(config);
9459
+ * },
9460
+ *
9461
+ * // optional method
9462
+ * 'requestError': function(rejection) {
9463
+ * // do something on error
9464
+ * if (canRecover(rejection)) {
9465
+ * return responseOrNewPromise
9466
+ * }
9467
+ * return $q.reject(rejection);
9468
+ * },
9469
+ *
9470
+ *
9471
+ *
9472
+ * // optional method
9473
+ * 'response': function(response) {
9474
+ * // do something on success
9475
+ * return response || $q.when(response);
9476
+ * },
9477
+ *
9478
+ * // optional method
9479
+ * 'responseError': function(rejection) {
9480
+ * // do something on error
9481
+ * if (canRecover(rejection)) {
9482
+ * return responseOrNewPromise
9483
+ * }
9484
+ * return $q.reject(rejection);
9485
+ * };
9486
+ * }
9487
+ * });
9488
+ *
9489
+ * $httpProvider.interceptors.push('myHttpInterceptor');
9490
+ *
9491
+ *
9492
+ * // register the interceptor via an anonymous factory
9493
+ * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
9494
+ * return {
9495
+ * 'request': function(config) {
9496
+ * // same as above
9497
+ * },
9498
+ * 'response': function(response) {
9499
+ * // same as above
9500
+ * }
9501
+ * });
9502
+ * </pre>
9503
+ *
9504
+ * # Response interceptors (DEPRECATED)
8830
9505
  *
8831
9506
  * Before you start creating interceptors, be sure to understand the
8832
9507
  * {@link ng.$q $q and deferred/promise APIs}.
@@ -9042,45 +9717,66 @@ function $HttpProvider() {
9042
9717
  </file>
9043
9718
  </example>
9044
9719
  */
9045
- function $http(config) {
9046
- config.method = uppercase(config.method);
9720
+ function $http(requestConfig) {
9721
+ var config = {
9722
+ transformRequest: defaults.transformRequest,
9723
+ transformResponse: defaults.transformResponse
9724
+ };
9725
+ var headers = {};
9047
9726
 
9048
- var xsrfHeader = {},
9049
- xsrfCookieName = config.xsrfCookieName || defaults.xsrfCookieName,
9050
- xsrfHeaderName = config.xsrfHeaderName || defaults.xsrfHeaderName,
9051
- xsrfToken = isSameDomain(config.url, $browser.url()) ?
9052
- $browser.cookies()[xsrfCookieName] : undefined;
9053
- xsrfHeader[xsrfHeaderName] = xsrfToken;
9727
+ extend(config, requestConfig);
9728
+ config.headers = headers;
9729
+ config.method = uppercase(config.method);
9054
9730
 
9055
- var reqTransformFn = config.transformRequest || defaults.transformRequest,
9056
- respTransformFn = config.transformResponse || defaults.transformResponse,
9057
- defHeaders = defaults.headers,
9058
- reqHeaders = extend(xsrfHeader,
9059
- defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
9060
- reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
9061
- promise;
9731
+ extend(headers,
9732
+ defaults.headers.common,
9733
+ defaults.headers[lowercase(config.method)],
9734
+ requestConfig.headers);
9062
9735
 
9063
- // strip content-type if data is undefined
9064
- if (isUndefined(config.data)) {
9065
- delete reqHeaders['Content-Type'];
9736
+ var xsrfValue = isSameDomain(config.url, $browser.url())
9737
+ ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
9738
+ : undefined;
9739
+ if (xsrfValue) {
9740
+ headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
9066
9741
  }
9067
9742
 
9068
- if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
9069
- config.withCredentials = defaults.withCredentials;
9070
- }
9071
9743
 
9072
- // send request
9073
- promise = sendReq(config, reqData, reqHeaders);
9744
+ var serverRequest = function(config) {
9745
+ var reqData = transformData(config.data, headersGetter(headers), config.transformRequest);
9746
+
9747
+ // strip content-type if data is undefined
9748
+ if (isUndefined(config.data)) {
9749
+ delete headers['Content-Type'];
9750
+ }
9751
+
9752
+ if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
9753
+ config.withCredentials = defaults.withCredentials;
9754
+ }
9074
9755
 
9756
+ // send request
9757
+ return sendReq(config, reqData, headers).then(transformResponse, transformResponse);
9758
+ };
9075
9759
 
9076
- // transform future response
9077
- promise = promise.then(transformResponse, transformResponse);
9760
+ var chain = [serverRequest, undefined];
9761
+ var promise = $q.when(config);
9078
9762
 
9079
9763
  // apply interceptors
9080
- forEach(responseInterceptors, function(interceptor) {
9081
- promise = interceptor(promise);
9764
+ forEach(reversedInterceptors, function(interceptor) {
9765
+ if (interceptor.request || interceptor.requestError) {
9766
+ chain.unshift(interceptor.request, interceptor.requestError);
9767
+ }
9768
+ if (interceptor.response || interceptor.responseError) {
9769
+ chain.push(interceptor.response, interceptor.responseError);
9770
+ }
9082
9771
  });
9083
9772
 
9773
+ while(chain.length) {
9774
+ var thenFn = chain.shift();
9775
+ var rejectFn = chain.shift();
9776
+
9777
+ promise = promise.then(thenFn, rejectFn);
9778
+ };
9779
+
9084
9780
  promise.success = function(fn) {
9085
9781
  promise.then(function(response) {
9086
9782
  fn(response.data, response.status, response.headers, config);
@@ -9100,7 +9796,7 @@ function $HttpProvider() {
9100
9796
  function transformResponse(response) {
9101
9797
  // make a copy since the response must be cacheable
9102
9798
  var resp = extend({}, response, {
9103
- data: transformData(response.data, response.headers, respTransformFn)
9799
+ data: transformData(response.data, response.headers, config.transformResponse)
9104
9800
  });
9105
9801
  return (isSuccess(response.status))
9106
9802
  ? resp
@@ -9252,8 +9948,10 @@ function $HttpProvider() {
9252
9948
  promise.then(removePendingReq, removePendingReq);
9253
9949
 
9254
9950
 
9255
- if (config.cache && config.method == 'GET') {
9256
- cache = isObject(config.cache) ? config.cache : defaultCache;
9951
+ if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') {
9952
+ cache = isObject(config.cache) ? config.cache
9953
+ : isObject(defaults.cache) ? defaults.cache
9954
+ : defaultCache;
9257
9955
  }
9258
9956
 
9259
9957
  if (cache) {
@@ -9351,6 +10049,7 @@ function $HttpProvider() {
9351
10049
 
9352
10050
  }];
9353
10051
  }
10052
+
9354
10053
  var XHR = window.XMLHttpRequest || function() {
9355
10054
  try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
9356
10055
  try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
@@ -9440,8 +10139,12 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
9440
10139
  }
9441
10140
  // end of the workaround.
9442
10141
 
9443
- completeRequest(callback, status || xhr.status, xhr.response || xhr.responseText,
9444
- responseHeaders);
10142
+ // responseText is the old-school way of retrieving response (supported by IE8 & 9)
10143
+ // response and responseType properties were introduced in XHR Level2 spec (supported by IE10)
10144
+ completeRequest(callback,
10145
+ status || xhr.status,
10146
+ (xhr.responseType ? xhr.response : xhr.responseText),
10147
+ responseHeaders);
9445
10148
  }
9446
10149
  };
9447
10150
 
@@ -9667,7 +10370,7 @@ function $TimeoutProvider() {
9667
10370
  *
9668
10371
  * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To
9669
10372
  * achieve this a filter definition consists of a factory function which is annotated with dependencies and is
9670
- * responsible for creating a the filter function.
10373
+ * responsible for creating a filter function.
9671
10374
  *
9672
10375
  * <pre>
9673
10376
  * // Filter registration
@@ -9822,11 +10525,11 @@ function $FilterProvider($provide) {
9822
10525
 
9823
10526
  Search: <input ng-model="searchText">
9824
10527
  <table id="searchTextResults">
9825
- <tr><th>Name</th><th>Phone</th><tr>
10528
+ <tr><th>Name</th><th>Phone</th></tr>
9826
10529
  <tr ng-repeat="friend in friends | filter:searchText">
9827
10530
  <td>{{friend.name}}</td>
9828
10531
  <td>{{friend.phone}}</td>
9829
- <tr>
10532
+ </tr>
9830
10533
  </table>
9831
10534
  <hr>
9832
10535
  Any: <input ng-model="search.$"> <br>
@@ -9834,11 +10537,11 @@ function $FilterProvider($provide) {
9834
10537
  Phone only <input ng-model="search.phone"å><br>
9835
10538
  Equality <input type="checkbox" ng-model="strict"><br>
9836
10539
  <table id="searchObjResults">
9837
- <tr><th>Name</th><th>Phone</th><tr>
10540
+ <tr><th>Name</th><th>Phone</th></tr>
9838
10541
  <tr ng-repeat="friend in friends | filter:search:strict">
9839
10542
  <td>{{friend.name}}</td>
9840
10543
  <td>{{friend.phone}}</td>
9841
- <tr>
10544
+ </tr>
9842
10545
  </table>
9843
10546
  </doc:source>
9844
10547
  <doc:scenario>
@@ -10185,7 +10888,8 @@ function timeZoneGetter(date) {
10185
10888
  var zone = -1 * date.getTimezoneOffset();
10186
10889
  var paddedZone = (zone >= 0) ? "+" : "";
10187
10890
 
10188
- paddedZone += padNumber(zone / 60, 2) + padNumber(Math.abs(zone % 60), 2);
10891
+ paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
10892
+ padNumber(Math.abs(zone % 60), 2);
10189
10893
 
10190
10894
  return paddedZone;
10191
10895
  }
@@ -10255,7 +10959,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
10255
10959
  * * `'s'`: Second in minute (0-59)
10256
10960
  * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
10257
10961
  * * `'a'`: am/pm marker
10258
- * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200)
10962
+ * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
10259
10963
  *
10260
10964
  * `format` string can also be one of the following predefined
10261
10965
  * {@link guide/i18n localizable formats}:
@@ -10545,7 +11249,7 @@ function limitToFilter(){
10545
11249
  * Orders a specified `array` by the `expression` predicate.
10546
11250
  *
10547
11251
  * Note: this function is used to augment the `Array` type in Angular expressions. See
10548
- * {@link ng.$filter} for more informaton about Angular arrays.
11252
+ * {@link ng.$filter} for more information about Angular arrays.
10549
11253
  *
10550
11254
  * @param {Array} array The array to sort.
10551
11255
  * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
@@ -10588,12 +11292,12 @@ function limitToFilter(){
10588
11292
  (<a href ng-click="predicate = '-name'; reverse=false">^</a>)</th>
10589
11293
  <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th>
10590
11294
  <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th>
10591
- <tr>
11295
+ </tr>
10592
11296
  <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
10593
11297
  <td>{{friend.name}}</td>
10594
11298
  <td>{{friend.phone}}</td>
10595
11299
  <td>{{friend.age}}</td>
10596
- <tr>
11300
+ </tr>
10597
11301
  </table>
10598
11302
  </div>
10599
11303
  </doc:source>
@@ -12195,7 +12899,7 @@ var VALID_CLASS = 'ng-valid',
12195
12899
  * @property {Array.<Function>} $formatters Whenever the model value changes, it executes all of
12196
12900
  * these functions to convert the value as well as validate.
12197
12901
  *
12198
- * @property {Object} $error An bject hash with all errors as keys.
12902
+ * @property {Object} $error An object hash with all errors as keys.
12199
12903
  *
12200
12904
  * @property {boolean} $pristine True if user has not interacted with the control yet.
12201
12905
  * @property {boolean} $dirty True if user has already interacted with the control.
@@ -12703,7 +13407,7 @@ var ngValueDirective = function() {
12703
13407
  * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
12704
13408
  * `{{ expression }}` which is similar but less verbose.
12705
13409
  *
12706
- * Once scenario in which the use of `ngBind` is prefered over `{{ expression }}` binding is when
13410
+ * Once scenario in which the use of `ngBind` is preferred over `{{ expression }}` binding is when
12707
13411
  * it's desirable to put bindings into template that is momentarily displayed by the browser in its
12708
13412
  * raw state before Angular compiles it. Since `ngBind` is an element attribute, it makes the
12709
13413
  * bindings invisible to the user while the page is loading.
@@ -13041,13 +13745,13 @@ var ngClassEvenDirective = classDirective('Even', 1);
13041
13745
  * directive to avoid the undesirable flicker effect caused by the html template display.
13042
13746
  *
13043
13747
  * The directive can be applied to the `<body>` element, but typically a fine-grained application is
13044
- * prefered in order to benefit from progressive rendering of the browser view.
13748
+ * preferred in order to benefit from progressive rendering of the browser view.
13045
13749
  *
13046
13750
  * `ngCloak` works in cooperation with a css rule that is embedded within `angular.js` and
13047
13751
  * `angular.min.js` files. Following is the css rule:
13048
13752
  *
13049
13753
  * <pre>
13050
- * [ng\:cloak], [ng-cloak], .ng-cloak {
13754
+ * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
13051
13755
  * display: none;
13052
13756
  * }
13053
13757
  * </pre>
@@ -13256,7 +13960,7 @@ var ngCspDirective = ['$sniffer', function($sniffer) {
13256
13960
  */
13257
13961
  var ngEventDirectives = {};
13258
13962
  forEach(
13259
- 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup'.split(' '),
13963
+ 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress'.split(' '),
13260
13964
  function(name) {
13261
13965
  var directiveName = directiveNormalize('ng-' + name);
13262
13966
  ngEventDirectives[directiveName] = ['$parse', function($parse) {
@@ -13415,6 +14119,22 @@ forEach(
13415
14119
  */
13416
14120
 
13417
14121
 
14122
+ /**
14123
+ * @ngdoc directive
14124
+ * @name ng.directive:ngKeypress
14125
+ *
14126
+ * @description
14127
+ * Specify custom behavior on keypress event.
14128
+ *
14129
+ * @element ANY
14130
+ * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
14131
+ * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
14132
+ *
14133
+ * @example
14134
+ * See {@link ng.directive:ngClick ngClick}
14135
+ */
14136
+
14137
+
13418
14138
  /**
13419
14139
  * @ngdoc directive
13420
14140
  * @name ng.directive:ngSubmit
@@ -13484,6 +14204,13 @@ var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
13484
14204
  * (e.g. ngInclude won't work for cross-domain requests on all browsers and for
13485
14205
  * file:// access on some browsers).
13486
14206
  *
14207
+ * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**
14208
+ * and **leave** effects.
14209
+ *
14210
+ * @animations
14211
+ * enter - happens just after the ngInclude contents change and a new DOM element is created and injected into the ngInclude container
14212
+ * leave - happens just after the ngInclude contents change and just before the former contents are removed from the DOM
14213
+ *
13487
14214
  * @scope
13488
14215
  *
13489
14216
  * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
@@ -13506,7 +14233,9 @@ var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
13506
14233
  </select>
13507
14234
  url of the template: <tt>{{template.url}}</tt>
13508
14235
  <hr/>
13509
- <div ng-include src="template.url"></div>
14236
+ <div class="example-animate-container"
14237
+ ng-include="template.url"
14238
+ ng-animate="{enter: 'example-enter', leave: 'example-leave'}"></div>
13510
14239
  </div>
13511
14240
  </file>
13512
14241
  <file name="script.js">
@@ -13518,10 +14247,45 @@ var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
13518
14247
  }
13519
14248
  </file>
13520
14249
  <file name="template1.html">
13521
- Content of template1.html
14250
+ <div>Content of template1.html</div>
13522
14251
  </file>
13523
14252
  <file name="template2.html">
13524
- Content of template2.html
14253
+ <div>Content of template2.html</div>
14254
+ </file>
14255
+ <file name="animations.css">
14256
+ .example-leave-setup,
14257
+ .example-enter-setup {
14258
+ -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
14259
+ -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
14260
+ -ms-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
14261
+ -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
14262
+ transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
14263
+
14264
+ position:absolute;
14265
+ top:0;
14266
+ left:0;
14267
+ right:0;
14268
+ bottom:0;
14269
+ }
14270
+
14271
+ .example-animate-container > * {
14272
+ display:block;
14273
+ padding:10px;
14274
+ }
14275
+
14276
+ .example-enter-setup {
14277
+ top:-50px;
14278
+ }
14279
+ .example-enter-setup.example-enter-start {
14280
+ top:0;
14281
+ }
14282
+
14283
+ .example-leave-setup {
14284
+ top:0;
14285
+ }
14286
+ .example-leave-setup.example-leave-start {
14287
+ top:50px;
14288
+ }
13525
14289
  </file>
13526
14290
  <file name="scenario.js">
13527
14291
  it('should load template1.html', function() {
@@ -13550,8 +14314,8 @@ var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
13550
14314
  * @description
13551
14315
  * Emitted every time the ngInclude content is reloaded.
13552
14316
  */
13553
- var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile',
13554
- function($http, $templateCache, $anchorScroll, $compile) {
14317
+ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animator',
14318
+ function($http, $templateCache, $anchorScroll, $compile, $animator) {
13555
14319
  return {
13556
14320
  restrict: 'ECA',
13557
14321
  terminal: true,
@@ -13560,7 +14324,8 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
13560
14324
  onloadExp = attr.onload || '',
13561
14325
  autoScrollExp = attr.autoscroll;
13562
14326
 
13563
- return function(scope, element) {
14327
+ return function(scope, element, attr) {
14328
+ var animate = $animator(scope, attr);
13564
14329
  var changeCounter = 0,
13565
14330
  childScope;
13566
14331
 
@@ -13569,8 +14334,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
13569
14334
  childScope.$destroy();
13570
14335
  childScope = null;
13571
14336
  }
13572
-
13573
- element.html('');
14337
+ animate.leave(element.contents(), element);
13574
14338
  };
13575
14339
 
13576
14340
  scope.$watch(srcExp, function ngIncludeWatchAction(src) {
@@ -13582,9 +14346,12 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
13582
14346
 
13583
14347
  if (childScope) childScope.$destroy();
13584
14348
  childScope = scope.$new();
14349
+ animate.leave(element.contents(), element);
13585
14350
 
13586
- element.html(response);
13587
- $compile(element.contents())(childScope);
14351
+ var contents = jqLite('<div/>').html(response).contents();
14352
+
14353
+ animate.enter(contents, element);
14354
+ $compile(contents)(childScope);
13588
14355
 
13589
14356
  if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
13590
14357
  $anchorScroll();
@@ -13595,7 +14362,9 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
13595
14362
  }).error(function() {
13596
14363
  if (thisChangeId === changeCounter) clearContent();
13597
14364
  });
13598
- } else clearContent();
14365
+ } else {
14366
+ clearContent();
14367
+ }
13599
14368
  });
13600
14369
  };
13601
14370
  }
@@ -13689,7 +14458,7 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
13689
14458
  * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
13690
14459
  * plural categories} in Angular's default en-US locale: "one" and "other".
13691
14460
  *
13692
- * While a pural category may match many numbers (for example, in en-US locale, "other" can match
14461
+ * While a plural category may match many numbers (for example, in en-US locale, "other" can match
13693
14462
  * any number that is not 1), an explicit number rule can only match one number. For example, the
13694
14463
  * explicit number rule for "3" matches the number 3. You will see the use of plural categories
13695
14464
  * and explicit number rules throughout later parts of this documentation.
@@ -13757,7 +14526,7 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
13757
14526
  * plural categories "one" and "other".
13758
14527
  *
13759
14528
  * @param {string|expression} count The variable to be bounded to.
13760
- * @param {string} when The mapping between plural category to its correspoding strings.
14529
+ * @param {string} when The mapping between plural category to its corresponding strings.
13761
14530
  * @param {number=} offset Offset to deduct from the total number.
13762
14531
  *
13763
14532
  * @example
@@ -13892,11 +14661,18 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
13892
14661
  * * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator.
13893
14662
  * * `$last` – `{boolean}` – true if the repeated element is last in the iterator.
13894
14663
  *
14664
+ * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**,
14665
+ * **leave** and **move** effects.
14666
+ *
14667
+ * @animations
14668
+ * enter - when a new item is added to the list or when an item is revealed after a filter
14669
+ * leave - when an item is removed from the list or when an item is filtered out
14670
+ * move - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
13895
14671
  *
13896
14672
  * @element ANY
13897
14673
  * @scope
13898
14674
  * @priority 1000
13899
- * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. Two
14675
+ * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
13900
14676
  * formats are currently supported:
13901
14677
  *
13902
14678
  * * `variable in expression` – where variable is the user defined loop variable and `expression`
@@ -13909,160 +14685,278 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
13909
14685
  *
13910
14686
  * For example: `(name, age) in {'adam':10, 'amalie':12}`.
13911
14687
  *
14688
+ * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function
14689
+ * which can be used to associate the objects in the collection with the DOM elements. If no tractking function
14690
+ * is specified the ng-repeat associates elements by identity in the collection. It is an error to have
14691
+ * more then one tractking function to resolve to the same key. (This would mean that two distinct objects are
14692
+ * mapped to the same DOM element, which is not possible.)
14693
+ *
14694
+ * For example: `item in items` is equivalent to `item in items track by $id(item)'. This implies that the DOM elements
14695
+ * will be associated by item identity in the array.
14696
+ *
14697
+ * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
14698
+ * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
14699
+ * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
14700
+ * element in the same way ian the DOM.
14701
+ *
14702
+ * For example: `item in items track by item.id` Is a typical pattern when the items come from the database. In this
14703
+ * case the object identity does not matter. Two objects are considered equivalent as long as their `id`
14704
+ * property is same.
14705
+ *
13912
14706
  * @example
13913
14707
  * This example initializes the scope to a list of names and
13914
14708
  * then uses `ngRepeat` to display every person:
13915
- <doc:example>
13916
- <doc:source>
13917
- <div ng-init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
13918
- I have {{friends.length}} friends. They are:
13919
- <ul>
13920
- <li ng-repeat="friend in friends">
13921
- [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
13922
- </li>
13923
- </ul>
13924
- </div>
13925
- </doc:source>
13926
- <doc:scenario>
13927
- it('should check ng-repeat', function() {
13928
- var r = using('.doc-example-live').repeater('ul li');
13929
- expect(r.count()).toBe(2);
13930
- expect(r.row(0)).toEqual(["1","John","25"]);
13931
- expect(r.row(1)).toEqual(["2","Mary","28"]);
13932
- });
13933
- </doc:scenario>
13934
- </doc:example>
13935
- */
13936
- var ngRepeatDirective = ngDirective({
13937
- transclude: 'element',
13938
- priority: 1000,
13939
- terminal: true,
13940
- compile: function(element, attr, linker) {
13941
- return function(scope, iterStartElement, attr){
13942
- var expression = attr.ngRepeat;
13943
- var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
13944
- lhs, rhs, valueIdent, keyIdent;
13945
- if (! match) {
13946
- throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" +
13947
- expression + "'.");
13948
- }
13949
- lhs = match[1];
13950
- rhs = match[2];
13951
- match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
13952
- if (!match) {
13953
- throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
13954
- lhs + "'.");
13955
- }
13956
- valueIdent = match[3] || match[1];
13957
- keyIdent = match[2];
13958
-
13959
- // Store a list of elements from previous run. This is a hash where key is the item from the
13960
- // iterator, and the value is an array of objects with following properties.
13961
- // - scope: bound scope
13962
- // - element: previous element.
13963
- // - index: position
13964
- // We need an array of these objects since the same object can be returned from the iterator.
13965
- // We expect this to be a rare case.
13966
- var lastOrder = new HashQueueMap();
13967
-
13968
- scope.$watch(function ngRepeatWatch(scope){
13969
- var index, length,
13970
- collection = scope.$eval(rhs),
13971
- cursor = iterStartElement, // current position of the node
13972
- // Same as lastOrder but it has the current state. It will become the
13973
- // lastOrder on the next iteration.
13974
- nextOrder = new HashQueueMap(),
13975
- arrayLength,
13976
- childScope,
13977
- key, value, // key/value of iteration
13978
- array,
13979
- last; // last object information {scope, element, index}
13980
-
13981
-
13982
-
13983
- if (!isArray(collection)) {
13984
- // if object, extract keys, sort them and use to determine order of iteration over obj props
13985
- array = [];
13986
- for(key in collection) {
13987
- if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
13988
- array.push(key);
13989
- }
13990
- }
13991
- array.sort();
13992
- } else {
13993
- array = collection || [];
13994
- }
14709
+ <example animations="true">
14710
+ <file name="index.html">
14711
+ <div ng-init="friends = [
14712
+ {name:'John', age:25, gender:'boy'},
14713
+ {name:'Jessie', age:30, gender:'girl'},
14714
+ {name:'Johanna', age:28, gender:'girl'},
14715
+ {name:'Joy', age:15, gender:'girl'},
14716
+ {name:'Mary', age:28, gender:'girl'},
14717
+ {name:'Peter', age:95, gender:'boy'},
14718
+ {name:'Sebastian', age:50, gender:'boy'},
14719
+ {name:'Erika', age:27, gender:'girl'},
14720
+ {name:'Patrick', age:40, gender:'boy'},
14721
+ {name:'Samantha', age:60, gender:'girl'}
14722
+ ]">
14723
+ I have {{friends.length}} friends. They are:
14724
+ <input type="search" ng-model="q" placeholder="filter friends..." />
14725
+ <ul>
14726
+ <li ng-repeat="friend in friends | filter:q"
14727
+ ng-animate="{enter: 'example-repeat-enter',
14728
+ leave: 'example-repeat-leave',
14729
+ move: 'example-repeat-move'}">
14730
+ [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
14731
+ </li>
14732
+ </ul>
14733
+ </div>
14734
+ </file>
14735
+ <file name="animations.css">
14736
+ .example-repeat-enter-setup,
14737
+ .example-repeat-leave-setup,
14738
+ .example-repeat-move-setup {
14739
+ -webkit-transition:all linear 0.5s;
14740
+ -moz-transition:all linear 0.5s;
14741
+ -ms-transition:all linear 0.5s;
14742
+ -o-transition:all linear 0.5s;
14743
+ transition:all linear 0.5s;
14744
+ }
13995
14745
 
13996
- arrayLength = array.length;
14746
+ .example-repeat-enter-setup {
14747
+ line-height:0;
14748
+ opacity:0;
14749
+ }
14750
+ .example-repeat-enter-setup.example-repeat-enter-start {
14751
+ line-height:20px;
14752
+ opacity:1;
14753
+ }
13997
14754
 
13998
- // we are not using forEach for perf reasons (trying to avoid #call)
13999
- for (index = 0, length = array.length; index < length; index++) {
14000
- key = (collection === array) ? index : array[index];
14001
- value = collection[key];
14755
+ .example-repeat-leave-setup {
14756
+ opacity:1;
14757
+ line-height:20px;
14758
+ }
14759
+ .example-repeat-leave-setup.example-repeat-leave-start {
14760
+ opacity:0;
14761
+ line-height:0;
14762
+ }
14002
14763
 
14003
- last = lastOrder.shift(value);
14764
+ .example-repeat-move-setup { }
14765
+ .example-repeat-move-setup.example-repeat-move-start { }
14766
+ </file>
14767
+ <file name="scenario.js">
14768
+ it('should render initial data set', function() {
14769
+ var r = using('.doc-example-live').repeater('ul li');
14770
+ expect(r.count()).toBe(10);
14771
+ expect(r.row(0)).toEqual(["1","John","25"]);
14772
+ expect(r.row(1)).toEqual(["2","Jessie","30"]);
14773
+ expect(r.row(9)).toEqual(["10","Samantha","60"]);
14774
+ expect(binding('friends.length')).toBe("10");
14775
+ });
14004
14776
 
14005
- if (last) {
14006
- // if we have already seen this object, then we need to reuse the
14007
- // associated scope/element
14008
- childScope = last.scope;
14009
- nextOrder.push(value, last);
14777
+ it('should update repeater when filter predicate changes', function() {
14778
+ var r = using('.doc-example-live').repeater('ul li');
14779
+ expect(r.count()).toBe(10);
14010
14780
 
14011
- if (index === last.index) {
14012
- // do nothing
14013
- cursor = last.element;
14014
- } else {
14015
- // existing item which got moved
14016
- last.index = index;
14017
- // This may be a noop, if the element is next, but I don't know of a good way to
14018
- // figure this out, since it would require extra DOM access, so let's just hope that
14019
- // the browsers realizes that it is noop, and treats it as such.
14020
- cursor.after(last.element);
14021
- cursor = last.element;
14022
- }
14023
- } else {
14024
- // new item which we don't know about
14025
- childScope = scope.$new();
14026
- }
14781
+ input('q').enter('ma');
14027
14782
 
14028
- childScope[valueIdent] = value;
14029
- if (keyIdent) childScope[keyIdent] = key;
14030
- childScope.$index = index;
14031
-
14032
- childScope.$first = (index === 0);
14033
- childScope.$last = (index === (arrayLength - 1));
14034
- childScope.$middle = !(childScope.$first || childScope.$last);
14035
-
14036
- if (!last) {
14037
- linker(childScope, function(clone){
14038
- cursor.after(clone);
14039
- last = {
14040
- scope: childScope,
14041
- element: (cursor = clone),
14042
- index: index
14043
- };
14044
- nextOrder.push(value, last);
14045
- });
14783
+ expect(r.count()).toBe(2);
14784
+ expect(r.row(0)).toEqual(["1","Mary","28"]);
14785
+ expect(r.row(1)).toEqual(["2","Samantha","60"]);
14786
+ });
14787
+ </file>
14788
+ </example>
14789
+ */
14790
+ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
14791
+ var NG_REMOVED = '$$NG_REMOVED';
14792
+ return {
14793
+ transclude: 'element',
14794
+ priority: 1000,
14795
+ terminal: true,
14796
+ compile: function(element, attr, linker) {
14797
+ return function($scope, $element, $attr){
14798
+ var animate = $animator($scope, $attr);
14799
+ var expression = $attr.ngRepeat;
14800
+ var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
14801
+ trackByExp, hashExpFn, trackByIdFn, lhs, rhs, valueIdentifier, keyIdentifier,
14802
+ hashFnLocals = {$id: hashKey};
14803
+
14804
+ if (!match) {
14805
+ throw Error("Expected ngRepeat in form of '_item_ in _collection_[ track by _id_]' but got '" +
14806
+ expression + "'.");
14807
+ }
14808
+
14809
+ lhs = match[1];
14810
+ rhs = match[2];
14811
+ trackByExp = match[4];
14812
+
14813
+ if (trackByExp) {
14814
+ hashExpFn = $parse(trackByExp);
14815
+ trackByIdFn = function(key, value, index) {
14816
+ // assign key, value, and $index to the locals so that they can be used in hash functions
14817
+ if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
14818
+ hashFnLocals[valueIdentifier] = value;
14819
+ hashFnLocals.$index = index;
14820
+ return hashExpFn($scope, hashFnLocals);
14821
+ };
14822
+ } else {
14823
+ trackByIdFn = function(key, value) {
14824
+ return hashKey(value);
14046
14825
  }
14047
14826
  }
14048
14827
 
14049
- //shrink children
14050
- for (key in lastOrder) {
14051
- if (lastOrder.hasOwnProperty(key)) {
14052
- array = lastOrder[key];
14053
- while(array.length) {
14054
- value = array.pop();
14055
- value.element.remove();
14056
- value.scope.$destroy();
14828
+ match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
14829
+ if (!match) {
14830
+ throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
14831
+ lhs + "'.");
14832
+ }
14833
+ valueIdentifier = match[3] || match[1];
14834
+ keyIdentifier = match[2];
14835
+
14836
+ // Store a list of elements from previous run. This is a hash where key is the item from the
14837
+ // iterator, and the value is objects with following properties.
14838
+ // - scope: bound scope
14839
+ // - element: previous element.
14840
+ // - index: position
14841
+ var lastBlockMap = {};
14842
+
14843
+ //watch props
14844
+ $scope.$watchCollection(rhs, function ngRepeatAction(collection){
14845
+ var index, length,
14846
+ cursor = $element, // current position of the node
14847
+ nextCursor,
14848
+ // Same as lastBlockMap but it has the current state. It will become the
14849
+ // lastBlockMap on the next iteration.
14850
+ nextBlockMap = {},
14851
+ arrayLength,
14852
+ childScope,
14853
+ key, value, // key/value of iteration
14854
+ trackById,
14855
+ collectionKeys,
14856
+ block, // last object information {scope, element, id}
14857
+ nextBlockOrder = [];
14858
+
14859
+
14860
+ if (isArray(collection)) {
14861
+ collectionKeys = collection;
14862
+ } else {
14863
+ // if object, extract keys, sort them and use to determine order of iteration over obj props
14864
+ collectionKeys = [];
14865
+ for (key in collection) {
14866
+ if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
14867
+ collectionKeys.push(key);
14868
+ }
14869
+ }
14870
+ collectionKeys.sort();
14871
+ }
14872
+
14873
+ arrayLength = collectionKeys.length;
14874
+
14875
+ // locate existing items
14876
+ length = nextBlockOrder.length = collectionKeys.length;
14877
+ for(index = 0; index < length; index++) {
14878
+ key = (collection === collectionKeys) ? index : collectionKeys[index];
14879
+ value = collection[key];
14880
+ trackById = trackByIdFn(key, value, index);
14881
+ if((block = lastBlockMap[trackById])) {
14882
+ delete lastBlockMap[trackById];
14883
+ nextBlockMap[trackById] = block;
14884
+ nextBlockOrder[index] = block;
14885
+ } else if (nextBlockMap.hasOwnProperty(trackById)) {
14886
+ // restore lastBlockMap
14887
+ forEach(nextBlockOrder, function(block) {
14888
+ if (block && block.element) lastBlockMap[block.id] = block;
14889
+ });
14890
+ // This is a duplicate and we need to throw an error
14891
+ throw new Error('Duplicates in a repeater are not allowed. Repeater: ' + expression);
14892
+ } else {
14893
+ // new never before seen block
14894
+ nextBlockOrder[index] = { id: trackById };
14895
+ }
14896
+ }
14897
+
14898
+ // remove existing items
14899
+ for (key in lastBlockMap) {
14900
+ if (lastBlockMap.hasOwnProperty(key)) {
14901
+ block = lastBlockMap[key];
14902
+ animate.leave(block.element);
14903
+ block.element[0][NG_REMOVED] = true;
14904
+ block.scope.$destroy();
14057
14905
  }
14058
14906
  }
14059
- }
14060
14907
 
14061
- lastOrder = nextOrder;
14062
- });
14063
- };
14064
- }
14065
- });
14908
+ // we are not using forEach for perf reasons (trying to avoid #call)
14909
+ for (index = 0, length = collectionKeys.length; index < length; index++) {
14910
+ key = (collection === collectionKeys) ? index : collectionKeys[index];
14911
+ value = collection[key];
14912
+ block = nextBlockOrder[index];
14913
+
14914
+ if (block.element) {
14915
+ // if we have already seen this object, then we need to reuse the
14916
+ // associated scope/element
14917
+ childScope = block.scope;
14918
+
14919
+ nextCursor = cursor[0];
14920
+ do {
14921
+ nextCursor = nextCursor.nextSibling;
14922
+ } while(nextCursor && nextCursor[NG_REMOVED]);
14923
+
14924
+ if (block.element[0] == nextCursor) {
14925
+ // do nothing
14926
+ cursor = block.element;
14927
+ } else {
14928
+ // existing item which got moved
14929
+ animate.move(block.element, null, cursor);
14930
+ cursor = block.element;
14931
+ }
14932
+ } else {
14933
+ // new item which we don't know about
14934
+ childScope = $scope.$new();
14935
+ }
14936
+
14937
+ childScope[valueIdentifier] = value;
14938
+ if (keyIdentifier) childScope[keyIdentifier] = key;
14939
+ childScope.$index = index;
14940
+ childScope.$first = (index === 0);
14941
+ childScope.$last = (index === (arrayLength - 1));
14942
+ childScope.$middle = !(childScope.$first || childScope.$last);
14943
+
14944
+ if (!block.element) {
14945
+ linker(childScope, function(clone) {
14946
+ animate.enter(clone, null, cursor);
14947
+ cursor = clone;
14948
+ block.scope = childScope;
14949
+ block.element = clone;
14950
+ nextBlockMap[block.id] = block;
14951
+ });
14952
+ }
14953
+ }
14954
+ lastBlockMap = nextBlockMap;
14955
+ });
14956
+ };
14957
+ }
14958
+ };
14959
+ }];
14066
14960
 
14067
14961
  /**
14068
14962
  * @ngdoc directive
@@ -14070,20 +14964,86 @@ var ngRepeatDirective = ngDirective({
14070
14964
  *
14071
14965
  * @description
14072
14966
  * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
14073
- * conditionally.
14967
+ * conditionally based on **"truthy"** values evaluated within an {expression}. In other
14968
+ * words, if the expression assigned to **ngShow evaluates to a true value** then **the element is set to visible**
14969
+ * (via `display:block` in css) and **if false** then **the element is set to hidden** (so display:none).
14970
+ * With ngHide this is the reverse whereas true values cause the element itself to become
14971
+ * hidden.
14972
+ *
14973
+ * Additionally, you can also provide animations via the ngAnimate attribute to animate the **show**
14974
+ * and **hide** effects.
14975
+ *
14976
+ * @animations
14977
+ * show - happens after the ngShow expression evaluates to a truthy value and the contents are set to visible
14978
+ * hide - happens before the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden
14074
14979
  *
14075
14980
  * @element ANY
14076
14981
  * @param {expression} ngShow If the {@link guide/expression expression} is truthy
14077
14982
  * then the element is shown or hidden respectively.
14078
14983
  *
14079
14984
  * @example
14080
- <doc:example>
14081
- <doc:source>
14082
- Click me: <input type="checkbox" ng-model="checked"><br/>
14083
- Show: <span ng-show="checked">I show up when your checkbox is checked.</span> <br/>
14084
- Hide: <span ng-hide="checked">I hide when your checkbox is checked.</span>
14085
- </doc:source>
14086
- <doc:scenario>
14985
+ <example animations="true">
14986
+ <file name="index.html">
14987
+ Click me: <input type="checkbox" ng-model="checked"><br/>
14988
+ <div>
14989
+ Show:
14990
+ <span class="check-element"
14991
+ ng-show="checked"
14992
+ ng-animate="{show: 'example-show', hide: 'example-hide'}">
14993
+ <span class="icon-thumbs-up"></span> I show up when your checkbox is checked.
14994
+ </span>
14995
+ </div>
14996
+ <div>
14997
+ Hide:
14998
+ <span class="check-element"
14999
+ ng-hide="checked"
15000
+ ng-animate="{show: 'example-show', hide: 'example-hide'}">
15001
+ <span class="icon-thumbs-down"></span> I hide when your checkbox is checked.
15002
+ </span>
15003
+ </div>
15004
+ </file>
15005
+ <file name="animations.css">
15006
+ .example-show-setup, .example-hide-setup {
15007
+ -webkit-transition:all linear 0.5s;
15008
+ -moz-transition:all linear 0.5s;
15009
+ -ms-transition:all linear 0.5s;
15010
+ -o-transition:all linear 0.5s;
15011
+ transition:all linear 0.5s;
15012
+ }
15013
+
15014
+ .example-show-setup {
15015
+ line-height:0;
15016
+ opacity:0;
15017
+ padding:0 10px;
15018
+ }
15019
+ .example-show-start.example-show-start {
15020
+ line-height:20px;
15021
+ opacity:1;
15022
+ padding:10px;
15023
+ border:1px solid black;
15024
+ background:white;
15025
+ }
15026
+
15027
+ .example-hide-setup {
15028
+ line-height:20px;
15029
+ opacity:1;
15030
+ padding:10px;
15031
+ border:1px solid black;
15032
+ background:white;
15033
+ }
15034
+ .example-hide-start.example-hide-start {
15035
+ line-height:0;
15036
+ opacity:0;
15037
+ padding:0 10px;
15038
+ }
15039
+
15040
+ .check-element {
15041
+ padding:10px;
15042
+ border:1px solid black;
15043
+ background:white;
15044
+ }
15045
+ </file>
15046
+ <file name="scenario.js">
14087
15047
  it('should check ng-show / ng-hide', function() {
14088
15048
  expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
14089
15049
  expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
@@ -14093,15 +15053,18 @@ var ngRepeatDirective = ngDirective({
14093
15053
  expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
14094
15054
  expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
14095
15055
  });
14096
- </doc:scenario>
14097
- </doc:example>
15056
+ </file>
15057
+ </example>
14098
15058
  */
14099
15059
  //TODO(misko): refactor to remove element from the DOM
14100
- var ngShowDirective = ngDirective(function(scope, element, attr){
14101
- scope.$watch(attr.ngShow, function ngShowWatchAction(value){
14102
- element.css('display', toBoolean(value) ? '' : 'none');
14103
- });
14104
- });
15060
+ var ngShowDirective = ['$animator', function($animator) {
15061
+ return function(scope, element, attr) {
15062
+ var animate = $animator(scope, attr);
15063
+ scope.$watch(attr.ngShow, function ngShowWatchAction(value){
15064
+ animate[toBoolean(value) ? 'show' : 'hide'](element);
15065
+ });
15066
+ };
15067
+ }];
14105
15068
 
14106
15069
 
14107
15070
  /**
@@ -14109,39 +15072,108 @@ var ngShowDirective = ngDirective(function(scope, element, attr){
14109
15072
  * @name ng.directive:ngHide
14110
15073
  *
14111
15074
  * @description
14112
- * The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML)
14113
- * conditionally.
15075
+ * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
15076
+ * conditionally based on **"truthy"** values evaluated within an {expression}. In other
15077
+ * words, if the expression assigned to **ngShow evaluates to a true value** then **the element is set to visible**
15078
+ * (via `display:block` in css) and **if false** then **the element is set to hidden** (so display:none).
15079
+ * With ngHide this is the reverse whereas true values cause the element itself to become
15080
+ * hidden.
15081
+ *
15082
+ * Additionally, you can also provide animations via the ngAnimate attribute to animate the **show**
15083
+ * and **hide** effects.
15084
+ *
15085
+ * @animations
15086
+ * show - happens after the ngHide expression evaluates to a non truthy value and the contents are set to visible
15087
+ * hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden
14114
15088
  *
14115
15089
  * @element ANY
14116
15090
  * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
14117
15091
  * the element is shown or hidden respectively.
14118
15092
  *
14119
15093
  * @example
14120
- <doc:example>
14121
- <doc:source>
14122
- Click me: <input type="checkbox" ng-model="checked"><br/>
14123
- Show: <span ng-show="checked">I show up when you checkbox is checked?</span> <br/>
14124
- Hide: <span ng-hide="checked">I hide when you checkbox is checked?</span>
14125
- </doc:source>
14126
- <doc:scenario>
15094
+ <example animations="true">
15095
+ <file name="index.html">
15096
+ Click me: <input type="checkbox" ng-model="checked"><br/>
15097
+ <div>
15098
+ Show:
15099
+ <span class="check-element"
15100
+ ng-show="checked"
15101
+ ng-animate="{show: 'example-show', hide: 'example-hide'}">
15102
+ <span class="icon-thumbs-up"></span> I show up when your checkbox is checked.
15103
+ </span>
15104
+ </div>
15105
+ <div>
15106
+ Hide:
15107
+ <span class="check-element"
15108
+ ng-hide="checked"
15109
+ ng-animate="{show: 'example-show', hide: 'example-hide'}">
15110
+ <span class="icon-thumbs-down"></span> I hide when your checkbox is checked.
15111
+ </span>
15112
+ </div>
15113
+ </file>
15114
+ <file name="animations.css">
15115
+ .example-show-setup, .example-hide-setup {
15116
+ -webkit-transition:all linear 0.5s;
15117
+ -moz-transition:all linear 0.5s;
15118
+ -ms-transition:all linear 0.5s;
15119
+ -o-transition:all linear 0.5s;
15120
+ transition:all linear 0.5s;
15121
+ }
15122
+
15123
+ .example-show-setup {
15124
+ line-height:0;
15125
+ opacity:0;
15126
+ padding:0 10px;
15127
+ }
15128
+ .example-show-start.example-show-start {
15129
+ line-height:20px;
15130
+ opacity:1;
15131
+ padding:10px;
15132
+ border:1px solid black;
15133
+ background:white;
15134
+ }
15135
+
15136
+ .example-hide-setup {
15137
+ line-height:20px;
15138
+ opacity:1;
15139
+ padding:10px;
15140
+ border:1px solid black;
15141
+ background:white;
15142
+ }
15143
+ .example-hide-start.example-hide-start {
15144
+ line-height:0;
15145
+ opacity:0;
15146
+ padding:0 10px;
15147
+ }
15148
+
15149
+ .check-element {
15150
+ padding:10px;
15151
+ border:1px solid black;
15152
+ background:white;
15153
+ }
15154
+ </file>
15155
+ <file name="scenario.js">
14127
15156
  it('should check ng-show / ng-hide', function() {
14128
- expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
14129
- expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
15157
+ expect(element('.doc-example-live .check-element:first:hidden').count()).toEqual(1);
15158
+ expect(element('.doc-example-live .check-element:last:visible').count()).toEqual(1);
14130
15159
 
14131
15160
  input('checked').check();
14132
15161
 
14133
- expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
14134
- expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
15162
+ expect(element('.doc-example-live .check-element:first:visible').count()).toEqual(1);
15163
+ expect(element('.doc-example-live .check-element:last:hidden').count()).toEqual(1);
14135
15164
  });
14136
- </doc:scenario>
14137
- </doc:example>
15165
+ </file>
15166
+ </example>
14138
15167
  */
14139
15168
  //TODO(misko): refactor to remove element from the DOM
14140
- var ngHideDirective = ngDirective(function(scope, element, attr){
14141
- scope.$watch(attr.ngHide, function ngHideWatchAction(value){
14142
- element.css('display', toBoolean(value) ? 'none' : '');
14143
- });
14144
- });
15169
+ var ngHideDirective = ['$animator', function($animator) {
15170
+ return function(scope, element, attr) {
15171
+ var animate = $animator(scope, attr);
15172
+ scope.$watch(attr.ngHide, function ngHideWatchAction(value){
15173
+ animate[toBoolean(value) ? 'hide' : 'show'](element);
15174
+ });
15175
+ };
15176
+ }];
14145
15177
 
14146
15178
  /**
14147
15179
  * @ngdoc directive
@@ -14195,18 +15227,37 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
14195
15227
  * @restrict EA
14196
15228
  *
14197
15229
  * @description
14198
- * Conditionally change the DOM structure.
15230
+ * The ngSwitch directive is used to conditionally swap DOM structure on your template based on a scope expression.
15231
+ * Elements within ngSwitch but without ngSwitchWhen or ngSwitchDefault directives will be preserved at the location
15232
+ * as specified in the template.
15233
+ *
15234
+ * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
15235
+ * from the template cache), ngSwitch simply choses one of the nested elements and makes it visible based on which element
15236
+ * matches the value obtained from the evaluated expression. In other words, you define a container element
15237
+ * (where you place the directive), place an expression on the **on="..." attribute**
15238
+ * (or the **ng-switch="..." attribute**), define any inner elements inside of the directive and place
15239
+ * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
15240
+ * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
15241
+ * attribute is displayed.
15242
+ *
15243
+ * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**
15244
+ * and **leave** effects.
14199
15245
  *
14200
- * @usageContent
14201
- * <ANY ng-switch-when="matchValue1">...</ANY>
15246
+ * @animations
15247
+ * enter - happens after the ngSwtich contents change and the matched child element is placed inside the container
15248
+ * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
15249
+ *
15250
+ * @usage
15251
+ * <ANY ng-switch="expression">
15252
+ * <ANY ng-switch-when="matchValue1">...</ANY>
14202
15253
  * <ANY ng-switch-when="matchValue2">...</ANY>
14203
- * ...
14204
15254
  * <ANY ng-switch-default>...</ANY>
15255
+ * </ANY>
14205
15256
  *
14206
15257
  * @scope
14207
15258
  * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
14208
15259
  * @paramDescription
14209
- * On child elments add:
15260
+ * On child elements add:
14210
15261
  *
14211
15262
  * * `ngSwitchWhen`: the case statement to match against. If match then this
14212
15263
  * case will be displayed. If the same match appears multiple times, all the
@@ -14215,79 +15266,122 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
14215
15266
  * are multiple default cases, all of them will be displayed when no other
14216
15267
  * case match.
14217
15268
  *
15269
+ *
14218
15270
  * @example
14219
- <doc:example>
14220
- <doc:source>
14221
- <script>
14222
- function Ctrl($scope) {
14223
- $scope.items = ['settings', 'home', 'other'];
14224
- $scope.selection = $scope.items[0];
14225
- }
14226
- </script>
14227
- <div ng-controller="Ctrl">
14228
- <select ng-model="selection" ng-options="item for item in items">
14229
- </select>
14230
- <tt>selection={{selection}}</tt>
14231
- <hr/>
14232
- <div ng-switch on="selection" >
15271
+ <example animations="true">
15272
+ <file name="index.html">
15273
+ <div ng-controller="Ctrl">
15274
+ <select ng-model="selection" ng-options="item for item in items">
15275
+ </select>
15276
+ <tt>selection={{selection}}</tt>
15277
+ <hr/>
15278
+ <div
15279
+ class="example-animate-container"
15280
+ ng-switch on="selection"
15281
+ ng-animate="{enter: 'example-enter', leave: 'example-leave'}">
14233
15282
  <div ng-switch-when="settings">Settings Div</div>
14234
- <span ng-switch-when="home">Home Span</span>
14235
- <span ng-switch-default>default</span>
14236
- </div>
15283
+ <div ng-switch-when="home">Home Span</div>
15284
+ <div ng-switch-default>default</div>
14237
15285
  </div>
14238
- </doc:source>
14239
- <doc:scenario>
14240
- it('should start in settings', function() {
14241
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
14242
- });
14243
- it('should change to home', function() {
14244
- select('selection').option('home');
14245
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
14246
- });
14247
- it('should select deafault', function() {
14248
- select('selection').option('other');
14249
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
14250
- });
14251
- </doc:scenario>
14252
- </doc:example>
14253
- */
14254
- var NG_SWITCH = 'ng-switch';
14255
- var ngSwitchDirective = valueFn({
14256
- restrict: 'EA',
14257
- require: 'ngSwitch',
14258
- // asks for $scope to fool the BC controller module
14259
- controller: ['$scope', function ngSwitchController() {
14260
- this.cases = {};
14261
- }],
14262
- link: function(scope, element, attr, ctrl) {
14263
- var watchExpr = attr.ngSwitch || attr.on,
14264
- selectedTranscludes,
14265
- selectedElements,
14266
- selectedScopes = [];
15286
+ </div>
15287
+ </file>
15288
+ <file name="script.js">
15289
+ function Ctrl($scope) {
15290
+ $scope.items = ['settings', 'home', 'other'];
15291
+ $scope.selection = $scope.items[0];
15292
+ }
15293
+ </file>
15294
+ <file name="animations.css">
15295
+ .example-leave-setup, .example-enter-setup {
15296
+ -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
15297
+ -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
15298
+ -ms-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
15299
+ -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
15300
+ transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
14267
15301
 
14268
- scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
14269
- for (var i= 0, ii=selectedScopes.length; i<ii; i++) {
14270
- selectedScopes[i].$destroy();
14271
- selectedElements[i].remove();
15302
+ position:absolute;
15303
+ top:0;
15304
+ left:0;
15305
+ right:0;
15306
+ bottom:0;
14272
15307
  }
14273
15308
 
14274
- selectedElements = [];
14275
- selectedScopes = [];
15309
+ .example-animate-container > * {
15310
+ display:block;
15311
+ padding:10px;
15312
+ }
14276
15313
 
14277
- if ((selectedTranscludes = ctrl.cases['!' + value] || ctrl.cases['?'])) {
14278
- scope.$eval(attr.change);
14279
- forEach(selectedTranscludes, function(selectedTransclude) {
14280
- var selectedScope = scope.$new();
14281
- selectedScopes.push(selectedScope);
14282
- selectedTransclude(selectedScope, function(caseElement) {
14283
- selectedElements.push(caseElement);
14284
- element.append(caseElement);
14285
- });
14286
- });
15314
+ .example-enter-setup {
15315
+ top:-50px;
14287
15316
  }
14288
- });
15317
+ .example-enter-start.example-enter-start {
15318
+ top:0;
15319
+ }
15320
+
15321
+ .example-leave-setup {
15322
+ top:0;
15323
+ }
15324
+ .example-leave-start.example-leave-start {
15325
+ top:50px;
15326
+ }
15327
+ </file>
15328
+ <file name="scenario.js">
15329
+ it('should start in settings', function() {
15330
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
15331
+ });
15332
+ it('should change to home', function() {
15333
+ select('selection').option('home');
15334
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
15335
+ });
15336
+ it('should select default', function() {
15337
+ select('selection').option('other');
15338
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
15339
+ });
15340
+ </file>
15341
+ </example>
15342
+ */
15343
+ var ngSwitchDirective = ['$animator', function($animator) {
15344
+ return {
15345
+ restrict: 'EA',
15346
+ require: 'ngSwitch',
15347
+
15348
+ // asks for $scope to fool the BC controller module
15349
+ controller: ['$scope', function ngSwitchController() {
15350
+ this.cases = {};
15351
+ }],
15352
+ link: function(scope, element, attr, ngSwitchController) {
15353
+ var animate = $animator(scope, attr);
15354
+ var watchExpr = attr.ngSwitch || attr.on,
15355
+ selectedTranscludes,
15356
+ selectedElements,
15357
+ selectedScopes = [];
15358
+
15359
+ scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
15360
+ for (var i= 0, ii=selectedScopes.length; i<ii; i++) {
15361
+ selectedScopes[i].$destroy();
15362
+ animate.leave(selectedElements[i]);
15363
+ }
15364
+
15365
+ selectedElements = [];
15366
+ selectedScopes = [];
15367
+
15368
+ if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
15369
+ scope.$eval(attr.change);
15370
+ forEach(selectedTranscludes, function(selectedTransclude) {
15371
+ var selectedScope = scope.$new();
15372
+ selectedScopes.push(selectedScope);
15373
+ selectedTransclude.transclude(selectedScope, function(caseElement) {
15374
+ var anchor = selectedTransclude.element;
15375
+
15376
+ selectedElements.push(caseElement);
15377
+ animate.enter(caseElement, anchor.parent(), anchor);
15378
+ });
15379
+ });
15380
+ }
15381
+ });
15382
+ }
14289
15383
  }
14290
- });
15384
+ }];
14291
15385
 
14292
15386
  var ngSwitchWhenDirective = ngDirective({
14293
15387
  transclude: 'element',
@@ -14296,7 +15390,7 @@ var ngSwitchWhenDirective = ngDirective({
14296
15390
  compile: function(element, attrs, transclude) {
14297
15391
  return function(scope, element, attr, ctrl) {
14298
15392
  ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
14299
- ctrl.cases['!' + attrs.ngSwitchWhen].push(transclude);
15393
+ ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: transclude, element: element });
14300
15394
  };
14301
15395
  }
14302
15396
  });
@@ -14308,7 +15402,7 @@ var ngSwitchDefaultDirective = ngDirective({
14308
15402
  compile: function(element, attrs, transclude) {
14309
15403
  return function(scope, element, attr, ctrl) {
14310
15404
  ctrl.cases['?'] = (ctrl.cases['?'] || []);
14311
- ctrl.cases['?'].push(transclude);
15405
+ ctrl.cases['?'].push({ transclude: transclude, element: element });
14312
15406
  };
14313
15407
  }
14314
15408
  });
@@ -14382,9 +15476,16 @@ var ngTranscludeDirective = ngDirective({
14382
15476
  * Every time the current route changes, the included view changes with it according to the
14383
15477
  * configuration of the `$route` service.
14384
15478
  *
15479
+ * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**
15480
+ * and **leave** effects.
15481
+ *
15482
+ * @animations
15483
+ * enter - happens just after the ngView contents are changed (when the new view DOM element is inserted into the DOM)
15484
+ * leave - happens just after the current ngView contents change and just before the former contents are removed from the DOM
15485
+ *
14385
15486
  * @scope
14386
15487
  * @example
14387
- <example module="ngView">
15488
+ <example module="ngView" animations="true">
14388
15489
  <file name="index.html">
14389
15490
  <div ng-controller="MainCntl">
14390
15491
  Choose:
@@ -14394,7 +15495,10 @@ var ngTranscludeDirective = ngDirective({
14394
15495
  <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
14395
15496
  <a href="Book/Scarlet">Scarlet Letter</a><br/>
14396
15497
 
14397
- <div ng-view></div>
15498
+ <div
15499
+ ng-view
15500
+ class="example-animate-container"
15501
+ ng-animate="{enter: 'example-enter', leave: 'example-leave'}"></div>
14398
15502
  <hr />
14399
15503
 
14400
15504
  <pre>$location.path() = {{$location.path()}}</pre>
@@ -14406,14 +15510,58 @@ var ngTranscludeDirective = ngDirective({
14406
15510
  </file>
14407
15511
 
14408
15512
  <file name="book.html">
14409
- controller: {{name}}<br />
14410
- Book Id: {{params.bookId}}<br />
15513
+ <div>
15514
+ controller: {{name}}<br />
15515
+ Book Id: {{params.bookId}}<br />
15516
+ </div>
14411
15517
  </file>
14412
15518
 
14413
15519
  <file name="chapter.html">
14414
- controller: {{name}}<br />
14415
- Book Id: {{params.bookId}}<br />
14416
- Chapter Id: {{params.chapterId}}
15520
+ <div>
15521
+ controller: {{name}}<br />
15522
+ Book Id: {{params.bookId}}<br />
15523
+ Chapter Id: {{params.chapterId}}
15524
+ </div>
15525
+ </file>
15526
+
15527
+ <file name="animations.css">
15528
+ .example-leave-setup, .example-enter-setup {
15529
+ -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
15530
+ -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
15531
+ -ms-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
15532
+ -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
15533
+ transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
15534
+ }
15535
+
15536
+ .example-animate-container {
15537
+ position:relative;
15538
+ height:100px;
15539
+ }
15540
+
15541
+ .example-animate-container > * {
15542
+ display:block;
15543
+ width:100%;
15544
+ border-left:1px solid black;
15545
+
15546
+ position:absolute;
15547
+ top:0;
15548
+ left:0;
15549
+ right:0;
15550
+ bottom:0;
15551
+ padding:10px;
15552
+ }
15553
+
15554
+ .example-enter-setup {
15555
+ left:100%;
15556
+ }
15557
+ .example-enter-setup.example-enter-start {
15558
+ left:0;
15559
+ }
15560
+
15561
+ .example-leave-setup { }
15562
+ .example-leave-setup.example-leave-start {
15563
+ left:-100%;
15564
+ }
14417
15565
  </file>
14418
15566
 
14419
15567
  <file name="script.js">
@@ -14475,15 +15623,16 @@ var ngTranscludeDirective = ngDirective({
14475
15623
  * Emitted every time the ngView content is reloaded.
14476
15624
  */
14477
15625
  var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile',
14478
- '$controller',
15626
+ '$controller', '$animator',
14479
15627
  function($http, $templateCache, $route, $anchorScroll, $compile,
14480
- $controller) {
15628
+ $controller, $animator) {
14481
15629
  return {
14482
15630
  restrict: 'ECA',
14483
15631
  terminal: true,
14484
15632
  link: function(scope, element, attr) {
14485
15633
  var lastScope,
14486
- onloadExp = attr.onload || '';
15634
+ onloadExp = attr.onload || '',
15635
+ animate = $animator(scope, attr);
14487
15636
 
14488
15637
  scope.$on('$routeChangeSuccess', update);
14489
15638
  update();
@@ -14497,7 +15646,7 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
14497
15646
  }
14498
15647
 
14499
15648
  function clearContent() {
14500
- element.html('');
15649
+ animate.leave(element.contents(), element);
14501
15650
  destroyLastScope();
14502
15651
  }
14503
15652
 
@@ -14506,8 +15655,8 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
14506
15655
  template = locals && locals.$template;
14507
15656
 
14508
15657
  if (template) {
14509
- element.html(template);
14510
- destroyLastScope();
15658
+ clearContent();
15659
+ animate.enter(jqLite('<div></div>').html(template).contents(), element);
14511
15660
 
14512
15661
  var link = $compile(element.contents()),
14513
15662
  current = $route.current,
@@ -14701,7 +15850,7 @@ var scriptDirective = ['$templateCache', function($templateCache) {
14701
15850
 
14702
15851
  var ngOptionsDirective = valueFn({ terminal: true });
14703
15852
  var selectDirective = ['$compile', '$parse', function($compile, $parse) {
14704
- //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777
15853
+ //0000111110000000000022220000000000000000000000333300000000000000444444444444444440000000005555555555555555500000006666666666666666600000000000000077770
14705
15854
  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+(.*)$/,
14706
15855
  nullModelCtrl = {$setViewValue: noop};
14707
15856
 
@@ -15150,6 +16299,7 @@ var styleDirective = valueFn({
15150
16299
  restrict: 'E',
15151
16300
  terminal: true
15152
16301
  });
16302
+
15153
16303
  //try to bind to jquery now so that one can write angular.element().read()
15154
16304
  //but we will rebind on bootstrap again.
15155
16305
  bindJQuery();