angularjs-rails 1.5.6 → 1.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.5.6
2
+ * @license AngularJS v1.5.8
3
3
  * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -167,6 +167,12 @@ ngTouch.factory('$swipe', [function() {
167
167
  move: 'touchmove',
168
168
  end: 'touchend',
169
169
  cancel: 'touchcancel'
170
+ },
171
+ 'pointer': {
172
+ start: 'pointerdown',
173
+ move: 'pointermove',
174
+ end: 'pointerup',
175
+ cancel: 'pointercancel'
170
176
  }
171
177
  };
172
178
 
@@ -201,15 +207,15 @@ ngTouch.factory('$swipe', [function() {
201
207
  * The main method of `$swipe`. It takes an element to be watched for swipe motions, and an
202
208
  * object containing event handlers.
203
209
  * The pointer types that should be used can be specified via the optional
204
- * third argument, which is an array of strings `'mouse'` and `'touch'`. By default,
205
- * `$swipe` will listen for `mouse` and `touch` events.
210
+ * third argument, which is an array of strings `'mouse'`, `'touch'` and `'pointer'`. By default,
211
+ * `$swipe` will listen for `mouse`, `touch` and `pointer` events.
206
212
  *
207
213
  * The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end`
208
214
  * receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }` and the raw
209
215
  * `event`. `cancel` receives the raw `event` as its single parameter.
210
216
  *
211
- * `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is
212
- * watching for `touchmove` or `mousemove` events. These events are ignored until the total
217
+ * `start` is called on either `mousedown`, `touchstart` or `pointerdown`. After this event, `$swipe` is
218
+ * watching for `touchmove`, `mousemove` or `pointermove` events. These events are ignored until the total
213
219
  * distance moved in either dimension exceeds a small threshold.
214
220
  *
215
221
  * Once this threshold is exceeded, either the horizontal or vertical delta is greater.
@@ -217,12 +223,12 @@ ngTouch.factory('$swipe', [function() {
217
223
  * - If the vertical distance is greater, this is a scroll, and we let the browser take over.
218
224
  * A `cancel` event is sent.
219
225
  *
220
- * `move` is called on `mousemove` and `touchmove` after the above logic has determined that
226
+ * `move` is called on `mousemove`, `touchmove` and `pointermove` after the above logic has determined that
221
227
  * a swipe is in progress.
222
228
  *
223
- * `end` is called when a swipe is successfully completed with a `touchend` or `mouseup`.
229
+ * `end` is called when a swipe is successfully completed with a `touchend`, `mouseup` or `pointerup`.
224
230
  *
225
- * `cancel` is called either on a `touchcancel` from the browser, or when we begin scrolling
231
+ * `cancel` is called either on a `touchcancel` or `pointercancel` from the browser, or when we begin scrolling
226
232
  * as described above.
227
233
  *
228
234
  */
@@ -236,7 +242,7 @@ ngTouch.factory('$swipe', [function() {
236
242
  // Whether a swipe is active.
237
243
  var active = false;
238
244
 
239
- pointerTypes = pointerTypes || ['mouse', 'touch'];
245
+ pointerTypes = pointerTypes || ['mouse', 'touch', 'pointer'];
240
246
  element.on(getEvents(pointerTypes, 'start'), function(event) {
241
247
  startCoords = getCoordinates(event);
242
248
  active = true;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.5.6
2
+ * @license AngularJS v1.5.8
3
3
  * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -57,7 +57,7 @@ function minErr(module, ErrorConstructor) {
57
57
  return match;
58
58
  });
59
59
 
60
- message += '\nhttp://errors.angularjs.org/1.5.6/' +
60
+ message += '\nhttp://errors.angularjs.org/1.5.8/' +
61
61
  (module ? module + '/' : '') + code;
62
62
 
63
63
  for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
@@ -126,7 +126,6 @@ function minErr(module, ErrorConstructor) {
126
126
  includes: true,
127
127
  arrayRemove: true,
128
128
  copy: true,
129
- shallowCopy: true,
130
129
  equals: true,
131
130
  csp: true,
132
131
  jq: true,
@@ -822,7 +821,13 @@ function arrayRemove(array, value) {
822
821
  * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
823
822
  * are deleted and then all elements/properties from the source are copied to it.
824
823
  * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
825
- * * If `source` is identical to 'destination' an exception will be thrown.
824
+ * * If `source` is identical to `destination` an exception will be thrown.
825
+ *
826
+ * <br />
827
+ * <div class="alert alert-warning">
828
+ * Only enumerable properties are taken into account. Non-enumerable properties (both on `source`
829
+ * and on `destination`) will be ignored.
830
+ * </div>
826
831
  *
827
832
  * @param {*} source The source that will be used to make a copy.
828
833
  * Can be any type, including primitives, `null`, and `undefined`.
@@ -831,41 +836,42 @@ function arrayRemove(array, value) {
831
836
  * @returns {*} The copy or updated `destination`, if `destination` was specified.
832
837
  *
833
838
  * @example
834
- <example module="copyExample">
835
- <file name="index.html">
836
- <div ng-controller="ExampleController">
837
- <form novalidate class="simple-form">
838
- Name: <input type="text" ng-model="user.name" /><br />
839
- E-mail: <input type="email" ng-model="user.email" /><br />
840
- Gender: <input type="radio" ng-model="user.gender" value="male" />male
841
- <input type="radio" ng-model="user.gender" value="female" />female<br />
842
- <button ng-click="reset()">RESET</button>
843
- <button ng-click="update(user)">SAVE</button>
844
- </form>
845
- <pre>form = {{user | json}}</pre>
846
- <pre>master = {{master | json}}</pre>
847
- </div>
848
-
849
- <script>
850
- angular.module('copyExample', [])
851
- .controller('ExampleController', ['$scope', function($scope) {
852
- $scope.master= {};
853
-
854
- $scope.update = function(user) {
855
- // Example with 1 argument
856
- $scope.master= angular.copy(user);
857
- };
839
+ <example module="copyExample">
840
+ <file name="index.html">
841
+ <div ng-controller="ExampleController">
842
+ <form novalidate class="simple-form">
843
+ <label>Name: <input type="text" ng-model="user.name" /></label><br />
844
+ <label>Age: <input type="number" ng-model="user.age" /></label><br />
845
+ Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label>
846
+ <label><input type="radio" ng-model="user.gender" value="female" />female</label><br />
847
+ <button ng-click="reset()">RESET</button>
848
+ <button ng-click="update(user)">SAVE</button>
849
+ </form>
850
+ <pre>form = {{user | json}}</pre>
851
+ <pre>master = {{master | json}}</pre>
852
+ </div>
853
+ </file>
854
+ <file name="script.js">
855
+ // Module: copyExample
856
+ angular.
857
+ module('copyExample', []).
858
+ controller('ExampleController', ['$scope', function($scope) {
859
+ $scope.master = {};
860
+
861
+ $scope.reset = function() {
862
+ // Example with 1 argument
863
+ $scope.user = angular.copy($scope.master);
864
+ };
858
865
 
859
- $scope.reset = function() {
860
- // Example with 2 arguments
861
- angular.copy($scope.master, $scope.user);
862
- };
866
+ $scope.update = function(user) {
867
+ // Example with 2 arguments
868
+ angular.copy(user, $scope.master);
869
+ };
863
870
 
864
- $scope.reset();
865
- }]);
866
- </script>
867
- </file>
868
- </example>
871
+ $scope.reset();
872
+ }]);
873
+ </file>
874
+ </example>
869
875
  */
870
876
  function copy(source, destination) {
871
877
  var stackSource = [];
@@ -972,7 +978,7 @@ function copy(source, destination) {
972
978
  case '[object Uint8ClampedArray]':
973
979
  case '[object Uint16Array]':
974
980
  case '[object Uint32Array]':
975
- return new source.constructor(copyElement(source.buffer));
981
+ return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length);
976
982
 
977
983
  case '[object ArrayBuffer]':
978
984
  //Support: IE10
@@ -1004,31 +1010,6 @@ function copy(source, destination) {
1004
1010
  }
1005
1011
  }
1006
1012
 
1007
- /**
1008
- * Creates a shallow copy of an object, an array or a primitive.
1009
- *
1010
- * Assumes that there are no proto properties for objects.
1011
- */
1012
- function shallowCopy(src, dst) {
1013
- if (isArray(src)) {
1014
- dst = dst || [];
1015
-
1016
- for (var i = 0, ii = src.length; i < ii; i++) {
1017
- dst[i] = src[i];
1018
- }
1019
- } else if (isObject(src)) {
1020
- dst = dst || {};
1021
-
1022
- for (var key in src) {
1023
- if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
1024
- dst[key] = src[key];
1025
- }
1026
- }
1027
- }
1028
-
1029
- return dst || src;
1030
- }
1031
-
1032
1013
 
1033
1014
  /**
1034
1015
  * @ngdoc function
@@ -2369,7 +2350,34 @@ function setupModuleLoader(window) {
2369
2350
 
2370
2351
  }
2371
2352
 
2372
- /* global: toDebugString: true */
2353
+ /* global shallowCopy: true */
2354
+
2355
+ /**
2356
+ * Creates a shallow copy of an object, an array or a primitive.
2357
+ *
2358
+ * Assumes that there are no proto properties for objects.
2359
+ */
2360
+ function shallowCopy(src, dst) {
2361
+ if (isArray(src)) {
2362
+ dst = dst || [];
2363
+
2364
+ for (var i = 0, ii = src.length; i < ii; i++) {
2365
+ dst[i] = src[i];
2366
+ }
2367
+ } else if (isObject(src)) {
2368
+ dst = dst || {};
2369
+
2370
+ for (var key in src) {
2371
+ if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
2372
+ dst[key] = src[key];
2373
+ }
2374
+ }
2375
+ }
2376
+
2377
+ return dst || src;
2378
+ }
2379
+
2380
+ /* global toDebugString: true */
2373
2381
 
2374
2382
  function serializeObject(obj) {
2375
2383
  var seen = [];
@@ -2473,6 +2481,7 @@ function toDebugString(obj) {
2473
2481
  $HttpParamSerializerJQLikeProvider,
2474
2482
  $HttpBackendProvider,
2475
2483
  $xhrFactoryProvider,
2484
+ $jsonpCallbacksProvider,
2476
2485
  $LocationProvider,
2477
2486
  $LogProvider,
2478
2487
  $ParseProvider,
@@ -2510,11 +2519,11 @@ function toDebugString(obj) {
2510
2519
  * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2511
2520
  */
2512
2521
  var version = {
2513
- full: '1.5.6', // all of these placeholder strings will be replaced by grunt's
2522
+ full: '1.5.8', // all of these placeholder strings will be replaced by grunt's
2514
2523
  major: 1, // package task
2515
2524
  minor: 5,
2516
- dot: 6,
2517
- codeName: 'arrow-stringification'
2525
+ dot: 8,
2526
+ codeName: 'arbitrary-fallbacks'
2518
2527
  };
2519
2528
 
2520
2529
 
@@ -2545,7 +2554,7 @@ function publishExternalAPI(angular) {
2545
2554
  'isDate': isDate,
2546
2555
  'lowercase': lowercase,
2547
2556
  'uppercase': uppercase,
2548
- 'callbacks': {counter: 0},
2557
+ 'callbacks': {$$counter: 0},
2549
2558
  'getTestability': getTestability,
2550
2559
  '$$minErr': minErr,
2551
2560
  '$$csp': csp,
@@ -2634,6 +2643,7 @@ function publishExternalAPI(angular) {
2634
2643
  $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2635
2644
  $httpBackend: $HttpBackendProvider,
2636
2645
  $xhrFactory: $xhrFactoryProvider,
2646
+ $jsonpCallbacks: $jsonpCallbacksProvider,
2637
2647
  $location: $LocationProvider,
2638
2648
  $log: $LogProvider,
2639
2649
  $parse: $ParseProvider,
@@ -2710,7 +2720,7 @@ function publishExternalAPI(angular) {
2710
2720
  * ## Angular's jqLite
2711
2721
  * jqLite provides only the following jQuery methods:
2712
2722
  *
2713
- * - [`addClass()`](http://api.jquery.com/addClass/)
2723
+ * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument
2714
2724
  * - [`after()`](http://api.jquery.com/after/)
2715
2725
  * - [`append()`](http://api.jquery.com/append/)
2716
2726
  * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
@@ -2737,7 +2747,7 @@ function publishExternalAPI(angular) {
2737
2747
  * - [`ready()`](http://api.jquery.com/ready/)
2738
2748
  * - [`remove()`](http://api.jquery.com/remove/)
2739
2749
  * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
2740
- * - [`removeClass()`](http://api.jquery.com/removeClass/)
2750
+ * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument
2741
2751
  * - [`removeData()`](http://api.jquery.com/removeData/)
2742
2752
  * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
2743
2753
  * - [`text()`](http://api.jquery.com/text/)
@@ -2872,7 +2882,7 @@ function jqLiteBuildFragment(html, context) {
2872
2882
  nodes.push(context.createTextNode(html));
2873
2883
  } else {
2874
2884
  // Convert html into DOM nodes
2875
- tmp = tmp || fragment.appendChild(context.createElement("div"));
2885
+ tmp = fragment.appendChild(context.createElement("div"));
2876
2886
  tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
2877
2887
  wrap = wrapMap[tag] || wrapMap._default;
2878
2888
  tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
@@ -4685,10 +4695,10 @@ function createInjector(modulesToLoad, strictDi) {
4685
4695
  if (msie <= 11) {
4686
4696
  return false;
4687
4697
  }
4688
- // Workaround for MS Edge.
4689
- // Check https://connect.microsoft.com/IE/Feedback/Details/2211653
4698
+ // Support: Edge 12-13 only
4699
+ // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6156135/
4690
4700
  return typeof func === 'function'
4691
- && /^(?:class\s|constructor\()/.test(stringifyFn(func));
4701
+ && /^(?:class\b|constructor\()/.test(stringifyFn(func));
4692
4702
  }
4693
4703
 
4694
4704
  function invoke(fn, self, locals, serviceName) {
@@ -5429,7 +5439,13 @@ var $AnimateProvider = ['$provide', function($provide) {
5429
5439
  * @param {DOMElement} parent the parent element which will append the element as
5430
5440
  * a child (so long as the after element is not present)
5431
5441
  * @param {DOMElement=} after the sibling element after which the element will be appended
5432
- * @param {object=} options an optional collection of options/styles that will be applied to the element
5442
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5443
+ * The object can have the following properties:
5444
+ *
5445
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5446
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5447
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5448
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5433
5449
  *
5434
5450
  * @return {Promise} the animation callback promise
5435
5451
  */
@@ -5455,7 +5471,13 @@ var $AnimateProvider = ['$provide', function($provide) {
5455
5471
  * @param {DOMElement} parent the parent element which will append the element as
5456
5472
  * a child (so long as the after element is not present)
5457
5473
  * @param {DOMElement=} after the sibling element after which the element will be appended
5458
- * @param {object=} options an optional collection of options/styles that will be applied to the element
5474
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5475
+ * The object can have the following properties:
5476
+ *
5477
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5478
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5479
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5480
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5459
5481
  *
5460
5482
  * @return {Promise} the animation callback promise
5461
5483
  */
@@ -5476,7 +5498,13 @@ var $AnimateProvider = ['$provide', function($provide) {
5476
5498
  * digest once the animation has completed.
5477
5499
  *
5478
5500
  * @param {DOMElement} element the element which will be removed from the DOM
5479
- * @param {object=} options an optional collection of options/styles that will be applied to the element
5501
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5502
+ * The object can have the following properties:
5503
+ *
5504
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5505
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5506
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5507
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5480
5508
  *
5481
5509
  * @return {Promise} the animation callback promise
5482
5510
  */
@@ -5500,7 +5528,13 @@ var $AnimateProvider = ['$provide', function($provide) {
5500
5528
  *
5501
5529
  * @param {DOMElement} element the element which the CSS classes will be applied to
5502
5530
  * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
5503
- * @param {object=} options an optional collection of options/styles that will be applied to the element
5531
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5532
+ * The object can have the following properties:
5533
+ *
5534
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5535
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5536
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5537
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5504
5538
  *
5505
5539
  * @return {Promise} the animation callback promise
5506
5540
  */
@@ -5524,7 +5558,13 @@ var $AnimateProvider = ['$provide', function($provide) {
5524
5558
  *
5525
5559
  * @param {DOMElement} element the element which the CSS classes will be applied to
5526
5560
  * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5527
- * @param {object=} options an optional collection of options/styles that will be applied to the element
5561
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5562
+ * The object can have the following properties:
5563
+ *
5564
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5565
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5566
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5567
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5528
5568
  *
5529
5569
  * @return {Promise} the animation callback promise
5530
5570
  */
@@ -5549,7 +5589,13 @@ var $AnimateProvider = ['$provide', function($provide) {
5549
5589
  * @param {DOMElement} element the element which the CSS classes will be applied to
5550
5590
  * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5551
5591
  * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5552
- * @param {object=} options an optional collection of options/styles that will be applied to the element
5592
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5593
+ * The object can have the following properties:
5594
+ *
5595
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5596
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5597
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5598
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5553
5599
  *
5554
5600
  * @return {Promise} the animation callback promise
5555
5601
  */
@@ -5590,7 +5636,13 @@ var $AnimateProvider = ['$provide', function($provide) {
5590
5636
  * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
5591
5637
  * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
5592
5638
  * (Note that if no animation is detected then this value will not be applied to the element.)
5593
- * @param {object=} options an optional collection of options/styles that will be applied to the element
5639
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5640
+ * The object can have the following properties:
5641
+ *
5642
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5643
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5644
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5645
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5594
5646
  *
5595
5647
  * @return {Promise} the animation callback promise
5596
5648
  */
@@ -6678,8 +6730,9 @@ function $TemplateCacheProvider() {
6678
6730
  * There are many different options for a directive.
6679
6731
  *
6680
6732
  * The difference resides in the return value of the factory function.
6681
- * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
6682
- * or just the `postLink` function (all other properties will have the default values).
6733
+ * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)}
6734
+ * that defines the directive properties, or just the `postLink` function (all other properties will have
6735
+ * the default values).
6683
6736
  *
6684
6737
  * <div class="alert alert-success">
6685
6738
  * **Best Practice:** It's recommended to use the "directive definition object" form.
@@ -6743,6 +6796,125 @@ function $TemplateCacheProvider() {
6743
6796
  * });
6744
6797
  * ```
6745
6798
  *
6799
+ * ### Life-cycle hooks
6800
+ * Directive controllers can provide the following methods that are called by Angular at points in the life-cycle of the
6801
+ * directive:
6802
+ * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
6803
+ * had their bindings initialized (and before the pre &amp; post linking functions for the directives on
6804
+ * this element). This is a good place to put initialization code for your controller.
6805
+ * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
6806
+ * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
6807
+ * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
6808
+ * component such as cloning the bound value to prevent accidental mutation of the outer value.
6809
+ * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on
6810
+ * changes. Any actions that you wish to take in response to the changes that you detect must be
6811
+ * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook
6812
+ * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not
6813
+ * be detected by Angular's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments;
6814
+ * if detecting changes, you must store the previous value(s) for comparison to the current values.
6815
+ * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
6816
+ * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
6817
+ * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
6818
+ * components will have their `$onDestroy()` hook called before child components.
6819
+ * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
6820
+ * function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
6821
+ * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
6822
+ * they are waiting for their template to load asynchronously and their own compilation and linking has been
6823
+ * suspended until that occurs.
6824
+ *
6825
+ * #### Comparison with Angular 2 life-cycle hooks
6826
+ * Angular 2 also uses life-cycle hooks for its components. While the Angular 1 life-cycle hooks are similar there are
6827
+ * some differences that you should be aware of, especially when it comes to moving your code from Angular 1 to Angular 2:
6828
+ *
6829
+ * * Angular 1 hooks are prefixed with `$`, such as `$onInit`. Angular 2 hooks are prefixed with `ng`, such as `ngOnInit`.
6830
+ * * Angular 1 hooks can be defined on the controller prototype or added to the controller inside its constructor.
6831
+ * In Angular 2 you can only define hooks on the prototype of the Component class.
6832
+ * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in Angular 1 than you would to
6833
+ * `ngDoCheck` in Angular 2
6834
+ * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be
6835
+ * propagated throughout the application.
6836
+ * Angular 2 does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an
6837
+ * error or do nothing depending upon the state of `enableProdMode()`.
6838
+ *
6839
+ * #### Life-cycle hook examples
6840
+ *
6841
+ * This example shows how you can check for mutations to a Date object even though the identity of the object
6842
+ * has not changed.
6843
+ *
6844
+ * <example name="doCheckDateExample" module="do-check-module">
6845
+ * <file name="app.js">
6846
+ * angular.module('do-check-module', [])
6847
+ * .component('app', {
6848
+ * template:
6849
+ * 'Month: <input ng-model="$ctrl.month" ng-change="$ctrl.updateDate()">' +
6850
+ * 'Date: {{ $ctrl.date }}' +
6851
+ * '<test date="$ctrl.date"></test>',
6852
+ * controller: function() {
6853
+ * this.date = new Date();
6854
+ * this.month = this.date.getMonth();
6855
+ * this.updateDate = function() {
6856
+ * this.date.setMonth(this.month);
6857
+ * };
6858
+ * }
6859
+ * })
6860
+ * .component('test', {
6861
+ * bindings: { date: '<' },
6862
+ * template:
6863
+ * '<pre>{{ $ctrl.log | json }}</pre>',
6864
+ * controller: function() {
6865
+ * var previousValue;
6866
+ * this.log = [];
6867
+ * this.$doCheck = function() {
6868
+ * var currentValue = this.date && this.date.valueOf();
6869
+ * if (previousValue !== currentValue) {
6870
+ * this.log.push('doCheck: date mutated: ' + this.date);
6871
+ * previousValue = currentValue;
6872
+ * }
6873
+ * };
6874
+ * }
6875
+ * });
6876
+ * </file>
6877
+ * <file name="index.html">
6878
+ * <app></app>
6879
+ * </file>
6880
+ * </example>
6881
+ *
6882
+ * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the
6883
+ * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large
6884
+ * arrays or objects can have a negative impact on your application performance)
6885
+ *
6886
+ * <example name="doCheckArrayExample" module="do-check-module">
6887
+ * <file name="index.html">
6888
+ * <div ng-init="items = []">
6889
+ * <button ng-click="items.push(items.length)">Add Item</button>
6890
+ * <button ng-click="items = []">Reset Items</button>
6891
+ * <pre>{{ items }}</pre>
6892
+ * <test items="items"></test>
6893
+ * </div>
6894
+ * </file>
6895
+ * <file name="app.js">
6896
+ * angular.module('do-check-module', [])
6897
+ * .component('test', {
6898
+ * bindings: { items: '<' },
6899
+ * template:
6900
+ * '<pre>{{ $ctrl.log | json }}</pre>',
6901
+ * controller: function() {
6902
+ * this.log = [];
6903
+ *
6904
+ * this.$doCheck = function() {
6905
+ * if (this.items_ref !== this.items) {
6906
+ * this.log.push('doCheck: items changed');
6907
+ * this.items_ref = this.items;
6908
+ * }
6909
+ * if (!angular.equals(this.items_clone, this.items)) {
6910
+ * this.log.push('doCheck: items mutated');
6911
+ * this.items_clone = angular.copy(this.items);
6912
+ * }
6913
+ * };
6914
+ * }
6915
+ * });
6916
+ * </file>
6917
+ * </example>
6746
6918
  *
6747
6919
  *
6748
6920
  * ### Directive Definition Object
@@ -6918,25 +7090,6 @@ function $TemplateCacheProvider() {
6918
7090
  * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns
6919
7091
  * `true` if the specified slot contains content (i.e. one or more DOM nodes).
6920
7092
  *
6921
- * The controller can provide the following methods that act as life-cycle hooks:
6922
- * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
6923
- * had their bindings initialized (and before the pre &amp; post linking functions for the directives on
6924
- * this element). This is a good place to put initialization code for your controller.
6925
- * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
6926
- * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
6927
- * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
6928
- * component such as cloning the bound value to prevent accidental mutation of the outer value.
6929
- * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
6930
- * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
6931
- * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
6932
- * components will have their `$onDestroy()` hook called before child components.
6933
- * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
6934
- * function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
6935
- * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
6936
- * they are waiting for their template to load asynchronously and their own compilation and linking has been
6937
- * suspended until that occurs.
6938
- *
6939
- *
6940
7093
  * #### `require`
6941
7094
  * Require another directive and inject its controller as the fourth argument to the linking function. The
6942
7095
  * `require` property can be a string, an array or an object:
@@ -7134,8 +7287,8 @@ function $TemplateCacheProvider() {
7134
7287
  * any other controller.
7135
7288
  *
7136
7289
  * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
7137
- * This is the same as the `$transclude`
7138
- * parameter of directive controllers, see there for details.
7290
+ * This is the same as the `$transclude` parameter of directive controllers,
7291
+ * see {@link ng.$compile#-controller- the controller section for details}.
7139
7292
  * `function([scope], cloneLinkingFn, futureParentElement)`.
7140
7293
  *
7141
7294
  * #### Pre-linking function
@@ -7937,11 +8090,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
7937
8090
  }
7938
8091
  // We must run this hook in an apply since the $$postDigest runs outside apply
7939
8092
  $rootScope.$apply(function() {
8093
+ var errors = [];
7940
8094
  for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) {
7941
- onChangesQueue[i]();
8095
+ try {
8096
+ onChangesQueue[i]();
8097
+ } catch (e) {
8098
+ errors.push(e);
8099
+ }
7942
8100
  }
7943
8101
  // Reset the queue to trigger a new schedule next time there is a change
7944
8102
  onChangesQueue = undefined;
8103
+ if (errors.length) {
8104
+ throw errors;
8105
+ }
7945
8106
  });
7946
8107
  } finally {
7947
8108
  onChangesTtl++;
@@ -8582,19 +8743,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
8582
8743
  addTextInterpolateDirective(directives, node.nodeValue);
8583
8744
  break;
8584
8745
  case NODE_TYPE_COMMENT: /* Comment */
8585
- try {
8586
- match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
8587
- if (match) {
8588
- nName = directiveNormalize(match[1]);
8589
- if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
8590
- attrs[nName] = trim(match[2]);
8591
- }
8592
- }
8593
- } catch (e) {
8594
- // turns out that under some circumstances IE9 throws errors when one attempts to read
8595
- // comment's node value.
8596
- // Just ignore it and continue. (Can't seem to reproduce in test case.)
8597
- }
8746
+ collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective);
8598
8747
  break;
8599
8748
  }
8600
8749
 
@@ -8602,6 +8751,24 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
8602
8751
  return directives;
8603
8752
  }
8604
8753
 
8754
+ function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
8755
+ // function created because of performance, try/catch disables
8756
+ // the optimization of the whole function #14848
8757
+ try {
8758
+ var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
8759
+ if (match) {
8760
+ var nName = directiveNormalize(match[1]);
8761
+ if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
8762
+ attrs[nName] = trim(match[2]);
8763
+ }
8764
+ }
8765
+ } catch (e) {
8766
+ // turns out that under some circumstances IE9 throws errors when one attempts to read
8767
+ // comment's node value.
8768
+ // Just ignore it and continue. (Can't seem to reproduce in test case.)
8769
+ }
8770
+ }
8771
+
8605
8772
  /**
8606
8773
  * Given a node with an directive-start it collects all of the siblings until it finds
8607
8774
  * directive-end.
@@ -9117,10 +9284,22 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
9117
9284
  forEach(elementControllers, function(controller) {
9118
9285
  var controllerInstance = controller.instance;
9119
9286
  if (isFunction(controllerInstance.$onChanges)) {
9120
- controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
9287
+ try {
9288
+ controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
9289
+ } catch (e) {
9290
+ $exceptionHandler(e);
9291
+ }
9121
9292
  }
9122
9293
  if (isFunction(controllerInstance.$onInit)) {
9123
- controllerInstance.$onInit();
9294
+ try {
9295
+ controllerInstance.$onInit();
9296
+ } catch (e) {
9297
+ $exceptionHandler(e);
9298
+ }
9299
+ }
9300
+ if (isFunction(controllerInstance.$doCheck)) {
9301
+ controllerScope.$watch(function() { controllerInstance.$doCheck(); });
9302
+ controllerInstance.$doCheck();
9124
9303
  }
9125
9304
  if (isFunction(controllerInstance.$onDestroy)) {
9126
9305
  controllerScope.$on('$destroy', function callOnDestroyHook() {
@@ -9384,18 +9563,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
9384
9563
 
9385
9564
  // copy the new attributes on the old attrs object
9386
9565
  forEach(src, function(value, key) {
9387
- if (key == 'class') {
9388
- safeAddClass($element, value);
9389
- dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
9390
- } else if (key == 'style') {
9391
- $element.attr('style', $element.attr('style') + ';' + value);
9392
- dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
9393
- // `dst` will never contain hasOwnProperty as DOM parser won't let it.
9394
- // You will get an "InvalidCharacterError: DOM Exception 5" error if you
9395
- // have an attribute like "has-own-property" or "data-has-own-property", etc.
9396
- } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
9566
+ // Check if we already set this attribute in the loop above.
9567
+ // `dst` will never contain hasOwnProperty as DOM parser won't let it.
9568
+ // You will get an "InvalidCharacterError: DOM Exception 5" error if you
9569
+ // have an attribute like "has-own-property" or "data-has-own-property", etc.
9570
+ if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') {
9397
9571
  dst[key] = value;
9398
- dstAttr[key] = srcAttr[key];
9572
+
9573
+ if (key !== 'class' && key !== 'style') {
9574
+ dstAttr[key] = srcAttr[key];
9575
+ }
9399
9576
  }
9400
9577
  });
9401
9578
  }
@@ -9770,7 +9947,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
9770
9947
  forEach(bindings, function initializeBinding(definition, scopeName) {
9771
9948
  var attrName = definition.attrName,
9772
9949
  optional = definition.optional,
9773
- mode = definition.mode, // @, =, or &
9950
+ mode = definition.mode, // @, =, <, or &
9774
9951
  lastValue,
9775
9952
  parentGet, parentSet, compare, removeWatch;
9776
9953
 
@@ -10256,18 +10433,21 @@ function $DocumentProvider() {
10256
10433
  *
10257
10434
  * ## Example:
10258
10435
  *
10436
+ * The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught
10437
+ * errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead
10438
+ * of `$log.error()`.
10439
+ *
10259
10440
  * ```js
10260
- * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
10261
- * return function(exception, cause) {
10262
- * exception.message += ' (caused by "' + cause + '")';
10263
- * throw exception;
10264
- * };
10265
- * });
10441
+ * angular.
10442
+ * module('exceptionOverwrite', []).
10443
+ * factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) {
10444
+ * return function myExceptionHandler(exception, cause) {
10445
+ * logErrorsToBackend(exception, cause);
10446
+ * $log.warn(exception, cause);
10447
+ * };
10448
+ * }]);
10266
10449
  * ```
10267
10450
  *
10268
- * This example will override the normal action of `$exceptionHandler`, to make angular
10269
- * exceptions fail hard when they happen, instead of just logging to the console.
10270
- *
10271
10451
  * <hr />
10272
10452
  * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
10273
10453
  * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
@@ -10277,7 +10457,7 @@ function $DocumentProvider() {
10277
10457
  * `try { ... } catch(e) { $exceptionHandler(e); }`
10278
10458
  *
10279
10459
  * @param {Error} exception Exception associated with the error.
10280
- * @param {string=} cause optional information about the context in which
10460
+ * @param {string=} cause Optional information about the context in which
10281
10461
  * the error was thrown.
10282
10462
  *
10283
10463
  */
@@ -10347,7 +10527,7 @@ function $HttpParamSerializerProvider() {
10347
10527
  * * `{'foo': 'bar'}` results in `foo=bar`
10348
10528
  * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
10349
10529
  * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
10350
- * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
10530
+ * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object)
10351
10531
  *
10352
10532
  * Note that serializer will sort the request parameters alphabetically.
10353
10533
  * */
@@ -10898,7 +11078,7 @@ function $HttpProvider() {
10898
11078
  *
10899
11079
  * ### Overriding the Default Transformations Per Request
10900
11080
  *
10901
- * If you wish override the request/response transformations only for a single request then provide
11081
+ * If you wish to override the request/response transformations only for a single request then provide
10902
11082
  * `transformRequest` and/or `transformResponse` properties on the configuration object passed
10903
11083
  * into `$http`.
10904
11084
  *
@@ -10941,7 +11121,7 @@ function $HttpProvider() {
10941
11121
  * * cache a specific response - set config.cache value to TRUE or to a cache object
10942
11122
  *
10943
11123
  * If caching is enabled, but neither the default cache nor config.cache are set to a cache object,
10944
- * then the default `$cacheFactory($http)` object is used.
11124
+ * then the default `$cacheFactory("$http")` object is used.
10945
11125
  *
10946
11126
  * The default cache value can be set by updating the
10947
11127
  * {@link ng.$http#defaults `$http.defaults.cache`} property or the
@@ -11269,48 +11449,25 @@ function $HttpProvider() {
11269
11449
  config.headers = mergeHeaders(requestConfig);
11270
11450
  config.method = uppercase(config.method);
11271
11451
  config.paramSerializer = isString(config.paramSerializer) ?
11272
- $injector.get(config.paramSerializer) : config.paramSerializer;
11273
-
11274
- var serverRequest = function(config) {
11275
- var headers = config.headers;
11276
- var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
11452
+ $injector.get(config.paramSerializer) : config.paramSerializer;
11277
11453
 
11278
- // strip content-type if data is undefined
11279
- if (isUndefined(reqData)) {
11280
- forEach(headers, function(value, header) {
11281
- if (lowercase(header) === 'content-type') {
11282
- delete headers[header];
11283
- }
11284
- });
11285
- }
11286
-
11287
- if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
11288
- config.withCredentials = defaults.withCredentials;
11289
- }
11290
-
11291
- // send request
11292
- return sendReq(config, reqData).then(transformResponse, transformResponse);
11293
- };
11294
-
11295
- var chain = [serverRequest, undefined];
11454
+ var requestInterceptors = [];
11455
+ var responseInterceptors = [];
11296
11456
  var promise = $q.when(config);
11297
11457
 
11298
11458
  // apply interceptors
11299
11459
  forEach(reversedInterceptors, function(interceptor) {
11300
11460
  if (interceptor.request || interceptor.requestError) {
11301
- chain.unshift(interceptor.request, interceptor.requestError);
11461
+ requestInterceptors.unshift(interceptor.request, interceptor.requestError);
11302
11462
  }
11303
11463
  if (interceptor.response || interceptor.responseError) {
11304
- chain.push(interceptor.response, interceptor.responseError);
11464
+ responseInterceptors.push(interceptor.response, interceptor.responseError);
11305
11465
  }
11306
11466
  });
11307
11467
 
11308
- while (chain.length) {
11309
- var thenFn = chain.shift();
11310
- var rejectFn = chain.shift();
11311
-
11312
- promise = promise.then(thenFn, rejectFn);
11313
- }
11468
+ promise = chainInterceptors(promise, requestInterceptors);
11469
+ promise = promise.then(serverRequest);
11470
+ promise = chainInterceptors(promise, responseInterceptors);
11314
11471
 
11315
11472
  if (useLegacyPromise) {
11316
11473
  promise.success = function(fn) {
@@ -11337,14 +11494,18 @@ function $HttpProvider() {
11337
11494
 
11338
11495
  return promise;
11339
11496
 
11340
- function transformResponse(response) {
11341
- // make a copy since the response must be cacheable
11342
- var resp = extend({}, response);
11343
- resp.data = transformData(response.data, response.headers, response.status,
11344
- config.transformResponse);
11345
- return (isSuccess(response.status))
11346
- ? resp
11347
- : $q.reject(resp);
11497
+
11498
+ function chainInterceptors(promise, interceptors) {
11499
+ for (var i = 0, ii = interceptors.length; i < ii;) {
11500
+ var thenFn = interceptors[i++];
11501
+ var rejectFn = interceptors[i++];
11502
+
11503
+ promise = promise.then(thenFn, rejectFn);
11504
+ }
11505
+
11506
+ interceptors.length = 0;
11507
+
11508
+ return promise;
11348
11509
  }
11349
11510
 
11350
11511
  function executeHeaderFns(headers, config) {
@@ -11388,6 +11549,37 @@ function $HttpProvider() {
11388
11549
  // execute if header value is a function for merged headers
11389
11550
  return executeHeaderFns(reqHeaders, shallowCopy(config));
11390
11551
  }
11552
+
11553
+ function serverRequest(config) {
11554
+ var headers = config.headers;
11555
+ var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
11556
+
11557
+ // strip content-type if data is undefined
11558
+ if (isUndefined(reqData)) {
11559
+ forEach(headers, function(value, header) {
11560
+ if (lowercase(header) === 'content-type') {
11561
+ delete headers[header];
11562
+ }
11563
+ });
11564
+ }
11565
+
11566
+ if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
11567
+ config.withCredentials = defaults.withCredentials;
11568
+ }
11569
+
11570
+ // send request
11571
+ return sendReq(config, reqData).then(transformResponse, transformResponse);
11572
+ }
11573
+
11574
+ function transformResponse(response) {
11575
+ // make a copy since the response must be cacheable
11576
+ var resp = extend({}, response);
11577
+ resp.data = transformData(response.data, response.headers, response.status,
11578
+ config.transformResponse);
11579
+ return (isSuccess(response.status))
11580
+ ? resp
11581
+ : $q.reject(resp);
11582
+ }
11391
11583
  }
11392
11584
 
11393
11585
  $http.pendingRequests = [];
@@ -11434,6 +11626,8 @@ function $HttpProvider() {
11434
11626
  *
11435
11627
  * @description
11436
11628
  * Shortcut method to perform `JSONP` request.
11629
+ * If you would like to customise where and how the callbacks are stored then try overriding
11630
+ * or decorating the {@link $jsonpCallbacks} service.
11437
11631
  *
11438
11632
  * @param {string} url Relative or absolute URL specifying the destination of the request.
11439
11633
  * The name of the callback should be the string `JSON_CALLBACK`.
@@ -11707,7 +11901,7 @@ function $xhrFactoryProvider() {
11707
11901
  /**
11708
11902
  * @ngdoc service
11709
11903
  * @name $httpBackend
11710
- * @requires $window
11904
+ * @requires $jsonpCallbacks
11711
11905
  * @requires $document
11712
11906
  * @requires $xhrFactory
11713
11907
  *
@@ -11722,8 +11916,8 @@ function $xhrFactoryProvider() {
11722
11916
  * $httpBackend} which can be trained with responses.
11723
11917
  */
11724
11918
  function $HttpBackendProvider() {
11725
- this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
11726
- return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
11919
+ this.$get = ['$browser', '$jsonpCallbacks', '$document', '$xhrFactory', function($browser, $jsonpCallbacks, $document, $xhrFactory) {
11920
+ return createHttpBackend($browser, $xhrFactory, $browser.defer, $jsonpCallbacks, $document[0]);
11727
11921
  }];
11728
11922
  }
11729
11923
 
@@ -11733,17 +11927,13 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
11733
11927
  $browser.$$incOutstandingRequestCount();
11734
11928
  url = url || $browser.url();
11735
11929
 
11736
- if (lowercase(method) == 'jsonp') {
11737
- var callbackId = '_' + (callbacks.counter++).toString(36);
11738
- callbacks[callbackId] = function(data) {
11739
- callbacks[callbackId].data = data;
11740
- callbacks[callbackId].called = true;
11741
- };
11742
-
11743
- var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
11744
- callbackId, function(status, text) {
11745
- completeRequest(callback, status, callbacks[callbackId].data, "", text);
11746
- callbacks[callbackId] = noop;
11930
+ if (lowercase(method) === 'jsonp') {
11931
+ var callbackPath = callbacks.createCallback(url);
11932
+ var jsonpDone = jsonpReq(url, callbackPath, function(status, text) {
11933
+ // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING)
11934
+ var response = (status === 200) && callbacks.getResponse(callbackPath);
11935
+ completeRequest(callback, status, response, "", text);
11936
+ callbacks.removeCallback(callbackPath);
11747
11937
  });
11748
11938
  } else {
11749
11939
 
@@ -11845,7 +12035,8 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
11845
12035
  }
11846
12036
  };
11847
12037
 
11848
- function jsonpReq(url, callbackId, done) {
12038
+ function jsonpReq(url, callbackPath, done) {
12039
+ url = url.replace('JSON_CALLBACK', callbackPath);
11849
12040
  // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
11850
12041
  // - fetches local scripts via XHR and evals them
11851
12042
  // - adds and immediately removes script elements from the document
@@ -11863,7 +12054,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
11863
12054
  var text = "unknown";
11864
12055
 
11865
12056
  if (event) {
11866
- if (event.type === "load" && !callbacks[callbackId].called) {
12057
+ if (event.type === "load" && !callbacks.wasCalled(callbackPath)) {
11867
12058
  event = { type: "error" };
11868
12059
  }
11869
12060
  text = event.type;
@@ -12062,7 +12253,7 @@ function $InterpolateProvider() {
12062
12253
  *
12063
12254
  * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
12064
12255
  *
12065
- * ####Escaped Interpolation
12256
+ * #### Escaped Interpolation
12066
12257
  * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
12067
12258
  * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
12068
12259
  * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
@@ -12485,6 +12676,87 @@ function $IntervalProvider() {
12485
12676
  }];
12486
12677
  }
12487
12678
 
12679
+ /**
12680
+ * @ngdoc service
12681
+ * @name $jsonpCallbacks
12682
+ * @requires $window
12683
+ * @description
12684
+ * This service handles the lifecycle of callbacks to handle JSONP requests.
12685
+ * Override this service if you wish to customise where the callbacks are stored and
12686
+ * how they vary compared to the requested url.
12687
+ */
12688
+ var $jsonpCallbacksProvider = function() {
12689
+ this.$get = ['$window', function($window) {
12690
+ var callbacks = $window.angular.callbacks;
12691
+ var callbackMap = {};
12692
+
12693
+ function createCallback(callbackId) {
12694
+ var callback = function(data) {
12695
+ callback.data = data;
12696
+ callback.called = true;
12697
+ };
12698
+ callback.id = callbackId;
12699
+ return callback;
12700
+ }
12701
+
12702
+ return {
12703
+ /**
12704
+ * @ngdoc method
12705
+ * @name $jsonpCallbacks#createCallback
12706
+ * @param {string} url the url of the JSONP request
12707
+ * @returns {string} the callback path to send to the server as part of the JSONP request
12708
+ * @description
12709
+ * {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback
12710
+ * to pass to the server, which will be used to call the callback with its payload in the JSONP response.
12711
+ */
12712
+ createCallback: function(url) {
12713
+ var callbackId = '_' + (callbacks.$$counter++).toString(36);
12714
+ var callbackPath = 'angular.callbacks.' + callbackId;
12715
+ var callback = createCallback(callbackId);
12716
+ callbackMap[callbackPath] = callbacks[callbackId] = callback;
12717
+ return callbackPath;
12718
+ },
12719
+ /**
12720
+ * @ngdoc method
12721
+ * @name $jsonpCallbacks#wasCalled
12722
+ * @param {string} callbackPath the path to the callback that was sent in the JSONP request
12723
+ * @returns {boolean} whether the callback has been called, as a result of the JSONP response
12724
+ * @description
12725
+ * {@link $httpBackend} calls this method to find out whether the JSONP response actually called the
12726
+ * callback that was passed in the request.
12727
+ */
12728
+ wasCalled: function(callbackPath) {
12729
+ return callbackMap[callbackPath].called;
12730
+ },
12731
+ /**
12732
+ * @ngdoc method
12733
+ * @name $jsonpCallbacks#getResponse
12734
+ * @param {string} callbackPath the path to the callback that was sent in the JSONP request
12735
+ * @returns {*} the data received from the response via the registered callback
12736
+ * @description
12737
+ * {@link $httpBackend} calls this method to get hold of the data that was provided to the callback
12738
+ * in the JSONP response.
12739
+ */
12740
+ getResponse: function(callbackPath) {
12741
+ return callbackMap[callbackPath].data;
12742
+ },
12743
+ /**
12744
+ * @ngdoc method
12745
+ * @name $jsonpCallbacks#removeCallback
12746
+ * @param {string} callbackPath the path to the callback that was sent in the JSONP request
12747
+ * @description
12748
+ * {@link $httpBackend} calls this method to remove the callback after the JSONP request has
12749
+ * completed or timed-out.
12750
+ */
12751
+ removeCallback: function(callbackPath) {
12752
+ var callback = callbackMap[callbackPath];
12753
+ delete callbacks[callback.id];
12754
+ delete callbackMap[callbackPath];
12755
+ }
12756
+ };
12757
+ }];
12758
+ };
12759
+
12488
12760
  /**
12489
12761
  * @ngdoc service
12490
12762
  * @name $locale
@@ -12824,6 +13096,12 @@ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
12824
13096
 
12825
13097
  var locationPrototype = {
12826
13098
 
13099
+ /**
13100
+ * Ensure absolute url is initialized.
13101
+ * @private
13102
+ */
13103
+ $$absUrl:'',
13104
+
12827
13105
  /**
12828
13106
  * Are we in html5 mode?
12829
13107
  * @private
@@ -14197,7 +14475,7 @@ AST.prototype = {
14197
14475
  var args = [];
14198
14476
  if (this.peekToken().text !== ')') {
14199
14477
  do {
14200
- args.push(this.expression());
14478
+ args.push(this.filterChain());
14201
14479
  } while (this.expect(','));
14202
14480
  }
14203
14481
  return args;
@@ -15924,7 +16202,7 @@ function $ParseProvider() {
15924
16202
  *
15925
16203
  * **Methods**
15926
16204
  *
15927
- * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or
16205
+ * - `then(successCallback, [errorCallback], [notifyCallback])` – regardless of when the promise was or
15928
16206
  * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
15929
16207
  * as soon as the result is available. The callbacks are called with a single argument: the result
15930
16208
  * or rejection reason. Additionally, the notify callback may be called zero or more times to
@@ -15935,7 +16213,8 @@ function $ParseProvider() {
15935
16213
  * with the value which is resolved in that promise using
15936
16214
  * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
15937
16215
  * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
15938
- * resolved or rejected from the notifyCallback method.
16216
+ * resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback
16217
+ * arguments are optional.
15939
16218
  *
15940
16219
  * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
15941
16220
  *
@@ -16350,6 +16629,30 @@ function qFactory(nextTick, exceptionHandler) {
16350
16629
  return deferred.promise;
16351
16630
  }
16352
16631
 
16632
+ /**
16633
+ * @ngdoc method
16634
+ * @name $q#race
16635
+ * @kind function
16636
+ *
16637
+ * @description
16638
+ * Returns a promise that resolves or rejects as soon as one of those promises
16639
+ * resolves or rejects, with the value or reason from that promise.
16640
+ *
16641
+ * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
16642
+ * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises`
16643
+ * resolves or rejects, with the value or reason from that promise.
16644
+ */
16645
+
16646
+ function race(promises) {
16647
+ var deferred = defer();
16648
+
16649
+ forEach(promises, function(promise) {
16650
+ when(promise).then(deferred.resolve, deferred.reject);
16651
+ });
16652
+
16653
+ return deferred.promise;
16654
+ }
16655
+
16353
16656
  var $Q = function Q(resolver) {
16354
16657
  if (!isFunction(resolver)) {
16355
16658
  throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
@@ -16379,6 +16682,7 @@ function qFactory(nextTick, exceptionHandler) {
16379
16682
  $Q.when = when;
16380
16683
  $Q.resolve = resolve;
16381
16684
  $Q.all = all;
16685
+ $Q.race = race;
16382
16686
 
16383
16687
  return $Q;
16384
16688
  }
@@ -19730,10 +20034,11 @@ function $FilterProvider($provide) {
19730
20034
  * - `Object`: A pattern object can be used to filter specific properties on objects contained
19731
20035
  * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
19732
20036
  * which have property `name` containing "M" and property `phone` containing "1". A special
19733
- * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
19734
- * property of the object or its nested object properties. That's equivalent to the simple
19735
- * substring match with a `string` as described above. The predicate can be negated by prefixing
19736
- * the string with `!`.
20037
+ * property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match
20038
+ * against any property of the object or its nested object properties. That's equivalent to the
20039
+ * simple substring match with a `string` as described above. The special property name can be
20040
+ * overwritten, using the `anyPropertyKey` parameter.
20041
+ * The predicate can be negated by prefixing the string with `!`.
19737
20042
  * For example `{name: "!M"}` predicate will return an array of items which have property `name`
19738
20043
  * not containing "M".
19739
20044
  *
@@ -19767,6 +20072,9 @@ function $FilterProvider($provide) {
19767
20072
  * Primitive values are converted to strings. Objects are not compared against primitives,
19768
20073
  * unless they have a custom `toString` method (e.g. `Date` objects).
19769
20074
  *
20075
+ * @param {string=} anyPropertyKey The special property name that matches against any property.
20076
+ * By default `$`.
20077
+ *
19770
20078
  * @example
19771
20079
  <example>
19772
20080
  <file name="index.html">
@@ -19835,8 +20143,9 @@ function $FilterProvider($provide) {
19835
20143
  </file>
19836
20144
  </example>
19837
20145
  */
20146
+
19838
20147
  function filterFilter() {
19839
- return function(array, expression, comparator) {
20148
+ return function(array, expression, comparator, anyPropertyKey) {
19840
20149
  if (!isArrayLike(array)) {
19841
20150
  if (array == null) {
19842
20151
  return array;
@@ -19845,6 +20154,7 @@ function filterFilter() {
19845
20154
  }
19846
20155
  }
19847
20156
 
20157
+ anyPropertyKey = anyPropertyKey || '$';
19848
20158
  var expressionType = getTypeForFilter(expression);
19849
20159
  var predicateFn;
19850
20160
  var matchAgainstAnyProp;
@@ -19861,7 +20171,7 @@ function filterFilter() {
19861
20171
  //jshint -W086
19862
20172
  case 'object':
19863
20173
  //jshint +W086
19864
- predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
20174
+ predicateFn = createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp);
19865
20175
  break;
19866
20176
  default:
19867
20177
  return array;
@@ -19872,8 +20182,8 @@ function filterFilter() {
19872
20182
  }
19873
20183
 
19874
20184
  // Helper functions for `filterFilter`
19875
- function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
19876
- var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
20185
+ function createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp) {
20186
+ var shouldMatchPrimitives = isObject(expression) && (anyPropertyKey in expression);
19877
20187
  var predicateFn;
19878
20188
 
19879
20189
  if (comparator === true) {
@@ -19901,25 +20211,25 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
19901
20211
 
19902
20212
  predicateFn = function(item) {
19903
20213
  if (shouldMatchPrimitives && !isObject(item)) {
19904
- return deepCompare(item, expression.$, comparator, false);
20214
+ return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false);
19905
20215
  }
19906
- return deepCompare(item, expression, comparator, matchAgainstAnyProp);
20216
+ return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp);
19907
20217
  };
19908
20218
 
19909
20219
  return predicateFn;
19910
20220
  }
19911
20221
 
19912
- function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
20222
+ function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) {
19913
20223
  var actualType = getTypeForFilter(actual);
19914
20224
  var expectedType = getTypeForFilter(expected);
19915
20225
 
19916
20226
  if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
19917
- return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
20227
+ return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp);
19918
20228
  } else if (isArray(actual)) {
19919
20229
  // In case `actual` is an array, consider it a match
19920
20230
  // if ANY of it's items matches `expected`
19921
20231
  return actual.some(function(item) {
19922
- return deepCompare(item, expected, comparator, matchAgainstAnyProp);
20232
+ return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp);
19923
20233
  });
19924
20234
  }
19925
20235
 
@@ -19928,11 +20238,11 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
19928
20238
  var key;
19929
20239
  if (matchAgainstAnyProp) {
19930
20240
  for (key in actual) {
19931
- if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
20241
+ if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) {
19932
20242
  return true;
19933
20243
  }
19934
20244
  }
19935
- return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
20245
+ return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, anyPropertyKey, false);
19936
20246
  } else if (expectedType === 'object') {
19937
20247
  for (key in expected) {
19938
20248
  var expectedVal = expected[key];
@@ -19940,9 +20250,9 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
19940
20250
  continue;
19941
20251
  }
19942
20252
 
19943
- var matchAnyProperty = key === '$';
20253
+ var matchAnyProperty = key === anyPropertyKey;
19944
20254
  var actualVal = matchAnyProperty ? actual : actual[key];
19945
- if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
20255
+ if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) {
19946
20256
  return false;
19947
20257
  }
19948
20258
  }
@@ -20680,21 +20990,22 @@ var uppercaseFilter = valueFn(uppercase);
20680
20990
  * @kind function
20681
20991
  *
20682
20992
  * @description
20683
- * Creates a new array or string containing only a specified number of elements. The elements
20684
- * are taken from either the beginning or the end of the source array, string or number, as specified by
20685
- * the value and sign (positive or negative) of `limit`. If a number is used as input, it is
20686
- * converted to a string.
20687
- *
20688
- * @param {Array|string|number} input Source array, string or number to be limited.
20689
- * @param {string|number} limit The length of the returned array or string. If the `limit` number
20993
+ * Creates a new array or string containing only a specified number of elements. The elements are
20994
+ * taken from either the beginning or the end of the source array, string or number, as specified by
20995
+ * the value and sign (positive or negative) of `limit`. Other array-like objects are also supported
20996
+ * (e.g. array subclasses, NodeLists, jqLite/jQuery collections etc). If a number is used as input,
20997
+ * it is converted to a string.
20998
+ *
20999
+ * @param {Array|ArrayLike|string|number} input - Array/array-like, string or number to be limited.
21000
+ * @param {string|number} limit - The length of the returned array or string. If the `limit` number
20690
21001
  * is positive, `limit` number of items from the beginning of the source array/string are copied.
20691
21002
  * If the number is negative, `limit` number of items from the end of the source array/string
20692
21003
  * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
20693
21004
  * the input will be returned unchanged.
20694
- * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
20695
- * indicates an offset from the end of `input`. Defaults to `0`.
20696
- * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
20697
- * had less than `limit` elements.
21005
+ * @param {(string|number)=} begin - Index at which to begin limitation. As a negative index,
21006
+ * `begin` indicates an offset from the end of `input`. Defaults to `0`.
21007
+ * @returns {Array|string} A new sub-array or substring of length `limit` or less if the input had
21008
+ * less than `limit` elements.
20698
21009
  *
20699
21010
  * @example
20700
21011
  <example module="limitToExample">
@@ -20782,67 +21093,157 @@ function limitToFilter() {
20782
21093
  if (isNaN(limit)) return input;
20783
21094
 
20784
21095
  if (isNumber(input)) input = input.toString();
20785
- if (!isArray(input) && !isString(input)) return input;
21096
+ if (!isArrayLike(input)) return input;
20786
21097
 
20787
21098
  begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
20788
21099
  begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
20789
21100
 
20790
21101
  if (limit >= 0) {
20791
- return input.slice(begin, begin + limit);
21102
+ return sliceFn(input, begin, begin + limit);
20792
21103
  } else {
20793
21104
  if (begin === 0) {
20794
- return input.slice(limit, input.length);
21105
+ return sliceFn(input, limit, input.length);
20795
21106
  } else {
20796
- return input.slice(Math.max(0, begin + limit), begin);
21107
+ return sliceFn(input, Math.max(0, begin + limit), begin);
20797
21108
  }
20798
21109
  }
20799
21110
  };
20800
21111
  }
20801
21112
 
21113
+ function sliceFn(input, begin, end) {
21114
+ if (isString(input)) return input.slice(begin, end);
21115
+
21116
+ return slice.call(input, begin, end);
21117
+ }
21118
+
20802
21119
  /**
20803
21120
  * @ngdoc filter
20804
21121
  * @name orderBy
20805
21122
  * @kind function
20806
21123
  *
20807
21124
  * @description
20808
- * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
20809
- * for strings and numerically for numbers. Note: if you notice numbers are not being sorted
20810
- * as expected, make sure they are actually being saved as numbers and not strings.
20811
- * Array-like values (e.g. NodeLists, jQuery objects, TypedArrays, Strings, etc) are also supported.
21125
+ * Returns an array containing the items from the specified `collection`, ordered by a `comparator`
21126
+ * function based on the values computed using the `expression` predicate.
21127
+ *
21128
+ * For example, `[{id: 'foo'}, {id: 'bar'}] | orderBy:'id'` would result in
21129
+ * `[{id: 'bar'}, {id: 'foo'}]`.
21130
+ *
21131
+ * The `collection` can be an Array or array-like object (e.g. NodeList, jQuery object, TypedArray,
21132
+ * String, etc).
21133
+ *
21134
+ * The `expression` can be a single predicate, or a list of predicates each serving as a tie-breaker
21135
+ * for the preceeding one. The `expression` is evaluated against each item and the output is used
21136
+ * for comparing with other items.
21137
+ *
21138
+ * You can change the sorting order by setting `reverse` to `true`. By default, items are sorted in
21139
+ * ascending order.
21140
+ *
21141
+ * The comparison is done using the `comparator` function. If none is specified, a default, built-in
21142
+ * comparator is used (see below for details - in a nutshell, it compares numbers numerically and
21143
+ * strings alphabetically).
21144
+ *
21145
+ * ### Under the hood
20812
21146
  *
20813
- * @param {Array} array The array (or array-like object) to sort.
20814
- * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
20815
- * used by the comparator to determine the order of elements.
21147
+ * Ordering the specified `collection` happens in two phases:
21148
+ *
21149
+ * 1. All items are passed through the predicate (or predicates), and the returned values are saved
21150
+ * along with their type (`string`, `number` etc). For example, an item `{label: 'foo'}`, passed
21151
+ * through a predicate that extracts the value of the `label` property, would be transformed to:
21152
+ * ```
21153
+ * {
21154
+ * value: 'foo',
21155
+ * type: 'string',
21156
+ * index: ...
21157
+ * }
21158
+ * ```
21159
+ * 2. The comparator function is used to sort the items, based on the derived values, types and
21160
+ * indices.
21161
+ *
21162
+ * If you use a custom comparator, it will be called with pairs of objects of the form
21163
+ * `{value: ..., type: '...', index: ...}` and is expected to return `0` if the objects are equal
21164
+ * (as far as the comparator is concerned), `-1` if the 1st one should be ranked higher than the
21165
+ * second, or `1` otherwise.
21166
+ *
21167
+ * In order to ensure that the sorting will be deterministic across platforms, if none of the
21168
+ * specified predicates can distinguish between two items, `orderBy` will automatically introduce a
21169
+ * dummy predicate that returns the item's index as `value`.
21170
+ * (If you are using a custom comparator, make sure it can handle this predicate as well.)
21171
+ *
21172
+ * Finally, in an attempt to simplify things, if a predicate returns an object as the extracted
21173
+ * value for an item, `orderBy` will try to convert that object to a primitive value, before passing
21174
+ * it to the comparator. The following rules govern the conversion:
21175
+ *
21176
+ * 1. If the object has a `valueOf()` method that returns a primitive, its return value will be
21177
+ * used instead.<br />
21178
+ * (If the object has a `valueOf()` method that returns another object, then the returned object
21179
+ * will be used in subsequent steps.)
21180
+ * 2. If the object has a custom `toString()` method (i.e. not the one inherited from `Object`) that
21181
+ * returns a primitive, its return value will be used instead.<br />
21182
+ * (If the object has a `toString()` method that returns another object, then the returned object
21183
+ * will be used in subsequent steps.)
21184
+ * 3. No conversion; the object itself is used.
21185
+ *
21186
+ * ### The default comparator
21187
+ *
21188
+ * The default, built-in comparator should be sufficient for most usecases. In short, it compares
21189
+ * numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to
21190
+ * using their index in the original collection, and sorts values of different types by type.
21191
+ *
21192
+ * More specifically, it follows these steps to determine the relative order of items:
21193
+ *
21194
+ * 1. If the compared values are of different types, compare the types themselves alphabetically.
21195
+ * 2. If both values are of type `string`, compare them alphabetically in a case- and
21196
+ * locale-insensitive way.
21197
+ * 3. If both values are objects, compare their indices instead.
21198
+ * 4. Otherwise, return:
21199
+ * - `0`, if the values are equal (by strict equality comparison, i.e. using `===`).
21200
+ * - `-1`, if the 1st value is "less than" the 2nd value (compared using the `<` operator).
21201
+ * - `1`, otherwise.
21202
+ *
21203
+ * **Note:** If you notice numbers not being sorted as expected, make sure they are actually being
21204
+ * saved as numbers and not strings.
21205
+ *
21206
+ * @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort.
21207
+ * @param {(Function|string|Array.<Function|string>)=} expression - A predicate (or list of
21208
+ * predicates) to be used by the comparator to determine the order of elements.
20816
21209
  *
20817
21210
  * Can be one of:
20818
21211
  *
20819
- * - `function`: Getter function. The result of this function will be sorted using the
20820
- * `<`, `===`, `>` operator.
20821
- * - `string`: An Angular expression. The result of this expression is used to compare elements
20822
- * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
20823
- * 3 first characters of a property called `name`). The result of a constant expression
20824
- * is interpreted as a property name to be used in comparisons (for example `"special name"`
20825
- * to sort object by the value of their `special name` property). An expression can be
20826
- * optionally prefixed with `+` or `-` to control ascending or descending sort order
20827
- * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
20828
- * element itself is used to compare where sorting.
20829
- * - `Array`: An array of function or string predicates. The first predicate in the array
20830
- * is used for sorting, but when two items are equivalent, the next predicate is used.
21212
+ * - `Function`: A getter function. This function will be called with each item as argument and
21213
+ * the return value will be used for sorting.
21214
+ * - `string`: An Angular expression. This expression will be evaluated against each item and the
21215
+ * result will be used for sorting. For example, use `'label'` to sort by a property called
21216
+ * `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label`
21217
+ * property.<br />
21218
+ * (The result of a constant expression is interpreted as a property name to be used for
21219
+ * comparison. For example, use `'"special name"'` (note the extra pair of quotes) to sort by a
21220
+ * property called `special name`.)<br />
21221
+ * An expression can be optionally prefixed with `+` or `-` to control the sorting direction,
21222
+ * ascending or descending. For example, `'+label'` or `'-label'`. If no property is provided,
21223
+ * (e.g. `'+'` or `'-'`), the collection element itself is used in comparisons.
21224
+ * - `Array`: An array of function and/or string predicates. If a predicate cannot determine the
21225
+ * relative order of two items, the next predicate is used as a tie-breaker.
20831
21226
  *
20832
- * If the predicate is missing or empty then it defaults to `'+'`.
21227
+ * **Note:** If the predicate is missing or empty then it defaults to `'+'`.
20833
21228
  *
20834
- * @param {boolean=} reverse Reverse the order of the array.
20835
- * @returns {Array} Sorted copy of the source array.
21229
+ * @param {boolean=} reverse - If `true`, reverse the sorting order.
21230
+ * @param {(Function)=} comparator - The comparator function used to determine the relative order of
21231
+ * value pairs. If omitted, the built-in comparator will be used.
21232
+ *
21233
+ * @returns {Array} - The sorted array.
20836
21234
  *
20837
21235
  *
20838
21236
  * @example
20839
- * The example below demonstrates a simple ngRepeat, where the data is sorted
20840
- * by age in descending order (predicate is set to `'-age'`).
20841
- * `reverse` is not set, which means it defaults to `false`.
20842
- <example module="orderByExample">
21237
+ * ### Ordering a table with `ngRepeat`
21238
+ *
21239
+ * The example below demonstrates a simple {@link ngRepeat ngRepeat}, where the data is sorted by
21240
+ * age in descending order (expression is set to `'-age'`). The `comparator` is not set, which means
21241
+ * it defaults to the built-in comparator.
21242
+ *
21243
+ <example name="orderBy-static" module="orderByExample1">
20843
21244
  <file name="index.html">
20844
21245
  <div ng-controller="ExampleController">
20845
- <table class="friend">
21246
+ <table class="friends">
20846
21247
  <tr>
20847
21248
  <th>Name</th>
20848
21249
  <th>Phone Number</th>
@@ -20857,43 +21258,77 @@ function limitToFilter() {
20857
21258
  </div>
20858
21259
  </file>
20859
21260
  <file name="script.js">
20860
- angular.module('orderByExample', [])
21261
+ angular.module('orderByExample1', [])
20861
21262
  .controller('ExampleController', ['$scope', function($scope) {
20862
- $scope.friends =
20863
- [{name:'John', phone:'555-1212', age:10},
20864
- {name:'Mary', phone:'555-9876', age:19},
20865
- {name:'Mike', phone:'555-4321', age:21},
20866
- {name:'Adam', phone:'555-5678', age:35},
20867
- {name:'Julie', phone:'555-8765', age:29}];
21263
+ $scope.friends = [
21264
+ {name: 'John', phone: '555-1212', age: 10},
21265
+ {name: 'Mary', phone: '555-9876', age: 19},
21266
+ {name: 'Mike', phone: '555-4321', age: 21},
21267
+ {name: 'Adam', phone: '555-5678', age: 35},
21268
+ {name: 'Julie', phone: '555-8765', age: 29}
21269
+ ];
20868
21270
  }]);
20869
21271
  </file>
21272
+ <file name="style.css">
21273
+ .friends {
21274
+ border-collapse: collapse;
21275
+ }
21276
+
21277
+ .friends th {
21278
+ border-bottom: 1px solid;
21279
+ }
21280
+ .friends td, .friends th {
21281
+ border-left: 1px solid;
21282
+ padding: 5px 10px;
21283
+ }
21284
+ .friends td:first-child, .friends th:first-child {
21285
+ border-left: none;
21286
+ }
21287
+ </file>
21288
+ <file name="protractor.js" type="protractor">
21289
+ // Element locators
21290
+ var names = element.all(by.repeater('friends').column('friend.name'));
21291
+
21292
+ it('should sort friends by age in reverse order', function() {
21293
+ expect(names.get(0).getText()).toBe('Adam');
21294
+ expect(names.get(1).getText()).toBe('Julie');
21295
+ expect(names.get(2).getText()).toBe('Mike');
21296
+ expect(names.get(3).getText()).toBe('Mary');
21297
+ expect(names.get(4).getText()).toBe('John');
21298
+ });
21299
+ </file>
20870
21300
  </example>
21301
+ * <hr />
20871
21302
  *
20872
- * The predicate and reverse parameters can be controlled dynamically through scope properties,
20873
- * as shown in the next example.
20874
21303
  * @example
20875
- <example module="orderByExample">
21304
+ * ### Changing parameters dynamically
21305
+ *
21306
+ * All parameters can be changed dynamically. The next example shows how you can make the columns of
21307
+ * a table sortable, by binding the `expression` and `reverse` parameters to scope properties.
21308
+ *
21309
+ <example name="orderBy-dynamic" module="orderByExample2">
20876
21310
  <file name="index.html">
20877
21311
  <div ng-controller="ExampleController">
20878
- <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
21312
+ <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
20879
21313
  <hr/>
20880
- <button ng-click="predicate=''">Set to unsorted</button>
20881
- <table class="friend">
21314
+ <button ng-click="propertyName = null; reverse = false">Set to unsorted</button>
21315
+ <hr/>
21316
+ <table class="friends">
20882
21317
  <tr>
20883
- <th>
20884
- <button ng-click="order('name')">Name</button>
20885
- <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
20886
- </th>
20887
- <th>
20888
- <button ng-click="order('phone')">Phone Number</button>
20889
- <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
20890
- </th>
20891
- <th>
20892
- <button ng-click="order('age')">Age</button>
20893
- <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
20894
- </th>
21318
+ <th>
21319
+ <button ng-click="sortBy('name')">Name</button>
21320
+ <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
21321
+ </th>
21322
+ <th>
21323
+ <button ng-click="sortBy('phone')">Phone Number</button>
21324
+ <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
21325
+ </th>
21326
+ <th>
21327
+ <button ng-click="sortBy('age')">Age</button>
21328
+ <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
21329
+ </th>
20895
21330
  </tr>
20896
- <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
21331
+ <tr ng-repeat="friend in friends | orderBy:propertyName:reverse">
20897
21332
  <td>{{friend.name}}</td>
20898
21333
  <td>{{friend.phone}}</td>
20899
21334
  <td>{{friend.age}}</td>
@@ -20902,100 +21337,335 @@ function limitToFilter() {
20902
21337
  </div>
20903
21338
  </file>
20904
21339
  <file name="script.js">
20905
- angular.module('orderByExample', [])
21340
+ angular.module('orderByExample2', [])
20906
21341
  .controller('ExampleController', ['$scope', function($scope) {
20907
- $scope.friends =
20908
- [{name:'John', phone:'555-1212', age:10},
20909
- {name:'Mary', phone:'555-9876', age:19},
20910
- {name:'Mike', phone:'555-4321', age:21},
20911
- {name:'Adam', phone:'555-5678', age:35},
20912
- {name:'Julie', phone:'555-8765', age:29}];
20913
- $scope.predicate = 'age';
21342
+ var friends = [
21343
+ {name: 'John', phone: '555-1212', age: 10},
21344
+ {name: 'Mary', phone: '555-9876', age: 19},
21345
+ {name: 'Mike', phone: '555-4321', age: 21},
21346
+ {name: 'Adam', phone: '555-5678', age: 35},
21347
+ {name: 'Julie', phone: '555-8765', age: 29}
21348
+ ];
21349
+
21350
+ $scope.propertyName = 'age';
20914
21351
  $scope.reverse = true;
20915
- $scope.order = function(predicate) {
20916
- $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
20917
- $scope.predicate = predicate;
21352
+ $scope.friends = friends;
21353
+
21354
+ $scope.sortBy = function(propertyName) {
21355
+ $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false;
21356
+ $scope.propertyName = propertyName;
20918
21357
  };
20919
21358
  }]);
20920
- </file>
21359
+ </file>
20921
21360
  <file name="style.css">
21361
+ .friends {
21362
+ border-collapse: collapse;
21363
+ }
21364
+
21365
+ .friends th {
21366
+ border-bottom: 1px solid;
21367
+ }
21368
+ .friends td, .friends th {
21369
+ border-left: 1px solid;
21370
+ padding: 5px 10px;
21371
+ }
21372
+ .friends td:first-child, .friends th:first-child {
21373
+ border-left: none;
21374
+ }
21375
+
20922
21376
  .sortorder:after {
20923
- content: '\25b2';
21377
+ content: '\25b2'; // BLACK UP-POINTING TRIANGLE
20924
21378
  }
20925
21379
  .sortorder.reverse:after {
20926
- content: '\25bc';
21380
+ content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE
20927
21381
  }
20928
21382
  </file>
21383
+ <file name="protractor.js" type="protractor">
21384
+ // Element locators
21385
+ var unsortButton = element(by.partialButtonText('unsorted'));
21386
+ var nameHeader = element(by.partialButtonText('Name'));
21387
+ var phoneHeader = element(by.partialButtonText('Phone'));
21388
+ var ageHeader = element(by.partialButtonText('Age'));
21389
+ var firstName = element(by.repeater('friends').column('friend.name').row(0));
21390
+ var lastName = element(by.repeater('friends').column('friend.name').row(4));
21391
+
21392
+ it('should sort friends by some property, when clicking on the column header', function() {
21393
+ expect(firstName.getText()).toBe('Adam');
21394
+ expect(lastName.getText()).toBe('John');
21395
+
21396
+ phoneHeader.click();
21397
+ expect(firstName.getText()).toBe('John');
21398
+ expect(lastName.getText()).toBe('Mary');
21399
+
21400
+ nameHeader.click();
21401
+ expect(firstName.getText()).toBe('Adam');
21402
+ expect(lastName.getText()).toBe('Mike');
21403
+
21404
+ ageHeader.click();
21405
+ expect(firstName.getText()).toBe('John');
21406
+ expect(lastName.getText()).toBe('Adam');
21407
+ });
21408
+
21409
+ it('should sort friends in reverse order, when clicking on the same column', function() {
21410
+ expect(firstName.getText()).toBe('Adam');
21411
+ expect(lastName.getText()).toBe('John');
21412
+
21413
+ ageHeader.click();
21414
+ expect(firstName.getText()).toBe('John');
21415
+ expect(lastName.getText()).toBe('Adam');
21416
+
21417
+ ageHeader.click();
21418
+ expect(firstName.getText()).toBe('Adam');
21419
+ expect(lastName.getText()).toBe('John');
21420
+ });
21421
+
21422
+ it('should restore the original order, when clicking "Set to unsorted"', function() {
21423
+ expect(firstName.getText()).toBe('Adam');
21424
+ expect(lastName.getText()).toBe('John');
21425
+
21426
+ unsortButton.click();
21427
+ expect(firstName.getText()).toBe('John');
21428
+ expect(lastName.getText()).toBe('Julie');
21429
+ });
21430
+ </file>
20929
21431
  </example>
21432
+ * <hr />
20930
21433
  *
20931
- * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
20932
- * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the
20933
- * desired parameters.
21434
+ * @example
21435
+ * ### Using `orderBy` inside a controller
20934
21436
  *
20935
- * Example:
21437
+ * It is also possible to call the `orderBy` filter manually, by injecting `orderByFilter`, and
21438
+ * calling it with the desired parameters. (Alternatively, you could inject the `$filter` factory
21439
+ * and retrieve the `orderBy` filter with `$filter('orderBy')`.)
20936
21440
  *
20937
- * @example
20938
- <example module="orderByExample">
20939
- <file name="index.html">
20940
- <div ng-controller="ExampleController">
20941
- <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
20942
- <table class="friend">
20943
- <tr>
20944
- <th>
20945
- <button ng-click="order('name')">Name</button>
20946
- <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
20947
- </th>
20948
- <th>
20949
- <button ng-click="order('phone')">Phone Number</button>
20950
- <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
20951
- </th>
20952
- <th>
20953
- <button ng-click="order('age')">Age</button>
20954
- <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
20955
- </th>
20956
- </tr>
20957
- <tr ng-repeat="friend in friends">
20958
- <td>{{friend.name}}</td>
20959
- <td>{{friend.phone}}</td>
20960
- <td>{{friend.age}}</td>
20961
- </tr>
20962
- </table>
20963
- </div>
20964
- </file>
21441
+ <example name="orderBy-call-manually" module="orderByExample3">
21442
+ <file name="index.html">
21443
+ <div ng-controller="ExampleController">
21444
+ <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
21445
+ <hr/>
21446
+ <button ng-click="sortBy(null)">Set to unsorted</button>
21447
+ <hr/>
21448
+ <table class="friends">
21449
+ <tr>
21450
+ <th>
21451
+ <button ng-click="sortBy('name')">Name</button>
21452
+ <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
21453
+ </th>
21454
+ <th>
21455
+ <button ng-click="sortBy('phone')">Phone Number</button>
21456
+ <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
21457
+ </th>
21458
+ <th>
21459
+ <button ng-click="sortBy('age')">Age</button>
21460
+ <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
21461
+ </th>
21462
+ </tr>
21463
+ <tr ng-repeat="friend in friends">
21464
+ <td>{{friend.name}}</td>
21465
+ <td>{{friend.phone}}</td>
21466
+ <td>{{friend.age}}</td>
21467
+ </tr>
21468
+ </table>
21469
+ </div>
21470
+ </file>
21471
+ <file name="script.js">
21472
+ angular.module('orderByExample3', [])
21473
+ .controller('ExampleController', ['$scope', 'orderByFilter', function($scope, orderBy) {
21474
+ var friends = [
21475
+ {name: 'John', phone: '555-1212', age: 10},
21476
+ {name: 'Mary', phone: '555-9876', age: 19},
21477
+ {name: 'Mike', phone: '555-4321', age: 21},
21478
+ {name: 'Adam', phone: '555-5678', age: 35},
21479
+ {name: 'Julie', phone: '555-8765', age: 29}
21480
+ ];
21481
+
21482
+ $scope.propertyName = 'age';
21483
+ $scope.reverse = true;
21484
+ $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);
20965
21485
 
20966
- <file name="script.js">
20967
- angular.module('orderByExample', [])
20968
- .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) {
20969
- var orderBy = $filter('orderBy');
20970
- $scope.friends = [
20971
- { name: 'John', phone: '555-1212', age: 10 },
20972
- { name: 'Mary', phone: '555-9876', age: 19 },
20973
- { name: 'Mike', phone: '555-4321', age: 21 },
20974
- { name: 'Adam', phone: '555-5678', age: 35 },
20975
- { name: 'Julie', phone: '555-8765', age: 29 }
20976
- ];
20977
- $scope.order = function(predicate) {
20978
- $scope.predicate = predicate;
20979
- $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
20980
- $scope.friends = orderBy($scope.friends, predicate, $scope.reverse);
20981
- };
20982
- $scope.order('age', true);
20983
- }]);
20984
- </file>
21486
+ $scope.sortBy = function(propertyName) {
21487
+ $scope.reverse = (propertyName !== null && $scope.propertyName === propertyName)
21488
+ ? !$scope.reverse : false;
21489
+ $scope.propertyName = propertyName;
21490
+ $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);
21491
+ };
21492
+ }]);
21493
+ </file>
21494
+ <file name="style.css">
21495
+ .friends {
21496
+ border-collapse: collapse;
21497
+ }
21498
+
21499
+ .friends th {
21500
+ border-bottom: 1px solid;
21501
+ }
21502
+ .friends td, .friends th {
21503
+ border-left: 1px solid;
21504
+ padding: 5px 10px;
21505
+ }
21506
+ .friends td:first-child, .friends th:first-child {
21507
+ border-left: none;
21508
+ }
20985
21509
 
20986
- <file name="style.css">
20987
21510
  .sortorder:after {
20988
- content: '\25b2';
21511
+ content: '\25b2'; // BLACK UP-POINTING TRIANGLE
20989
21512
  }
20990
21513
  .sortorder.reverse:after {
20991
- content: '\25bc';
21514
+ content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE
20992
21515
  }
20993
- </file>
20994
- </example>
21516
+ </file>
21517
+ <file name="protractor.js" type="protractor">
21518
+ // Element locators
21519
+ var unsortButton = element(by.partialButtonText('unsorted'));
21520
+ var nameHeader = element(by.partialButtonText('Name'));
21521
+ var phoneHeader = element(by.partialButtonText('Phone'));
21522
+ var ageHeader = element(by.partialButtonText('Age'));
21523
+ var firstName = element(by.repeater('friends').column('friend.name').row(0));
21524
+ var lastName = element(by.repeater('friends').column('friend.name').row(4));
21525
+
21526
+ it('should sort friends by some property, when clicking on the column header', function() {
21527
+ expect(firstName.getText()).toBe('Adam');
21528
+ expect(lastName.getText()).toBe('John');
21529
+
21530
+ phoneHeader.click();
21531
+ expect(firstName.getText()).toBe('John');
21532
+ expect(lastName.getText()).toBe('Mary');
21533
+
21534
+ nameHeader.click();
21535
+ expect(firstName.getText()).toBe('Adam');
21536
+ expect(lastName.getText()).toBe('Mike');
21537
+
21538
+ ageHeader.click();
21539
+ expect(firstName.getText()).toBe('John');
21540
+ expect(lastName.getText()).toBe('Adam');
21541
+ });
21542
+
21543
+ it('should sort friends in reverse order, when clicking on the same column', function() {
21544
+ expect(firstName.getText()).toBe('Adam');
21545
+ expect(lastName.getText()).toBe('John');
21546
+
21547
+ ageHeader.click();
21548
+ expect(firstName.getText()).toBe('John');
21549
+ expect(lastName.getText()).toBe('Adam');
21550
+
21551
+ ageHeader.click();
21552
+ expect(firstName.getText()).toBe('Adam');
21553
+ expect(lastName.getText()).toBe('John');
21554
+ });
21555
+
21556
+ it('should restore the original order, when clicking "Set to unsorted"', function() {
21557
+ expect(firstName.getText()).toBe('Adam');
21558
+ expect(lastName.getText()).toBe('John');
21559
+
21560
+ unsortButton.click();
21561
+ expect(firstName.getText()).toBe('John');
21562
+ expect(lastName.getText()).toBe('Julie');
21563
+ });
21564
+ </file>
21565
+ </example>
21566
+ * <hr />
21567
+ *
21568
+ * @example
21569
+ * ### Using a custom comparator
21570
+ *
21571
+ * If you have very specific requirements about the way items are sorted, you can pass your own
21572
+ * comparator function. For example, you might need to compare some strings in a locale-sensitive
21573
+ * way. (When specifying a custom comparator, you also need to pass a value for the `reverse`
21574
+ * argument - passing `false` retains the default sorting order, i.e. ascending.)
21575
+ *
21576
+ <example name="orderBy-custom-comparator" module="orderByExample4">
21577
+ <file name="index.html">
21578
+ <div ng-controller="ExampleController">
21579
+ <div class="friends-container custom-comparator">
21580
+ <h3>Locale-sensitive Comparator</h3>
21581
+ <table class="friends">
21582
+ <tr>
21583
+ <th>Name</th>
21584
+ <th>Favorite Letter</th>
21585
+ </tr>
21586
+ <tr ng-repeat="friend in friends | orderBy:'favoriteLetter':false:localeSensitiveComparator">
21587
+ <td>{{friend.name}}</td>
21588
+ <td>{{friend.favoriteLetter}}</td>
21589
+ </tr>
21590
+ </table>
21591
+ </div>
21592
+ <div class="friends-container default-comparator">
21593
+ <h3>Default Comparator</h3>
21594
+ <table class="friends">
21595
+ <tr>
21596
+ <th>Name</th>
21597
+ <th>Favorite Letter</th>
21598
+ </tr>
21599
+ <tr ng-repeat="friend in friends | orderBy:'favoriteLetter'">
21600
+ <td>{{friend.name}}</td>
21601
+ <td>{{friend.favoriteLetter}}</td>
21602
+ </tr>
21603
+ </table>
21604
+ </div>
21605
+ </div>
21606
+ </file>
21607
+ <file name="script.js">
21608
+ angular.module('orderByExample4', [])
21609
+ .controller('ExampleController', ['$scope', function($scope) {
21610
+ $scope.friends = [
21611
+ {name: 'John', favoriteLetter: 'Ä'},
21612
+ {name: 'Mary', favoriteLetter: 'Ü'},
21613
+ {name: 'Mike', favoriteLetter: 'Ö'},
21614
+ {name: 'Adam', favoriteLetter: 'H'},
21615
+ {name: 'Julie', favoriteLetter: 'Z'}
21616
+ ];
21617
+
21618
+ $scope.localeSensitiveComparator = function(v1, v2) {
21619
+ // If we don't get strings, just compare by index
21620
+ if (v1.type !== 'string' || v2.type !== 'string') {
21621
+ return (v1.index < v2.index) ? -1 : 1;
21622
+ }
21623
+
21624
+ // Compare strings alphabetically, taking locale into account
21625
+ return v1.value.localeCompare(v2.value);
21626
+ };
21627
+ }]);
21628
+ </file>
21629
+ <file name="style.css">
21630
+ .friends-container {
21631
+ display: inline-block;
21632
+ margin: 0 30px;
21633
+ }
21634
+
21635
+ .friends {
21636
+ border-collapse: collapse;
21637
+ }
21638
+
21639
+ .friends th {
21640
+ border-bottom: 1px solid;
21641
+ }
21642
+ .friends td, .friends th {
21643
+ border-left: 1px solid;
21644
+ padding: 5px 10px;
21645
+ }
21646
+ .friends td:first-child, .friends th:first-child {
21647
+ border-left: none;
21648
+ }
21649
+ </file>
21650
+ <file name="protractor.js" type="protractor">
21651
+ // Element locators
21652
+ var container = element(by.css('.custom-comparator'));
21653
+ var names = container.all(by.repeater('friends').column('friend.name'));
21654
+
21655
+ it('should sort friends by favorite letter (in correct alphabetical order)', function() {
21656
+ expect(names.get(0).getText()).toBe('John');
21657
+ expect(names.get(1).getText()).toBe('Adam');
21658
+ expect(names.get(2).getText()).toBe('Mike');
21659
+ expect(names.get(3).getText()).toBe('Mary');
21660
+ expect(names.get(4).getText()).toBe('Julie');
21661
+ });
21662
+ </file>
21663
+ </example>
21664
+ *
20995
21665
  */
20996
21666
  orderByFilter.$inject = ['$parse'];
20997
21667
  function orderByFilter($parse) {
20998
- return function(array, sortPredicate, reverseOrder) {
21668
+ return function(array, sortPredicate, reverseOrder, compareFn) {
20999
21669
 
21000
21670
  if (array == null) return array;
21001
21671
  if (!isArrayLike(array)) {
@@ -21005,11 +21675,12 @@ function orderByFilter($parse) {
21005
21675
  if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
21006
21676
  if (sortPredicate.length === 0) { sortPredicate = ['+']; }
21007
21677
 
21008
- var predicates = processPredicates(sortPredicate, reverseOrder);
21009
- // Add a predicate at the end that evaluates to the element index. This makes the
21010
- // sort stable as it works as a tie-breaker when all the input predicates cannot
21011
- // distinguish between two elements.
21012
- predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1});
21678
+ var predicates = processPredicates(sortPredicate);
21679
+
21680
+ var descending = reverseOrder ? -1 : 1;
21681
+
21682
+ // Define the `compare()` function. Use a default comparator if none is specified.
21683
+ var compare = isFunction(compareFn) ? compareFn : defaultCompare;
21013
21684
 
21014
21685
  // The next three lines are a version of a Swartzian Transform idiom from Perl
21015
21686
  // (sometimes called the Decorate-Sort-Undecorate idiom)
@@ -21021,8 +21692,12 @@ function orderByFilter($parse) {
21021
21692
  return array;
21022
21693
 
21023
21694
  function getComparisonObject(value, index) {
21695
+ // NOTE: We are adding an extra `tieBreaker` value based on the element's index.
21696
+ // This will be used to keep the sort stable when none of the input predicates can
21697
+ // distinguish between two elements.
21024
21698
  return {
21025
21699
  value: value,
21700
+ tieBreaker: {value: index, type: 'number', index: index},
21026
21701
  predicateValues: predicates.map(function(predicate) {
21027
21702
  return getPredicateValue(predicate.get(value), index);
21028
21703
  })
@@ -21030,18 +21705,19 @@ function orderByFilter($parse) {
21030
21705
  }
21031
21706
 
21032
21707
  function doComparison(v1, v2) {
21033
- var result = 0;
21034
- for (var index=0, length = predicates.length; index < length; ++index) {
21035
- result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending;
21036
- if (result) break;
21708
+ for (var i = 0, ii = predicates.length; i < ii; i++) {
21709
+ var result = compare(v1.predicateValues[i], v2.predicateValues[i]);
21710
+ if (result) {
21711
+ return result * predicates[i].descending * descending;
21712
+ }
21037
21713
  }
21038
- return result;
21714
+
21715
+ return compare(v1.tieBreaker, v2.tieBreaker) * descending;
21039
21716
  }
21040
21717
  };
21041
21718
 
21042
- function processPredicates(sortPredicate, reverseOrder) {
21043
- reverseOrder = reverseOrder ? -1 : 1;
21044
- return sortPredicate.map(function(predicate) {
21719
+ function processPredicates(sortPredicates) {
21720
+ return sortPredicates.map(function(predicate) {
21045
21721
  var descending = 1, get = identity;
21046
21722
 
21047
21723
  if (isFunction(predicate)) {
@@ -21059,7 +21735,7 @@ function orderByFilter($parse) {
21059
21735
  }
21060
21736
  }
21061
21737
  }
21062
- return { get: get, descending: descending * reverseOrder };
21738
+ return {get: get, descending: descending};
21063
21739
  });
21064
21740
  }
21065
21741
 
@@ -21074,9 +21750,9 @@ function orderByFilter($parse) {
21074
21750
  }
21075
21751
  }
21076
21752
 
21077
- function objectValue(value, index) {
21753
+ function objectValue(value) {
21078
21754
  // If `valueOf` is a valid function use that
21079
- if (typeof value.valueOf === 'function') {
21755
+ if (isFunction(value.valueOf)) {
21080
21756
  value = value.valueOf();
21081
21757
  if (isPrimitive(value)) return value;
21082
21758
  }
@@ -21085,8 +21761,8 @@ function orderByFilter($parse) {
21085
21761
  value = value.toString();
21086
21762
  if (isPrimitive(value)) return value;
21087
21763
  }
21088
- // We have a basic object so we use the position of the object in the collection
21089
- return index;
21764
+
21765
+ return value;
21090
21766
  }
21091
21767
 
21092
21768
  function getPredicateValue(value, index) {
@@ -21094,23 +21770,39 @@ function orderByFilter($parse) {
21094
21770
  if (value === null) {
21095
21771
  type = 'string';
21096
21772
  value = 'null';
21097
- } else if (type === 'string') {
21098
- value = value.toLowerCase();
21099
21773
  } else if (type === 'object') {
21100
- value = objectValue(value, index);
21774
+ value = objectValue(value);
21101
21775
  }
21102
- return { value: value, type: type };
21776
+ return {value: value, type: type, index: index};
21103
21777
  }
21104
21778
 
21105
- function compare(v1, v2) {
21779
+ function defaultCompare(v1, v2) {
21106
21780
  var result = 0;
21107
- if (v1.type === v2.type) {
21108
- if (v1.value !== v2.value) {
21109
- result = v1.value < v2.value ? -1 : 1;
21781
+ var type1 = v1.type;
21782
+ var type2 = v2.type;
21783
+
21784
+ if (type1 === type2) {
21785
+ var value1 = v1.value;
21786
+ var value2 = v2.value;
21787
+
21788
+ if (type1 === 'string') {
21789
+ // Compare strings case-insensitively
21790
+ value1 = value1.toLowerCase();
21791
+ value2 = value2.toLowerCase();
21792
+ } else if (type1 === 'object') {
21793
+ // For basic objects, use the position of the object
21794
+ // in the collection instead of the value
21795
+ if (isObject(value1)) value1 = v1.index;
21796
+ if (isObject(value2)) value2 = v2.index;
21797
+ }
21798
+
21799
+ if (value1 !== value2) {
21800
+ result = value1 < value2 ? -1 : 1;
21110
21801
  }
21111
21802
  } else {
21112
- result = v1.type < v2.type ? -1 : 1;
21803
+ result = type1 < type2 ? -1 : 1;
21113
21804
  }
21805
+
21114
21806
  return result;
21115
21807
  }
21116
21808
  }
@@ -21390,9 +22082,11 @@ var htmlAnchorDirective = valueFn({
21390
22082
  *
21391
22083
  * @description
21392
22084
  *
21393
- * Sets the `readOnly` attribute on the element, if the expression inside `ngReadonly` is truthy.
22085
+ * Sets the `readonly` attribute on the element, if the expression inside `ngReadonly` is truthy.
22086
+ * Note that `readonly` applies only to `input` elements with specific types. [See the input docs on
22087
+ * MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly) for more information.
21394
22088
  *
21395
- * A special directive is necessary because we cannot use interpolation inside the `readOnly`
22089
+ * A special directive is necessary because we cannot use interpolation inside the `readonly`
21396
22090
  * attribute. See the {@link guide/interpolation interpolation guide} for more info.
21397
22091
  *
21398
22092
  * @example
@@ -21429,6 +22123,13 @@ var htmlAnchorDirective = valueFn({
21429
22123
  * A special directive is necessary because we cannot use interpolation inside the `selected`
21430
22124
  * attribute. See the {@link guide/interpolation interpolation guide} for more info.
21431
22125
  *
22126
+ * <div class="alert alert-warning">
22127
+ * **Note:** `ngSelected` does not interact with the `select` and `ngModel` directives, it only
22128
+ * sets the `selected` attribute on the element. If you are using `ngModel` on the select, you
22129
+ * should not use `ngSelected` on the options, as `ngModel` will set the select value and
22130
+ * selected options.
22131
+ * </div>
22132
+ *
21432
22133
  * @example
21433
22134
  <example>
21434
22135
  <file name="index.html">
@@ -21465,6 +22166,11 @@ var htmlAnchorDirective = valueFn({
21465
22166
  * A special directive is necessary because we cannot use interpolation inside the `open`
21466
22167
  * attribute. See the {@link guide/interpolation interpolation guide} for more info.
21467
22168
  *
22169
+ * ## A note about browser compatibility
22170
+ *
22171
+ * Edge, Firefox, and Internet Explorer do not support the `details` element, it is
22172
+ * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead.
22173
+ *
21468
22174
  * @example
21469
22175
  <example>
21470
22176
  <file name="index.html">
@@ -22155,7 +22861,9 @@ var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-
22155
22861
  // 9. Fragment
22156
22862
  // 1111111111111111 222 333333 44444 555555555555555555555555 666 77777777 8888888 999
22157
22863
  var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i;
22158
- var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
22864
+ /* jshint maxlen:220 */
22865
+ var EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
22866
+ /* jshint maxlen:200 */
22159
22867
  var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
22160
22868
  var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/;
22161
22869
  var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
@@ -23540,7 +24248,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
23540
24248
 
23541
24249
  attr.$observe('min', function(val) {
23542
24250
  if (isDefined(val) && !isNumber(val)) {
23543
- val = parseFloat(val, 10);
24251
+ val = parseFloat(val);
23544
24252
  }
23545
24253
  minVal = isNumber(val) && !isNaN(val) ? val : undefined;
23546
24254
  // TODO(matsko): implement validateLater to reduce number of validations
@@ -23556,7 +24264,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
23556
24264
 
23557
24265
  attr.$observe('max', function(val) {
23558
24266
  if (isDefined(val) && !isNumber(val)) {
23559
- val = parseFloat(val, 10);
24267
+ val = parseFloat(val);
23560
24268
  }
23561
24269
  maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
23562
24270
  // TODO(matsko): implement validateLater to reduce number of validations
@@ -24363,6 +25071,11 @@ function classDirective(name, selector) {
24363
25071
  * When the expression changes, the previously added classes are removed and only then are the
24364
25072
  * new classes added.
24365
25073
  *
25074
+ * @knownIssue
25075
+ * You should not use {@link guide/interpolation interpolation} in the value of the `class`
25076
+ * attribute, when using the `ngClass` directive on the same element.
25077
+ * See {@link guide/interpolation#known-issues here} for more info.
25078
+ *
24366
25079
  * @animations
24367
25080
  * | Animation | Occurs |
24368
25081
  * |----------------------------------|-------------------------------------|
@@ -28308,7 +29021,7 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
28308
29021
 
28309
29022
  for (var i = options.items.length - 1; i >= 0; i--) {
28310
29023
  var option = options.items[i];
28311
- if (option.group) {
29024
+ if (isDefined(option.group)) {
28312
29025
  jqLiteRemove(option.element.parentNode);
28313
29026
  } else {
28314
29027
  jqLiteRemove(option.element);
@@ -28340,7 +29053,8 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
28340
29053
  listFragment.appendChild(groupElement);
28341
29054
 
28342
29055
  // Update the label on the group element
28343
- groupElement.label = option.group;
29056
+ // "null" is special cased because of Safari
29057
+ groupElement.label = option.group === null ? 'null' : option.group;
28344
29058
 
28345
29059
  // Store it for use later
28346
29060
  groupElementMap[option.group] = groupElement;
@@ -28676,7 +29390,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale,
28676
29390
  * it's a prefix used by Angular for public (`$`) and private (`$$`) properties.
28677
29391
  *
28678
29392
  * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with
28679
- * objects, and will throw if used with one.
29393
+ * objects, and will throw an error if used with one.
28680
29394
  *
28681
29395
  * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array
28682
29396
  * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
@@ -29525,6 +30239,11 @@ var ngHideDirective = ['$animate', function($animate) {
29525
30239
  * @description
29526
30240
  * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
29527
30241
  *
30242
+ * @knownIssue
30243
+ * You should not use {@link guide/interpolation interpolation} in the value of the `style`
30244
+ * attribute, when using the `ngStyle` directive on the same element.
30245
+ * See {@link guide/interpolation#known-issues here} for more info.
30246
+ *
29528
30247
  * @element ANY
29529
30248
  * @param {expression} ngStyle
29530
30249
  *
@@ -29936,37 +30655,63 @@ var ngSwitchDefaultDirective = ngDirective({
29936
30655
  * </example>
29937
30656
  */
29938
30657
  var ngTranscludeMinErr = minErr('ngTransclude');
29939
- var ngTranscludeDirective = ngDirective({
29940
- restrict: 'EAC',
29941
- link: function($scope, $element, $attrs, controller, $transclude) {
30658
+ var ngTranscludeDirective = ['$compile', function($compile) {
30659
+ return {
30660
+ restrict: 'EAC',
30661
+ terminal: true,
30662
+ compile: function ngTranscludeCompile(tElement) {
29942
30663
 
29943
- if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) {
29944
- // If the attribute is of the form: `ng-transclude="ng-transclude"`
29945
- // then treat it like the default
29946
- $attrs.ngTransclude = '';
29947
- }
30664
+ // Remove and cache any original content to act as a fallback
30665
+ var fallbackLinkFn = $compile(tElement.contents());
30666
+ tElement.empty();
29948
30667
 
29949
- function ngTranscludeCloneAttachFn(clone) {
29950
- if (clone.length) {
29951
- $element.empty();
29952
- $element.append(clone);
29953
- }
29954
- }
30668
+ return function ngTranscludePostLink($scope, $element, $attrs, controller, $transclude) {
29955
30669
 
29956
- if (!$transclude) {
29957
- throw ngTranscludeMinErr('orphan',
29958
- 'Illegal use of ngTransclude directive in the template! ' +
29959
- 'No parent directive that requires a transclusion found. ' +
29960
- 'Element: {0}',
29961
- startingTag($element));
29962
- }
30670
+ if (!$transclude) {
30671
+ throw ngTranscludeMinErr('orphan',
30672
+ 'Illegal use of ngTransclude directive in the template! ' +
30673
+ 'No parent directive that requires a transclusion found. ' +
30674
+ 'Element: {0}',
30675
+ startingTag($element));
30676
+ }
29963
30677
 
29964
- // If there is no slot name defined or the slot name is not optional
29965
- // then transclude the slot
29966
- var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot;
29967
- $transclude(ngTranscludeCloneAttachFn, null, slotName);
29968
- }
29969
- });
30678
+
30679
+ // If the attribute is of the form: `ng-transclude="ng-transclude"` then treat it like the default
30680
+ if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) {
30681
+ $attrs.ngTransclude = '';
30682
+ }
30683
+ var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot;
30684
+
30685
+ // If the slot is required and no transclusion content is provided then this call will throw an error
30686
+ $transclude(ngTranscludeCloneAttachFn, null, slotName);
30687
+
30688
+ // If the slot is optional and no transclusion content is provided then use the fallback content
30689
+ if (slotName && !$transclude.isSlotFilled(slotName)) {
30690
+ useFallbackContent();
30691
+ }
30692
+
30693
+ function ngTranscludeCloneAttachFn(clone, transcludedScope) {
30694
+ if (clone.length) {
30695
+ $element.append(clone);
30696
+ } else {
30697
+ useFallbackContent();
30698
+ // There is nothing linked against the transcluded scope since no content was available,
30699
+ // so it should be safe to clean up the generated scope.
30700
+ transcludedScope.$destroy();
30701
+ }
30702
+ }
30703
+
30704
+ function useFallbackContent() {
30705
+ // Since this is the fallback content rather than the transcluded content,
30706
+ // we link against the scope of this directive rather than the transcluded scope
30707
+ fallbackLinkFn($scope, function(clone) {
30708
+ $element.append(clone);
30709
+ });
30710
+ }
30711
+ };
30712
+ }
30713
+ };
30714
+ }];
29970
30715
 
29971
30716
  /**
29972
30717
  * @ngdoc directive