angularjs-rails 1.2.26 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/lib/angularjs-rails/version.rb +2 -2
  3. data/vendor/assets/javascripts/angular-animate.js +879 -456
  4. data/vendor/assets/javascripts/angular-aria.js +250 -0
  5. data/vendor/assets/javascripts/angular-cookies.js +1 -1
  6. data/vendor/assets/javascripts/angular-loader.js +17 -10
  7. data/vendor/assets/javascripts/angular-messages.js +400 -0
  8. data/vendor/assets/javascripts/angular-mocks.js +220 -110
  9. data/vendor/assets/javascripts/angular-resource.js +287 -247
  10. data/vendor/assets/javascripts/angular-route.js +111 -54
  11. data/vendor/assets/javascripts/angular-sanitize.js +1 -1
  12. data/vendor/assets/javascripts/angular-scenario.js +11579 -8665
  13. data/vendor/assets/javascripts/angular-touch.js +49 -11
  14. data/vendor/assets/javascripts/angular.js +6660 -3106
  15. data/vendor/assets/javascripts/unstable/angular-animate.js +240 -71
  16. data/vendor/assets/javascripts/unstable/angular-aria.js +12 -12
  17. data/vendor/assets/javascripts/unstable/angular-cookies.js +1 -1
  18. data/vendor/assets/javascripts/unstable/angular-loader.js +4 -4
  19. data/vendor/assets/javascripts/unstable/angular-messages.js +1 -1
  20. data/vendor/assets/javascripts/unstable/angular-mocks.js +21 -14
  21. data/vendor/assets/javascripts/unstable/angular-resource.js +2 -2
  22. data/vendor/assets/javascripts/unstable/angular-route.js +1 -1
  23. data/vendor/assets/javascripts/unstable/angular-sanitize.js +1 -1
  24. data/vendor/assets/javascripts/unstable/angular-scenario.js +562 -262
  25. data/vendor/assets/javascripts/unstable/angular-touch.js +1 -1
  26. data/vendor/assets/javascripts/unstable/angular.js +562 -262
  27. metadata +16 -14
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.5
2
+ * @license AngularJS v1.3.0
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -10,18 +10,18 @@
10
10
  * @name ngAria
11
11
  * @description
12
12
  *
13
- * The `ngAria` module provides support for adding aria tags that convey state or semantic information
14
- * about the application in order to allow assistive technologies to convey appropriate information to
15
- * persons with disabilities.
13
+ * The `ngAria` module provides support for adding <abbr title="Accessible Rich Internet Applications">ARIA</abbr>
14
+ * attributes that convey state or semantic information about the application in order to allow assistive technologies
15
+ * to convey appropriate information to persons with disabilities.
16
16
  *
17
17
  * <div doc-module-components="ngAria"></div>
18
18
  *
19
19
  * # Usage
20
- * To enable the addition of the aria tags, just require the module into your application and the tags will
20
+ * To enable the addition of the ARIA tags, just require the module into your application and the tags will
21
21
  * hook into your ng-show/ng-hide, input, textarea, button, select and ng-required directives and adds the
22
- * appropriate aria-tags.
22
+ * appropriate ARIA attributes.
23
23
  *
24
- * Currently, the following aria tags are implemented:
24
+ * Currently, the following ARIA attributes are implemented:
25
25
  *
26
26
  * + aria-hidden
27
27
  * + aria-checked
@@ -34,7 +34,7 @@
34
34
  * + aria-valuemax
35
35
  * + tabindex
36
36
  *
37
- * You can disable individual aria tags by using the {@link ngAria.$ariaProvider#config config} method.
37
+ * You can disable individual ARIA attributes by using the {@link ngAria.$ariaProvider#config config} method.
38
38
  */
39
39
 
40
40
  /* global -ngAriaModule */
@@ -47,7 +47,7 @@ var ngAriaModule = angular.module('ngAria', ['ng']).
47
47
  *
48
48
  * @description
49
49
  *
50
- * Used for configuring aria attributes.
50
+ * Used for configuring ARIA attributes.
51
51
  *
52
52
  * ## Dependencies
53
53
  * Requires the {@link ngAria} module to be installed.
@@ -68,7 +68,7 @@ function $AriaProvider() {
68
68
  * @ngdoc method
69
69
  * @name $ariaProvider#config
70
70
  *
71
- * @param {object} config object to enable/disable specific aria tags
71
+ * @param {object} config object to enable/disable specific ARIA attributes
72
72
  *
73
73
  * - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags
74
74
  * - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags
@@ -80,7 +80,7 @@ function $AriaProvider() {
80
80
  * - **tabindex** – `{boolean}` – Enables/disables tabindex tags
81
81
  *
82
82
  * @description
83
- * Enables/disables various aria tags
83
+ * Enables/disables various ARIA attributes
84
84
  */
85
85
  this.config = function(newConfig) {
86
86
  config = angular.extend(config, newConfig);
@@ -113,7 +113,7 @@ function $AriaProvider() {
113
113
  *
114
114
  * @description
115
115
  *
116
- * Contains helper methods for applying aria tags to HTML
116
+ * Contains helper methods for applying ARIA attributes to HTML
117
117
  *
118
118
  * ## Dependencies
119
119
  * Requires the {@link ngAria} module to be installed.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.5
2
+ * @license AngularJS v1.3.0
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.5
2
+ * @license AngularJS v1.3.0
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -72,7 +72,7 @@ function minErr(module, ErrorConstructor) {
72
72
  return match;
73
73
  });
74
74
 
75
- message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.5/' +
75
+ message = message + '\nhttp://errors.angularjs.org/1.3.0/' +
76
76
  (module ? module + '/' : '') + code;
77
77
  for (i = 2; i < arguments.length; i++) {
78
78
  message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
@@ -304,7 +304,7 @@ function setupModuleLoader(window) {
304
304
  * })
305
305
  * ```
306
306
  *
307
- * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
307
+ * See {@link ng.$animateProvider#register $animateProvider.register()} and
308
308
  * {@link ngAnimate ngAnimate module} for more information.
309
309
  */
310
310
  animation: invokeLater('$animateProvider', 'register'),
@@ -354,7 +354,7 @@ function setupModuleLoader(window) {
354
354
  * @description
355
355
  * Use this method to register work which needs to be performed on module loading.
356
356
  * For more about how to configure services, see
357
- * {@link providers#providers_provider-recipe Provider Recipe}.
357
+ * {@link providers#provider-recipe Provider Recipe}.
358
358
  */
359
359
  config: config,
360
360
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.5
2
+ * @license AngularJS v1.3.0
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.5
2
+ * @license AngularJS v1.3.0
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -201,7 +201,7 @@ angular.mock.$Browser.prototype = {
201
201
  *
202
202
  * @description
203
203
  * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors
204
- * passed into the `$exceptionHandler`.
204
+ * passed to the `$exceptionHandler`.
205
205
  */
206
206
 
207
207
  /**
@@ -210,7 +210,7 @@ angular.mock.$Browser.prototype = {
210
210
  *
211
211
  * @description
212
212
  * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed
213
- * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration
213
+ * to it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration
214
214
  * information.
215
215
  *
216
216
  *
@@ -250,9 +250,8 @@ angular.mock.$ExceptionHandlerProvider = function() {
250
250
  *
251
251
  * @param {string} mode Mode of operation, defaults to `rethrow`.
252
252
  *
253
- * - `rethrow`: If any errors are passed into the handler in tests, it typically
254
- * means that there is a bug in the application or test, so this mock will
255
- * make these tests fail.
253
+ * - `rethrow`: If any errors are passed to the handler in tests, it typically means that there
254
+ * is a bug in the application or test, so this mock will make these tests fail.
256
255
  * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log`
257
256
  * mode stores an array of errors in `$exceptionHandler.errors`, to allow later
258
257
  * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and
@@ -343,7 +342,7 @@ angular.mock.$LogProvider = function() {
343
342
  * @name $log#log.logs
344
343
  *
345
344
  * @description
346
- * Array of messages logged using {@link ngMock.$log#log}.
345
+ * Array of messages logged using {@link ng.$log#log `log()`}.
347
346
  *
348
347
  * @example
349
348
  * ```js
@@ -357,7 +356,7 @@ angular.mock.$LogProvider = function() {
357
356
  * @name $log#info.logs
358
357
  *
359
358
  * @description
360
- * Array of messages logged using {@link ngMock.$log#info}.
359
+ * Array of messages logged using {@link ng.$log#info `info()`}.
361
360
  *
362
361
  * @example
363
362
  * ```js
@@ -371,7 +370,7 @@ angular.mock.$LogProvider = function() {
371
370
  * @name $log#warn.logs
372
371
  *
373
372
  * @description
374
- * Array of messages logged using {@link ngMock.$log#warn}.
373
+ * Array of messages logged using {@link ng.$log#warn `warn()`}.
375
374
  *
376
375
  * @example
377
376
  * ```js
@@ -385,7 +384,7 @@ angular.mock.$LogProvider = function() {
385
384
  * @name $log#error.logs
386
385
  *
387
386
  * @description
388
- * Array of messages logged using {@link ngMock.$log#error}.
387
+ * Array of messages logged using {@link ng.$log#error `error()`}.
389
388
  *
390
389
  * @example
391
390
  * ```js
@@ -399,7 +398,7 @@ angular.mock.$LogProvider = function() {
399
398
  * @name $log#debug.logs
400
399
  *
401
400
  * @description
402
- * Array of messages logged using {@link ngMock.$log#debug}.
401
+ * Array of messages logged using {@link ng.$log#debug `debug()`}.
403
402
  *
404
403
  * @example
405
404
  * ```js
@@ -415,8 +414,8 @@ angular.mock.$LogProvider = function() {
415
414
  * @name $log#assertEmpty
416
415
  *
417
416
  * @description
418
- * Assert that the all of the logging methods have no logged messages. If messages present, an
419
- * exception is thrown.
417
+ * Assert that all of the logging methods have no logged messages. If any messages are present,
418
+ * an exception is thrown.
420
419
  */
421
420
  $log.assertEmpty = function() {
422
421
  var errors = [];
@@ -811,7 +810,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
811
810
  };
812
811
 
813
812
  angular.forEach(
814
- ['enter','leave','move','addClass','removeClass','setClass'], function(method) {
813
+ ['animate','enter','leave','move','addClass','removeClass','setClass'], function(method) {
815
814
  animate[method] = function() {
816
815
  animate.queue.push({
817
816
  event : method,
@@ -1002,6 +1001,11 @@ angular.mock.dump = function(object) {
1002
1001
  * First we create the controller under test:
1003
1002
  *
1004
1003
  ```js
1004
+ // The module code
1005
+ angular
1006
+ .module('MyApp', [])
1007
+ .controller('MyController', MyController);
1008
+
1005
1009
  // The controller code
1006
1010
  function MyController($scope, $http) {
1007
1011
  var authToken;
@@ -1031,6 +1035,9 @@ angular.mock.dump = function(object) {
1031
1035
  describe('MyController', function() {
1032
1036
  var $httpBackend, $rootScope, createController, authRequestHandler;
1033
1037
 
1038
+ // Set up the module
1039
+ beforeEach(module('MyApp'));
1040
+
1034
1041
  beforeEach(inject(function($injector) {
1035
1042
  // Set up the mock http service responses
1036
1043
  $httpBackend = $injector.get('$httpBackend');
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.5
2
+ * @license AngularJS v1.3.0
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -118,7 +118,7 @@ function shallowClearAndCopy(src, dst) {
118
118
  *
119
119
  * @param {Object.<Object>=} actions Hash with declaration of custom action that should extend
120
120
  * the default set of resource actions. The declaration should be created in the format of {@link
121
- * ng.$http#usage_parameters $http.config}:
121
+ * ng.$http#usage $http.config}:
122
122
  *
123
123
  * {action1: {method:?, params:?, isArray:?, headers:?, ...},
124
124
  * action2: {method:?, params:?, isArray:?, headers:?, ...},
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.5
2
+ * @license AngularJS v1.3.0
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.5
2
+ * @license AngularJS v1.3.0
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -9190,7 +9190,7 @@ return jQuery;
9190
9190
  }));
9191
9191
 
9192
9192
  /**
9193
- * @license AngularJS v1.3.0-rc.5
9193
+ * @license AngularJS v1.3.0
9194
9194
  * (c) 2010-2014 Google, Inc. http://angularjs.org
9195
9195
  * License: MIT
9196
9196
  */
@@ -9263,7 +9263,7 @@ function minErr(module, ErrorConstructor) {
9263
9263
  return match;
9264
9264
  });
9265
9265
 
9266
- message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.5/' +
9266
+ message = message + '\nhttp://errors.angularjs.org/1.3.0/' +
9267
9267
  (module ? module + '/' : '') + code;
9268
9268
  for (i = 2; i < arguments.length; i++) {
9269
9269
  message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
@@ -9605,7 +9605,8 @@ function setHashKey(obj, h) {
9605
9605
  *
9606
9606
  * @description
9607
9607
  * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
9608
- * to `dst`. You can specify multiple `src` objects.
9608
+ * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
9609
+ * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
9609
9610
  *
9610
9611
  * @param {Object} dst Destination object.
9611
9612
  * @param {...Object} src Source object(s).
@@ -11116,7 +11117,7 @@ function setupModuleLoader(window) {
11116
11117
  * })
11117
11118
  * ```
11118
11119
  *
11119
- * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
11120
+ * See {@link ng.$animateProvider#register $animateProvider.register()} and
11120
11121
  * {@link ngAnimate ngAnimate module} for more information.
11121
11122
  */
11122
11123
  animation: invokeLater('$animateProvider', 'register'),
@@ -11166,7 +11167,7 @@ function setupModuleLoader(window) {
11166
11167
  * @description
11167
11168
  * Use this method to register work which needs to be performed on module loading.
11168
11169
  * For more about how to configure services, see
11169
- * {@link providers#providers_provider-recipe Provider Recipe}.
11170
+ * {@link providers#provider-recipe Provider Recipe}.
11170
11171
  */
11171
11172
  config: config,
11172
11173
 
@@ -11313,11 +11314,11 @@ function setupModuleLoader(window) {
11313
11314
  * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
11314
11315
  */
11315
11316
  var version = {
11316
- full: '1.3.0-rc.5', // all of these placeholder strings will be replaced by grunt's
11317
+ full: '1.3.0', // all of these placeholder strings will be replaced by grunt's
11317
11318
  major: 1, // package task
11318
11319
  minor: 3,
11319
11320
  dot: 0,
11320
- codeName: 'impossible-choreography'
11321
+ codeName: 'superluminal-nudge'
11321
11322
  };
11322
11323
 
11323
11324
 
@@ -11492,7 +11493,7 @@ function publishExternalAPI(angular){
11492
11493
  * - [`addClass()`](http://api.jquery.com/addClass/)
11493
11494
  * - [`after()`](http://api.jquery.com/after/)
11494
11495
  * - [`append()`](http://api.jquery.com/append/)
11495
- * - [`attr()`](http://api.jquery.com/attr/)
11496
+ * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
11496
11497
  * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
11497
11498
  * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
11498
11499
  * - [`clone()`](http://api.jquery.com/clone/)
@@ -11727,18 +11728,22 @@ function jqLiteOff(element, type, fn, unsupported) {
11727
11728
  if (!type) {
11728
11729
  for (type in events) {
11729
11730
  if (type !== '$destroy') {
11730
- removeEventListenerFn(element, type, events[type]);
11731
+ removeEventListenerFn(element, type, handle);
11731
11732
  }
11732
11733
  delete events[type];
11733
11734
  }
11734
11735
  } else {
11735
11736
  forEach(type.split(' '), function(type) {
11736
- if (isUndefined(fn)) {
11737
- removeEventListenerFn(element, type, events[type]);
11738
- delete events[type];
11739
- } else {
11740
- arrayRemove(events[type] || [], fn);
11737
+ if (isDefined(fn)) {
11738
+ var listenerFns = events[type];
11739
+ arrayRemove(listenerFns || [], fn);
11740
+ if (listenerFns && listenerFns.length > 0) {
11741
+ return;
11742
+ }
11741
11743
  }
11744
+
11745
+ removeEventListenerFn(element, type, handle);
11746
+ delete events[type];
11742
11747
  });
11743
11748
  }
11744
11749
  }
@@ -11902,6 +11907,20 @@ function jqLiteRemove(element, keepData) {
11902
11907
  if (parent) parent.removeChild(element);
11903
11908
  }
11904
11909
 
11910
+
11911
+ function jqLiteDocumentLoaded(action, win) {
11912
+ win = win || window;
11913
+ if (win.document.readyState === 'complete') {
11914
+ // Force the action to be run async for consistent behaviour
11915
+ // from the action's point of view
11916
+ // i.e. it will definitely not be in a $apply
11917
+ win.setTimeout(action);
11918
+ } else {
11919
+ // No need to unbind this handler as load is only ever called once
11920
+ jqLite(win).on('load', action);
11921
+ }
11922
+ }
11923
+
11905
11924
  //////////////////////////////////////////
11906
11925
  // Functions which are declared directly.
11907
11926
  //////////////////////////////////////////
@@ -12524,7 +12543,7 @@ HashMap.prototype = {
12524
12543
 
12525
12544
  * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
12526
12545
  * {@link angular.module}. The `ng` module must be explicitly added.
12527
- * @returns {function()} Injector object. See {@link auto.$injector $injector}.
12546
+ * @returns {injector} Injector object. See {@link auto.$injector $injector}.
12528
12547
  *
12529
12548
  * @example
12530
12549
  * Typical usage
@@ -13175,7 +13194,7 @@ function createInjector(modulesToLoad, strictDi) {
13175
13194
 
13176
13195
  function enforceReturnValue(name, factory) {
13177
13196
  return function enforcedReturnValue() {
13178
- var result = instanceInjector.invoke(factory);
13197
+ var result = instanceInjector.invoke(factory, this, undefined, name);
13179
13198
  if (isUndefined(result)) {
13180
13199
  throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
13181
13200
  }
@@ -13353,93 +13372,252 @@ function createInjector(modulesToLoad, strictDi) {
13353
13372
  createInjector.$$annotate = annotate;
13354
13373
 
13355
13374
  /**
13356
- * @ngdoc service
13357
- * @name $anchorScroll
13358
- * @kind function
13359
- * @requires $window
13360
- * @requires $location
13361
- * @requires $rootScope
13375
+ * @ngdoc provider
13376
+ * @name $anchorScrollProvider
13362
13377
  *
13363
13378
  * @description
13364
- * When called, it checks current value of `$location.hash()` and scrolls to the related element,
13365
- * according to rules specified in
13366
- * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
13367
- *
13368
- * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor.
13369
- * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
13370
- *
13371
- * @example
13372
- <example module="anchorScrollExample">
13373
- <file name="index.html">
13374
- <div id="scrollArea" ng-controller="ScrollController">
13375
- <a ng-click="gotoBottom()">Go to bottom</a>
13376
- <a id="bottom"></a> You're at the bottom!
13377
- </div>
13378
- </file>
13379
- <file name="script.js">
13380
- angular.module('anchorScrollExample', [])
13381
- .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
13382
- function ($scope, $location, $anchorScroll) {
13383
- $scope.gotoBottom = function() {
13384
- // set the location.hash to the id of
13385
- // the element you wish to scroll to.
13386
- $location.hash('bottom');
13387
-
13388
- // call $anchorScroll()
13389
- $anchorScroll();
13390
- };
13391
- }]);
13392
- </file>
13393
- <file name="style.css">
13394
- #scrollArea {
13395
- height: 350px;
13396
- overflow: auto;
13397
- }
13398
-
13399
- #bottom {
13400
- display: block;
13401
- margin-top: 2000px;
13402
- }
13403
- </file>
13404
- </example>
13379
+ * Use `$anchorScrollProvider` to disable automatic scrolling whenever
13380
+ * {@link ng.$location#hash $location.hash()} changes.
13405
13381
  */
13406
13382
  function $AnchorScrollProvider() {
13407
13383
 
13408
13384
  var autoScrollingEnabled = true;
13409
13385
 
13386
+ /**
13387
+ * @ngdoc method
13388
+ * @name $anchorScrollProvider#disableAutoScrolling
13389
+ *
13390
+ * @description
13391
+ * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically will detect changes to
13392
+ * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
13393
+ * Use this method to disable automatic scrolling.
13394
+ *
13395
+ * If automatic scrolling is disabled, one must explicitly call
13396
+ * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
13397
+ * current hash.
13398
+ */
13410
13399
  this.disableAutoScrolling = function() {
13411
13400
  autoScrollingEnabled = false;
13412
13401
  };
13413
13402
 
13403
+ /**
13404
+ * @ngdoc service
13405
+ * @name $anchorScroll
13406
+ * @kind function
13407
+ * @requires $window
13408
+ * @requires $location
13409
+ * @requires $rootScope
13410
+ *
13411
+ * @description
13412
+ * When called, it checks the current value of {@link ng.$location#hash $location.hash()} and
13413
+ * scrolls to the related element, according to the rules specified in the
13414
+ * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
13415
+ *
13416
+ * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
13417
+ * match any anchor whenever it changes. This can be disabled by calling
13418
+ * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
13419
+ *
13420
+ * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
13421
+ * vertical scroll-offset (either fixed or dynamic).
13422
+ *
13423
+ * @property {(number|function|jqLite)} yOffset
13424
+ * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
13425
+ * positioned elements at the top of the page, such as navbars, headers etc.
13426
+ *
13427
+ * `yOffset` can be specified in various ways:
13428
+ * - **number**: A fixed number of pixels to be used as offset.<br /><br />
13429
+ * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
13430
+ * a number representing the offset (in pixels).<br /><br />
13431
+ * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
13432
+ * the top of the page to the element's bottom will be used as offset.<br />
13433
+ * **Note**: The element will be taken into account only as long as its `position` is set to
13434
+ * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
13435
+ * their height and/or positioning according to the viewport's size.
13436
+ *
13437
+ * <br />
13438
+ * <div class="alert alert-warning">
13439
+ * In order for `yOffset` to work properly, scrolling should take place on the document's root and
13440
+ * not some child element.
13441
+ * </div>
13442
+ *
13443
+ * @example
13444
+ <example module="anchorScrollExample">
13445
+ <file name="index.html">
13446
+ <div id="scrollArea" ng-controller="ScrollController">
13447
+ <a ng-click="gotoBottom()">Go to bottom</a>
13448
+ <a id="bottom"></a> You're at the bottom!
13449
+ </div>
13450
+ </file>
13451
+ <file name="script.js">
13452
+ angular.module('anchorScrollExample', [])
13453
+ .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
13454
+ function ($scope, $location, $anchorScroll) {
13455
+ $scope.gotoBottom = function() {
13456
+ // set the location.hash to the id of
13457
+ // the element you wish to scroll to.
13458
+ $location.hash('bottom');
13459
+
13460
+ // call $anchorScroll()
13461
+ $anchorScroll();
13462
+ };
13463
+ }]);
13464
+ </file>
13465
+ <file name="style.css">
13466
+ #scrollArea {
13467
+ height: 280px;
13468
+ overflow: auto;
13469
+ }
13470
+
13471
+ #bottom {
13472
+ display: block;
13473
+ margin-top: 2000px;
13474
+ }
13475
+ </file>
13476
+ </example>
13477
+ *
13478
+ * <hr />
13479
+ * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
13480
+ * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
13481
+ *
13482
+ * @example
13483
+ <example module="anchorScrollOffsetExample">
13484
+ <file name="index.html">
13485
+ <div class="fixed-header" ng-controller="headerCtrl">
13486
+ <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
13487
+ Go to anchor {{x}}
13488
+ </a>
13489
+ </div>
13490
+ <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
13491
+ Anchor {{x}} of 5
13492
+ </div>
13493
+ </file>
13494
+ <file name="script.js">
13495
+ angular.module('anchorScrollOffsetExample', [])
13496
+ .run(['$anchorScroll', function($anchorScroll) {
13497
+ $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
13498
+ }])
13499
+ .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
13500
+ function ($anchorScroll, $location, $scope) {
13501
+ $scope.gotoAnchor = function(x) {
13502
+ var newHash = 'anchor' + x;
13503
+ if ($location.hash() !== newHash) {
13504
+ // set the $location.hash to `newHash` and
13505
+ // $anchorScroll will automatically scroll to it
13506
+ $location.hash('anchor' + x);
13507
+ } else {
13508
+ // call $anchorScroll() explicitly,
13509
+ // since $location.hash hasn't changed
13510
+ $anchorScroll();
13511
+ }
13512
+ };
13513
+ }
13514
+ ]);
13515
+ </file>
13516
+ <file name="style.css">
13517
+ body {
13518
+ padding-top: 50px;
13519
+ }
13520
+
13521
+ .anchor {
13522
+ border: 2px dashed DarkOrchid;
13523
+ padding: 10px 10px 200px 10px;
13524
+ }
13525
+
13526
+ .fixed-header {
13527
+ background-color: rgba(0, 0, 0, 0.2);
13528
+ height: 50px;
13529
+ position: fixed;
13530
+ top: 0; left: 0; right: 0;
13531
+ }
13532
+
13533
+ .fixed-header > a {
13534
+ display: inline-block;
13535
+ margin: 5px 15px;
13536
+ }
13537
+ </file>
13538
+ </example>
13539
+ */
13414
13540
  this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
13415
13541
  var document = $window.document;
13542
+ var scrollScheduled = false;
13416
13543
 
13417
- // helper function to get first anchor from a NodeList
13418
- // can't use filter.filter, as it accepts only instances of Array
13419
- // and IE can't convert NodeList to an array using [].slice
13420
- // TODO(vojta): use filter if we change it to accept lists as well
13544
+ // Helper function to get first anchor from a NodeList
13545
+ // (using `Array#some()` instead of `angular#forEach()` since it's more performant
13546
+ // and working in all supported browsers.)
13421
13547
  function getFirstAnchor(list) {
13422
13548
  var result = null;
13423
- forEach(list, function(element) {
13424
- if (!result && nodeName_(element) === 'a') result = element;
13549
+ Array.prototype.some.call(list, function(element) {
13550
+ if (nodeName_(element) === 'a') {
13551
+ result = element;
13552
+ return true;
13553
+ }
13425
13554
  });
13426
13555
  return result;
13427
13556
  }
13428
13557
 
13558
+ function getYOffset() {
13559
+
13560
+ var offset = scroll.yOffset;
13561
+
13562
+ if (isFunction(offset)) {
13563
+ offset = offset();
13564
+ } else if (isElement(offset)) {
13565
+ var elem = offset[0];
13566
+ var style = $window.getComputedStyle(elem);
13567
+ if (style.position !== 'fixed') {
13568
+ offset = 0;
13569
+ } else {
13570
+ offset = elem.getBoundingClientRect().bottom;
13571
+ }
13572
+ } else if (!isNumber(offset)) {
13573
+ offset = 0;
13574
+ }
13575
+
13576
+ return offset;
13577
+ }
13578
+
13579
+ function scrollTo(elem) {
13580
+ if (elem) {
13581
+ elem.scrollIntoView();
13582
+
13583
+ var offset = getYOffset();
13584
+
13585
+ if (offset) {
13586
+ // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
13587
+ // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
13588
+ // top of the viewport.
13589
+ //
13590
+ // IF the number of pixels from the top of `elem` to the end of the page's content is less
13591
+ // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
13592
+ // way down the page.
13593
+ //
13594
+ // This is often the case for elements near the bottom of the page.
13595
+ //
13596
+ // In such cases we do not need to scroll the whole `offset` up, just the difference between
13597
+ // the top of the element and the offset, which is enough to align the top of `elem` at the
13598
+ // desired position.
13599
+ var elemTop = elem.getBoundingClientRect().top;
13600
+ $window.scrollBy(0, elemTop - offset);
13601
+ }
13602
+ } else {
13603
+ $window.scrollTo(0, 0);
13604
+ }
13605
+ }
13606
+
13429
13607
  function scroll() {
13430
13608
  var hash = $location.hash(), elm;
13431
13609
 
13432
13610
  // empty hash, scroll to the top of the page
13433
- if (!hash) $window.scrollTo(0, 0);
13611
+ if (!hash) scrollTo(null);
13434
13612
 
13435
13613
  // element with given id
13436
- else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
13614
+ else if ((elm = document.getElementById(hash))) scrollTo(elm);
13437
13615
 
13438
13616
  // first anchor with given name :-D
13439
- else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
13617
+ else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
13440
13618
 
13441
13619
  // no element and hash == 'top', scroll to the top of the page
13442
- else if (hash === 'top') $window.scrollTo(0, 0);
13620
+ else if (hash === 'top') scrollTo(null);
13443
13621
  }
13444
13622
 
13445
13623
  // does not scroll when user clicks on anchor link that is currently on
@@ -13450,7 +13628,9 @@ function $AnchorScrollProvider() {
13450
13628
  // skip the initial scroll if $location.hash is empty
13451
13629
  if (newVal === oldVal && newVal === '') return;
13452
13630
 
13453
- $rootScope.$evalAsync(scroll);
13631
+ jqLiteDocumentLoaded(function() {
13632
+ $rootScope.$evalAsync(scroll);
13633
+ });
13454
13634
  });
13455
13635
  }
13456
13636
 
@@ -13558,7 +13738,7 @@ var $AnimateProvider = ['$provide', function($provide) {
13558
13738
  return defer.promise;
13559
13739
  }
13560
13740
 
13561
- function resolveElementClasses(element, cache) {
13741
+ function resolveElementClasses(element, classes) {
13562
13742
  var toAdd = [], toRemove = [];
13563
13743
 
13564
13744
  var hasClasses = createMap();
@@ -13566,7 +13746,7 @@ var $AnimateProvider = ['$provide', function($provide) {
13566
13746
  hasClasses[className] = true;
13567
13747
  });
13568
13748
 
13569
- forEach(cache.classes, function(status, className) {
13749
+ forEach(classes, function(status, className) {
13570
13750
  var hasClass = hasClasses[className];
13571
13751
 
13572
13752
  // If the most recent class manipulation (via $animate) was to remove the class, and the
@@ -13580,7 +13760,8 @@ var $AnimateProvider = ['$provide', function($provide) {
13580
13760
  }
13581
13761
  });
13582
13762
 
13583
- return (toAdd.length + toRemove.length) > 0 && [toAdd.length && toAdd, toRemove.length && toRemove];
13763
+ return (toAdd.length + toRemove.length) > 0 &&
13764
+ [toAdd.length ? toAdd : null, toRemove.length ? toRemove : null];
13584
13765
  }
13585
13766
 
13586
13767
  function cachedClassManipulation(cache, classes, op) {
@@ -13602,6 +13783,13 @@ var $AnimateProvider = ['$provide', function($provide) {
13602
13783
  return currentDefer.promise;
13603
13784
  }
13604
13785
 
13786
+ function applyStyles(element, options) {
13787
+ if (angular.isObject(options)) {
13788
+ var styles = extend(options.from || {}, options.to || {});
13789
+ element.css(styles);
13790
+ }
13791
+ }
13792
+
13605
13793
  /**
13606
13794
  *
13607
13795
  * @ngdoc service
@@ -13620,6 +13808,10 @@ var $AnimateProvider = ['$provide', function($provide) {
13620
13808
  * page}.
13621
13809
  */
13622
13810
  return {
13811
+ animate : function(element, from, to) {
13812
+ applyStyles(element, { from: from, to: to });
13813
+ return asyncPromise();
13814
+ },
13623
13815
 
13624
13816
  /**
13625
13817
  *
@@ -13634,9 +13826,11 @@ var $AnimateProvider = ['$provide', function($provide) {
13634
13826
  * a child (if the after element is not present)
13635
13827
  * @param {DOMElement} after the sibling element which will append the element
13636
13828
  * after itself
13829
+ * @param {object=} options an optional collection of styles that will be applied to the element.
13637
13830
  * @return {Promise} the animation callback promise
13638
13831
  */
13639
- enter : function(element, parent, after) {
13832
+ enter : function(element, parent, after, options) {
13833
+ applyStyles(element, options);
13640
13834
  after ? after.after(element)
13641
13835
  : parent.prepend(element);
13642
13836
  return asyncPromise();
@@ -13650,9 +13844,10 @@ var $AnimateProvider = ['$provide', function($provide) {
13650
13844
  * @description Removes the element from the DOM. When the function is called a promise
13651
13845
  * is returned that will be resolved at a later time.
13652
13846
  * @param {DOMElement} element the element which will be removed from the DOM
13847
+ * @param {object=} options an optional collection of options that will be applied to the element.
13653
13848
  * @return {Promise} the animation callback promise
13654
13849
  */
13655
- leave : function(element) {
13850
+ leave : function(element, options) {
13656
13851
  element.remove();
13657
13852
  return asyncPromise();
13658
13853
  },
@@ -13672,12 +13867,13 @@ var $AnimateProvider = ['$provide', function($provide) {
13672
13867
  * inserted into (if the after element is not present)
13673
13868
  * @param {DOMElement} after the sibling element where the element will be
13674
13869
  * positioned next to
13870
+ * @param {object=} options an optional collection of options that will be applied to the element.
13675
13871
  * @return {Promise} the animation callback promise
13676
13872
  */
13677
- move : function(element, parent, after) {
13873
+ move : function(element, parent, after, options) {
13678
13874
  // Do not remove element before insert. Removing will cause data associated with the
13679
13875
  // element to be dropped. Insert will implicitly do the remove.
13680
- return this.enter(element, parent, after);
13876
+ return this.enter(element, parent, after, options);
13681
13877
  },
13682
13878
 
13683
13879
  /**
@@ -13690,13 +13886,14 @@ var $AnimateProvider = ['$provide', function($provide) {
13690
13886
  * @param {DOMElement} element the element which will have the className value
13691
13887
  * added to it
13692
13888
  * @param {string} className the CSS class which will be added to the element
13889
+ * @param {object=} options an optional collection of options that will be applied to the element.
13693
13890
  * @return {Promise} the animation callback promise
13694
13891
  */
13695
- addClass : function(element, className) {
13696
- return this.setClass(element, className, []);
13892
+ addClass : function(element, className, options) {
13893
+ return this.setClass(element, className, [], options);
13697
13894
  },
13698
13895
 
13699
- $$addClassImmediately : function addClassImmediately(element, className) {
13896
+ $$addClassImmediately : function(element, className, options) {
13700
13897
  element = jqLite(element);
13701
13898
  className = !isString(className)
13702
13899
  ? (isArray(className) ? className.join(' ') : '')
@@ -13704,6 +13901,8 @@ var $AnimateProvider = ['$provide', function($provide) {
13704
13901
  forEach(element, function (element) {
13705
13902
  jqLiteAddClass(element, className);
13706
13903
  });
13904
+ applyStyles(element, options);
13905
+ return asyncPromise();
13707
13906
  },
13708
13907
 
13709
13908
  /**
@@ -13716,13 +13915,14 @@ var $AnimateProvider = ['$provide', function($provide) {
13716
13915
  * @param {DOMElement} element the element which will have the className value
13717
13916
  * removed from it
13718
13917
  * @param {string} className the CSS class which will be removed from the element
13918
+ * @param {object=} options an optional collection of options that will be applied to the element.
13719
13919
  * @return {Promise} the animation callback promise
13720
13920
  */
13721
- removeClass : function(element, className) {
13722
- return this.setClass(element, [], className);
13921
+ removeClass : function(element, className, options) {
13922
+ return this.setClass(element, [], className, options);
13723
13923
  },
13724
13924
 
13725
- $$removeClassImmediately : function removeClassImmediately(element, className) {
13925
+ $$removeClassImmediately : function(element, className, options) {
13726
13926
  element = jqLite(element);
13727
13927
  className = !isString(className)
13728
13928
  ? (isArray(className) ? className.join(' ') : '')
@@ -13730,6 +13930,7 @@ var $AnimateProvider = ['$provide', function($provide) {
13730
13930
  forEach(element, function (element) {
13731
13931
  jqLiteRemoveClass(element, className);
13732
13932
  });
13933
+ applyStyles(element, options);
13733
13934
  return asyncPromise();
13734
13935
  },
13735
13936
 
@@ -13744,28 +13945,24 @@ var $AnimateProvider = ['$provide', function($provide) {
13744
13945
  * removed from it
13745
13946
  * @param {string} add the CSS classes which will be added to the element
13746
13947
  * @param {string} remove the CSS class which will be removed from the element
13948
+ * @param {object=} options an optional collection of options that will be applied to the element.
13747
13949
  * @return {Promise} the animation callback promise
13748
13950
  */
13749
- setClass : function(element, add, remove, runSynchronously) {
13951
+ setClass : function(element, add, remove, options) {
13750
13952
  var self = this;
13751
13953
  var STORAGE_KEY = '$$animateClasses';
13752
13954
  var createdCache = false;
13753
13955
  element = jqLite(element);
13754
13956
 
13755
- if (runSynchronously) {
13756
- // TODO(@caitp/@matsko): Remove undocumented `runSynchronously` parameter, and always
13757
- // perform DOM manipulation asynchronously or in postDigest.
13758
- self.$$addClassImmediately(element, add);
13759
- self.$$removeClassImmediately(element, remove);
13760
- return asyncPromise();
13761
- }
13762
-
13763
13957
  var cache = element.data(STORAGE_KEY);
13764
13958
  if (!cache) {
13765
13959
  cache = {
13766
- classes: {}
13960
+ classes: {},
13961
+ options : options
13767
13962
  };
13768
13963
  createdCache = true;
13964
+ } else if (options && cache.options) {
13965
+ cache.options = angular.extend(cache.options || {}, options);
13769
13966
  }
13770
13967
 
13771
13968
  var classes = cache.classes;
@@ -13780,11 +13977,14 @@ var $AnimateProvider = ['$provide', function($provide) {
13780
13977
  var cache = element.data(STORAGE_KEY);
13781
13978
  element.removeData(STORAGE_KEY);
13782
13979
 
13783
- var classes = cache && resolveElementClasses(element, cache);
13784
-
13785
- if (classes) {
13786
- if (classes[0]) self.$$addClassImmediately(element, classes[0]);
13787
- if (classes[1]) self.$$removeClassImmediately(element, classes[1]);
13980
+ // in the event that the element is removed before postDigest
13981
+ // is run then the cache will be undefined and there will be
13982
+ // no need anymore to add or remove and of the element classes
13983
+ if (cache) {
13984
+ var classes = resolveElementClasses(element, cache.classes);
13985
+ if (classes) {
13986
+ self.$$setClassImmediately(element, classes[0], classes[1], cache.options);
13987
+ }
13788
13988
  }
13789
13989
 
13790
13990
  done();
@@ -13795,6 +13995,13 @@ var $AnimateProvider = ['$provide', function($provide) {
13795
13995
  return cache.promise;
13796
13996
  },
13797
13997
 
13998
+ $$setClassImmediately : function(element, add, remove, options) {
13999
+ add && this.$$addClassImmediately(element, add);
14000
+ remove && this.$$removeClassImmediately(element, remove);
14001
+ applyStyles(element, options);
14002
+ return asyncPromise();
14003
+ },
14004
+
13798
14005
  enabled : noop,
13799
14006
  cancel : noop
13800
14007
  };
@@ -13935,11 +14142,14 @@ function Browser(window, document, $log, $sniffer) {
13935
14142
  // URL API
13936
14143
  //////////////////////////////////////////////////////////////
13937
14144
 
13938
- var lastBrowserUrl = location.href,
13939
- lastHistoryState = history.state,
14145
+ var cachedState, lastHistoryState,
14146
+ lastBrowserUrl = location.href,
13940
14147
  baseElement = document.find('base'),
13941
14148
  reloadLocation = null;
13942
14149
 
14150
+ cacheState();
14151
+ lastHistoryState = cachedState;
14152
+
13943
14153
  /**
13944
14154
  * @name $browser#url
13945
14155
  *
@@ -13974,21 +14184,26 @@ function Browser(window, document, $log, $sniffer) {
13974
14184
 
13975
14185
  // setter
13976
14186
  if (url) {
14187
+ var sameState = lastHistoryState === state;
14188
+
13977
14189
  // Don't change anything if previous and current URLs and states match. This also prevents
13978
14190
  // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
13979
14191
  // See https://github.com/angular/angular.js/commit/ffb2701
13980
- if (lastBrowserUrl === url && (!$sniffer.history || history.state === state)) {
14192
+ if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
13981
14193
  return;
13982
14194
  }
13983
14195
  var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
13984
14196
  lastBrowserUrl = url;
14197
+ lastHistoryState = state;
13985
14198
  // Don't use history API if only the hash changed
13986
14199
  // due to a bug in IE10/IE11 which leads
13987
14200
  // to not firing a `hashchange` nor `popstate` event
13988
14201
  // in some cases (see #9143).
13989
- if ($sniffer.history && (!sameBase || history.state !== state)) {
14202
+ if ($sniffer.history && (!sameBase || !sameState)) {
13990
14203
  history[replace ? 'replaceState' : 'pushState'](state, '', url);
13991
- lastHistoryState = history.state;
14204
+ cacheState();
14205
+ // Do the assignment again so that those two variables are referentially identical.
14206
+ lastHistoryState = cachedState;
13992
14207
  } else {
13993
14208
  if (!sameBase) {
13994
14209
  reloadLocation = url;
@@ -14020,20 +14235,40 @@ function Browser(window, document, $log, $sniffer) {
14020
14235
  * @returns {object} state
14021
14236
  */
14022
14237
  self.state = function() {
14023
- return isUndefined(history.state) ? null : history.state;
14238
+ return cachedState;
14024
14239
  };
14025
14240
 
14026
14241
  var urlChangeListeners = [],
14027
14242
  urlChangeInit = false;
14028
14243
 
14244
+ function cacheStateAndFireUrlChange() {
14245
+ cacheState();
14246
+ fireUrlChange();
14247
+ }
14248
+
14249
+ // This variable should be used *only* inside the cacheState function.
14250
+ var lastCachedState = null;
14251
+ function cacheState() {
14252
+ // This should be the only place in $browser where `history.state` is read.
14253
+ cachedState = window.history.state;
14254
+ cachedState = isUndefined(cachedState) ? null : cachedState;
14255
+
14256
+ // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
14257
+ if (equals(cachedState, lastCachedState)) {
14258
+ cachedState = lastCachedState;
14259
+ }
14260
+ lastCachedState = cachedState;
14261
+ }
14262
+
14029
14263
  function fireUrlChange() {
14030
- if (lastBrowserUrl === self.url() && lastHistoryState === history.state) {
14264
+ if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
14031
14265
  return;
14032
14266
  }
14033
14267
 
14034
14268
  lastBrowserUrl = self.url();
14269
+ lastHistoryState = cachedState;
14035
14270
  forEach(urlChangeListeners, function(listener) {
14036
- listener(self.url(), history.state);
14271
+ listener(self.url(), cachedState);
14037
14272
  });
14038
14273
  }
14039
14274
 
@@ -14066,9 +14301,9 @@ function Browser(window, document, $log, $sniffer) {
14066
14301
  // changed by push/replaceState
14067
14302
 
14068
14303
  // html5 history api - popstate event
14069
- if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange);
14304
+ if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
14070
14305
  // hashchange event
14071
- jqLite(window).on('hashchange', fireUrlChange);
14306
+ jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
14072
14307
 
14073
14308
  urlChangeInit = true;
14074
14309
  }
@@ -14109,6 +14344,14 @@ function Browser(window, document, $log, $sniffer) {
14109
14344
  var lastCookieString = '';
14110
14345
  var cookiePath = self.baseHref();
14111
14346
 
14347
+ function safeDecodeURIComponent(str) {
14348
+ try {
14349
+ return decodeURIComponent(str);
14350
+ } catch (e) {
14351
+ return str;
14352
+ }
14353
+ }
14354
+
14112
14355
  /**
14113
14356
  * @name $browser#cookies
14114
14357
  *
@@ -14162,12 +14405,12 @@ function Browser(window, document, $log, $sniffer) {
14162
14405
  cookie = cookieArray[i];
14163
14406
  index = cookie.indexOf('=');
14164
14407
  if (index > 0) { //ignore nameless cookies
14165
- name = decodeURIComponent(cookie.substring(0, index));
14408
+ name = safeDecodeURIComponent(cookie.substring(0, index));
14166
14409
  // the first value that is seen for a cookie is the most
14167
14410
  // specific one. values for the same cookie name that
14168
14411
  // follow are for less specific paths.
14169
14412
  if (lastCookies[name] === undefined) {
14170
- lastCookies[name] = decodeURIComponent(cookie.substring(index + 1));
14413
+ lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
14171
14414
  }
14172
14415
  }
14173
14416
  }
@@ -14692,6 +14935,7 @@ function $TemplateCacheProvider() {
14692
14935
  * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
14693
14936
  * transclude: false,
14694
14937
  * restrict: 'A',
14938
+ * templateNamespace: 'html',
14695
14939
  * scope: false,
14696
14940
  * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
14697
14941
  * controllerAs: 'stringAlias',
@@ -14746,8 +14990,8 @@ function $TemplateCacheProvider() {
14746
14990
  * When this property is set to true, the HTML compiler will collect DOM nodes between
14747
14991
  * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
14748
14992
  * together as the directive elements. It is recomended that this feature be used on directives
14749
- * which are not strictly behavioural (such as {@link api/ng.directive:ngClick ngClick}), and which
14750
- * do not manipulate or replace child nodes (such as {@link api/ng.directive:ngInclude ngInclude}).
14993
+ * which are not strictly behavioural (such as {@link ngClick}), and which
14994
+ * do not manipulate or replace child nodes (such as {@link ngInclude}).
14751
14995
  *
14752
14996
  * #### `priority`
14753
14997
  * When there are multiple directives defined on a single DOM element, sometimes it
@@ -14760,7 +15004,8 @@ function $TemplateCacheProvider() {
14760
15004
  * #### `terminal`
14761
15005
  * If set to true then the current `priority` will be the last set of directives
14762
15006
  * which will execute (any directives at the current priority will still execute
14763
- * as the order of execution on same `priority` is undefined).
15007
+ * as the order of execution on same `priority` is undefined). Note that expressions
15008
+ * and other directives used in the directive's template will also be excluded from execution.
14764
15009
  *
14765
15010
  * #### `scope`
14766
15011
  * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the
@@ -14907,7 +15152,7 @@ function $TemplateCacheProvider() {
14907
15152
  * You can specify `templateUrl` as a string representing the URL or as a function which takes two
14908
15153
  * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
14909
15154
  * a string value representing the url. In either case, the template URL is passed through {@link
14910
- * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
15155
+ * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
14911
15156
  *
14912
15157
  *
14913
15158
  * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
@@ -14917,7 +15162,7 @@ function $TemplateCacheProvider() {
14917
15162
  * * `false` - the template will replace the contents of the directive's element.
14918
15163
  *
14919
15164
  * The replacement process migrates all of the attributes / classes from the old element to the new
14920
- * one. See the {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive
15165
+ * one. See the {@link guide/directive#template-expanding-directive
14921
15166
  * Directives Guide} for an example.
14922
15167
  *
14923
15168
  * There are very few scenarios where element replacement is required for the application function,
@@ -15072,7 +15317,7 @@ function $TemplateCacheProvider() {
15072
15317
  * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
15073
15318
  * object that contains the compiled DOM, which is linked to the correct transclusion scope.
15074
15319
  *
15075
- * When you call a transclusion function you can pass in a **clone attach function**. This function is accepts
15320
+ * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
15076
15321
  * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
15077
15322
  * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
15078
15323
  *
@@ -15692,12 +15937,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15692
15937
  * @param {string} key Normalized key. (ie ngAttribute) .
15693
15938
  * @param {function(interpolatedValue)} fn Function that will be called whenever
15694
15939
  the interpolated value of the attribute changes.
15695
- * See {@link ng.$compile#attributes $compile} for more info.
15940
+ * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
15696
15941
  * @returns {function()} Returns a deregistration function for this observer.
15697
15942
  */
15698
15943
  $observe: function(key, fn) {
15699
15944
  var attrs = this,
15700
- $$observers = (attrs.$$observers || (attrs.$$observers = Object.create(null))),
15945
+ $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
15701
15946
  listeners = ($$observers[key] || ($$observers[key] = []));
15702
15947
 
15703
15948
  listeners.push(fn);
@@ -17373,6 +17618,14 @@ function $DocumentProvider(){
17373
17618
  * This example will override the normal action of `$exceptionHandler`, to make angular
17374
17619
  * exceptions fail hard when they happen, instead of just logging to the console.
17375
17620
  *
17621
+ * <hr />
17622
+ * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
17623
+ * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
17624
+ * (unless executed during a digest).
17625
+ *
17626
+ * If you wish, you can manually delegate exceptions, e.g.
17627
+ * `try { ... } catch(e) { $exceptionHandler(e); }`
17628
+ *
17376
17629
  * @param {Error} exception Exception associated with the error.
17377
17630
  * @param {string=} cause optional information about the context in which
17378
17631
  * the error was thrown.
@@ -17540,7 +17793,7 @@ function $HttpProvider() {
17540
17793
  * @description
17541
17794
  *
17542
17795
  * Configure $http service to combine processing of multiple http responses received at around
17543
- * the same time via {@link ng.$rootScope#applyAsync $rootScope.$applyAsync}. This can result in
17796
+ * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
17544
17797
  * significant performance improvement for bigger applications that make many HTTP requests
17545
17798
  * concurrently (common during application bootstrap).
17546
17799
  *
@@ -17616,7 +17869,21 @@ function $HttpProvider() {
17616
17869
  * with two $http specific methods: `success` and `error`.
17617
17870
  *
17618
17871
  * ```js
17619
- * $http({method: 'GET', url: '/someUrl'}).
17872
+ * // Simple GET request example :
17873
+ * $http.get('/someUrl').
17874
+ * success(function(data, status, headers, config) {
17875
+ * // this callback will be called asynchronously
17876
+ * // when the response is available
17877
+ * }).
17878
+ * error(function(data, status, headers, config) {
17879
+ * // called asynchronously if an error occurs
17880
+ * // or server returns response with an error status.
17881
+ * });
17882
+ * ```
17883
+ *
17884
+ * ```js
17885
+ * // Simple POST request example (passing data) :
17886
+ * $http.post('/someUrl', {msg:'hello word!'}).
17620
17887
  * success(function(data, status, headers, config) {
17621
17888
  * // this callback will be called asynchronously
17622
17889
  * // when the response is available
@@ -17627,6 +17894,7 @@ function $HttpProvider() {
17627
17894
  * });
17628
17895
  * ```
17629
17896
  *
17897
+ *
17630
17898
  * Since the returned value of calling the $http function is a `promise`, you can also use
17631
17899
  * the `then` method to register callbacks, and these callbacks will receive a single argument –
17632
17900
  * an object representing the response. See the API signature and type info below for more
@@ -17779,7 +18047,7 @@ function $HttpProvider() {
17779
18047
  *
17780
18048
  * You can change the default cache to a new object (built with
17781
18049
  * {@link ng.$cacheFactory `$cacheFactory`}) by updating the
17782
- * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set
18050
+ * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set
17783
18051
  * their `cache` property to `true` will now use this cache object.
17784
18052
  *
17785
18053
  * If you set the default cache to `false` then only requests that specify their own custom
@@ -18141,9 +18409,12 @@ function $HttpProvider() {
18141
18409
 
18142
18410
  function transformResponse(response) {
18143
18411
  // make a copy since the response must be cacheable
18144
- var resp = extend({}, response, {
18145
- data: transformData(response.data, response.headers, config.transformResponse)
18146
- });
18412
+ var resp = extend({}, response);
18413
+ if (!response.data) {
18414
+ resp.data = response.data;
18415
+ } else {
18416
+ resp.data = transformData(response.data, response.headers, config.transformResponse);
18417
+ }
18147
18418
  return (isSuccess(response.status))
18148
18419
  ? resp
18149
18420
  : $q.reject(resp);
@@ -18886,16 +19157,13 @@ function $InterpolateProvider() {
18886
19157
  return '';
18887
19158
  }
18888
19159
  switch (typeof value) {
18889
- case 'string': {
19160
+ case 'string':
18890
19161
  break;
18891
- }
18892
- case 'number': {
19162
+ case 'number':
18893
19163
  value = '' + value;
18894
19164
  break;
18895
- }
18896
- default: {
19165
+ default:
18897
19166
  value = toJson(value);
18898
- }
18899
19167
  }
18900
19168
 
18901
19169
  return value;
@@ -19717,6 +19985,7 @@ var locationPrototype = {
19717
19985
  search = search.toString();
19718
19986
  this.$$search = parseKeyValue(search);
19719
19987
  } else if (isObject(search)) {
19988
+ search = copy(search, {});
19720
19989
  // remove object undefined or null properties
19721
19990
  forEach(search, function(value, key) {
19722
19991
  if (value == null) delete search[key];
@@ -19902,8 +20171,8 @@ function $LocationProvider(){
19902
20171
  * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
19903
20172
  * true, and a base tag is not present, an error will be thrown when `$location` is injected.
19904
20173
  * See the {@link guide/$location $location guide for more information}
19905
- * - **rewriteLinks** - `{boolean}` - (default: `false`) When html5Mode is enabled, disables
19906
- * url rewriting for relative linksTurns off url rewriting for relative links.
20174
+ * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
20175
+ * enables/disables url rewriting for relative links.
19907
20176
  *
19908
20177
  * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
19909
20178
  */
@@ -19914,7 +20183,7 @@ function $LocationProvider(){
19914
20183
  } else if (isObject(mode)) {
19915
20184
 
19916
20185
  if (isBoolean(mode.enabled)) {
19917
- html5Mode.enabled = mode.enabled;
20186
+ html5Mode.enabled = mode.enabled;
19918
20187
  }
19919
20188
 
19920
20189
  if (isBoolean(mode.requireBase)) {
@@ -19922,7 +20191,7 @@ function $LocationProvider(){
19922
20191
  }
19923
20192
 
19924
20193
  if (isBoolean(mode.rewriteLinks)) {
19925
- html5Mode.rewriteLinks = mode.rewriteLinks;
20194
+ html5Mode.rewriteLinks = mode.rewriteLinks;
19926
20195
  }
19927
20196
 
19928
20197
  return this;
@@ -19941,7 +20210,7 @@ function $LocationProvider(){
19941
20210
  * This change can be prevented by calling
19942
20211
  * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
19943
20212
  * details about event object. Upon successful change
19944
- * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
20213
+ * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
19945
20214
  *
19946
20215
  * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
19947
20216
  * the browser supports the HTML5 History API.
@@ -20093,9 +20362,10 @@ function $LocationProvider(){
20093
20362
  var oldUrl = $browser.url();
20094
20363
  var oldState = $browser.state();
20095
20364
  var currentReplace = $location.$$replace;
20365
+ var urlOrStateChanged = oldUrl !== $location.absUrl() ||
20366
+ ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
20096
20367
 
20097
- if (initializing || oldUrl !== $location.absUrl() ||
20098
- ($location.$$html5 && $sniffer.history && oldState !== $location.$$state)) {
20368
+ if (initializing || urlOrStateChanged) {
20099
20369
  initializing = false;
20100
20370
 
20101
20371
  $rootScope.$evalAsync(function() {
@@ -20104,8 +20374,10 @@ function $LocationProvider(){
20104
20374
  $location.$$parse(oldUrl);
20105
20375
  $location.$$state = oldState;
20106
20376
  } else {
20107
- setBrowserUrlWithFallback($location.absUrl(), currentReplace,
20108
- oldState === $location.$$state ? null : $location.$$state);
20377
+ if (urlOrStateChanged) {
20378
+ setBrowserUrlWithFallback($location.absUrl(), currentReplace,
20379
+ oldState === $location.$$state ? null : $location.$$state);
20380
+ }
20109
20381
  afterLocationChange(oldUrl, oldState);
20110
20382
  }
20111
20383
  });
@@ -20387,7 +20659,6 @@ CONSTANTS['this'].sharedGetter = true;
20387
20659
 
20388
20660
  //Operators - will be wrapped by binaryFn/unaryFn/assignment/filter
20389
20661
  var OPERATORS = extend(createMap(), {
20390
- /* jshint bitwise : false */
20391
20662
  '+':function(self, locals, a,b){
20392
20663
  a=a(self, locals); b=b(self, locals);
20393
20664
  if (isDefined(a)) {
@@ -20404,7 +20675,6 @@ var OPERATORS = extend(createMap(), {
20404
20675
  '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
20405
20676
  '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
20406
20677
  '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
20407
- '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
20408
20678
  '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
20409
20679
  '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
20410
20680
  '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
@@ -20415,14 +20685,12 @@ var OPERATORS = extend(createMap(), {
20415
20685
  '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
20416
20686
  '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
20417
20687
  '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
20418
- '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
20419
20688
  '!':function(self, locals, a){return !a(self, locals);},
20420
20689
 
20421
20690
  //Tokenized as operators but parsed as assignment/filters
20422
20691
  '=':true,
20423
20692
  '|':true
20424
20693
  });
20425
- /* jshint bitwise: true */
20426
20694
  var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
20427
20695
 
20428
20696
 
@@ -21460,16 +21728,17 @@ function $ParseProvider() {
21460
21728
  }
21461
21729
 
21462
21730
  function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
21463
- var unwatch;
21731
+ var unwatch, lastValue;
21464
21732
  return unwatch = scope.$watch(function oneTimeWatch(scope) {
21465
21733
  return parsedExpression(scope);
21466
21734
  }, function oneTimeListener(value, old, scope) {
21735
+ lastValue = value;
21467
21736
  if (isFunction(listener)) {
21468
21737
  listener.call(this, value, old, scope);
21469
21738
  }
21470
21739
  if (isAllDefined(value)) {
21471
21740
  scope.$$postDigest(function () {
21472
- if(isAllDefined(value)) unwatch();
21741
+ if(isAllDefined(lastValue)) unwatch();
21473
21742
  });
21474
21743
  }
21475
21744
  }, objectEquality);
@@ -21545,24 +21814,27 @@ function $ParseProvider() {
21545
21814
  * It can be used like so:
21546
21815
  *
21547
21816
  * ```js
21548
- * return $q(function(resolve, reject) {
21549
- * // perform some asynchronous operation, resolve or reject the promise when appropriate.
21550
- * setInterval(function() {
21551
- * if (pollStatus > 0) {
21552
- * resolve(polledValue);
21553
- * } else if (pollStatus < 0) {
21554
- * reject(polledValue);
21555
- * } else {
21556
- * pollStatus = pollAgain(function(value) {
21557
- * polledValue = value;
21558
- * });
21559
- * }
21560
- * }, 10000);
21561
- * }).
21562
- * then(function(value) {
21563
- * // handle success
21817
+ * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
21818
+ * // are available in the current lexical scope (they could have been injected or passed in).
21819
+ *
21820
+ * function asyncGreet(name) {
21821
+ * // perform some asynchronous operation, resolve or reject the promise when appropriate.
21822
+ * return $q(function(resolve, reject) {
21823
+ * setTimeout(function() {
21824
+ * if (okToGreet(name)) {
21825
+ * resolve('Hello, ' + name + '!');
21826
+ * } else {
21827
+ * reject('Greeting ' + name + ' is not allowed.');
21828
+ * }
21829
+ * }, 1000);
21830
+ * });
21831
+ * }
21832
+ *
21833
+ * var promise = asyncGreet('Robin Hood');
21834
+ * promise.then(function(greeting) {
21835
+ * alert('Success: ' + greeting);
21564
21836
  * }, function(reason) {
21565
- * // handle failure
21837
+ * alert('Failed: ' + reason);
21566
21838
  * });
21567
21839
  * ```
21568
21840
  *
@@ -21578,7 +21850,7 @@ function $ParseProvider() {
21578
21850
  * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
21579
21851
  *
21580
21852
  * ```js
21581
- * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet`
21853
+ * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
21582
21854
  * // are available in the current lexical scope (they could have been injected or passed in).
21583
21855
  *
21584
21856
  * function asyncGreet(name) {
@@ -23918,7 +24190,7 @@ function $SceDelegateProvider() {
23918
24190
  *
23919
24191
  * As of version 1.2, Angular ships with SCE enabled by default.
23920
24192
  *
23921
- * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows
24193
+ * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow
23922
24194
  * one to execute arbitrary javascript by the use of the expression() syntax. Refer
23923
24195
  * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
23924
24196
  * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>`
@@ -23965,7 +24237,7 @@ function $SceDelegateProvider() {
23965
24237
  *
23966
24238
  * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
23967
24239
  * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link
23968
- * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
24240
+ * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
23969
24241
  * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
23970
24242
  *
23971
24243
  * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
@@ -24235,13 +24507,13 @@ function $SceProvider() {
24235
24507
  * sce.js and sceSpecs.js would need to be aware of this detail.
24236
24508
  */
24237
24509
 
24238
- this.$get = ['$parse', '$sniffer', '$sceDelegate', function(
24239
- $parse, $sniffer, $sceDelegate) {
24240
- // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows
24510
+ this.$get = ['$document', '$parse', '$sceDelegate', function(
24511
+ $document, $parse, $sceDelegate) {
24512
+ // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
24241
24513
  // the "expression(javascript expression)" syntax which is insecure.
24242
- if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) {
24514
+ if (enabled && $document[0].documentMode < 8) {
24243
24515
  throw $sceMinErr('iequirks',
24244
- 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' +
24516
+ 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
24245
24517
  'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
24246
24518
  'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
24247
24519
  }
@@ -24464,7 +24736,7 @@ function $SceProvider() {
24464
24736
  *
24465
24737
  * @description
24466
24738
  * Shorthand method. `$sce.parseAsHtml(expression string)` →
24467
- * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`}
24739
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
24468
24740
  *
24469
24741
  * @param {string} expression String expression to compile.
24470
24742
  * @returns {function(context, locals)} a function which represents the compiled expression:
@@ -24481,7 +24753,7 @@ function $SceProvider() {
24481
24753
  *
24482
24754
  * @description
24483
24755
  * Shorthand method. `$sce.parseAsCss(value)` →
24484
- * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`}
24756
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
24485
24757
  *
24486
24758
  * @param {string} expression String expression to compile.
24487
24759
  * @returns {function(context, locals)} a function which represents the compiled expression:
@@ -24498,7 +24770,7 @@ function $SceProvider() {
24498
24770
  *
24499
24771
  * @description
24500
24772
  * Shorthand method. `$sce.parseAsUrl(value)` →
24501
- * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`}
24773
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
24502
24774
  *
24503
24775
  * @param {string} expression String expression to compile.
24504
24776
  * @returns {function(context, locals)} a function which represents the compiled expression:
@@ -24515,7 +24787,7 @@ function $SceProvider() {
24515
24787
  *
24516
24788
  * @description
24517
24789
  * Shorthand method. `$sce.parseAsResourceUrl(value)` →
24518
- * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`}
24790
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
24519
24791
  *
24520
24792
  * @param {string} expression String expression to compile.
24521
24793
  * @returns {function(context, locals)} a function which represents the compiled expression:
@@ -24532,7 +24804,7 @@ function $SceProvider() {
24532
24804
  *
24533
24805
  * @description
24534
24806
  * Shorthand method. `$sce.parseAsJs(value)` →
24535
- * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`}
24807
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
24536
24808
  *
24537
24809
  * @param {string} expression String expression to compile.
24538
24810
  * @returns {function(context, locals)} a function which represents the compiled expression:
@@ -24586,7 +24858,6 @@ function $SnifferProvider() {
24586
24858
  int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
24587
24859
  boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
24588
24860
  document = $document[0] || {},
24589
- documentMode = document.documentMode,
24590
24861
  vendorPrefix,
24591
24862
  vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
24592
24863
  bodyStyle = document.body && document.body.style,
@@ -24646,9 +24917,7 @@ function $SnifferProvider() {
24646
24917
  vendorPrefix: vendorPrefix,
24647
24918
  transitions : transitions,
24648
24919
  animations : animations,
24649
- android: android,
24650
- msie : msie,
24651
- msieDocumentMode: documentMode
24920
+ android: android
24652
24921
  };
24653
24922
  }];
24654
24923
  }
@@ -25353,17 +25622,17 @@ function filterFilter() {
25353
25622
  }
25354
25623
 
25355
25624
  var search = function(obj, text){
25356
- if (typeof text == 'string' && text.charAt(0) === '!') {
25625
+ if (typeof text === 'string' && text.charAt(0) === '!') {
25357
25626
  return !search(obj, text.substr(1));
25358
25627
  }
25359
25628
  switch (typeof obj) {
25360
- case "boolean":
25361
- case "number":
25362
- case "string":
25629
+ case 'boolean':
25630
+ case 'number':
25631
+ case 'string':
25363
25632
  return comparator(obj, text);
25364
- case "object":
25633
+ case 'object':
25365
25634
  switch (typeof text) {
25366
- case "object":
25635
+ case 'object':
25367
25636
  return comparator(obj, text);
25368
25637
  default:
25369
25638
  for ( var objKey in obj) {
@@ -25374,7 +25643,7 @@ function filterFilter() {
25374
25643
  break;
25375
25644
  }
25376
25645
  return false;
25377
- case "array":
25646
+ case 'array':
25378
25647
  for ( var i = 0; i < obj.length; i++) {
25379
25648
  if (search(obj[i], text)) {
25380
25649
  return true;
@@ -25386,13 +25655,13 @@ function filterFilter() {
25386
25655
  }
25387
25656
  };
25388
25657
  switch (typeof expression) {
25389
- case "boolean":
25390
- case "number":
25391
- case "string":
25658
+ case 'boolean':
25659
+ case 'number':
25660
+ case 'string':
25392
25661
  // Set up expression object and fall through
25393
25662
  expression = {$:expression};
25394
25663
  // jshint -W086
25395
- case "object":
25664
+ case 'object':
25396
25665
  // jshint +W086
25397
25666
  for (var key in expression) {
25398
25667
  (function(path) {
@@ -25431,6 +25700,7 @@ function filterFilter() {
25431
25700
  *
25432
25701
  * @param {number} amount Input to filter.
25433
25702
  * @param {string=} symbol Currency symbol or identifier to be displayed.
25703
+ * @param {number=} fractionSize Number of decimal places to round the amount to.
25434
25704
  * @returns {string} Formatted number.
25435
25705
  *
25436
25706
  *
@@ -25446,13 +25716,15 @@ function filterFilter() {
25446
25716
  <div ng-controller="ExampleController">
25447
25717
  <input type="number" ng-model="amount"> <br>
25448
25718
  default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
25449
- custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span>
25719
+ custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
25720
+ no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
25450
25721
  </div>
25451
25722
  </file>
25452
25723
  <file name="protractor.js" type="protractor">
25453
25724
  it('should init with 1234.56', function() {
25454
25725
  expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
25455
- expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56');
25726
+ expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
25727
+ expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
25456
25728
  });
25457
25729
  it('should update', function() {
25458
25730
  if (browser.params.browser == 'safari') {
@@ -25463,7 +25735,8 @@ function filterFilter() {
25463
25735
  element(by.model('amount')).clear();
25464
25736
  element(by.model('amount')).sendKeys('-1234');
25465
25737
  expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)');
25466
- expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)');
25738
+ expect(element(by.id('currency-custom')).getText()).toBe('(USD$1,234.00)');
25739
+ expect(element(by.id('currency-no-fractions')).getText()).toBe('(USD$1,234)');
25467
25740
  });
25468
25741
  </file>
25469
25742
  </example>
@@ -25471,13 +25744,20 @@ function filterFilter() {
25471
25744
  currencyFilter.$inject = ['$locale'];
25472
25745
  function currencyFilter($locale) {
25473
25746
  var formats = $locale.NUMBER_FORMATS;
25474
- return function(amount, currencySymbol){
25475
- if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
25747
+ return function(amount, currencySymbol, fractionSize){
25748
+ if (isUndefined(currencySymbol)) {
25749
+ currencySymbol = formats.CURRENCY_SYM;
25750
+ }
25751
+
25752
+ if (isUndefined(fractionSize)) {
25753
+ // TODO: read the default value from the locale file
25754
+ fractionSize = 2;
25755
+ }
25476
25756
 
25477
25757
  // if null or undefined pass it through
25478
25758
  return (amount == null)
25479
25759
  ? amount
25480
- : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
25760
+ : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
25481
25761
  replace(/\u00A4/g, currencySymbol);
25482
25762
  };
25483
25763
  }
@@ -25994,7 +26274,7 @@ var uppercaseFilter = valueFn(uppercase);
25994
26274
  <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
25995
26275
  Limit {{letters}} to: <input type="number" step="1" ng-model="letterLimit">
25996
26276
  <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
25997
- Limit {{longNumber}} to: <input type="integer" ng-model="longNumberLimit">
26277
+ Limit {{longNumber}} to: <input type="number" step="1" ng-model="longNumberLimit">
25998
26278
  <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
25999
26279
  </div>
26000
26280
  </file>
@@ -27231,15 +27511,13 @@ var formDirectiveFactory = function(isNgForm) {
27231
27511
  parentFormCtrl.$$renameControl(controller, alias);
27232
27512
  });
27233
27513
  }
27234
- if (parentFormCtrl !== nullFormCtrl) {
27235
- formElement.on('$destroy', function() {
27236
- parentFormCtrl.$removeControl(controller);
27237
- if (alias) {
27238
- setter(scope, alias, undefined, alias);
27239
- }
27240
- extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
27241
- });
27242
- }
27514
+ formElement.on('$destroy', function() {
27515
+ parentFormCtrl.$removeControl(controller);
27516
+ if (alias) {
27517
+ setter(scope, alias, undefined, alias);
27518
+ }
27519
+ extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
27520
+ });
27243
27521
  }
27244
27522
  };
27245
27523
  }
@@ -30486,7 +30764,7 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
30486
30764
  *
30487
30765
  * You may also bypass sanitization for values you know are safe. To do so, bind to
30488
30766
  * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
30489
- * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}.
30767
+ * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
30490
30768
  *
30491
30769
  * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
30492
30770
  * will have an exception (instead of an exploit.)
@@ -30798,8 +31076,8 @@ function classDirective(name, selector) {
30798
31076
  The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
30799
31077
  Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
30800
31078
  any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
30801
- to view the step by step details of {@link ngAnimate.$animate#addclass $animate.addClass} and
30802
- {@link ngAnimate.$animate#removeclass $animate.removeClass}.
31079
+ to view the step by step details of {@link ng.$animate#addClass $animate.addClass} and
31080
+ {@link ng.$animate#removeClass $animate.removeClass}.
30803
31081
  */
30804
31082
  var ngClassDirective = classDirective('', true);
30805
31083
 
@@ -31205,7 +31483,7 @@ var ngControllerDirective = [function() {
31205
31483
  * @description
31206
31484
  * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support.
31207
31485
  *
31208
- * This is necessary when developing things like Google Chrome Extensions.
31486
+ * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
31209
31487
  *
31210
31488
  * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things).
31211
31489
  * For Angular to be CSP compatible there are only two things that we need to do differently:
@@ -31901,7 +32179,7 @@ forEach(
31901
32179
  Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/>
31902
32180
  Show when checked:
31903
32181
  <span ng-if="checked" class="animate-if">
31904
- I'm removed when the checkbox is unchecked.
32182
+ This is removed when the checkbox is unchecked.
31905
32183
  </span>
31906
32184
  </file>
31907
32185
  <file name="animations.css">
@@ -31955,15 +32233,15 @@ var ngIfDirective = ['$animate', function($animate) {
31955
32233
  });
31956
32234
  }
31957
32235
  } else {
31958
- if(previousElements) {
32236
+ if (previousElements) {
31959
32237
  previousElements.remove();
31960
32238
  previousElements = null;
31961
32239
  }
31962
- if(childScope) {
32240
+ if (childScope) {
31963
32241
  childScope.$destroy();
31964
32242
  childScope = null;
31965
32243
  }
31966
- if(block) {
32244
+ if (block) {
31967
32245
  previousElements = getBlockNodes(block.clone);
31968
32246
  $animate.leave(previousElements).then(function() {
31969
32247
  previousElements = null;
@@ -31985,10 +32263,10 @@ var ngIfDirective = ['$animate', function($animate) {
31985
32263
  * Fetches, compiles and includes an external HTML fragment.
31986
32264
  *
31987
32265
  * By default, the template URL is restricted to the same domain and protocol as the
31988
- * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
32266
+ * application document. This is done by calling {@link $sce#getTrustedResourceUrl
31989
32267
  * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
31990
32268
  * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
31991
- * [wrap them](ng.$sce#trustAsResourceUrl) as trusted values. Refer to Angular's {@link
32269
+ * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link
31992
32270
  * ng.$sce Strict Contextual Escaping}.
31993
32271
  *
31994
32272
  * In addition, the browser's
@@ -32687,13 +32965,6 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
32687
32965
  * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
32688
32966
  * will be associated by item identity in the array.
32689
32967
  *
32690
- * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
32691
- * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
32692
- * when a filter is active on the repeater, but the filtered result set is empty.
32693
- *
32694
- * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
32695
- * the items have been processed through the filter.
32696
- *
32697
32968
  * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
32698
32969
  * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
32699
32970
  * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
@@ -32706,6 +32977,13 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
32706
32977
  * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
32707
32978
  * to items in conjunction with a tracking expression.
32708
32979
  *
32980
+ * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
32981
+ * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
32982
+ * when a filter is active on the repeater, but the filtered result set is empty.
32983
+ *
32984
+ * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
32985
+ * the items have been processed through the filter.
32986
+ *
32709
32987
  * @example
32710
32988
  * This example initializes the scope to a list of names and
32711
32989
  * then uses `ngRepeat` to display every person:
@@ -33193,7 +33471,9 @@ var ngShowDirective = ['$animate', function($animate) {
33193
33471
  // we can control when the element is actually displayed on screen without having
33194
33472
  // to have a global/greedy CSS selector that breaks when other animations are run.
33195
33473
  // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
33196
- $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
33474
+ $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
33475
+ tempClasses : NG_HIDE_IN_PROGRESS_CLASS
33476
+ });
33197
33477
  });
33198
33478
  }
33199
33479
  };
@@ -33350,7 +33630,9 @@ var ngHideDirective = ['$animate', function($animate) {
33350
33630
  scope.$watch(attr.ngHide, function ngHideWatchAction(value){
33351
33631
  // The comment inside of the ngShowDirective explains why we add and
33352
33632
  // remove a temporary class for the show/hide animation
33353
- $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
33633
+ $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
33634
+ tempClasses : NG_HIDE_IN_PROGRESS_CLASS
33635
+ });
33354
33636
  });
33355
33637
  }
33356
33638
  };
@@ -33775,9 +34057,23 @@ var ngOptionsMinErr = minErr('ngOptions');
33775
34057
  * <div class="alert alert-info">
33776
34058
  * **Note:** Using `select as` will bind the result of the `select as` expression to the model, but
33777
34059
  * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
33778
- * or property name (for object data sources) of the value within the collection.
34060
+ * or property name (for object data sources) of the value within the collection.
33779
34061
  * </div>
33780
34062
  *
34063
+ * **Note:** Using `select as` together with `trackexpr` is not recommended.
34064
+ * Reasoning:
34065
+ * - Example: &lt;select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"&gt;
34066
+ * values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}],
34067
+ * $scope.selected = {name: 'aSubItem'};
34068
+ * - track by is always applied to `value`, with the purpose of preserving the selection,
34069
+ * (to `item` in this case)
34070
+ * - to calculate whether an item is selected we do the following:
34071
+ * 1. apply `track by` to the values in the array, e.g.
34072
+ * In the example: [1,2]
34073
+ * 2. apply `track by` to the already selected value in `ngModel`:
34074
+ * In the example: this is not possible, as `track by` refers to `item.id`, but the selected
34075
+ * value from `ngModel` is `{name: aSubItem}`.
34076
+ *
33781
34077
  * @param {string} ngModel Assignable angular expression to data-bind to.
33782
34078
  * @param {string=} name Property name of the form under which the control is published.
33783
34079
  * @param {string=} required The control is considered valid only if value is entered.
@@ -33814,23 +34110,6 @@ var ngOptionsMinErr = minErr('ngOptions');
33814
34110
  * used to identify the objects in the array. The `trackexpr` will most likely refer to the
33815
34111
  * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
33816
34112
  * even when the options are recreated (e.g. reloaded from the server).
33817
-
33818
- * <div class="alert alert-info">
33819
- * **Note:** Using `select as` together with `trackexpr` is not possible (and will throw).
33820
- * Reasoning:
33821
- * - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
33822
- * values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItemß'}}],
33823
- * $scope.selected = {name: 'aSubItem'};
33824
- * - track by is always applied to `value`, with purpose to preserve the selection,
33825
- * (to `item` in this case)
33826
- * - to calculate whether an item is selected we do the following:
33827
- * 1. apply `track by` to the values in the array, e.g.
33828
- * In the example: [1,2]
33829
- * 2. apply `track by` to the already selected value in `ngModel`:
33830
- * In the example: this is not possible, as `track by` refers to `item.id`, but the selected
33831
- * value from `ngModel` is `{name: aSubItem}`.
33832
- *
33833
- * </div>
33834
34113
  *
33835
34114
  * @example
33836
34115
  <example module="selectExample">
@@ -33940,7 +34219,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
33940
34219
  // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
33941
34220
  // Adding an <option selected="selected"> element to a <select required="required"> should
33942
34221
  // automatically select the new element
33943
- if (element[0].hasAttribute('selected')) {
34222
+ if (element && element[0].hasAttribute('selected')) {
33944
34223
  element[0].selected = true;
33945
34224
  }
33946
34225
  };
@@ -34103,13 +34382,6 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
34103
34382
  //re-usable object to represent option's locals
34104
34383
  locals = {};
34105
34384
 
34106
- if (trackFn && selectAsFn) {
34107
- throw ngOptionsMinErr('trkslct',
34108
- "Comprehension expression cannot contain both selectAs '{0}' " +
34109
- "and trackBy '{1}' expressions.",
34110
- selectAs, track);
34111
- }
34112
-
34113
34385
  if (nullOption) {
34114
34386
  // compile the element since there might be bindings in it
34115
34387
  $compile(nullOption)(scope);
@@ -34200,7 +34472,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
34200
34472
  function createIsSelectedFn(viewValue) {
34201
34473
  var selectedSet;
34202
34474
  if (multiple) {
34203
- if (!selectAs && trackFn && isArray(viewValue)) {
34475
+ if (trackFn && isArray(viewValue)) {
34204
34476
 
34205
34477
  selectedSet = new HashMap([]);
34206
34478
  for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
@@ -34210,15 +34482,16 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
34210
34482
  } else {
34211
34483
  selectedSet = new HashMap(viewValue);
34212
34484
  }
34213
- } else if (!selectAsFn && trackFn) {
34485
+ } else if (trackFn) {
34214
34486
  viewValue = callExpression(trackFn, null, viewValue);
34215
34487
  }
34488
+
34216
34489
  return function isSelected(key, value) {
34217
34490
  var compareValueFn;
34218
- if (selectAsFn) {
34219
- compareValueFn = selectAsFn;
34220
- } else if (trackFn) {
34491
+ if (trackFn) {
34221
34492
  compareValueFn = trackFn;
34493
+ } else if (selectAsFn) {
34494
+ compareValueFn = selectAsFn;
34222
34495
  } else {
34223
34496
  compareValueFn = valueFn;
34224
34497
  }
@@ -34238,6 +34511,23 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
34238
34511
  }
34239
34512
  }
34240
34513
 
34514
+ /**
34515
+ * A new labelMap is created with each render.
34516
+ * This function is called for each existing option with added=false,
34517
+ * and each new option with added=true.
34518
+ * - Labels that are passed to this method twice,
34519
+ * (once with added=true and once with added=false) will end up with a value of 0, and
34520
+ * will cause no change to happen to the corresponding option.
34521
+ * - Labels that are passed to this method only once with added=false will end up with a
34522
+ * value of -1 and will eventually be passed to selectCtrl.removeOption()
34523
+ * - Labels that are passed to this method only once with added=true will end up with a
34524
+ * value of 1 and will eventually be passed to selectCtrl.addOption()
34525
+ */
34526
+ function updateLabelMap(labelMap, label, added) {
34527
+ labelMap[label] = labelMap[label] || 0;
34528
+ labelMap[label] += (added ? 1 : -1);
34529
+ }
34530
+
34241
34531
  function render() {
34242
34532
  renderScheduled = false;
34243
34533
 
@@ -34255,6 +34545,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
34255
34545
  value,
34256
34546
  groupLength, length,
34257
34547
  groupIndex, index,
34548
+ labelMap = {},
34258
34549
  selected,
34259
34550
  isSelected = createIsSelectedFn(viewValue),
34260
34551
  anySelected = false,
@@ -34337,6 +34628,8 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
34337
34628
  // reuse elements
34338
34629
  lastElement = existingOption.element;
34339
34630
  if (existingOption.label !== option.label) {
34631
+ updateLabelMap(labelMap, existingOption.label, false);
34632
+ updateLabelMap(labelMap, option.label, true);
34340
34633
  lastElement.text(existingOption.label = option.label);
34341
34634
  }
34342
34635
  if (existingOption.id !== option.id) {
@@ -34376,7 +34669,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
34376
34669
  id: option.id,
34377
34670
  selected: option.selected
34378
34671
  });
34379
- selectCtrl.addOption(option.label, element);
34672
+ updateLabelMap(labelMap, option.label, true);
34380
34673
  if (lastElement) {
34381
34674
  lastElement.after(element);
34382
34675
  } else {
@@ -34389,9 +34682,16 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
34389
34682
  index++; // increment since the existingOptions[0] is parent element not OPTION
34390
34683
  while(existingOptions.length > index) {
34391
34684
  option = existingOptions.pop();
34392
- selectCtrl.removeOption(option.label);
34685
+ updateLabelMap(labelMap, option.label, false);
34393
34686
  option.element.remove();
34394
34687
  }
34688
+ forEach(labelMap, function (count, label) {
34689
+ if (count > 0) {
34690
+ selectCtrl.addOption(label);
34691
+ } else if (count < 0) {
34692
+ selectCtrl.removeOption(label);
34693
+ }
34694
+ });
34395
34695
  }
34396
34696
  // remove any excessive OPTGROUPs from select
34397
34697
  while(optionGroupsCache.length > groupIndex) {