angularjs-rails 1.2.25 → 1.2.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.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>');