angular-gem 1.1.3 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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();