angularjs-rails 1.2.25 → 1.2.26

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.3
2
+ * @license AngularJS v1.3.0-rc.5
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.3
2
+ * @license AngularJS v1.3.0-rc.5
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -71,7 +71,7 @@ function minErr(module, ErrorConstructor) {
71
71
  return match;
72
72
  });
73
73
 
74
- message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.3/' +
74
+ message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.5/' +
75
75
  (module ? module + '/' : '') + code;
76
76
  for (i = 2; i < arguments.length; i++) {
77
77
  message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
@@ -87,6 +87,7 @@ function minErr(module, ErrorConstructor) {
87
87
  jqLite: true,
88
88
  jQuery: true,
89
89
  slice: true,
90
+ splice: true,
90
91
  push: true,
91
92
  toString: true,
92
93
  ngMinErr: true,
@@ -163,6 +164,12 @@ function minErr(module, ErrorConstructor) {
163
164
  getBlockNodes: true,
164
165
  hasOwnProperty: true,
165
166
  createMap: true,
167
+
168
+ NODE_TYPE_ELEMENT: true,
169
+ NODE_TYPE_TEXT: true,
170
+ NODE_TYPE_COMMENT: true,
171
+ NODE_TYPE_DOCUMENT: true,
172
+ NODE_TYPE_DOCUMENT_FRAGMENT: true,
166
173
  */
167
174
 
168
175
  ////////////////////////////////////
@@ -242,6 +249,7 @@ var /** holds major version number for IE or NaN for real browsers */
242
249
  jqLite, // delay binding since jQuery could be loaded after us.
243
250
  jQuery, // delay binding
244
251
  slice = [].slice,
252
+ splice = [].splice,
245
253
  push = [].push,
246
254
  toString = Object.prototype.toString,
247
255
  ngMinErr = minErr('ng'),
@@ -252,13 +260,10 @@ var /** holds major version number for IE or NaN for real browsers */
252
260
  uid = 0;
253
261
 
254
262
  /**
255
- * IE 11 changed the format of the UserAgent string.
256
- * See http://msdn.microsoft.com/en-us/library/ms537503.aspx
263
+ * documentMode is an IE-only property
264
+ * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
257
265
  */
258
- msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
259
- if (isNaN(msie)) {
260
- msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
261
- }
266
+ msie = document.documentMode;
262
267
 
263
268
 
264
269
  /**
@@ -274,7 +279,7 @@ function isArrayLike(obj) {
274
279
 
275
280
  var length = obj.length;
276
281
 
277
- if (obj.nodeType === 1 && length) {
282
+ if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
278
283
  return true;
279
284
  }
280
285
 
@@ -1110,11 +1115,9 @@ function startingTag(element) {
1110
1115
  // are not allowed to have children. So we just ignore it.
1111
1116
  element.empty();
1112
1117
  } catch(e) {}
1113
- // As Per DOM Standards
1114
- var TEXT_NODE = 3;
1115
1118
  var elemHtml = jqLite('<div>').append(element).html();
1116
1119
  try {
1117
- return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
1120
+ return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1118
1121
  elemHtml.
1119
1122
  match(/^(<[^>]+>)/)[1].
1120
1123
  replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
@@ -1693,6 +1696,12 @@ function createMap() {
1693
1696
  return Object.create(null);
1694
1697
  }
1695
1698
 
1699
+ var NODE_TYPE_ELEMENT = 1;
1700
+ var NODE_TYPE_TEXT = 3;
1701
+ var NODE_TYPE_COMMENT = 8;
1702
+ var NODE_TYPE_DOCUMENT = 9;
1703
+ var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
1704
+
1696
1705
  /**
1697
1706
  * @ngdoc type
1698
1707
  * @name angular.Module
@@ -2112,11 +2121,11 @@ function setupModuleLoader(window) {
2112
2121
  * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2113
2122
  */
2114
2123
  var version = {
2115
- full: '1.3.0-rc.3', // all of these placeholder strings will be replaced by grunt's
2124
+ full: '1.3.0-rc.5', // all of these placeholder strings will be replaced by grunt's
2116
2125
  major: 1, // package task
2117
2126
  minor: 3,
2118
2127
  dot: 0,
2119
- codeName: 'aggressive-pacifism'
2128
+ codeName: 'impossible-choreography'
2120
2129
  };
2121
2130
 
2122
2131
 
@@ -2150,8 +2159,7 @@ function publishExternalAPI(angular){
2150
2159
  'getTestability': getTestability,
2151
2160
  '$$minErr': minErr,
2152
2161
  '$$csp': csp,
2153
- 'reloadWithDebugInfo': reloadWithDebugInfo,
2154
- '$$hasClass': jqLiteHasClass
2162
+ 'reloadWithDebugInfo': reloadWithDebugInfo
2155
2163
  });
2156
2164
 
2157
2165
  angularModule = setupModuleLoader(window);
@@ -2297,7 +2305,7 @@ function publishExternalAPI(angular){
2297
2305
  * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2298
2306
  * - [`clone()`](http://api.jquery.com/clone/)
2299
2307
  * - [`contents()`](http://api.jquery.com/contents/)
2300
- * - [`css()`](http://api.jquery.com/css/)
2308
+ * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()`
2301
2309
  * - [`data()`](http://api.jquery.com/data/)
2302
2310
  * - [`detach()`](http://api.jquery.com/detach/)
2303
2311
  * - [`empty()`](http://api.jquery.com/empty/)
@@ -2419,7 +2427,7 @@ function jqLiteAcceptsData(node) {
2419
2427
  // The window object can accept data but has no nodeType
2420
2428
  // Otherwise we are only interested in elements (1) and documents (9)
2421
2429
  var nodeType = node.nodeType;
2422
- return nodeType === 1 || !nodeType || nodeType === 9;
2430
+ return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
2423
2431
  }
2424
2432
 
2425
2433
  function jqLiteBuildFragment(html, context) {
@@ -2672,7 +2680,7 @@ function jqLiteController(element, name) {
2672
2680
  function jqLiteInheritedData(element, name, value) {
2673
2681
  // if element is the document object work with the html element instead
2674
2682
  // this makes $(document).scope() possible
2675
- if(element.nodeType == 9) {
2683
+ if(element.nodeType == NODE_TYPE_DOCUMENT) {
2676
2684
  element = element.documentElement;
2677
2685
  }
2678
2686
  var names = isArray(name) ? name : [name];
@@ -2685,7 +2693,7 @@ function jqLiteInheritedData(element, name, value) {
2685
2693
  // If dealing with a document fragment node with a host element, and no parent, use the host
2686
2694
  // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
2687
2695
  // to lookup parent controllers.
2688
- element = element.parentNode || (element.nodeType === 11 && element.host);
2696
+ element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
2689
2697
  }
2690
2698
  }
2691
2699
 
@@ -2863,7 +2871,7 @@ forEach({
2863
2871
  function getText(element, value) {
2864
2872
  if (isUndefined(value)) {
2865
2873
  var nodeType = element.nodeType;
2866
- return (nodeType === 1 || nodeType === 3) ? element.textContent : '';
2874
+ return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
2867
2875
  }
2868
2876
  element.textContent = value;
2869
2877
  }
@@ -3085,7 +3093,7 @@ forEach({
3085
3093
  children: function(element) {
3086
3094
  var children = [];
3087
3095
  forEach(element.childNodes, function(element){
3088
- if (element.nodeType === 1)
3096
+ if (element.nodeType === NODE_TYPE_ELEMENT)
3089
3097
  children.push(element);
3090
3098
  });
3091
3099
  return children;
@@ -3097,7 +3105,7 @@ forEach({
3097
3105
 
3098
3106
  append: function(element, node) {
3099
3107
  var nodeType = element.nodeType;
3100
- if (nodeType !== 1 && nodeType !== 11) return;
3108
+ if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3101
3109
 
3102
3110
  node = new JQLite(node);
3103
3111
 
@@ -3108,7 +3116,7 @@ forEach({
3108
3116
  },
3109
3117
 
3110
3118
  prepend: function(element, node) {
3111
- if (element.nodeType === 1) {
3119
+ if (element.nodeType === NODE_TYPE_ELEMENT) {
3112
3120
  var index = element.firstChild;
3113
3121
  forEach(new JQLite(node), function(child){
3114
3122
  element.insertBefore(child, index);
@@ -3159,7 +3167,7 @@ forEach({
3159
3167
 
3160
3168
  parent: function(element) {
3161
3169
  var parent = element.parentNode;
3162
- return parent && parent.nodeType !== 11 ? parent : null;
3170
+ return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3163
3171
  },
3164
3172
 
3165
3173
  next: function(element) {
@@ -3318,13 +3326,13 @@ HashMap.prototype = {
3318
3326
  * @kind function
3319
3327
  *
3320
3328
  * @description
3321
- * Creates an injector function that can be used for retrieving services as well as for
3329
+ * Creates an injector object that can be used for retrieving services as well as for
3322
3330
  * dependency injection (see {@link guide/di dependency injection}).
3323
3331
  *
3324
3332
 
3325
3333
  * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
3326
3334
  * {@link angular.module}. The `ng` module must be explicitly added.
3327
- * @returns {function()} Injector function. See {@link auto.$injector $injector}.
3335
+ * @returns {function()} Injector object. See {@link auto.$injector $injector}.
3328
3336
  *
3329
3337
  * @example
3330
3338
  * Typical usage
@@ -3431,7 +3439,6 @@ function annotate(fn, strictDi, name) {
3431
3439
  /**
3432
3440
  * @ngdoc service
3433
3441
  * @name $injector
3434
- * @kind function
3435
3442
  *
3436
3443
  * @description
3437
3444
  *
@@ -3446,7 +3453,7 @@ function annotate(fn, strictDi, name) {
3446
3453
  * expect($injector.get('$injector')).toBe($injector);
3447
3454
  * expect($injector.invoke(function($injector) {
3448
3455
  * return $injector;
3449
- * }).toBe($injector);
3456
+ * })).toBe($injector);
3450
3457
  * ```
3451
3458
  *
3452
3459
  * # Injection Function Annotation
@@ -3513,8 +3520,8 @@ function annotate(fn, strictDi, name) {
3513
3520
  * @description
3514
3521
  * Allows the user to query if the particular service exists.
3515
3522
  *
3516
- * @param {string} Name of the service to query.
3517
- * @returns {boolean} returns true if injector has given service.
3523
+ * @param {string} name Name of the service to query.
3524
+ * @returns {boolean} `true` if injector has given service.
3518
3525
  */
3519
3526
 
3520
3527
  /**
@@ -3974,7 +3981,21 @@ function createInjector(modulesToLoad, strictDi) {
3974
3981
  return providerCache[name + providerSuffix] = provider_;
3975
3982
  }
3976
3983
 
3977
- function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
3984
+ function enforceReturnValue(name, factory) {
3985
+ return function enforcedReturnValue() {
3986
+ var result = instanceInjector.invoke(factory);
3987
+ if (isUndefined(result)) {
3988
+ throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
3989
+ }
3990
+ return result;
3991
+ };
3992
+ }
3993
+
3994
+ function factory(name, factoryFn, enforce) {
3995
+ return provider(name, {
3996
+ $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
3997
+ });
3998
+ }
3978
3999
 
3979
4000
  function service(name, constructor) {
3980
4001
  return factory(name, ['$injector', function($injector) {
@@ -3982,7 +4003,7 @@ function createInjector(modulesToLoad, strictDi) {
3982
4003
  }]);
3983
4004
  }
3984
4005
 
3985
- function value(name, val) { return factory(name, valueFn(val)); }
4006
+ function value(name, val) { return factory(name, valueFn(val), false); }
3986
4007
 
3987
4008
  function constant(name, value) {
3988
4009
  assertNotHasOwnProperty(name, 'constant');
@@ -4233,7 +4254,10 @@ function $AnchorScrollProvider() {
4233
4254
  // (no url change, no $location.hash() change), browser native does scroll
4234
4255
  if (autoScrollingEnabled) {
4235
4256
  $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
4236
- function autoScrollWatchAction() {
4257
+ function autoScrollWatchAction(newVal, oldVal) {
4258
+ // skip the initial scroll if $location.hash is empty
4259
+ if (newVal === oldVal && newVal === '') return;
4260
+
4237
4261
  $rootScope.$evalAsync(scroll);
4238
4262
  });
4239
4263
  }
@@ -4323,9 +4347,57 @@ var $AnimateProvider = ['$provide', function($provide) {
4323
4347
  return this.$$classNameFilter;
4324
4348
  };
4325
4349
 
4326
- this.$get = ['$$q', '$$asyncCallback', function($$q, $$asyncCallback) {
4350
+ this.$get = ['$$q', '$$asyncCallback', '$rootScope', function($$q, $$asyncCallback, $rootScope) {
4327
4351
 
4328
4352
  var currentDefer;
4353
+
4354
+ function runAnimationPostDigest(fn) {
4355
+ var cancelFn, defer = $$q.defer();
4356
+ defer.promise.$$cancelFn = function ngAnimateMaybeCancel() {
4357
+ cancelFn && cancelFn();
4358
+ };
4359
+
4360
+ $rootScope.$$postDigest(function ngAnimatePostDigest() {
4361
+ cancelFn = fn(function ngAnimateNotifyComplete() {
4362
+ defer.resolve();
4363
+ });
4364
+ });
4365
+
4366
+ return defer.promise;
4367
+ }
4368
+
4369
+ function resolveElementClasses(element, cache) {
4370
+ var toAdd = [], toRemove = [];
4371
+
4372
+ var hasClasses = createMap();
4373
+ forEach((element.attr('class') || '').split(/\s+/), function(className) {
4374
+ hasClasses[className] = true;
4375
+ });
4376
+
4377
+ forEach(cache.classes, function(status, className) {
4378
+ var hasClass = hasClasses[className];
4379
+
4380
+ // If the most recent class manipulation (via $animate) was to remove the class, and the
4381
+ // element currently has the class, the class is scheduled for removal. Otherwise, if
4382
+ // the most recent class manipulation (via $animate) was to add the class, and the
4383
+ // element does not currently have the class, the class is scheduled to be added.
4384
+ if (status === false && hasClass) {
4385
+ toRemove.push(className);
4386
+ } else if (status === true && !hasClass) {
4387
+ toAdd.push(className);
4388
+ }
4389
+ });
4390
+
4391
+ return (toAdd.length + toRemove.length) > 0 && [toAdd.length && toAdd, toRemove.length && toRemove];
4392
+ }
4393
+
4394
+ function cachedClassManipulation(cache, classes, op) {
4395
+ for (var i=0, ii = classes.length; i < ii; ++i) {
4396
+ var className = classes[i];
4397
+ cache[className] = op;
4398
+ }
4399
+ }
4400
+
4329
4401
  function asyncPromise() {
4330
4402
  // only serve one instance of a promise in order to save CPU cycles
4331
4403
  if (!currentDefer) {
@@ -4429,13 +4501,17 @@ var $AnimateProvider = ['$provide', function($provide) {
4429
4501
  * @return {Promise} the animation callback promise
4430
4502
  */
4431
4503
  addClass : function(element, className) {
4504
+ return this.setClass(element, className, []);
4505
+ },
4506
+
4507
+ $$addClassImmediately : function addClassImmediately(element, className) {
4508
+ element = jqLite(element);
4432
4509
  className = !isString(className)
4433
4510
  ? (isArray(className) ? className.join(' ') : '')
4434
4511
  : className;
4435
4512
  forEach(element, function (element) {
4436
4513
  jqLiteAddClass(element, className);
4437
4514
  });
4438
- return asyncPromise();
4439
4515
  },
4440
4516
 
4441
4517
  /**
@@ -4451,6 +4527,11 @@ var $AnimateProvider = ['$provide', function($provide) {
4451
4527
  * @return {Promise} the animation callback promise
4452
4528
  */
4453
4529
  removeClass : function(element, className) {
4530
+ return this.setClass(element, [], className);
4531
+ },
4532
+
4533
+ $$removeClassImmediately : function removeClassImmediately(element, className) {
4534
+ element = jqLite(element);
4454
4535
  className = !isString(className)
4455
4536
  ? (isArray(className) ? className.join(' ') : '')
4456
4537
  : className;
@@ -4473,10 +4554,53 @@ var $AnimateProvider = ['$provide', function($provide) {
4473
4554
  * @param {string} remove the CSS class which will be removed from the element
4474
4555
  * @return {Promise} the animation callback promise
4475
4556
  */
4476
- setClass : function(element, add, remove) {
4477
- this.addClass(element, add);
4478
- this.removeClass(element, remove);
4479
- return asyncPromise();
4557
+ setClass : function(element, add, remove, runSynchronously) {
4558
+ var self = this;
4559
+ var STORAGE_KEY = '$$animateClasses';
4560
+ var createdCache = false;
4561
+ element = jqLite(element);
4562
+
4563
+ if (runSynchronously) {
4564
+ // TODO(@caitp/@matsko): Remove undocumented `runSynchronously` parameter, and always
4565
+ // perform DOM manipulation asynchronously or in postDigest.
4566
+ self.$$addClassImmediately(element, add);
4567
+ self.$$removeClassImmediately(element, remove);
4568
+ return asyncPromise();
4569
+ }
4570
+
4571
+ var cache = element.data(STORAGE_KEY);
4572
+ if (!cache) {
4573
+ cache = {
4574
+ classes: {}
4575
+ };
4576
+ createdCache = true;
4577
+ }
4578
+
4579
+ var classes = cache.classes;
4580
+
4581
+ add = isArray(add) ? add : add.split(' ');
4582
+ remove = isArray(remove) ? remove : remove.split(' ');
4583
+ cachedClassManipulation(classes, add, true);
4584
+ cachedClassManipulation(classes, remove, false);
4585
+
4586
+ if (createdCache) {
4587
+ cache.promise = runAnimationPostDigest(function(done) {
4588
+ var cache = element.data(STORAGE_KEY);
4589
+ element.removeData(STORAGE_KEY);
4590
+
4591
+ var classes = cache && resolveElementClasses(element, cache);
4592
+
4593
+ if (classes) {
4594
+ if (classes[0]) self.$$addClassImmediately(element, classes[0]);
4595
+ if (classes[1]) self.$$removeClassImmediately(element, classes[1]);
4596
+ }
4597
+
4598
+ done();
4599
+ });
4600
+ element.data(STORAGE_KEY, cache);
4601
+ }
4602
+
4603
+ return cache.promise;
4480
4604
  },
4481
4605
 
4482
4606
  enabled : noop,
@@ -4495,6 +4619,8 @@ function $$AsyncCallbackProvider(){
4495
4619
  }];
4496
4620
  }
4497
4621
 
4622
+ /* global stripHash: true */
4623
+
4498
4624
  /**
4499
4625
  * ! This is a private undocumented service !
4500
4626
  *
@@ -4618,8 +4744,9 @@ function Browser(window, document, $log, $sniffer) {
4618
4744
  //////////////////////////////////////////////////////////////
4619
4745
 
4620
4746
  var lastBrowserUrl = location.href,
4747
+ lastHistoryState = history.state,
4621
4748
  baseElement = document.find('base'),
4622
- newLocation = null;
4749
+ reloadLocation = null;
4623
4750
 
4624
4751
  /**
4625
4752
  * @name $browser#url
@@ -4638,26 +4765,42 @@ function Browser(window, document, $log, $sniffer) {
4638
4765
  * {@link ng.$location $location service} to change url.
4639
4766
  *
4640
4767
  * @param {string} url New url (when used as setter)
4641
- * @param {boolean=} replace Should new url replace current history record ?
4768
+ * @param {boolean=} replace Should new url replace current history record?
4769
+ * @param {object=} state object to use with pushState/replaceState
4642
4770
  */
4643
- self.url = function(url, replace) {
4771
+ self.url = function(url, replace, state) {
4772
+ // In modern browsers `history.state` is `null` by default; treating it separately
4773
+ // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
4774
+ // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
4775
+ if (isUndefined(state)) {
4776
+ state = null;
4777
+ }
4778
+
4644
4779
  // Android Browser BFCache causes location, history reference to become stale.
4645
4780
  if (location !== window.location) location = window.location;
4646
4781
  if (history !== window.history) history = window.history;
4647
4782
 
4648
4783
  // setter
4649
4784
  if (url) {
4650
- if (lastBrowserUrl == url) return;
4785
+ // Don't change anything if previous and current URLs and states match. This also prevents
4786
+ // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
4787
+ // See https://github.com/angular/angular.js/commit/ffb2701
4788
+ if (lastBrowserUrl === url && (!$sniffer.history || history.state === state)) {
4789
+ return;
4790
+ }
4791
+ var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
4651
4792
  lastBrowserUrl = url;
4652
- if ($sniffer.history) {
4653
- if (replace) history.replaceState(null, '', url);
4654
- else {
4655
- history.pushState(null, '', url);
4656
- // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
4657
- baseElement.attr('href', baseElement.attr('href'));
4658
- }
4793
+ // Don't use history API if only the hash changed
4794
+ // due to a bug in IE10/IE11 which leads
4795
+ // to not firing a `hashchange` nor `popstate` event
4796
+ // in some cases (see #9143).
4797
+ if ($sniffer.history && (!sameBase || history.state !== state)) {
4798
+ history[replace ? 'replaceState' : 'pushState'](state, '', url);
4799
+ lastHistoryState = history.state;
4659
4800
  } else {
4660
- newLocation = url;
4801
+ if (!sameBase) {
4802
+ reloadLocation = url;
4803
+ }
4661
4804
  if (replace) {
4662
4805
  location.replace(url);
4663
4806
  } else {
@@ -4667,23 +4810,38 @@ function Browser(window, document, $log, $sniffer) {
4667
4810
  return self;
4668
4811
  // getter
4669
4812
  } else {
4670
- // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href
4671
- // methods not updating location.href synchronously.
4813
+ // - reloadLocation is needed as browsers don't allow to read out
4814
+ // the new location.href if a reload happened.
4672
4815
  // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
4673
- return newLocation || location.href.replace(/%27/g,"'");
4816
+ return reloadLocation || location.href.replace(/%27/g,"'");
4674
4817
  }
4675
4818
  };
4676
4819
 
4820
+ /**
4821
+ * @name $browser#state
4822
+ *
4823
+ * @description
4824
+ * This method is a getter.
4825
+ *
4826
+ * Return history.state or null if history.state is undefined.
4827
+ *
4828
+ * @returns {object} state
4829
+ */
4830
+ self.state = function() {
4831
+ return isUndefined(history.state) ? null : history.state;
4832
+ };
4833
+
4677
4834
  var urlChangeListeners = [],
4678
4835
  urlChangeInit = false;
4679
4836
 
4680
4837
  function fireUrlChange() {
4681
- newLocation = null;
4682
- if (lastBrowserUrl == self.url()) return;
4838
+ if (lastBrowserUrl === self.url() && lastHistoryState === history.state) {
4839
+ return;
4840
+ }
4683
4841
 
4684
4842
  lastBrowserUrl = self.url();
4685
4843
  forEach(urlChangeListeners, function(listener) {
4686
- listener(self.url());
4844
+ listener(self.url(), history.state);
4687
4845
  });
4688
4846
  }
4689
4847
 
@@ -4718,9 +4876,7 @@ function Browser(window, document, $log, $sniffer) {
4718
4876
  // html5 history api - popstate event
4719
4877
  if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange);
4720
4878
  // hashchange event
4721
- if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange);
4722
- // polling
4723
- else self.addPollFn(fireUrlChange);
4879
+ jqLite(window).on('hashchange', fireUrlChange);
4724
4880
 
4725
4881
  urlChangeInit = true;
4726
4882
  }
@@ -5495,8 +5651,11 @@ function $TemplateCacheProvider() {
5495
5651
  * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
5496
5652
  * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
5497
5653
  * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
5654
+ * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
5498
5655
  * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
5499
5656
  * `null` to the `link` fn if not found.
5657
+ * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
5658
+ * `null` to the `link` fn if not found.
5500
5659
  *
5501
5660
  *
5502
5661
  * #### `controllerAs`
@@ -5574,22 +5733,18 @@ function $TemplateCacheProvider() {
5574
5733
  * (because SVG doesn't work with custom elements in the DOM tree).
5575
5734
  *
5576
5735
  * #### `transclude`
5577
- * compile the content of the element and make it available to the directive.
5578
- * Typically used with {@link ng.directive:ngTransclude
5579
- * ngTransclude}. The advantage of transclusion is that the linking function receives a
5580
- * transclusion function which is pre-bound to the correct scope. In a typical setup the widget
5581
- * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate`
5582
- * scope. This makes it possible for the widget to have private state, and the transclusion to
5583
- * be bound to the parent (pre-`isolate`) scope.
5736
+ * Extract the contents of the element where the directive appears and make it available to the directive.
5737
+ * The contents are compiled and provided to the directive as a **transclusion function**. See the
5738
+ * {@link $compile#transclusion Transclusion} section below.
5584
5739
  *
5585
- * * `true` - transclude the content of the directive.
5586
- * * `'element'` - transclude the whole element including any directives defined at lower priority.
5740
+ * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
5741
+ * directive's element or the entire element:
5742
+ *
5743
+ * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
5744
+ * * `'element'` - transclude the whole of the directive's element including any directives on this
5745
+ * element that defined at a lower priority than this directive. When used, the `template`
5746
+ * property is ignored.
5587
5747
  *
5588
- * <div class="alert alert-warning">
5589
- * **Note:** When testing an element transclude directive you must not place the directive at the root of the
5590
- * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
5591
- * Testing Transclusion Directives}.
5592
- * </div>
5593
5748
  *
5594
5749
  * #### `compile`
5595
5750
  *
@@ -5687,7 +5842,121 @@ function $TemplateCacheProvider() {
5687
5842
  * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
5688
5843
  * for their async templates to be resolved.
5689
5844
  *
5690
- * <a name="Attributes"></a>
5845
+ *
5846
+ * ### Transclusion
5847
+ *
5848
+ * Transclusion is the process of extracting a collection of DOM element from one part of the DOM and
5849
+ * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
5850
+ * scope from where they were taken.
5851
+ *
5852
+ * Transclusion is used (often with {@link ngTransclude}) to insert the
5853
+ * original contents of a directive's element into a specified place in the template of the directive.
5854
+ * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
5855
+ * content has access to the properties on the scope from which it was taken, even if the directive
5856
+ * has isolated scope.
5857
+ * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
5858
+ *
5859
+ * This makes it possible for the widget to have private state for its template, while the transcluded
5860
+ * content has access to its originating scope.
5861
+ *
5862
+ * <div class="alert alert-warning">
5863
+ * **Note:** When testing an element transclude directive you must not place the directive at the root of the
5864
+ * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
5865
+ * Testing Transclusion Directives}.
5866
+ * </div>
5867
+ *
5868
+ * #### Transclusion Functions
5869
+ *
5870
+ * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
5871
+ * function** to the directive's `link` function and `controller`. This transclusion function is a special
5872
+ * **linking function** that will return the compiled contents linked to a new transclusion scope.
5873
+ *
5874
+ * <div class="alert alert-info">
5875
+ * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
5876
+ * ngTransclude will deal with it for us.
5877
+ * </div>
5878
+ *
5879
+ * If you want to manually control the insertion and removal of the transcluded content in your directive
5880
+ * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
5881
+ * object that contains the compiled DOM, which is linked to the correct transclusion scope.
5882
+ *
5883
+ * When you call a transclusion function you can pass in a **clone attach function**. This function is accepts
5884
+ * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
5885
+ * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
5886
+ *
5887
+ * <div class="alert alert-info">
5888
+ * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
5889
+ * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
5890
+ * </div>
5891
+ *
5892
+ * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
5893
+ * attach function**:
5894
+ *
5895
+ * ```js
5896
+ * var transcludedContent, transclusionScope;
5897
+ *
5898
+ * $transclude(function(clone, scope) {
5899
+ * element.append(clone);
5900
+ * transcludedContent = clone;
5901
+ * transclusionScope = scope;
5902
+ * });
5903
+ * ```
5904
+ *
5905
+ * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
5906
+ * associated transclusion scope:
5907
+ *
5908
+ * ```js
5909
+ * transcludedContent.remove();
5910
+ * transclusionScope.$destroy();
5911
+ * ```
5912
+ *
5913
+ * <div class="alert alert-info">
5914
+ * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
5915
+ * (by calling the transclude function to get the DOM and and calling `element.remove()` to remove it),
5916
+ * then you are also responsible for calling `$destroy` on the transclusion scope.
5917
+ * </div>
5918
+ *
5919
+ * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
5920
+ * automatically destroy their transluded clones as necessary so you do not need to worry about this if
5921
+ * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
5922
+ *
5923
+ *
5924
+ * #### Transclusion Scopes
5925
+ *
5926
+ * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
5927
+ * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
5928
+ * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
5929
+ * was taken.
5930
+ *
5931
+ * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
5932
+ * like this:
5933
+ *
5934
+ * ```html
5935
+ * <div ng-app>
5936
+ * <div isolate>
5937
+ * <div transclusion>
5938
+ * </div>
5939
+ * </div>
5940
+ * </div>
5941
+ * ```
5942
+ *
5943
+ * The `$parent` scope hierarchy will look like this:
5944
+ *
5945
+ * ```
5946
+ * - $rootScope
5947
+ * - isolate
5948
+ * - transclusion
5949
+ * ```
5950
+ *
5951
+ * but the scopes will inherit prototypically from different scopes to their `$parent`.
5952
+ *
5953
+ * ```
5954
+ * - $rootScope
5955
+ * - transclusion
5956
+ * - isolate
5957
+ * ```
5958
+ *
5959
+ *
5691
5960
  * ### Attributes
5692
5961
  *
5693
5962
  * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
@@ -5725,7 +5994,7 @@ function $TemplateCacheProvider() {
5725
5994
  * }
5726
5995
  * ```
5727
5996
  *
5728
- * Below is an example using `$compileProvider`.
5997
+ * ## Example
5729
5998
  *
5730
5999
  * <div class="alert alert-warning">
5731
6000
  * **Note**: Typically directives are registered with `module.directive`. The example below is
@@ -5850,7 +6119,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5850
6119
  Suffix = 'Directive',
5851
6120
  COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,
5852
6121
  CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/,
5853
- ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset');
6122
+ ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
6123
+ REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
5854
6124
 
5855
6125
  // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
5856
6126
  // The assumption is that future DOM event attribute names will begin with
@@ -6155,10 +6425,44 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6155
6425
 
6156
6426
  nodeName = nodeName_(this.$$element);
6157
6427
 
6158
- // sanitize a[href] and img[src] values
6159
6428
  if ((nodeName === 'a' && key === 'href') ||
6160
6429
  (nodeName === 'img' && key === 'src')) {
6430
+ // sanitize a[href] and img[src] values
6161
6431
  this[key] = value = $$sanitizeUri(value, key === 'src');
6432
+ } else if (nodeName === 'img' && key === 'srcset') {
6433
+ // sanitize img[srcset] values
6434
+ var result = "";
6435
+
6436
+ // first check if there are spaces because it's not the same pattern
6437
+ var trimmedSrcset = trim(value);
6438
+ // ( 999x ,| 999w ,| ,|, )
6439
+ var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
6440
+ var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
6441
+
6442
+ // split srcset into tuple of uri and descriptor except for the last item
6443
+ var rawUris = trimmedSrcset.split(pattern);
6444
+
6445
+ // for each tuples
6446
+ var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
6447
+ for (var i=0; i<nbrUrisWith2parts; i++) {
6448
+ var innerIdx = i*2;
6449
+ // sanitize the uri
6450
+ result += $$sanitizeUri(trim( rawUris[innerIdx]), true);
6451
+ // add the descriptor
6452
+ result += ( " " + trim(rawUris[innerIdx+1]));
6453
+ }
6454
+
6455
+ // split the last item into uri and descriptor
6456
+ var lastTuple = trim(rawUris[i*2]).split(/\s/);
6457
+
6458
+ // sanitize the last uri
6459
+ result += $$sanitizeUri(trim(lastTuple[0]), true);
6460
+
6461
+ // and add the last descriptor if any
6462
+ if( lastTuple.length === 2) {
6463
+ result += (" " + trim(lastTuple[1]));
6464
+ }
6465
+ this[key] = value = result;
6162
6466
  }
6163
6467
 
6164
6468
  if (writeAttr !== false) {
@@ -6196,12 +6500,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6196
6500
  * @param {string} key Normalized key. (ie ngAttribute) .
6197
6501
  * @param {function(interpolatedValue)} fn Function that will be called whenever
6198
6502
  the interpolated value of the attribute changes.
6199
- * See the {@link guide/directive#Attributes Directives} guide for more info.
6503
+ * See {@link ng.$compile#attributes $compile} for more info.
6200
6504
  * @returns {function()} Returns a deregistration function for this observer.
6201
6505
  */
6202
6506
  $observe: function(key, fn) {
6203
6507
  var attrs = this,
6204
- $$observers = (attrs.$$observers || (attrs.$$observers = {})),
6508
+ $$observers = (attrs.$$observers || (attrs.$$observers = Object.create(null))),
6205
6509
  listeners = ($$observers[key] || ($$observers[key] = []));
6206
6510
 
6207
6511
  listeners.push(fn);
@@ -6277,7 +6581,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6277
6581
  // We can not compile top level text elements since text nodes can be merged and we will
6278
6582
  // not be able to attach scope data to them, so we will wrap them in <span>
6279
6583
  forEach($compileNodes, function(node, index){
6280
- if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
6584
+ if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
6281
6585
  $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
6282
6586
  }
6283
6587
  });
@@ -6286,27 +6590,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6286
6590
  maxPriority, ignoreDirective, previousCompileContext);
6287
6591
  compile.$$addScopeClass($compileNodes);
6288
6592
  var namespace = null;
6289
- var namespaceAdaptedCompileNodes = $compileNodes;
6290
- var lastCompileNode;
6291
6593
  return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){
6292
6594
  assertArg(scope, 'scope');
6293
6595
  if (!namespace) {
6294
6596
  namespace = detectNamespaceForChildElements(futureParentElement);
6295
6597
  }
6296
- if (namespace !== 'html' && $compileNodes[0] !== lastCompileNode) {
6297
- namespaceAdaptedCompileNodes = jqLite(
6598
+ var $linkNode;
6599
+ if (namespace !== 'html') {
6600
+ // When using a directive with replace:true and templateUrl the $compileNodes
6601
+ // (or a child element inside of them)
6602
+ // might change, so we need to recreate the namespace adapted compileNodes
6603
+ // for call to the link function.
6604
+ // Note: This will already clone the nodes...
6605
+ $linkNode = jqLite(
6298
6606
  wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
6299
6607
  );
6608
+ } else if (cloneConnectFn) {
6609
+ // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
6610
+ // and sometimes changes the structure of the DOM.
6611
+ $linkNode = JQLitePrototype.clone.call($compileNodes);
6612
+ } else {
6613
+ $linkNode = $compileNodes;
6300
6614
  }
6301
- // When using a directive with replace:true and templateUrl the $compileNodes
6302
- // might change, so we need to recreate the namespace adapted compileNodes.
6303
- lastCompileNode = $compileNodes[0];
6304
-
6305
- // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
6306
- // and sometimes changes the structure of the DOM.
6307
- var $linkNode = cloneConnectFn
6308
- ? JQLitePrototype.clone.call(namespaceAdaptedCompileNodes) // IMPORTANT!!!
6309
- : namespaceAdaptedCompileNodes;
6310
6615
 
6311
6616
  if (transcludeControllers) {
6312
6617
  for (var controllerName in transcludeControllers) {
@@ -6449,20 +6754,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6449
6754
 
6450
6755
  function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn, elementTransclusion) {
6451
6756
 
6452
- var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement) {
6453
- var scopeCreated = false;
6757
+ var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
6454
6758
 
6455
6759
  if (!transcludedScope) {
6456
- transcludedScope = scope.$new();
6760
+ transcludedScope = scope.$new(false, containingScope);
6457
6761
  transcludedScope.$$transcluded = true;
6458
- scopeCreated = true;
6459
6762
  }
6460
6763
 
6461
- var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
6462
- if (scopeCreated && !elementTransclusion) {
6463
- clone.on('$destroy', function() { transcludedScope.$destroy(); });
6464
- }
6465
- return clone;
6764
+ return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
6466
6765
  };
6467
6766
 
6468
6767
  return boundTranscludeFn;
@@ -6485,7 +6784,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6485
6784
  className;
6486
6785
 
6487
6786
  switch(nodeType) {
6488
- case 1: /* Element */
6787
+ case NODE_TYPE_ELEMENT: /* Element */
6489
6788
  // use the node name: <directive>
6490
6789
  addDirective(directives,
6491
6790
  directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
@@ -6497,37 +6796,35 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6497
6796
  var attrEndName = false;
6498
6797
 
6499
6798
  attr = nAttrs[j];
6500
- if (!msie || msie >= 8 || attr.specified) {
6501
- name = attr.name;
6502
- value = trim(attr.value);
6503
-
6504
- // support ngAttr attribute binding
6505
- ngAttrName = directiveNormalize(name);
6506
- if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
6507
- name = snake_case(ngAttrName.substr(6), '-');
6508
- }
6799
+ name = attr.name;
6800
+ value = trim(attr.value);
6509
6801
 
6510
- var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
6511
- if (directiveIsMultiElement(directiveNName)) {
6512
- if (ngAttrName === directiveNName + 'Start') {
6513
- attrStartName = name;
6514
- attrEndName = name.substr(0, name.length - 5) + 'end';
6515
- name = name.substr(0, name.length - 6);
6516
- }
6517
- }
6802
+ // support ngAttr attribute binding
6803
+ ngAttrName = directiveNormalize(name);
6804
+ if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
6805
+ name = snake_case(ngAttrName.substr(6), '-');
6806
+ }
6518
6807
 
6519
- nName = directiveNormalize(name.toLowerCase());
6520
- attrsMap[nName] = name;
6521
- if (isNgAttr || !attrs.hasOwnProperty(nName)) {
6522
- attrs[nName] = value;
6523
- if (getBooleanAttrName(node, nName)) {
6524
- attrs[nName] = true; // presence means true
6525
- }
6808
+ var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
6809
+ if (directiveIsMultiElement(directiveNName)) {
6810
+ if (ngAttrName === directiveNName + 'Start') {
6811
+ attrStartName = name;
6812
+ attrEndName = name.substr(0, name.length - 5) + 'end';
6813
+ name = name.substr(0, name.length - 6);
6526
6814
  }
6527
- addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
6528
- addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
6529
- attrEndName);
6530
6815
  }
6816
+
6817
+ nName = directiveNormalize(name.toLowerCase());
6818
+ attrsMap[nName] = name;
6819
+ if (isNgAttr || !attrs.hasOwnProperty(nName)) {
6820
+ attrs[nName] = value;
6821
+ if (getBooleanAttrName(node, nName)) {
6822
+ attrs[nName] = true; // presence means true
6823
+ }
6824
+ }
6825
+ addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
6826
+ addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
6827
+ attrEndName);
6531
6828
  }
6532
6829
 
6533
6830
  // use class as directive
@@ -6542,10 +6839,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6542
6839
  }
6543
6840
  }
6544
6841
  break;
6545
- case 3: /* Text Node */
6842
+ case NODE_TYPE_TEXT: /* Text Node */
6546
6843
  addTextInterpolateDirective(directives, node.nodeValue);
6547
6844
  break;
6548
- case 8: /* Comment */
6845
+ case NODE_TYPE_COMMENT: /* Comment */
6549
6846
  try {
6550
6847
  match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
6551
6848
  if (match) {
@@ -6585,7 +6882,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6585
6882
  "Unterminated attribute, found '{0}' but no matching '{1}' found.",
6586
6883
  attrStart, attrEnd);
6587
6884
  }
6588
- if (node.nodeType == 1 /** Element **/) {
6885
+ if (node.nodeType == NODE_TYPE_ELEMENT) {
6589
6886
  if (node.hasAttribute(attrStart)) depth++;
6590
6887
  if (node.hasAttribute(attrEnd)) depth--;
6591
6888
  }
@@ -6764,11 +7061,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6764
7061
  if (jqLiteIsTextNode(directiveValue)) {
6765
7062
  $template = [];
6766
7063
  } else {
6767
- $template = jqLite(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
7064
+ $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
6768
7065
  }
6769
7066
  compileNode = $template[0];
6770
7067
 
6771
- if ($template.length != 1 || compileNode.nodeType !== 1) {
7068
+ if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
6772
7069
  throw $compileMinErr('tplrt',
6773
7070
  "Template for directive '{0}' must have exactly one root element. {1}",
6774
7071
  directiveName, '');
@@ -6872,14 +7169,26 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6872
7169
 
6873
7170
  function getControllers(directiveName, require, $element, elementControllers) {
6874
7171
  var value, retrievalMethod = 'data', optional = false;
7172
+ var $searchElement = $element;
7173
+ var match;
6875
7174
  if (isString(require)) {
6876
- while((value = require.charAt(0)) == '^' || value == '?') {
6877
- require = require.substr(1);
6878
- if (value == '^') {
6879
- retrievalMethod = 'inheritedData';
6880
- }
6881
- optional = optional || value == '?';
7175
+ match = require.match(REQUIRE_PREFIX_REGEXP);
7176
+ require = require.substring(match[0].length);
7177
+
7178
+ if (match[3]) {
7179
+ if (match[1]) match[3] = null;
7180
+ else match[1] = match[3];
7181
+ }
7182
+ if (match[1] === '^') {
7183
+ retrievalMethod = 'inheritedData';
7184
+ } else if (match[1] === '^^') {
7185
+ retrievalMethod = 'inheritedData';
7186
+ $searchElement = $element.parent();
7187
+ }
7188
+ if (match[2] === '?') {
7189
+ optional = true;
6882
7190
  }
7191
+
6883
7192
  value = null;
6884
7193
 
6885
7194
  if (elementControllers && retrievalMethod === 'data') {
@@ -6887,7 +7196,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6887
7196
  value = value.instance;
6888
7197
  }
6889
7198
  }
6890
- value = value || $element[retrievalMethod]('$' + require + 'Controller');
7199
+ value = value || $searchElement[retrievalMethod]('$' + require + 'Controller');
6891
7200
 
6892
7201
  if (!value && !optional) {
6893
7202
  throw $compileMinErr('ctreq',
@@ -7093,7 +7402,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
7093
7402
  if (!futureParentElement) {
7094
7403
  futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
7095
7404
  }
7096
- return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement);
7405
+ return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
7097
7406
  }
7098
7407
  }
7099
7408
  }
@@ -7234,11 +7543,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
7234
7543
  if (jqLiteIsTextNode(content)) {
7235
7544
  $template = [];
7236
7545
  } else {
7237
- $template = jqLite(wrapTemplate(templateNamespace, trim(content)));
7546
+ $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
7238
7547
  }
7239
7548
  compileNode = $template[0];
7240
7549
 
7241
- if ($template.length != 1 || compileNode.nodeType !== 1) {
7550
+ if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
7242
7551
  throw $compileMinErr('tplrt',
7243
7552
  "Template for directive '{0}' must have exactly one root element. {1}",
7244
7553
  origAsyncDirective.name, templateUrl);
@@ -7277,6 +7586,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
7277
7586
  boundTranscludeFn = linkQueue.shift(),
7278
7587
  linkNode = $compileNode[0];
7279
7588
 
7589
+ if (scope.$$destroyed) continue;
7590
+
7280
7591
  if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
7281
7592
  var oldClasses = beforeTemplateLinkNode.className;
7282
7593
 
@@ -7303,6 +7614,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
7303
7614
 
7304
7615
  return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
7305
7616
  var childBoundTranscludeFn = boundTranscludeFn;
7617
+ if (scope.$$destroyed) return;
7306
7618
  if (linkQueue) {
7307
7619
  linkQueue.push(scope);
7308
7620
  linkQueue.push(node);
@@ -7419,6 +7731,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
7419
7731
  "ng- versions (such as ng-click instead of onclick) instead.");
7420
7732
  }
7421
7733
 
7734
+ // If the attribute was removed, then we are done
7735
+ if (!attr[name]) {
7736
+ return;
7737
+ }
7738
+
7422
7739
  // we need to interpolate again, in case the attribute value has been updated
7423
7740
  // (e.g. by another directive's compile function)
7424
7741
  interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name),
@@ -7646,6 +7963,23 @@ function tokenDifference(str1, str2) {
7646
7963
  return values;
7647
7964
  }
7648
7965
 
7966
+ function removeComments(jqNodes) {
7967
+ jqNodes = jqLite(jqNodes);
7968
+ var i = jqNodes.length;
7969
+
7970
+ if (i <= 1) {
7971
+ return jqNodes;
7972
+ }
7973
+
7974
+ while (i--) {
7975
+ var node = jqNodes[i];
7976
+ if (node.nodeType === NODE_TYPE_COMMENT) {
7977
+ splice.call(jqNodes, i, 1);
7978
+ }
7979
+ }
7980
+ return jqNodes;
7981
+ }
7982
+
7649
7983
  /**
7650
7984
  * @ngdoc provider
7651
7985
  * @name $controllerProvider
@@ -7949,7 +8283,8 @@ function $HttpProvider() {
7949
8283
  var JSON_START = /^\s*(\[|\{[^\{])/,
7950
8284
  JSON_END = /[\}\]]\s*$/,
7951
8285
  PROTECTION_PREFIX = /^\)\]\}',?\n/,
7952
- CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'};
8286
+ APPLICATION_JSON = 'application/json',
8287
+ CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
7953
8288
 
7954
8289
  /**
7955
8290
  * @ngdoc property
@@ -7974,12 +8309,15 @@ function $HttpProvider() {
7974
8309
  **/
7975
8310
  var defaults = this.defaults = {
7976
8311
  // transform incoming response data
7977
- transformResponse: [function(data) {
8312
+ transformResponse: [function defaultHttpResponseTransform(data, headers) {
7978
8313
  if (isString(data)) {
7979
8314
  // strip json vulnerability protection prefix
7980
8315
  data = data.replace(PROTECTION_PREFIX, '');
7981
- if (JSON_START.test(data) && JSON_END.test(data))
8316
+ var contentType = headers('Content-Type');
8317
+ if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) ||
8318
+ (JSON_START.test(data) && JSON_END.test(data))) {
7982
8319
  data = fromJson(data);
8320
+ }
7983
8321
  }
7984
8322
  return data;
7985
8323
  }],
@@ -8937,18 +9275,8 @@ function $HttpProvider() {
8937
9275
  }];
8938
9276
  }
8939
9277
 
8940
- function createXhr(method) {
8941
- //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest
8942
- //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest
8943
- //if it is available
8944
- if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) ||
8945
- !window.XMLHttpRequest)) {
8946
- return new window.ActiveXObject("Microsoft.XMLHTTP");
8947
- } else if (window.XMLHttpRequest) {
8948
- return new window.XMLHttpRequest();
8949
- }
8950
-
8951
- throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest.");
9278
+ function createXhr() {
9279
+ return new window.XMLHttpRequest();
8952
9280
  }
8953
9281
 
8954
9282
  /**
@@ -8974,11 +9302,8 @@ function $HttpBackendProvider() {
8974
9302
  }
8975
9303
 
8976
9304
  function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
8977
- var ABORTED = -1;
8978
-
8979
9305
  // TODO(vojta): fix the signature
8980
9306
  return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
8981
- var status;
8982
9307
  $browser.$$incOutstandingRequestCount();
8983
9308
  url = url || $browser.url();
8984
9309
 
@@ -8996,7 +9321,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
8996
9321
  });
8997
9322
  } else {
8998
9323
 
8999
- var xhr = createXhr(method);
9324
+ var xhr = createXhr();
9000
9325
 
9001
9326
  xhr.open(method, url, true);
9002
9327
  forEach(headers, function(value, key) {
@@ -9005,44 +9330,39 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
9005
9330
  }
9006
9331
  });
9007
9332
 
9008
- // In IE6 and 7, this might be called synchronously when xhr.send below is called and the
9009
- // response is in the cache. the promise api will ensure that to the app code the api is
9010
- // always async
9011
- xhr.onreadystatechange = function() {
9012
- // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by
9013
- // xhrs that are resolved while the app is in the background (see #5426).
9014
- // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before
9015
- // continuing
9016
- //
9017
- // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and
9018
- // Safari respectively.
9019
- if (xhr && xhr.readyState == 4) {
9020
- var responseHeaders = null,
9021
- response = null,
9022
- statusText = '';
9023
-
9024
- if(status !== ABORTED) {
9025
- responseHeaders = xhr.getAllResponseHeaders();
9026
-
9027
- // responseText is the old-school way of retrieving response (supported by IE8 & 9)
9028
- // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
9029
- response = ('response' in xhr) ? xhr.response : xhr.responseText;
9030
- }
9333
+ xhr.onload = function requestLoaded() {
9334
+ var statusText = xhr.statusText || '';
9031
9335
 
9032
- // Accessing statusText on an aborted xhr object will
9033
- // throw an 'c00c023f error' in IE9 and lower, don't touch it.
9034
- if (!(status === ABORTED && msie < 10)) {
9035
- statusText = xhr.statusText;
9036
- }
9336
+ // responseText is the old-school way of retrieving response (supported by IE8 & 9)
9337
+ // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
9338
+ var response = ('response' in xhr) ? xhr.response : xhr.responseText;
9037
9339
 
9038
- completeRequest(callback,
9039
- status || xhr.status,
9040
- response,
9041
- responseHeaders,
9042
- statusText);
9340
+ // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
9341
+ var status = xhr.status === 1223 ? 204 : xhr.status;
9342
+
9343
+ // fix status code when it is 0 (0 status is undocumented).
9344
+ // Occurs when accessing file resources or on Android 4.1 stock browser
9345
+ // while retrieving files from application cache.
9346
+ if (status === 0) {
9347
+ status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
9043
9348
  }
9349
+
9350
+ completeRequest(callback,
9351
+ status,
9352
+ response,
9353
+ xhr.getAllResponseHeaders(),
9354
+ statusText);
9044
9355
  };
9045
9356
 
9357
+ var requestError = function () {
9358
+ // The response is always empty
9359
+ // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
9360
+ completeRequest(callback, -1, null, null, '');
9361
+ };
9362
+
9363
+ xhr.onerror = requestError;
9364
+ xhr.onabort = requestError;
9365
+
9046
9366
  if (withCredentials) {
9047
9367
  xhr.withCredentials = true;
9048
9368
  }
@@ -9075,7 +9395,6 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
9075
9395
 
9076
9396
 
9077
9397
  function timeoutRequest() {
9078
- status = ABORTED;
9079
9398
  jsonpDone && jsonpDone();
9080
9399
  xhr && xhr.abort();
9081
9400
  }
@@ -9085,17 +9404,6 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
9085
9404
  timeoutId && $browserDefer.cancel(timeoutId);
9086
9405
  jsonpDone = xhr = null;
9087
9406
 
9088
- // fix status code when it is 0 (0 status is undocumented).
9089
- // Occurs when accessing file resources or on Android 4.1 stock browser
9090
- // while retrieving files from application cache.
9091
- if (status === 0) {
9092
- status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
9093
- }
9094
-
9095
- // normalize IE bug (http://bugs.jquery.com/ticket/1450)
9096
- status = status === 1223 ? 204 : status;
9097
- statusText = statusText || '';
9098
-
9099
9407
  callback(status, response, headersString, statusText);
9100
9408
  $browser.$$completeOutstandingRequest(noop);
9101
9409
  }
@@ -10047,9 +10355,7 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {
10047
10355
  }
10048
10356
 
10049
10357
 
10050
- LocationHashbangInHtml5Url.prototype =
10051
- LocationHashbangUrl.prototype =
10052
- LocationHtml5Url.prototype = {
10358
+ var locationPrototype = {
10053
10359
 
10054
10360
  /**
10055
10361
  * Are we in html5 mode?
@@ -10058,7 +10364,7 @@ LocationHashbangInHtml5Url.prototype =
10058
10364
  $$html5: false,
10059
10365
 
10060
10366
  /**
10061
- * Has any change been replacing ?
10367
+ * Has any change been replacing?
10062
10368
  * @private
10063
10369
  */
10064
10370
  $$replace: false,
@@ -10160,7 +10466,7 @@ LocationHashbangInHtml5Url.prototype =
10160
10466
  * @return {string} path
10161
10467
  */
10162
10468
  path: locationGetterSetter('$$path', function(path) {
10163
- path = path ? path.toString() : '';
10469
+ path = path !== null ? path.toString() : '';
10164
10470
  return path.charAt(0) == '/' ? path : '/' + path;
10165
10471
  }),
10166
10472
 
@@ -10257,7 +10563,7 @@ LocationHashbangInHtml5Url.prototype =
10257
10563
  * @return {string} hash
10258
10564
  */
10259
10565
  hash: locationGetterSetter('$$hash', function(hash) {
10260
- return hash ? hash.toString() : '';
10566
+ return hash !== null ? hash.toString() : '';
10261
10567
  }),
10262
10568
 
10263
10569
  /**
@@ -10274,6 +10580,46 @@ LocationHashbangInHtml5Url.prototype =
10274
10580
  }
10275
10581
  };
10276
10582
 
10583
+ forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function (Location) {
10584
+ Location.prototype = Object.create(locationPrototype);
10585
+
10586
+ /**
10587
+ * @ngdoc method
10588
+ * @name $location#state
10589
+ *
10590
+ * @description
10591
+ * This method is getter / setter.
10592
+ *
10593
+ * Return the history state object when called without any parameter.
10594
+ *
10595
+ * Change the history state object when called with one parameter and return `$location`.
10596
+ * The state object is later passed to `pushState` or `replaceState`.
10597
+ *
10598
+ * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
10599
+ * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
10600
+ * older browsers (like IE9 or Android < 4.0), don't use this method.
10601
+ *
10602
+ * @param {object=} state State object for pushState or replaceState
10603
+ * @return {object} state
10604
+ */
10605
+ Location.prototype.state = function(state) {
10606
+ if (!arguments.length)
10607
+ return this.$$state;
10608
+
10609
+ if (Location !== LocationHtml5Url || !this.$$html5) {
10610
+ throw $locationMinErr('nostate', 'History API state support is available only ' +
10611
+ 'in HTML5 mode and only in browsers supporting HTML5 History API');
10612
+ }
10613
+ // The user might modify `stateObject` after invoking `$location.state(stateObject)`
10614
+ // but we're changing the $$state reference to $browser.state() during the $digest
10615
+ // so the modification window is narrow.
10616
+ this.$$state = isUndefined(state) ? null : state;
10617
+
10618
+ return this;
10619
+ };
10620
+ });
10621
+
10622
+
10277
10623
  function locationGetter(property) {
10278
10624
  return function() {
10279
10625
  return this[property];
@@ -10330,7 +10676,8 @@ function $LocationProvider(){
10330
10676
  var hashPrefix = '',
10331
10677
  html5Mode = {
10332
10678
  enabled: false,
10333
- requireBase: true
10679
+ requireBase: true,
10680
+ rewriteLinks: true
10334
10681
  };
10335
10682
 
10336
10683
  /**
@@ -10354,15 +10701,17 @@ function $LocationProvider(){
10354
10701
  * @name $locationProvider#html5Mode
10355
10702
  * @description
10356
10703
  * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
10357
- * If object, sets `enabled` and `requireBase` to respective values.
10358
- * - **enabled** – `{boolean}` – Sets `html5Mode.enabled`. If true, will rely on
10359
- * `history.pushState` to change urls where supported. Will fall back to hash-prefixed paths
10360
- * in browsers that do not support `pushState`.
10361
- * - **requireBase** - `{boolean}` - Sets `html5Mode.requireBase` (default: `true`). When
10362
- * html5Mode is enabled, specifies whether or not a <base> tag is required to be present. If
10363
- * `enabled` and `requireBase` are true, and a base tag is not present, an error will be
10364
- * thrown when `$location` is injected. See the
10365
- * {@link guide/$location $location guide for more information}
10704
+ * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
10705
+ * properties:
10706
+ * - **enabled** – `{boolean}` (default: false) If true, will rely on `history.pushState` to
10707
+ * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
10708
+ * support `pushState`.
10709
+ * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
10710
+ * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
10711
+ * true, and a base tag is not present, an error will be thrown when `$location` is injected.
10712
+ * See the {@link guide/$location $location guide for more information}
10713
+ * - **rewriteLinks** - `{boolean}` - (default: `false`) When html5Mode is enabled, disables
10714
+ * url rewriting for relative linksTurns off url rewriting for relative links.
10366
10715
  *
10367
10716
  * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
10368
10717
  */
@@ -10371,12 +10720,19 @@ function $LocationProvider(){
10371
10720
  html5Mode.enabled = mode;
10372
10721
  return this;
10373
10722
  } else if (isObject(mode)) {
10374
- html5Mode.enabled = isBoolean(mode.enabled) ?
10375
- mode.enabled :
10376
- html5Mode.enabled;
10377
- html5Mode.requireBase = isBoolean(mode.requireBase) ?
10378
- mode.requireBase :
10379
- html5Mode.requireBase;
10723
+
10724
+ if (isBoolean(mode.enabled)) {
10725
+ html5Mode.enabled = mode.enabled;
10726
+ }
10727
+
10728
+ if (isBoolean(mode.requireBase)) {
10729
+ html5Mode.requireBase = mode.requireBase;
10730
+ }
10731
+
10732
+ if (isBoolean(mode.rewriteLinks)) {
10733
+ html5Mode.rewriteLinks = mode.rewriteLinks;
10734
+ }
10735
+
10380
10736
  return this;
10381
10737
  } else {
10382
10738
  return html5Mode;
@@ -10388,14 +10744,21 @@ function $LocationProvider(){
10388
10744
  * @name $location#$locationChangeStart
10389
10745
  * @eventType broadcast on root scope
10390
10746
  * @description
10391
- * Broadcasted before a URL will change. This change can be prevented by calling
10747
+ * Broadcasted before a URL will change.
10748
+ *
10749
+ * This change can be prevented by calling
10392
10750
  * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
10393
10751
  * details about event object. Upon successful change
10394
10752
  * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
10395
10753
  *
10754
+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
10755
+ * the browser supports the HTML5 History API.
10756
+ *
10396
10757
  * @param {Object} angularEvent Synthetic event object.
10397
10758
  * @param {string} newUrl New URL
10398
10759
  * @param {string=} oldUrl URL that was before it was changed.
10760
+ * @param {string=} newState New history state object
10761
+ * @param {string=} oldState History state object that was before it was changed.
10399
10762
  */
10400
10763
 
10401
10764
  /**
@@ -10405,9 +10768,14 @@ function $LocationProvider(){
10405
10768
  * @description
10406
10769
  * Broadcasted after a URL was changed.
10407
10770
  *
10771
+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
10772
+ * the browser supports the HTML5 History API.
10773
+ *
10408
10774
  * @param {Object} angularEvent Synthetic event object.
10409
10775
  * @param {string} newUrl New URL
10410
10776
  * @param {string=} oldUrl URL that was before it was changed.
10777
+ * @param {string=} newState New history state object
10778
+ * @param {string=} oldState History state object that was before it was changed.
10411
10779
  */
10412
10780
 
10413
10781
  this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
@@ -10432,13 +10800,34 @@ function $LocationProvider(){
10432
10800
  $location = new LocationMode(appBase, '#' + hashPrefix);
10433
10801
  $location.$$parseLinkUrl(initialUrl, initialUrl);
10434
10802
 
10803
+ $location.$$state = $browser.state();
10804
+
10435
10805
  var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
10436
10806
 
10807
+ function setBrowserUrlWithFallback(url, replace, state) {
10808
+ var oldUrl = $location.url();
10809
+ var oldState = $location.$$state;
10810
+ try {
10811
+ $browser.url(url, replace, state);
10812
+
10813
+ // Make sure $location.state() returns referentially identical (not just deeply equal)
10814
+ // state object; this makes possible quick checking if the state changed in the digest
10815
+ // loop. Checking deep equality would be too expensive.
10816
+ $location.$$state = $browser.state();
10817
+ } catch (e) {
10818
+ // Restore old values if pushState fails
10819
+ $location.url(oldUrl);
10820
+ $location.$$state = oldState;
10821
+
10822
+ throw e;
10823
+ }
10824
+ }
10825
+
10437
10826
  $rootElement.on('click', function(event) {
10438
10827
  // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
10439
10828
  // currently we open nice url link and redirect then
10440
10829
 
10441
- if (event.ctrlKey || event.metaKey || event.which == 2) return;
10830
+ if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2) return;
10442
10831
 
10443
10832
  var elm = jqLite(event.target);
10444
10833
 
@@ -10464,6 +10853,9 @@ function $LocationProvider(){
10464
10853
 
10465
10854
  if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
10466
10855
  if ($location.$$parseLinkUrl(absHref, relHref)) {
10856
+ // We do a preventDefault for all urls that are part of the angular application,
10857
+ // in html5mode and also without, so that we are able to abort navigation without
10858
+ // getting double entries in the location history.
10467
10859
  event.preventDefault();
10468
10860
  // update location manually
10469
10861
  if ($location.absUrl() != $browser.url()) {
@@ -10481,52 +10873,63 @@ function $LocationProvider(){
10481
10873
  $browser.url($location.absUrl(), true);
10482
10874
  }
10483
10875
 
10484
- // update $location when $browser url changes
10485
- $browser.onUrlChange(function(newUrl) {
10486
- if ($location.absUrl() != newUrl) {
10487
- $rootScope.$evalAsync(function() {
10488
- var oldUrl = $location.absUrl();
10876
+ var initializing = true;
10489
10877
 
10490
- $location.$$parse(newUrl);
10491
- if ($rootScope.$broadcast('$locationChangeStart', newUrl,
10492
- oldUrl).defaultPrevented) {
10493
- $location.$$parse(oldUrl);
10494
- $browser.url(oldUrl);
10495
- } else {
10496
- afterLocationChange(oldUrl);
10497
- }
10498
- });
10499
- if (!$rootScope.$$phase) $rootScope.$digest();
10500
- }
10878
+ // update $location when $browser url changes
10879
+ $browser.onUrlChange(function(newUrl, newState) {
10880
+ $rootScope.$evalAsync(function() {
10881
+ var oldUrl = $location.absUrl();
10882
+ var oldState = $location.$$state;
10883
+
10884
+ $location.$$parse(newUrl);
10885
+ $location.$$state = newState;
10886
+ if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
10887
+ newState, oldState).defaultPrevented) {
10888
+ $location.$$parse(oldUrl);
10889
+ $location.$$state = oldState;
10890
+ setBrowserUrlWithFallback(oldUrl, false, oldState);
10891
+ } else {
10892
+ initializing = false;
10893
+ afterLocationChange(oldUrl, oldState);
10894
+ }
10895
+ });
10896
+ if (!$rootScope.$$phase) $rootScope.$digest();
10501
10897
  });
10502
10898
 
10503
10899
  // update browser
10504
- var changeCounter = 0;
10505
10900
  $rootScope.$watch(function $locationWatch() {
10506
10901
  var oldUrl = $browser.url();
10902
+ var oldState = $browser.state();
10507
10903
  var currentReplace = $location.$$replace;
10508
10904
 
10509
- if (!changeCounter || oldUrl != $location.absUrl()) {
10510
- changeCounter++;
10905
+ if (initializing || oldUrl !== $location.absUrl() ||
10906
+ ($location.$$html5 && $sniffer.history && oldState !== $location.$$state)) {
10907
+ initializing = false;
10908
+
10511
10909
  $rootScope.$evalAsync(function() {
10512
- if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
10513
- defaultPrevented) {
10910
+ if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl,
10911
+ $location.$$state, oldState).defaultPrevented) {
10514
10912
  $location.$$parse(oldUrl);
10913
+ $location.$$state = oldState;
10515
10914
  } else {
10516
- $browser.url($location.absUrl(), currentReplace);
10517
- afterLocationChange(oldUrl);
10915
+ setBrowserUrlWithFallback($location.absUrl(), currentReplace,
10916
+ oldState === $location.$$state ? null : $location.$$state);
10917
+ afterLocationChange(oldUrl, oldState);
10518
10918
  }
10519
10919
  });
10520
10920
  }
10921
+
10521
10922
  $location.$$replace = false;
10522
10923
 
10523
- return changeCounter;
10924
+ // we don't need to return anything because $evalAsync will make the digest loop dirty when
10925
+ // there is a change
10524
10926
  });
10525
10927
 
10526
10928
  return $location;
10527
10929
 
10528
- function afterLocationChange(oldUrl) {
10529
- $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
10930
+ function afterLocationChange(oldUrl, oldState) {
10931
+ $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
10932
+ $location.$$state, oldState);
10530
10933
  }
10531
10934
  }];
10532
10935
  }
@@ -10785,6 +11188,11 @@ forEach({
10785
11188
  CONSTANTS[name] = constantGetter;
10786
11189
  });
10787
11190
 
11191
+ //Not quite a constant, but can be lex/parsed the same
11192
+ CONSTANTS['this'] = function(self) { return self; };
11193
+ CONSTANTS['this'].sharedGetter = true;
11194
+
11195
+
10788
11196
  //Operators - will be wrapped by binaryFn/unaryFn/assignment/filter
10789
11197
  var OPERATORS = extend(createMap(), {
10790
11198
  /* jshint bitwise : false */
@@ -12648,14 +13056,11 @@ function $RootScopeProvider(){
12648
13056
  this.$$phase = this.$parent = this.$$watchers =
12649
13057
  this.$$nextSibling = this.$$prevSibling =
12650
13058
  this.$$childHead = this.$$childTail = null;
12651
- this['this'] = this.$root = this;
13059
+ this.$root = this;
12652
13060
  this.$$destroyed = false;
12653
- this.$$asyncQueue = [];
12654
- this.$$postDigestQueue = [];
12655
13061
  this.$$listeners = {};
12656
13062
  this.$$listenerCount = {};
12657
13063
  this.$$isolateBindings = null;
12658
- this.$$applyAsyncQueue = [];
12659
13064
  }
12660
13065
 
12661
13066
  /**
@@ -12704,18 +13109,23 @@ function $RootScopeProvider(){
12704
13109
  * When creating widgets, it is useful for the widget to not accidentally read parent
12705
13110
  * state.
12706
13111
  *
13112
+ * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
13113
+ * of the newly created scope. Defaults to `this` scope if not provided.
13114
+ * This is used when creating a transclude scope to correctly place it
13115
+ * in the scope hierarchy while maintaining the correct prototypical
13116
+ * inheritance.
13117
+ *
12707
13118
  * @returns {Object} The newly created child scope.
12708
13119
  *
12709
13120
  */
12710
- $new: function(isolate) {
13121
+ $new: function(isolate, parent) {
12711
13122
  var child;
12712
13123
 
13124
+ parent = parent || this;
13125
+
12713
13126
  if (isolate) {
12714
13127
  child = new Scope();
12715
13128
  child.$root = this.$root;
12716
- // ensure that there is just one async queue per $rootScope and its children
12717
- child.$$asyncQueue = this.$$asyncQueue;
12718
- child.$$postDigestQueue = this.$$postDigestQueue;
12719
13129
  } else {
12720
13130
  // Only create a child scope class if somebody asks for one,
12721
13131
  // but cache it to allow the VM to optimize lookups.
@@ -12732,16 +13142,27 @@ function $RootScopeProvider(){
12732
13142
  }
12733
13143
  child = new this.$$ChildScope();
12734
13144
  }
12735
- child['this'] = child;
12736
- child.$parent = this;
12737
- child.$$prevSibling = this.$$childTail;
12738
- if (this.$$childHead) {
12739
- this.$$childTail.$$nextSibling = child;
12740
- this.$$childTail = child;
13145
+ child.$parent = parent;
13146
+ child.$$prevSibling = parent.$$childTail;
13147
+ if (parent.$$childHead) {
13148
+ parent.$$childTail.$$nextSibling = child;
13149
+ parent.$$childTail = child;
12741
13150
  } else {
12742
- this.$$childHead = this.$$childTail = child;
13151
+ parent.$$childHead = parent.$$childTail = child;
12743
13152
  }
13153
+
13154
+ // When the new scope is not isolated or we inherit from `this`, and
13155
+ // the parent scope is destroyed, the property `$$destroyed` is inherited
13156
+ // prototypically. In all other cases, this property needs to be set
13157
+ // when the parent scope is destroyed.
13158
+ // The listener needs to be added after the parent is set
13159
+ if (isolate || parent != this) child.$on('$destroy', destroyChild);
13160
+
12744
13161
  return child;
13162
+
13163
+ function destroyChild() {
13164
+ child.$$destroyed = true;
13165
+ }
12745
13166
  },
12746
13167
 
12747
13168
  /**
@@ -13217,8 +13638,6 @@ function $RootScopeProvider(){
13217
13638
  $digest: function() {
13218
13639
  var watch, value, last,
13219
13640
  watchers,
13220
- asyncQueue = this.$$asyncQueue,
13221
- postDigestQueue = this.$$postDigestQueue,
13222
13641
  length,
13223
13642
  dirty, ttl = TTL,
13224
13643
  next, current, target = this,
@@ -13383,6 +13802,10 @@ function $RootScopeProvider(){
13383
13802
  if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
13384
13803
  if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
13385
13804
 
13805
+ // Disable listeners, watchers and apply/digest methods
13806
+ this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
13807
+ this.$on = this.$watch = this.$watchGroup = function() { return noop; };
13808
+ this.$$listeners = {};
13386
13809
 
13387
13810
  // All of the code below is bogus code that works around V8's memory leak via optimized code
13388
13811
  // and inline caches.
@@ -13393,15 +13816,7 @@ function $RootScopeProvider(){
13393
13816
  // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
13394
13817
 
13395
13818
  this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
13396
- this.$$childTail = this.$root = null;
13397
-
13398
- // don't reset these to null in case some async task tries to register a listener/watch/task
13399
- this.$$listeners = {};
13400
- this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = [];
13401
-
13402
- // prevent NPEs since these methods have references to properties we nulled out
13403
- this.$destroy = this.$digest = this.$apply = noop;
13404
- this.$on = this.$watch = this.$watchGroup = function() { return noop; };
13819
+ this.$$childTail = this.$root = this.$$watchers = null;
13405
13820
  },
13406
13821
 
13407
13822
  /**
@@ -13468,19 +13883,19 @@ function $RootScopeProvider(){
13468
13883
  $evalAsync: function(expr) {
13469
13884
  // if we are outside of an $digest loop and this is the first time we are scheduling async
13470
13885
  // task also schedule async auto-flush
13471
- if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
13886
+ if (!$rootScope.$$phase && !asyncQueue.length) {
13472
13887
  $browser.defer(function() {
13473
- if ($rootScope.$$asyncQueue.length) {
13888
+ if (asyncQueue.length) {
13474
13889
  $rootScope.$digest();
13475
13890
  }
13476
13891
  });
13477
13892
  }
13478
13893
 
13479
- this.$$asyncQueue.push({scope: this, expression: expr});
13894
+ asyncQueue.push({scope: this, expression: expr});
13480
13895
  },
13481
13896
 
13482
13897
  $$postDigest : function(fn) {
13483
- this.$$postDigestQueue.push(fn);
13898
+ postDigestQueue.push(fn);
13484
13899
  },
13485
13900
 
13486
13901
  /**
@@ -13564,7 +13979,7 @@ function $RootScopeProvider(){
13564
13979
  */
13565
13980
  $applyAsync: function(expr) {
13566
13981
  var scope = this;
13567
- expr && $rootScope.$$applyAsyncQueue.push($applyAsyncExpression);
13982
+ expr && applyAsyncQueue.push($applyAsyncExpression);
13568
13983
  scheduleApplyAsync();
13569
13984
 
13570
13985
  function $applyAsyncExpression() {
@@ -13773,6 +14188,11 @@ function $RootScopeProvider(){
13773
14188
 
13774
14189
  var $rootScope = new Scope();
13775
14190
 
14191
+ //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
14192
+ var asyncQueue = $rootScope.$$asyncQueue = [];
14193
+ var postDigestQueue = $rootScope.$$postDigestQueue = [];
14194
+ var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
14195
+
13776
14196
  return $rootScope;
13777
14197
 
13778
14198
 
@@ -13806,10 +14226,9 @@ function $RootScopeProvider(){
13806
14226
  function initWatchVal() {}
13807
14227
 
13808
14228
  function flushApplyAsync() {
13809
- var queue = $rootScope.$$applyAsyncQueue;
13810
- while (queue.length) {
14229
+ while (applyAsyncQueue.length) {
13811
14230
  try {
13812
- queue.shift()();
14231
+ applyAsyncQueue.shift()();
13813
14232
  } catch(e) {
13814
14233
  $exceptionHandler(e);
13815
14234
  }
@@ -13888,12 +14307,9 @@ function $$SanitizeUriProvider() {
13888
14307
  return function sanitizeUri(uri, isImage) {
13889
14308
  var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
13890
14309
  var normalizedVal;
13891
- // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
13892
- if (!msie || msie >= 8 ) {
13893
- normalizedVal = urlResolve(uri).href;
13894
- if (normalizedVal !== '' && !normalizedVal.match(regex)) {
13895
- return 'unsafe:'+normalizedVal;
13896
- }
14310
+ normalizedVal = urlResolve(uri).href;
14311
+ if (normalizedVal !== '' && !normalizedVal.match(regex)) {
14312
+ return 'unsafe:'+normalizedVal;
13897
14313
  }
13898
14314
  return uri;
13899
14315
  };
@@ -14965,7 +15381,6 @@ function $SceProvider() {
14965
15381
  * @requires $document
14966
15382
  *
14967
15383
  * @property {boolean} history Does the browser support html5 history api ?
14968
- * @property {boolean} hashchange Does the browser support hashchange event ?
14969
15384
  * @property {boolean} transitions Does the browser support CSS transition events ?
14970
15385
  * @property {boolean} animations Does the browser support CSS animation events ?
14971
15386
  *
@@ -15022,9 +15437,6 @@ function $SnifferProvider() {
15022
15437
  // jshint -W018
15023
15438
  history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
15024
15439
  // jshint +W018
15025
- hashchange: 'onhashchange' in $window &&
15026
- // IE8 compatible mode lies
15027
- (!documentMode || documentMode > 7),
15028
15440
  hasEvent: function(event) {
15029
15441
  // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
15030
15442
  // it. In particular the event is not fired when backspace or delete key are pressed or
@@ -16494,7 +16906,7 @@ function limitToFilter(){
16494
16906
  * correctly, make sure they are actually being saved as numbers and not strings.
16495
16907
  *
16496
16908
  * @param {Array} array The array to sort.
16497
- * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
16909
+ * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
16498
16910
  * used by the comparator to determine the order of elements.
16499
16911
  *
16500
16912
  * Can be one of:
@@ -16507,10 +16919,13 @@ function limitToFilter(){
16507
16919
  * is interpreted as a property name to be used in comparisons (for example `"special name"`
16508
16920
  * to sort object by the value of their `special name` property). An expression can be
16509
16921
  * optionally prefixed with `+` or `-` to control ascending or descending sort order
16510
- * (for example, `+name` or `-name`).
16922
+ * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
16923
+ * element itself is used to compare where sorting.
16511
16924
  * - `Array`: An array of function or string predicates. The first predicate in the array
16512
16925
  * is used for sorting, but when two items are equivalent, the next predicate is used.
16513
16926
  *
16927
+ * If the predicate is missing or empty then it defaults to `'+'`.
16928
+ *
16514
16929
  * @param {boolean=} reverse Reverse the order of the array.
16515
16930
  * @returns {Array} Sorted copy of the source array.
16516
16931
  *
@@ -16599,8 +17014,8 @@ orderByFilter.$inject = ['$parse'];
16599
17014
  function orderByFilter($parse){
16600
17015
  return function(array, sortPredicate, reverseOrder) {
16601
17016
  if (!(isArrayLike(array))) return array;
16602
- if (!sortPredicate) return array;
16603
17017
  sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
17018
+ if (sortPredicate.length === 0) { sortPredicate = ['+']; }
16604
17019
  sortPredicate = sortPredicate.map(function(predicate){
16605
17020
  var descending = false, get = predicate || identity;
16606
17021
  if (isString(predicate)) {
@@ -16608,6 +17023,12 @@ function orderByFilter($parse){
16608
17023
  descending = predicate.charAt(0) == '-';
16609
17024
  predicate = predicate.substring(1);
16610
17025
  }
17026
+ if ( predicate === '' ) {
17027
+ // Effectively no predicate was passed so we compare identity
17028
+ return reverseComparator(function(a,b) {
17029
+ return compare(a, b);
17030
+ }, descending);
17031
+ }
16611
17032
  get = $parse(predicate);
16612
17033
  if (get.constant) {
16613
17034
  var key = get();
@@ -16683,22 +17104,6 @@ function ngDirective(directive) {
16683
17104
  var htmlAnchorDirective = valueFn({
16684
17105
  restrict: 'E',
16685
17106
  compile: function(element, attr) {
16686
-
16687
- if (msie <= 8) {
16688
-
16689
- // turn <a href ng-click="..">link</a> into a stylable link in IE
16690
- // but only if it doesn't have name attribute, in which case it's an anchor
16691
- if (!attr.href && !attr.name) {
16692
- attr.$set('href', '');
16693
- }
16694
-
16695
- // add a comment node to anchors to workaround IE bug that causes element content to be reset
16696
- // to new attribute content if attribute is updated with value containing @ and element also
16697
- // contains value with @
16698
- // see issue #1949
16699
- element.append(document.createComment('IE fix'));
16700
- }
16701
-
16702
17107
  if (!attr.href && !attr.xlinkHref && !attr.name) {
16703
17108
  return function(scope, element) {
16704
17109
  // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
@@ -17146,11 +17551,9 @@ var nullFormCtrl = {
17146
17551
  $$renameControl: nullFormRenameControl,
17147
17552
  $removeControl: noop,
17148
17553
  $setValidity: noop,
17149
- $$setPending: noop,
17150
17554
  $setDirty: noop,
17151
17555
  $setPristine: noop,
17152
- $setSubmitted: noop,
17153
- $$clearControlValidity: noop
17556
+ $setSubmitted: noop
17154
17557
  },
17155
17558
  SUBMITTED_CLASS = 'ng-submitted';
17156
17559
 
@@ -17215,9 +17618,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
17215
17618
 
17216
17619
  parentForm.$addControl(form);
17217
17620
 
17218
- // Setup initial state of the control
17219
- element.addClass(PRISTINE_CLASS);
17220
-
17221
17621
  /**
17222
17622
  * @ngdoc method
17223
17623
  * @name form.FormController#$rollbackViewValue
@@ -17590,10 +17990,14 @@ var formDirectiveFactory = function(isNgForm) {
17590
17990
  name: 'form',
17591
17991
  restrict: isNgForm ? 'EAC' : 'E',
17592
17992
  controller: FormController,
17593
- compile: function() {
17993
+ compile: function ngFormCompile(formElement) {
17994
+ // Setup initial state of the control
17995
+ formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
17996
+
17594
17997
  return {
17595
- pre: function(scope, formElement, attr, controller) {
17596
- if (!attr.action) {
17998
+ pre: function ngFormPreLink(scope, formElement, attr, controller) {
17999
+ // if `action` attr is not present on the form, prevent the default action (submission)
18000
+ if (!('action' in attr)) {
17597
18001
  // we can't use jq events because if a form is destroyed during submission the default
17598
18002
  // action is not prevented. see #1238
17599
18003
  //
@@ -18747,16 +19151,15 @@ function createDateInputType(type, regexp, parseDate, format) {
18747
19151
  badInputChecker(scope, element, attr, ctrl);
18748
19152
  baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
18749
19153
  var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
19154
+ var previousDate;
18750
19155
 
18751
19156
  ctrl.$$parserName = type;
18752
19157
  ctrl.$parsers.push(function(value) {
18753
19158
  if (ctrl.$isEmpty(value)) return null;
18754
19159
  if (regexp.test(value)) {
18755
- var previousDate = ctrl.$modelValue;
18756
- if (previousDate && timezone === 'UTC') {
18757
- var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
18758
- previousDate = new Date(previousDate.getTime() + timezoneOffset);
18759
- }
19160
+ // Note: We cannot read ctrl.$modelValue, as there might be a different
19161
+ // parser/formatter in the processing chain so that the model
19162
+ // contains some different data format!
18760
19163
  var parsedDate = parseDate(value, previousDate);
18761
19164
  if (timezone === 'UTC') {
18762
19165
  parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
@@ -18767,8 +19170,18 @@ function createDateInputType(type, regexp, parseDate, format) {
18767
19170
  });
18768
19171
 
18769
19172
  ctrl.$formatters.push(function(value) {
18770
- if (isDate(value)) {
19173
+ if (!ctrl.$isEmpty(value)) {
19174
+ if (!isDate(value)) {
19175
+ throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
19176
+ }
19177
+ previousDate = value;
19178
+ if (previousDate && timezone === 'UTC') {
19179
+ var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
19180
+ previousDate = new Date(previousDate.getTime() + timezoneOffset);
19181
+ }
18771
19182
  return $filter('date')(value, format, timezone);
19183
+ } else {
19184
+ previousDate = null;
18772
19185
  }
18773
19186
  return '';
18774
19187
  });
@@ -18794,6 +19207,11 @@ function createDateInputType(type, regexp, parseDate, format) {
18794
19207
  ctrl.$validate();
18795
19208
  });
18796
19209
  }
19210
+ // Override the standard $isEmpty to detect invalid dates as well
19211
+ ctrl.$isEmpty = function(value) {
19212
+ // Invalid Date: getTime() returns NaN
19213
+ return !value || (value.getTime && value.getTime() !== value.getTime());
19214
+ };
18797
19215
 
18798
19216
  function parseObservedDateValue(val) {
18799
19217
  return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
@@ -19108,10 +19526,12 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
19108
19526
  return {
19109
19527
  restrict: 'E',
19110
19528
  require: ['?ngModel'],
19111
- link: function(scope, element, attr, ctrls) {
19112
- if (ctrls[0]) {
19113
- (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
19114
- $browser, $filter, $parse);
19529
+ link: {
19530
+ pre: function(scope, element, attr, ctrls) {
19531
+ if (ctrls[0]) {
19532
+ (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
19533
+ $browser, $filter, $parse);
19534
+ }
19115
19535
  }
19116
19536
  }
19117
19537
  };
@@ -19412,11 +19832,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
19412
19832
  var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
19413
19833
  currentValidationRunId = 0;
19414
19834
 
19415
- // Setup initial state of the control
19416
- $element
19417
- .addClass(PRISTINE_CLASS)
19418
- .addClass(UNTOUCHED_CLASS);
19419
-
19420
19835
  /**
19421
19836
  * @ngdoc method
19422
19837
  * @name ngModel.NgModelController#$setValidity
@@ -19709,14 +20124,17 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
19709
20124
  };
19710
20125
 
19711
20126
  this.$$parseAndValidate = function() {
19712
- var parserValid = true,
19713
- viewValue = ctrl.$$lastCommittedViewValue,
19714
- modelValue = viewValue;
19715
- for(var i = 0; i < ctrl.$parsers.length; i++) {
19716
- modelValue = ctrl.$parsers[i](modelValue);
19717
- if (isUndefined(modelValue)) {
19718
- parserValid = false;
19719
- break;
20127
+ var viewValue = ctrl.$$lastCommittedViewValue;
20128
+ var modelValue = viewValue;
20129
+ var parserValid = isUndefined(modelValue) ? undefined : true;
20130
+
20131
+ if (parserValid) {
20132
+ for(var i = 0; i < ctrl.$parsers.length; i++) {
20133
+ modelValue = ctrl.$parsers[i](modelValue);
20134
+ if (isUndefined(modelValue)) {
20135
+ parserValid = false;
20136
+ break;
20137
+ }
19720
20138
  }
19721
20139
  }
19722
20140
  if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
@@ -20033,42 +20451,51 @@ var ngModelDirective = function() {
20033
20451
  restrict: 'A',
20034
20452
  require: ['ngModel', '^?form', '^?ngModelOptions'],
20035
20453
  controller: NgModelController,
20036
- link: {
20037
- pre: function(scope, element, attr, ctrls) {
20038
- var modelCtrl = ctrls[0],
20039
- formCtrl = ctrls[1] || nullFormCtrl;
20454
+ // Prelink needs to run before any input directive
20455
+ // so that we can set the NgModelOptions in NgModelController
20456
+ // before anyone else uses it.
20457
+ priority: 1,
20458
+ compile: function ngModelCompile(element) {
20459
+ // Setup initial state of the control
20460
+ element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
20040
20461
 
20041
- modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
20462
+ return {
20463
+ pre: function ngModelPreLink(scope, element, attr, ctrls) {
20464
+ var modelCtrl = ctrls[0],
20465
+ formCtrl = ctrls[1] || nullFormCtrl;
20042
20466
 
20043
- // notify others, especially parent forms
20044
- formCtrl.$addControl(modelCtrl);
20467
+ modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
20045
20468
 
20046
- attr.$observe('name', function(newValue) {
20047
- if (modelCtrl.$name !== newValue) {
20048
- formCtrl.$$renameControl(modelCtrl, newValue);
20049
- }
20050
- });
20469
+ // notify others, especially parent forms
20470
+ formCtrl.$addControl(modelCtrl);
20051
20471
 
20052
- scope.$on('$destroy', function() {
20053
- formCtrl.$removeControl(modelCtrl);
20054
- });
20055
- },
20056
- post: function(scope, element, attr, ctrls) {
20057
- var modelCtrl = ctrls[0];
20058
- if (modelCtrl.$options && modelCtrl.$options.updateOn) {
20059
- element.on(modelCtrl.$options.updateOn, function(ev) {
20060
- modelCtrl.$$debounceViewValueCommit(ev && ev.type);
20472
+ attr.$observe('name', function(newValue) {
20473
+ if (modelCtrl.$name !== newValue) {
20474
+ formCtrl.$$renameControl(modelCtrl, newValue);
20475
+ }
20061
20476
  });
20062
- }
20063
20477
 
20064
- element.on('blur', function(ev) {
20065
- if (modelCtrl.$touched) return;
20478
+ scope.$on('$destroy', function() {
20479
+ formCtrl.$removeControl(modelCtrl);
20480
+ });
20481
+ },
20482
+ post: function ngModelPostLink(scope, element, attr, ctrls) {
20483
+ var modelCtrl = ctrls[0];
20484
+ if (modelCtrl.$options && modelCtrl.$options.updateOn) {
20485
+ element.on(modelCtrl.$options.updateOn, function(ev) {
20486
+ modelCtrl.$$debounceViewValueCommit(ev && ev.type);
20487
+ });
20488
+ }
20066
20489
 
20067
- scope.$apply(function() {
20068
- modelCtrl.$setTouched();
20490
+ element.on('blur', function(ev) {
20491
+ if (modelCtrl.$touched) return;
20492
+
20493
+ scope.$apply(function() {
20494
+ modelCtrl.$setTouched();
20495
+ });
20069
20496
  });
20070
- });
20071
- }
20497
+ }
20498
+ };
20072
20499
  }
20073
20500
  };
20074
20501
  };
@@ -20623,8 +21050,9 @@ function addSetValidityMethod(context) {
20623
21050
  parentForm = context.parentForm,
20624
21051
  $animate = context.$animate;
20625
21052
 
21053
+ classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
21054
+
20626
21055
  ctrl.$setValidity = setValidity;
20627
- toggleValidationCss('', true);
20628
21056
 
20629
21057
  function setValidity(validationErrorKey, state, options) {
20630
21058
  if (state === undefined) {
@@ -20774,11 +21202,9 @@ var ngBindDirective = ['$compile', function($compile) {
20774
21202
  $compile.$$addBindingClass(templateElement);
20775
21203
  return function ngBindLink(scope, element, attr) {
20776
21204
  $compile.$$addBindingInfo(element, attr.ngBind);
21205
+ element = element[0];
20777
21206
  scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
20778
- // We are purposefully using == here rather than === because we want to
20779
- // catch when value is "null or undefined"
20780
- // jshint -W041
20781
- element.text(value == undefined ? '' : value);
21207
+ element.textContent = value === undefined ? '' : value;
20782
21208
  });
20783
21209
  };
20784
21210
  }
@@ -20844,8 +21270,9 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
20844
21270
  return function ngBindTemplateLink(scope, element, attr) {
20845
21271
  var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
20846
21272
  $compile.$$addBindingInfo(element, interpolateFn.expressions);
21273
+ element = element[0];
20847
21274
  attr.$observe('ngBindTemplate', function(value) {
20848
- element.text(value);
21275
+ element.textContent = value === undefined ? '' : value;
20849
21276
  });
20850
21277
  };
20851
21278
  }
@@ -20862,7 +21289,10 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
20862
21289
  * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link
20863
21290
  * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize`
20864
21291
  * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in
20865
- * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to
21292
+ * core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to
21293
+ * include "angular-sanitize.js" in your application.
21294
+ *
21295
+ * You may also bypass sanitization for values you know are safe. To do so, bind to
20866
21296
  * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
20867
21297
  * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}.
20868
21298
  *
@@ -21624,7 +22054,125 @@ var ngControllerDirective = [function() {
21624
22054
  ...
21625
22055
  </html>
21626
22056
  ```
21627
- */
22057
+ * @example
22058
+ // Note: the suffix `.csp` in the example name triggers
22059
+ // csp mode in our http server!
22060
+ <example name="example.csp" module="cspExample" ng-csp="true">
22061
+ <file name="index.html">
22062
+ <div ng-controller="MainController as ctrl">
22063
+ <div>
22064
+ <button ng-click="ctrl.inc()" id="inc">Increment</button>
22065
+ <span id="counter">
22066
+ {{ctrl.counter}}
22067
+ </span>
22068
+ </div>
22069
+
22070
+ <div>
22071
+ <button ng-click="ctrl.evil()" id="evil">Evil</button>
22072
+ <span id="evilError">
22073
+ {{ctrl.evilError}}
22074
+ </span>
22075
+ </div>
22076
+ </div>
22077
+ </file>
22078
+ <file name="script.js">
22079
+ angular.module('cspExample', [])
22080
+ .controller('MainController', function() {
22081
+ this.counter = 0;
22082
+ this.inc = function() {
22083
+ this.counter++;
22084
+ };
22085
+ this.evil = function() {
22086
+ // jshint evil:true
22087
+ try {
22088
+ eval('1+2');
22089
+ } catch (e) {
22090
+ this.evilError = e.message;
22091
+ }
22092
+ };
22093
+ });
22094
+ </file>
22095
+ <file name="protractor.js" type="protractor">
22096
+ var util, webdriver;
22097
+
22098
+ var incBtn = element(by.id('inc'));
22099
+ var counter = element(by.id('counter'));
22100
+ var evilBtn = element(by.id('evil'));
22101
+ var evilError = element(by.id('evilError'));
22102
+
22103
+ function getAndClearSevereErrors() {
22104
+ return browser.manage().logs().get('browser').then(function(browserLog) {
22105
+ return browserLog.filter(function(logEntry) {
22106
+ return logEntry.level.value > webdriver.logging.Level.WARNING.value;
22107
+ });
22108
+ });
22109
+ }
22110
+
22111
+ function clearErrors() {
22112
+ getAndClearSevereErrors();
22113
+ }
22114
+
22115
+ function expectNoErrors() {
22116
+ getAndClearSevereErrors().then(function(filteredLog) {
22117
+ expect(filteredLog.length).toEqual(0);
22118
+ if (filteredLog.length) {
22119
+ console.log('browser console errors: ' + util.inspect(filteredLog));
22120
+ }
22121
+ });
22122
+ }
22123
+
22124
+ function expectError(regex) {
22125
+ getAndClearSevereErrors().then(function(filteredLog) {
22126
+ var found = false;
22127
+ filteredLog.forEach(function(log) {
22128
+ if (log.message.match(regex)) {
22129
+ found = true;
22130
+ }
22131
+ });
22132
+ if (!found) {
22133
+ throw new Error('expected an error that matches ' + regex);
22134
+ }
22135
+ });
22136
+ }
22137
+
22138
+ beforeEach(function() {
22139
+ util = require('util');
22140
+ webdriver = require('protractor/node_modules/selenium-webdriver');
22141
+ });
22142
+
22143
+ // For now, we only test on Chrome,
22144
+ // as Safari does not load the page with Protractor's injected scripts,
22145
+ // and Firefox webdriver always disables content security policy (#6358)
22146
+ if (browser.params.browser !== 'chrome') {
22147
+ return;
22148
+ }
22149
+
22150
+ it('should not report errors when the page is loaded', function() {
22151
+ // clear errors so we are not dependent on previous tests
22152
+ clearErrors();
22153
+ // Need to reload the page as the page is already loaded when
22154
+ // we come here
22155
+ browser.driver.getCurrentUrl().then(function(url) {
22156
+ browser.get(url);
22157
+ });
22158
+ expectNoErrors();
22159
+ });
22160
+
22161
+ it('should evaluate expressions', function() {
22162
+ expect(counter.getText()).toEqual('0');
22163
+ incBtn.click();
22164
+ expect(counter.getText()).toEqual('1');
22165
+ expectNoErrors();
22166
+ });
22167
+
22168
+ it('should throw and report an error when using "eval"', function() {
22169
+ evilBtn.click();
22170
+ expect(evilError.getText()).toMatch(/Content Security Policy/);
22171
+ expectError(/Content Security Policy/);
22172
+ });
22173
+ </file>
22174
+ </example>
22175
+ */
21628
22176
 
21629
22177
  // ngCsp is not implemented as a proper directive any more, because we need it be processed while we
21630
22178
  // bootstrap the system (before $parse is instantiated), for this reason we just have
@@ -22131,7 +22679,7 @@ forEach(
22131
22679
  * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
22132
22680
  * is created when the element is restored. The scope created within `ngIf` inherits from
22133
22681
  * its parent scope using
22134
- * [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance).
22682
+ * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
22135
22683
  * An important implication of this is if `ngModel` is used within `ngIf` to bind to
22136
22684
  * a javascript primitive defined in the parent scope. In this case any modifications made to the
22137
22685
  * variable within the child scope will override (hide) the value in the parent scope.
@@ -22145,8 +22693,8 @@ forEach(
22145
22693
  * and `leave` effects.
22146
22694
  *
22147
22695
  * @animations
22148
- * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container
22149
- * leave - happens just before the ngIf contents are removed from the DOM
22696
+ * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
22697
+ * leave - happens just before the `ngIf` contents are removed from the DOM
22150
22698
  *
22151
22699
  * @element ANY
22152
22700
  * @scope
@@ -23286,6 +23834,8 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
23286
23834
  };
23287
23835
  }];
23288
23836
 
23837
+ var NG_HIDE_CLASS = 'ng-hide';
23838
+ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
23289
23839
  /**
23290
23840
  * @ngdoc directive
23291
23841
  * @name ngShow
@@ -23447,7 +23997,11 @@ var ngShowDirective = ['$animate', function($animate) {
23447
23997
  multiElement: true,
23448
23998
  link: function(scope, element, attr) {
23449
23999
  scope.$watch(attr.ngShow, function ngShowWatchAction(value){
23450
- $animate[value ? 'removeClass' : 'addClass'](element, 'ng-hide');
24000
+ // we're adding a temporary, animation-specific class for ng-hide since this way
24001
+ // we can control when the element is actually displayed on screen without having
24002
+ // to have a global/greedy CSS selector that breaks when other animations are run.
24003
+ // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
24004
+ $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
23451
24005
  });
23452
24006
  }
23453
24007
  };
@@ -23602,7 +24156,9 @@ var ngHideDirective = ['$animate', function($animate) {
23602
24156
  multiElement: true,
23603
24157
  link: function(scope, element, attr) {
23604
24158
  scope.$watch(attr.ngHide, function ngHideWatchAction(value){
23605
- $animate[value ? 'addClass' : 'removeClass'](element, 'ng-hide');
24159
+ // The comment inside of the ngShowDirective explains why we add and
24160
+ // remove a temporary class for the show/hide animation
24161
+ $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
23606
24162
  });
23607
24163
  }
23608
24164
  };
@@ -24024,6 +24580,12 @@ var ngOptionsMinErr = minErr('ngOptions');
24024
24580
  * be bound to string values at present.
24025
24581
  * </div>
24026
24582
  *
24583
+ * <div class="alert alert-info">
24584
+ * **Note:** Using `select as` will bind the result of the `select as` expression to the model, but
24585
+ * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
24586
+ * or property name (for object data sources) of the value within the collection.
24587
+ * </div>
24588
+ *
24027
24589
  * @param {string} ngModel Assignable angular expression to data-bind to.
24028
24590
  * @param {string=} name Property name of the form under which the control is published.
24029
24591
  * @param {string=} required The control is considered valid only if value is entered.
@@ -24058,7 +24620,25 @@ var ngOptionsMinErr = minErr('ngOptions');
24058
24620
  * DOM element.
24059
24621
  * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
24060
24622
  * used to identify the objects in the array. The `trackexpr` will most likely refer to the
24061
- * `value` variable (e.g. `value.propertyName`).
24623
+ * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
24624
+ * even when the options are recreated (e.g. reloaded from the server).
24625
+
24626
+ * <div class="alert alert-info">
24627
+ * **Note:** Using `select as` together with `trackexpr` is not possible (and will throw).
24628
+ * Reasoning:
24629
+ * - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
24630
+ * values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItemß'}}],
24631
+ * $scope.selected = {name: 'aSubItem'};
24632
+ * - track by is always applied to `value`, with purpose to preserve the selection,
24633
+ * (to `item` in this case)
24634
+ * - to calculate whether an item is selected we do the following:
24635
+ * 1. apply `track by` to the values in the array, e.g.
24636
+ * In the example: [1,2]
24637
+ * 2. apply `track by` to the already selected value in `ngModel`:
24638
+ * In the example: this is not possible, as `track by` refers to `item.id`, but the selected
24639
+ * value from `ngModel` is `{name: aSubItem}`.
24640
+ *
24641
+ * </div>
24062
24642
  *
24063
24643
  * @example
24064
24644
  <example module="selectExample">
@@ -24315,6 +24895,8 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
24315
24895
 
24316
24896
  var displayFn = $parse(match[2] || match[1]),
24317
24897
  valueName = match[4] || match[6],
24898
+ selectAs = / as /.test(match[0]) && match[1],
24899
+ selectAsFn = selectAs ? $parse(selectAs) : null,
24318
24900
  keyName = match[5],
24319
24901
  groupByFn = $parse(match[3] || ''),
24320
24902
  valueFn = $parse(match[2] ? match[1] : valueName),
@@ -24325,7 +24907,16 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
24325
24907
  // We try to reuse these if possible
24326
24908
  // - optionGroupsCache[0] is the options with no option group
24327
24909
  // - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
24328
- optionGroupsCache = [[{element: selectElement, label:''}]];
24910
+ optionGroupsCache = [[{element: selectElement, label:''}]],
24911
+ //re-usable object to represent option's locals
24912
+ locals = {};
24913
+
24914
+ if (trackFn && selectAsFn) {
24915
+ throw ngOptionsMinErr('trkslct',
24916
+ "Comprehension expression cannot contain both selectAs '{0}' " +
24917
+ "and trackBy '{1}' expressions.",
24918
+ selectAs, track);
24919
+ }
24329
24920
 
24330
24921
  if (nullOption) {
24331
24922
  // compile the element since there might be bindings in it
@@ -24343,103 +24934,110 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
24343
24934
  // clear contents, we'll add what's needed based on the model
24344
24935
  selectElement.empty();
24345
24936
 
24346
- selectElement.on('change', function() {
24937
+ selectElement.on('change', selectionChanged);
24938
+
24939
+ ctrl.$render = render;
24940
+
24941
+ scope.$watchCollection(valuesFn, scheduleRendering);
24942
+ scope.$watchCollection(getLabels, scheduleRendering);
24943
+
24944
+ if (multiple) {
24945
+ scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering);
24946
+ }
24947
+
24948
+ // ------------------------------------------------------------------ //
24949
+
24950
+ function callExpression(exprFn, key, value) {
24951
+ locals[valueName] = value;
24952
+ if (keyName) locals[keyName] = key;
24953
+ return exprFn(scope, locals);
24954
+ }
24955
+
24956
+ function selectionChanged() {
24347
24957
  scope.$apply(function() {
24348
24958
  var optionGroup,
24349
24959
  collection = valuesFn(scope) || [],
24350
- locals = {},
24351
24960
  key, value, optionElement, index, groupIndex, length, groupLength, trackIndex;
24352
-
24961
+ var viewValue;
24353
24962
  if (multiple) {
24354
- value = [];
24355
- for (groupIndex = 0, groupLength = optionGroupsCache.length;
24356
- groupIndex < groupLength;
24357
- groupIndex++) {
24358
- // list of options for that group. (first item has the parent)
24359
- optionGroup = optionGroupsCache[groupIndex];
24360
-
24361
- for(index = 1, length = optionGroup.length; index < length; index++) {
24362
- if ((optionElement = optionGroup[index].element)[0].selected) {
24363
- key = optionElement.val();
24364
- if (keyName) locals[keyName] = key;
24365
- if (trackFn) {
24366
- for (trackIndex = 0; trackIndex < collection.length; trackIndex++) {
24367
- locals[valueName] = collection[trackIndex];
24368
- if (trackFn(scope, locals) == key) break;
24369
- }
24370
- } else {
24371
- locals[valueName] = collection[key];
24372
- }
24373
- value.push(valueFn(scope, locals));
24374
- }
24375
- }
24376
- }
24963
+ viewValue = [];
24964
+ forEach(selectElement.val(), function(selectedKey) {
24965
+ viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
24966
+ });
24377
24967
  } else {
24378
- key = selectElement.val();
24379
- if (key == '?') {
24380
- value = undefined;
24381
- } else if (key === ''){
24382
- value = null;
24383
- } else {
24384
- if (trackFn) {
24385
- for (trackIndex = 0; trackIndex < collection.length; trackIndex++) {
24386
- locals[valueName] = collection[trackIndex];
24387
- if (trackFn(scope, locals) == key) {
24388
- value = valueFn(scope, locals);
24389
- break;
24390
- }
24391
- }
24392
- } else {
24393
- locals[valueName] = collection[key];
24394
- if (keyName) locals[keyName] = key;
24395
- value = valueFn(scope, locals);
24396
- }
24397
- }
24968
+ var selectedKey = selectElement.val();
24969
+ viewValue = getViewValue(selectedKey, collection[selectedKey]);
24398
24970
  }
24399
- ctrl.$setViewValue(value);
24971
+ ctrl.$setViewValue(viewValue);
24400
24972
  render();
24401
24973
  });
24402
- });
24974
+ }
24403
24975
 
24404
- ctrl.$render = render;
24976
+ function getViewValue(key, value) {
24977
+ if (key === '?') {
24978
+ return undefined;
24979
+ } else if (key === '') {
24980
+ return null;
24981
+ } else {
24982
+ var viewValueFn = selectAsFn ? selectAsFn : valueFn;
24983
+ return callExpression(viewValueFn, key, value);
24984
+ }
24985
+ }
24405
24986
 
24406
- scope.$watchCollection(valuesFn, scheduleRendering);
24407
- scope.$watchCollection(function () {
24408
- var locals = {},
24409
- values = valuesFn(scope);
24410
- if (values) {
24411
- var toDisplay = new Array(values.length);
24987
+ function getLabels() {
24988
+ var values = valuesFn(scope);
24989
+ var toDisplay;
24990
+ if (values && isArray(values)) {
24991
+ toDisplay = new Array(values.length);
24412
24992
  for (var i = 0, ii = values.length; i < ii; i++) {
24413
- locals[valueName] = values[i];
24414
- toDisplay[i] = displayFn(scope, locals);
24993
+ toDisplay[i] = callExpression(displayFn, i, values[i]);
24415
24994
  }
24416
24995
  return toDisplay;
24996
+ } else if (values) {
24997
+ // TODO: Add a test for this case
24998
+ toDisplay = {};
24999
+ for (var prop in values) {
25000
+ if (values.hasOwnProperty(prop)) {
25001
+ toDisplay[prop] = callExpression(displayFn, prop, values[prop]);
25002
+ }
25003
+ }
24417
25004
  }
24418
- }, scheduleRendering);
24419
-
24420
- if (multiple) {
24421
- scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering);
25005
+ return toDisplay;
24422
25006
  }
24423
25007
 
24424
-
24425
- function getSelectedSet() {
24426
- var selectedSet = false;
25008
+ function createIsSelectedFn(viewValue) {
25009
+ var selectedSet;
24427
25010
  if (multiple) {
24428
- var modelValue = ctrl.$modelValue;
24429
- if (trackFn && isArray(modelValue)) {
25011
+ if (!selectAs && trackFn && isArray(viewValue)) {
25012
+
24430
25013
  selectedSet = new HashMap([]);
24431
- var locals = {};
24432
- for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) {
24433
- locals[valueName] = modelValue[trackIndex];
24434
- selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]);
25014
+ for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
25015
+ // tracking by key
25016
+ selectedSet.put(callExpression(trackFn, null, viewValue[trackIndex]), true);
24435
25017
  }
24436
25018
  } else {
24437
- selectedSet = new HashMap(modelValue);
25019
+ selectedSet = new HashMap(viewValue);
24438
25020
  }
25021
+ } else if (!selectAsFn && trackFn) {
25022
+ viewValue = callExpression(trackFn, null, viewValue);
24439
25023
  }
24440
- return selectedSet;
24441
- }
25024
+ return function isSelected(key, value) {
25025
+ var compareValueFn;
25026
+ if (selectAsFn) {
25027
+ compareValueFn = selectAsFn;
25028
+ } else if (trackFn) {
25029
+ compareValueFn = trackFn;
25030
+ } else {
25031
+ compareValueFn = valueFn;
25032
+ }
24442
25033
 
25034
+ if (multiple) {
25035
+ return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
25036
+ } else {
25037
+ return viewValue == callExpression(compareValueFn, key, value);
25038
+ }
25039
+ };
25040
+ }
24443
25041
 
24444
25042
  function scheduleRendering() {
24445
25043
  if (!renderScheduled) {
@@ -24448,78 +25046,64 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
24448
25046
  }
24449
25047
  }
24450
25048
 
24451
-
24452
25049
  function render() {
24453
25050
  renderScheduled = false;
24454
25051
 
24455
- // Temporary location for the option groups before we render them
25052
+ // Temporary location for the option groups before we render them
24456
25053
  var optionGroups = {'':[]},
24457
25054
  optionGroupNames = [''],
24458
25055
  optionGroupName,
24459
25056
  optionGroup,
24460
25057
  option,
24461
25058
  existingParent, existingOptions, existingOption,
24462
- modelValue = ctrl.$modelValue,
25059
+ viewValue = ctrl.$viewValue,
24463
25060
  values = valuesFn(scope) || [],
24464
25061
  keys = keyName ? sortedKeys(values) : values,
24465
25062
  key,
25063
+ value,
24466
25064
  groupLength, length,
24467
25065
  groupIndex, index,
24468
- locals = {},
24469
25066
  selected,
24470
- selectedSet = getSelectedSet(),
25067
+ isSelected = createIsSelectedFn(viewValue),
25068
+ anySelected = false,
24471
25069
  lastElement,
24472
25070
  element,
24473
25071
  label;
24474
25072
 
24475
-
24476
25073
  // We now build up the list of options we need (we merge later)
24477
25074
  for (index = 0; length = keys.length, index < length; index++) {
24478
-
24479
25075
  key = index;
24480
25076
  if (keyName) {
24481
25077
  key = keys[index];
24482
25078
  if ( key.charAt(0) === '$' ) continue;
24483
- locals[keyName] = key;
24484
25079
  }
25080
+ value = values[key];
24485
25081
 
24486
- locals[valueName] = values[key];
24487
-
24488
- optionGroupName = groupByFn(scope, locals) || '';
25082
+ optionGroupName = callExpression(groupByFn, key, value) || '';
24489
25083
  if (!(optionGroup = optionGroups[optionGroupName])) {
24490
25084
  optionGroup = optionGroups[optionGroupName] = [];
24491
25085
  optionGroupNames.push(optionGroupName);
24492
25086
  }
24493
- if (multiple) {
24494
- selected = isDefined(
24495
- selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals))
24496
- );
24497
- } else {
24498
- if (trackFn) {
24499
- var modelCast = {};
24500
- modelCast[valueName] = modelValue;
24501
- selected = trackFn(scope, modelCast) === trackFn(scope, locals);
24502
- } else {
24503
- selected = modelValue === valueFn(scope, locals);
24504
- }
24505
- selectedSet = selectedSet || selected; // see if at least one item is selected
24506
- }
24507
- label = displayFn(scope, locals); // what will be seen by the user
25087
+
25088
+ selected = isSelected(key, value);
25089
+ anySelected = anySelected || selected;
25090
+
25091
+ label = callExpression(displayFn, key, value); // what will be seen by the user
24508
25092
 
24509
25093
  // doing displayFn(scope, locals) || '' overwrites zero values
24510
25094
  label = isDefined(label) ? label : '';
24511
25095
  optionGroup.push({
24512
25096
  // either the index into array or key from object
24513
- id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index),
25097
+ id: (keyName ? keys[index] : index),
24514
25098
  label: label,
24515
25099
  selected: selected // determine if we should be selected
24516
25100
  });
24517
25101
  }
24518
25102
  if (!multiple) {
24519
- if (nullOption || modelValue === null) {
25103
+ if (nullOption || viewValue === null) {
24520
25104
  // insert null option if we have a placeholder, or the model is null
24521
- optionGroups[''].unshift({id:'', label:'', selected:!selectedSet});
24522
- } else if (!selectedSet) {
25105
+ optionGroups[''].unshift({id:'', label:'', selected:!anySelected});
25106
+ } else if (!anySelected) {
24523
25107
  // option could not be found, we have to insert the undefined item
24524
25108
  optionGroups[''].unshift({id:'?', label:'', selected:true});
24525
25109
  }
@@ -24600,6 +25184,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
24600
25184
  id: option.id,
24601
25185
  selected: option.selected
24602
25186
  });
25187
+ selectCtrl.addOption(option.label, element);
24603
25188
  if (lastElement) {
24604
25189
  lastElement.after(element);
24605
25190
  } else {
@@ -24611,7 +25196,9 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
24611
25196
  // remove any excessive OPTIONs in a group
24612
25197
  index++; // increment since the existingOptions[0] is parent element not OPTION
24613
25198
  while(existingOptions.length > index) {
24614
- existingOptions.pop().element.remove();
25199
+ option = existingOptions.pop();
25200
+ selectCtrl.removeOption(option.label);
25201
+ option.element.remove();
24615
25202
  }
24616
25203
  }
24617
25204
  // remove any excessive OPTGROUPs from select
@@ -24647,11 +25234,7 @@ var optionDirective = ['$interpolate', function($interpolate) {
24647
25234
  selectCtrl = parent.data(selectCtrlName) ||
24648
25235
  parent.parent().data(selectCtrlName); // in case we are in optgroup
24649
25236
 
24650
- if (selectCtrl && selectCtrl.databound) {
24651
- // For some reason Opera defaults to true and if not overridden this messes up the repeater.
24652
- // We don't want the view to drive the initialization of the model anyway.
24653
- element.prop('selected', false);
24654
- } else {
25237
+ if (!selectCtrl || !selectCtrl.databound) {
24655
25238
  selectCtrl = nullSelectCtrl;
24656
25239
  }
24657
25240
 
@@ -24698,4 +25281,4 @@ var styleDirective = valueFn({
24698
25281
 
24699
25282
  })(window, document);
24700
25283
 
24701
- !window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-animate){display:none !important;}ng\\:form{display:block;}</style>');
25284
+ !window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}</style>');