rsanchez
2014-10-15 7686a892d556333194349f73fee3a268b6202d66
securis/src/main/resources/static/js/angular/angular.js
....@@ -1,5 +1,5 @@
11 /**
2
- * @license AngularJS v1.3.0-rc.5
2
+ * @license AngularJS v1.3.0
33 * (c) 2010-2014 Google, Inc. http://angularjs.org
44 * License: MIT
55 */
....@@ -71,7 +71,7 @@
7171 return match;
7272 });
7373
74
- message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.5/' +
74
+ message = message + '\nhttp://errors.angularjs.org/1.3.0/' +
7575 (module ? module + '/' : '') + code;
7676 for (i = 2; i < arguments.length; i++) {
7777 message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
....@@ -413,7 +413,8 @@
413413 *
414414 * @description
415415 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
416
- * to `dst`. You can specify multiple `src` objects.
416
+ * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
417
+ * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
417418 *
418419 * @param {Object} dst Destination object.
419420 * @param {...Object} src Source object(s).
....@@ -1924,7 +1925,7 @@
19241925 * })
19251926 * ```
19261927 *
1927
- * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
1928
+ * See {@link ng.$animateProvider#register $animateProvider.register()} and
19281929 * {@link ngAnimate ngAnimate module} for more information.
19291930 */
19301931 animation: invokeLater('$animateProvider', 'register'),
....@@ -1974,7 +1975,7 @@
19741975 * @description
19751976 * Use this method to register work which needs to be performed on module loading.
19761977 * For more about how to configure services, see
1977
- * {@link providers#providers_provider-recipe Provider Recipe}.
1978
+ * {@link providers#provider-recipe Provider Recipe}.
19781979 */
19791980 config: config,
19801981
....@@ -2121,11 +2122,11 @@
21212122 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
21222123 */
21232124 var version = {
2124
- full: '1.3.0-rc.5', // all of these placeholder strings will be replaced by grunt's
2125
+ full: '1.3.0', // all of these placeholder strings will be replaced by grunt's
21252126 major: 1, // package task
21262127 minor: 3,
21272128 dot: 0,
2128
- codeName: 'impossible-choreography'
2129
+ codeName: 'superluminal-nudge'
21292130 };
21302131
21312132
....@@ -2300,7 +2301,7 @@
23002301 * - [`addClass()`](http://api.jquery.com/addClass/)
23012302 * - [`after()`](http://api.jquery.com/after/)
23022303 * - [`append()`](http://api.jquery.com/append/)
2303
- * - [`attr()`](http://api.jquery.com/attr/)
2304
+ * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
23042305 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
23052306 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
23062307 * - [`clone()`](http://api.jquery.com/clone/)
....@@ -2535,18 +2536,22 @@
25352536 if (!type) {
25362537 for (type in events) {
25372538 if (type !== '$destroy') {
2538
- removeEventListenerFn(element, type, events[type]);
2539
+ removeEventListenerFn(element, type, handle);
25392540 }
25402541 delete events[type];
25412542 }
25422543 } else {
25432544 forEach(type.split(' '), function(type) {
2544
- if (isUndefined(fn)) {
2545
- removeEventListenerFn(element, type, events[type]);
2546
- delete events[type];
2547
- } else {
2548
- arrayRemove(events[type] || [], fn);
2545
+ if (isDefined(fn)) {
2546
+ var listenerFns = events[type];
2547
+ arrayRemove(listenerFns || [], fn);
2548
+ if (listenerFns && listenerFns.length > 0) {
2549
+ return;
2550
+ }
25492551 }
2552
+
2553
+ removeEventListenerFn(element, type, handle);
2554
+ delete events[type];
25502555 });
25512556 }
25522557 }
....@@ -2708,6 +2713,20 @@
27082713 if (!keepData) jqLiteDealoc(element);
27092714 var parent = element.parentNode;
27102715 if (parent) parent.removeChild(element);
2716
+}
2717
+
2718
+
2719
+function jqLiteDocumentLoaded(action, win) {
2720
+ win = win || window;
2721
+ if (win.document.readyState === 'complete') {
2722
+ // Force the action to be run async for consistent behaviour
2723
+ // from the action's point of view
2724
+ // i.e. it will definitely not be in a $apply
2725
+ win.setTimeout(action);
2726
+ } else {
2727
+ // No need to unbind this handler as load is only ever called once
2728
+ jqLite(win).on('load', action);
2729
+ }
27112730 }
27122731
27132732 //////////////////////////////////////////
....@@ -3332,7 +3351,7 @@
33323351
33333352 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
33343353 * {@link angular.module}. The `ng` module must be explicitly added.
3335
- * @returns {function()} Injector object. See {@link auto.$injector $injector}.
3354
+ * @returns {injector} Injector object. See {@link auto.$injector $injector}.
33363355 *
33373356 * @example
33383357 * Typical usage
....@@ -3983,7 +4002,7 @@
39834002
39844003 function enforceReturnValue(name, factory) {
39854004 return function enforcedReturnValue() {
3986
- var result = instanceInjector.invoke(factory);
4005
+ var result = instanceInjector.invoke(factory, this, undefined, name);
39874006 if (isUndefined(result)) {
39884007 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
39894008 }
....@@ -4161,93 +4180,252 @@
41614180 createInjector.$$annotate = annotate;
41624181
41634182 /**
4164
- * @ngdoc service
4165
- * @name $anchorScroll
4166
- * @kind function
4167
- * @requires $window
4168
- * @requires $location
4169
- * @requires $rootScope
4183
+ * @ngdoc provider
4184
+ * @name $anchorScrollProvider
41704185 *
41714186 * @description
4172
- * When called, it checks current value of `$location.hash()` and scrolls to the related element,
4173
- * according to rules specified in
4174
- * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
4175
- *
4176
- * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor.
4177
- * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
4178
- *
4179
- * @example
4180
- <example module="anchorScrollExample">
4181
- <file name="index.html">
4182
- <div id="scrollArea" ng-controller="ScrollController">
4183
- <a ng-click="gotoBottom()">Go to bottom</a>
4184
- <a id="bottom"></a> You're at the bottom!
4185
- </div>
4186
- </file>
4187
- <file name="script.js">
4188
- angular.module('anchorScrollExample', [])
4189
- .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
4190
- function ($scope, $location, $anchorScroll) {
4191
- $scope.gotoBottom = function() {
4192
- // set the location.hash to the id of
4193
- // the element you wish to scroll to.
4194
- $location.hash('bottom');
4195
-
4196
- // call $anchorScroll()
4197
- $anchorScroll();
4198
- };
4199
- }]);
4200
- </file>
4201
- <file name="style.css">
4202
- #scrollArea {
4203
- height: 350px;
4204
- overflow: auto;
4205
- }
4206
-
4207
- #bottom {
4208
- display: block;
4209
- margin-top: 2000px;
4210
- }
4211
- </file>
4212
- </example>
4187
+ * Use `$anchorScrollProvider` to disable automatic scrolling whenever
4188
+ * {@link ng.$location#hash $location.hash()} changes.
42134189 */
42144190 function $AnchorScrollProvider() {
42154191
42164192 var autoScrollingEnabled = true;
42174193
4194
+ /**
4195
+ * @ngdoc method
4196
+ * @name $anchorScrollProvider#disableAutoScrolling
4197
+ *
4198
+ * @description
4199
+ * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically will detect changes to
4200
+ * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
4201
+ * Use this method to disable automatic scrolling.
4202
+ *
4203
+ * If automatic scrolling is disabled, one must explicitly call
4204
+ * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
4205
+ * current hash.
4206
+ */
42184207 this.disableAutoScrolling = function() {
42194208 autoScrollingEnabled = false;
42204209 };
42214210
4211
+ /**
4212
+ * @ngdoc service
4213
+ * @name $anchorScroll
4214
+ * @kind function
4215
+ * @requires $window
4216
+ * @requires $location
4217
+ * @requires $rootScope
4218
+ *
4219
+ * @description
4220
+ * When called, it checks the current value of {@link ng.$location#hash $location.hash()} and
4221
+ * scrolls to the related element, according to the rules specified in the
4222
+ * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
4223
+ *
4224
+ * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
4225
+ * match any anchor whenever it changes. This can be disabled by calling
4226
+ * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
4227
+ *
4228
+ * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
4229
+ * vertical scroll-offset (either fixed or dynamic).
4230
+ *
4231
+ * @property {(number|function|jqLite)} yOffset
4232
+ * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
4233
+ * positioned elements at the top of the page, such as navbars, headers etc.
4234
+ *
4235
+ * `yOffset` can be specified in various ways:
4236
+ * - **number**: A fixed number of pixels to be used as offset.<br /><br />
4237
+ * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
4238
+ * a number representing the offset (in pixels).<br /><br />
4239
+ * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
4240
+ * the top of the page to the element's bottom will be used as offset.<br />
4241
+ * **Note**: The element will be taken into account only as long as its `position` is set to
4242
+ * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
4243
+ * their height and/or positioning according to the viewport's size.
4244
+ *
4245
+ * <br />
4246
+ * <div class="alert alert-warning">
4247
+ * In order for `yOffset` to work properly, scrolling should take place on the document's root and
4248
+ * not some child element.
4249
+ * </div>
4250
+ *
4251
+ * @example
4252
+ <example module="anchorScrollExample">
4253
+ <file name="index.html">
4254
+ <div id="scrollArea" ng-controller="ScrollController">
4255
+ <a ng-click="gotoBottom()">Go to bottom</a>
4256
+ <a id="bottom"></a> You're at the bottom!
4257
+ </div>
4258
+ </file>
4259
+ <file name="script.js">
4260
+ angular.module('anchorScrollExample', [])
4261
+ .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
4262
+ function ($scope, $location, $anchorScroll) {
4263
+ $scope.gotoBottom = function() {
4264
+ // set the location.hash to the id of
4265
+ // the element you wish to scroll to.
4266
+ $location.hash('bottom');
4267
+
4268
+ // call $anchorScroll()
4269
+ $anchorScroll();
4270
+ };
4271
+ }]);
4272
+ </file>
4273
+ <file name="style.css">
4274
+ #scrollArea {
4275
+ height: 280px;
4276
+ overflow: auto;
4277
+ }
4278
+
4279
+ #bottom {
4280
+ display: block;
4281
+ margin-top: 2000px;
4282
+ }
4283
+ </file>
4284
+ </example>
4285
+ *
4286
+ * <hr />
4287
+ * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
4288
+ * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
4289
+ *
4290
+ * @example
4291
+ <example module="anchorScrollOffsetExample">
4292
+ <file name="index.html">
4293
+ <div class="fixed-header" ng-controller="headerCtrl">
4294
+ <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
4295
+ Go to anchor {{x}}
4296
+ </a>
4297
+ </div>
4298
+ <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
4299
+ Anchor {{x}} of 5
4300
+ </div>
4301
+ </file>
4302
+ <file name="script.js">
4303
+ angular.module('anchorScrollOffsetExample', [])
4304
+ .run(['$anchorScroll', function($anchorScroll) {
4305
+ $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
4306
+ }])
4307
+ .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
4308
+ function ($anchorScroll, $location, $scope) {
4309
+ $scope.gotoAnchor = function(x) {
4310
+ var newHash = 'anchor' + x;
4311
+ if ($location.hash() !== newHash) {
4312
+ // set the $location.hash to `newHash` and
4313
+ // $anchorScroll will automatically scroll to it
4314
+ $location.hash('anchor' + x);
4315
+ } else {
4316
+ // call $anchorScroll() explicitly,
4317
+ // since $location.hash hasn't changed
4318
+ $anchorScroll();
4319
+ }
4320
+ };
4321
+ }
4322
+ ]);
4323
+ </file>
4324
+ <file name="style.css">
4325
+ body {
4326
+ padding-top: 50px;
4327
+ }
4328
+
4329
+ .anchor {
4330
+ border: 2px dashed DarkOrchid;
4331
+ padding: 10px 10px 200px 10px;
4332
+ }
4333
+
4334
+ .fixed-header {
4335
+ background-color: rgba(0, 0, 0, 0.2);
4336
+ height: 50px;
4337
+ position: fixed;
4338
+ top: 0; left: 0; right: 0;
4339
+ }
4340
+
4341
+ .fixed-header > a {
4342
+ display: inline-block;
4343
+ margin: 5px 15px;
4344
+ }
4345
+ </file>
4346
+ </example>
4347
+ */
42224348 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
42234349 var document = $window.document;
4350
+ var scrollScheduled = false;
42244351
4225
- // helper function to get first anchor from a NodeList
4226
- // can't use filter.filter, as it accepts only instances of Array
4227
- // and IE can't convert NodeList to an array using [].slice
4228
- // TODO(vojta): use filter if we change it to accept lists as well
4352
+ // Helper function to get first anchor from a NodeList
4353
+ // (using `Array#some()` instead of `angular#forEach()` since it's more performant
4354
+ // and working in all supported browsers.)
42294355 function getFirstAnchor(list) {
42304356 var result = null;
4231
- forEach(list, function(element) {
4232
- if (!result && nodeName_(element) === 'a') result = element;
4357
+ Array.prototype.some.call(list, function(element) {
4358
+ if (nodeName_(element) === 'a') {
4359
+ result = element;
4360
+ return true;
4361
+ }
42334362 });
42344363 return result;
4364
+ }
4365
+
4366
+ function getYOffset() {
4367
+
4368
+ var offset = scroll.yOffset;
4369
+
4370
+ if (isFunction(offset)) {
4371
+ offset = offset();
4372
+ } else if (isElement(offset)) {
4373
+ var elem = offset[0];
4374
+ var style = $window.getComputedStyle(elem);
4375
+ if (style.position !== 'fixed') {
4376
+ offset = 0;
4377
+ } else {
4378
+ offset = elem.getBoundingClientRect().bottom;
4379
+ }
4380
+ } else if (!isNumber(offset)) {
4381
+ offset = 0;
4382
+ }
4383
+
4384
+ return offset;
4385
+ }
4386
+
4387
+ function scrollTo(elem) {
4388
+ if (elem) {
4389
+ elem.scrollIntoView();
4390
+
4391
+ var offset = getYOffset();
4392
+
4393
+ if (offset) {
4394
+ // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
4395
+ // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
4396
+ // top of the viewport.
4397
+ //
4398
+ // IF the number of pixels from the top of `elem` to the end of the page's content is less
4399
+ // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
4400
+ // way down the page.
4401
+ //
4402
+ // This is often the case for elements near the bottom of the page.
4403
+ //
4404
+ // In such cases we do not need to scroll the whole `offset` up, just the difference between
4405
+ // the top of the element and the offset, which is enough to align the top of `elem` at the
4406
+ // desired position.
4407
+ var elemTop = elem.getBoundingClientRect().top;
4408
+ $window.scrollBy(0, elemTop - offset);
4409
+ }
4410
+ } else {
4411
+ $window.scrollTo(0, 0);
4412
+ }
42354413 }
42364414
42374415 function scroll() {
42384416 var hash = $location.hash(), elm;
42394417
42404418 // empty hash, scroll to the top of the page
4241
- if (!hash) $window.scrollTo(0, 0);
4419
+ if (!hash) scrollTo(null);
42424420
42434421 // element with given id
4244
- else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
4422
+ else if ((elm = document.getElementById(hash))) scrollTo(elm);
42454423
42464424 // first anchor with given name :-D
4247
- else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
4425
+ else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
42484426
42494427 // no element and hash == 'top', scroll to the top of the page
4250
- else if (hash === 'top') $window.scrollTo(0, 0);
4428
+ else if (hash === 'top') scrollTo(null);
42514429 }
42524430
42534431 // does not scroll when user clicks on anchor link that is currently on
....@@ -4258,7 +4436,9 @@
42584436 // skip the initial scroll if $location.hash is empty
42594437 if (newVal === oldVal && newVal === '') return;
42604438
4261
- $rootScope.$evalAsync(scroll);
4439
+ jqLiteDocumentLoaded(function() {
4440
+ $rootScope.$evalAsync(scroll);
4441
+ });
42624442 });
42634443 }
42644444
....@@ -4366,7 +4546,7 @@
43664546 return defer.promise;
43674547 }
43684548
4369
- function resolveElementClasses(element, cache) {
4549
+ function resolveElementClasses(element, classes) {
43704550 var toAdd = [], toRemove = [];
43714551
43724552 var hasClasses = createMap();
....@@ -4374,7 +4554,7 @@
43744554 hasClasses[className] = true;
43754555 });
43764556
4377
- forEach(cache.classes, function(status, className) {
4557
+ forEach(classes, function(status, className) {
43784558 var hasClass = hasClasses[className];
43794559
43804560 // If the most recent class manipulation (via $animate) was to remove the class, and the
....@@ -4388,7 +4568,8 @@
43884568 }
43894569 });
43904570
4391
- return (toAdd.length + toRemove.length) > 0 && [toAdd.length && toAdd, toRemove.length && toRemove];
4571
+ return (toAdd.length + toRemove.length) > 0 &&
4572
+ [toAdd.length ? toAdd : null, toRemove.length ? toRemove : null];
43924573 }
43934574
43944575 function cachedClassManipulation(cache, classes, op) {
....@@ -4410,6 +4591,13 @@
44104591 return currentDefer.promise;
44114592 }
44124593
4594
+ function applyStyles(element, options) {
4595
+ if (angular.isObject(options)) {
4596
+ var styles = extend(options.from || {}, options.to || {});
4597
+ element.css(styles);
4598
+ }
4599
+ }
4600
+
44134601 /**
44144602 *
44154603 * @ngdoc service
....@@ -4428,6 +4616,10 @@
44284616 * page}.
44294617 */
44304618 return {
4619
+ animate : function(element, from, to) {
4620
+ applyStyles(element, { from: from, to: to });
4621
+ return asyncPromise();
4622
+ },
44314623
44324624 /**
44334625 *
....@@ -4442,9 +4634,11 @@
44424634 * a child (if the after element is not present)
44434635 * @param {DOMElement} after the sibling element which will append the element
44444636 * after itself
4637
+ * @param {object=} options an optional collection of styles that will be applied to the element.
44454638 * @return {Promise} the animation callback promise
44464639 */
4447
- enter : function(element, parent, after) {
4640
+ enter : function(element, parent, after, options) {
4641
+ applyStyles(element, options);
44484642 after ? after.after(element)
44494643 : parent.prepend(element);
44504644 return asyncPromise();
....@@ -4458,9 +4652,10 @@
44584652 * @description Removes the element from the DOM. When the function is called a promise
44594653 * is returned that will be resolved at a later time.
44604654 * @param {DOMElement} element the element which will be removed from the DOM
4655
+ * @param {object=} options an optional collection of options that will be applied to the element.
44614656 * @return {Promise} the animation callback promise
44624657 */
4463
- leave : function(element) {
4658
+ leave : function(element, options) {
44644659 element.remove();
44654660 return asyncPromise();
44664661 },
....@@ -4480,12 +4675,13 @@
44804675 * inserted into (if the after element is not present)
44814676 * @param {DOMElement} after the sibling element where the element will be
44824677 * positioned next to
4678
+ * @param {object=} options an optional collection of options that will be applied to the element.
44834679 * @return {Promise} the animation callback promise
44844680 */
4485
- move : function(element, parent, after) {
4681
+ move : function(element, parent, after, options) {
44864682 // Do not remove element before insert. Removing will cause data associated with the
44874683 // element to be dropped. Insert will implicitly do the remove.
4488
- return this.enter(element, parent, after);
4684
+ return this.enter(element, parent, after, options);
44894685 },
44904686
44914687 /**
....@@ -4498,13 +4694,14 @@
44984694 * @param {DOMElement} element the element which will have the className value
44994695 * added to it
45004696 * @param {string} className the CSS class which will be added to the element
4697
+ * @param {object=} options an optional collection of options that will be applied to the element.
45014698 * @return {Promise} the animation callback promise
45024699 */
4503
- addClass : function(element, className) {
4504
- return this.setClass(element, className, []);
4700
+ addClass : function(element, className, options) {
4701
+ return this.setClass(element, className, [], options);
45054702 },
45064703
4507
- $$addClassImmediately : function addClassImmediately(element, className) {
4704
+ $$addClassImmediately : function(element, className, options) {
45084705 element = jqLite(element);
45094706 className = !isString(className)
45104707 ? (isArray(className) ? className.join(' ') : '')
....@@ -4512,6 +4709,8 @@
45124709 forEach(element, function (element) {
45134710 jqLiteAddClass(element, className);
45144711 });
4712
+ applyStyles(element, options);
4713
+ return asyncPromise();
45154714 },
45164715
45174716 /**
....@@ -4524,13 +4723,14 @@
45244723 * @param {DOMElement} element the element which will have the className value
45254724 * removed from it
45264725 * @param {string} className the CSS class which will be removed from the element
4726
+ * @param {object=} options an optional collection of options that will be applied to the element.
45274727 * @return {Promise} the animation callback promise
45284728 */
4529
- removeClass : function(element, className) {
4530
- return this.setClass(element, [], className);
4729
+ removeClass : function(element, className, options) {
4730
+ return this.setClass(element, [], className, options);
45314731 },
45324732
4533
- $$removeClassImmediately : function removeClassImmediately(element, className) {
4733
+ $$removeClassImmediately : function(element, className, options) {
45344734 element = jqLite(element);
45354735 className = !isString(className)
45364736 ? (isArray(className) ? className.join(' ') : '')
....@@ -4538,6 +4738,7 @@
45384738 forEach(element, function (element) {
45394739 jqLiteRemoveClass(element, className);
45404740 });
4741
+ applyStyles(element, options);
45414742 return asyncPromise();
45424743 },
45434744
....@@ -4552,28 +4753,24 @@
45524753 * removed from it
45534754 * @param {string} add the CSS classes which will be added to the element
45544755 * @param {string} remove the CSS class which will be removed from the element
4756
+ * @param {object=} options an optional collection of options that will be applied to the element.
45554757 * @return {Promise} the animation callback promise
45564758 */
4557
- setClass : function(element, add, remove, runSynchronously) {
4759
+ setClass : function(element, add, remove, options) {
45584760 var self = this;
45594761 var STORAGE_KEY = '$$animateClasses';
45604762 var createdCache = false;
45614763 element = jqLite(element);
45624764
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
-
45714765 var cache = element.data(STORAGE_KEY);
45724766 if (!cache) {
45734767 cache = {
4574
- classes: {}
4768
+ classes: {},
4769
+ options : options
45754770 };
45764771 createdCache = true;
4772
+ } else if (options && cache.options) {
4773
+ cache.options = angular.extend(cache.options || {}, options);
45774774 }
45784775
45794776 var classes = cache.classes;
....@@ -4588,11 +4785,14 @@
45884785 var cache = element.data(STORAGE_KEY);
45894786 element.removeData(STORAGE_KEY);
45904787
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]);
4788
+ // in the event that the element is removed before postDigest
4789
+ // is run then the cache will be undefined and there will be
4790
+ // no need anymore to add or remove and of the element classes
4791
+ if (cache) {
4792
+ var classes = resolveElementClasses(element, cache.classes);
4793
+ if (classes) {
4794
+ self.$$setClassImmediately(element, classes[0], classes[1], cache.options);
4795
+ }
45964796 }
45974797
45984798 done();
....@@ -4601,6 +4801,13 @@
46014801 }
46024802
46034803 return cache.promise;
4804
+ },
4805
+
4806
+ $$setClassImmediately : function(element, add, remove, options) {
4807
+ add && this.$$addClassImmediately(element, add);
4808
+ remove && this.$$removeClassImmediately(element, remove);
4809
+ applyStyles(element, options);
4810
+ return asyncPromise();
46044811 },
46054812
46064813 enabled : noop,
....@@ -4743,10 +4950,13 @@
47434950 // URL API
47444951 //////////////////////////////////////////////////////////////
47454952
4746
- var lastBrowserUrl = location.href,
4747
- lastHistoryState = history.state,
4953
+ var cachedState, lastHistoryState,
4954
+ lastBrowserUrl = location.href,
47484955 baseElement = document.find('base'),
47494956 reloadLocation = null;
4957
+
4958
+ cacheState();
4959
+ lastHistoryState = cachedState;
47504960
47514961 /**
47524962 * @name $browser#url
....@@ -4782,21 +4992,26 @@
47824992
47834993 // setter
47844994 if (url) {
4995
+ var sameState = lastHistoryState === state;
4996
+
47854997 // Don't change anything if previous and current URLs and states match. This also prevents
47864998 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
47874999 // See https://github.com/angular/angular.js/commit/ffb2701
4788
- if (lastBrowserUrl === url && (!$sniffer.history || history.state === state)) {
5000
+ if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
47895001 return;
47905002 }
47915003 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
47925004 lastBrowserUrl = url;
5005
+ lastHistoryState = state;
47935006 // Don't use history API if only the hash changed
47945007 // due to a bug in IE10/IE11 which leads
47955008 // to not firing a `hashchange` nor `popstate` event
47965009 // in some cases (see #9143).
4797
- if ($sniffer.history && (!sameBase || history.state !== state)) {
5010
+ if ($sniffer.history && (!sameBase || !sameState)) {
47985011 history[replace ? 'replaceState' : 'pushState'](state, '', url);
4799
- lastHistoryState = history.state;
5012
+ cacheState();
5013
+ // Do the assignment again so that those two variables are referentially identical.
5014
+ lastHistoryState = cachedState;
48005015 } else {
48015016 if (!sameBase) {
48025017 reloadLocation = url;
....@@ -4828,20 +5043,40 @@
48285043 * @returns {object} state
48295044 */
48305045 self.state = function() {
4831
- return isUndefined(history.state) ? null : history.state;
5046
+ return cachedState;
48325047 };
48335048
48345049 var urlChangeListeners = [],
48355050 urlChangeInit = false;
48365051
5052
+ function cacheStateAndFireUrlChange() {
5053
+ cacheState();
5054
+ fireUrlChange();
5055
+ }
5056
+
5057
+ // This variable should be used *only* inside the cacheState function.
5058
+ var lastCachedState = null;
5059
+ function cacheState() {
5060
+ // This should be the only place in $browser where `history.state` is read.
5061
+ cachedState = window.history.state;
5062
+ cachedState = isUndefined(cachedState) ? null : cachedState;
5063
+
5064
+ // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
5065
+ if (equals(cachedState, lastCachedState)) {
5066
+ cachedState = lastCachedState;
5067
+ }
5068
+ lastCachedState = cachedState;
5069
+ }
5070
+
48375071 function fireUrlChange() {
4838
- if (lastBrowserUrl === self.url() && lastHistoryState === history.state) {
5072
+ if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
48395073 return;
48405074 }
48415075
48425076 lastBrowserUrl = self.url();
5077
+ lastHistoryState = cachedState;
48435078 forEach(urlChangeListeners, function(listener) {
4844
- listener(self.url(), history.state);
5079
+ listener(self.url(), cachedState);
48455080 });
48465081 }
48475082
....@@ -4874,9 +5109,9 @@
48745109 // changed by push/replaceState
48755110
48765111 // html5 history api - popstate event
4877
- if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange);
5112
+ if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
48785113 // hashchange event
4879
- jqLite(window).on('hashchange', fireUrlChange);
5114
+ jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
48805115
48815116 urlChangeInit = true;
48825117 }
....@@ -4916,6 +5151,14 @@
49165151 var lastCookies = {};
49175152 var lastCookieString = '';
49185153 var cookiePath = self.baseHref();
5154
+
5155
+ function safeDecodeURIComponent(str) {
5156
+ try {
5157
+ return decodeURIComponent(str);
5158
+ } catch (e) {
5159
+ return str;
5160
+ }
5161
+ }
49195162
49205163 /**
49215164 * @name $browser#cookies
....@@ -4970,12 +5213,12 @@
49705213 cookie = cookieArray[i];
49715214 index = cookie.indexOf('=');
49725215 if (index > 0) { //ignore nameless cookies
4973
- name = decodeURIComponent(cookie.substring(0, index));
5216
+ name = safeDecodeURIComponent(cookie.substring(0, index));
49745217 // the first value that is seen for a cookie is the most
49755218 // specific one. values for the same cookie name that
49765219 // follow are for less specific paths.
49775220 if (lastCookies[name] === undefined) {
4978
- lastCookies[name] = decodeURIComponent(cookie.substring(index + 1));
5221
+ lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
49795222 }
49805223 }
49815224 }
....@@ -5500,6 +5743,7 @@
55005743 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
55015744 * transclude: false,
55025745 * restrict: 'A',
5746
+ * templateNamespace: 'html',
55035747 * scope: false,
55045748 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
55055749 * controllerAs: 'stringAlias',
....@@ -5554,8 +5798,8 @@
55545798 * When this property is set to true, the HTML compiler will collect DOM nodes between
55555799 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
55565800 * together as the directive elements. It is recomended that this feature be used on directives
5557
- * which are not strictly behavioural (such as {@link api/ng.directive:ngClick ngClick}), and which
5558
- * do not manipulate or replace child nodes (such as {@link api/ng.directive:ngInclude ngInclude}).
5801
+ * which are not strictly behavioural (such as {@link ngClick}), and which
5802
+ * do not manipulate or replace child nodes (such as {@link ngInclude}).
55595803 *
55605804 * #### `priority`
55615805 * When there are multiple directives defined on a single DOM element, sometimes it
....@@ -5568,7 +5812,8 @@
55685812 * #### `terminal`
55695813 * If set to true then the current `priority` will be the last set of directives
55705814 * which will execute (any directives at the current priority will still execute
5571
- * as the order of execution on same `priority` is undefined).
5815
+ * as the order of execution on same `priority` is undefined). Note that expressions
5816
+ * and other directives used in the directive's template will also be excluded from execution.
55725817 *
55735818 * #### `scope`
55745819 * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the
....@@ -5715,7 +5960,7 @@
57155960 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
57165961 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
57175962 * a string value representing the url. In either case, the template URL is passed through {@link
5718
- * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
5963
+ * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
57195964 *
57205965 *
57215966 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
....@@ -5725,7 +5970,7 @@
57255970 * * `false` - the template will replace the contents of the directive's element.
57265971 *
57275972 * The replacement process migrates all of the attributes / classes from the old element to the new
5728
- * one. See the {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive
5973
+ * one. See the {@link guide/directive#template-expanding-directive
57295974 * Directives Guide} for an example.
57305975 *
57315976 * There are very few scenarios where element replacement is required for the application function,
....@@ -5880,7 +6125,7 @@
58806125 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
58816126 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
58826127 *
5883
- * When you call a transclusion function you can pass in a **clone attach function**. This function is accepts
6128
+ * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
58846129 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
58856130 * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
58866131 *
....@@ -6500,12 +6745,12 @@
65006745 * @param {string} key Normalized key. (ie ngAttribute) .
65016746 * @param {function(interpolatedValue)} fn Function that will be called whenever
65026747 the interpolated value of the attribute changes.
6503
- * See {@link ng.$compile#attributes $compile} for more info.
6748
+ * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
65046749 * @returns {function()} Returns a deregistration function for this observer.
65056750 */
65066751 $observe: function(key, fn) {
65076752 var attrs = this,
6508
- $$observers = (attrs.$$observers || (attrs.$$observers = Object.create(null))),
6753
+ $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
65096754 listeners = ($$observers[key] || ($$observers[key] = []));
65106755
65116756 listeners.push(fn);
....@@ -8181,6 +8426,14 @@
81818426 * This example will override the normal action of `$exceptionHandler`, to make angular
81828427 * exceptions fail hard when they happen, instead of just logging to the console.
81838428 *
8429
+ * <hr />
8430
+ * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
8431
+ * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
8432
+ * (unless executed during a digest).
8433
+ *
8434
+ * If you wish, you can manually delegate exceptions, e.g.
8435
+ * `try { ... } catch(e) { $exceptionHandler(e); }`
8436
+ *
81848437 * @param {Error} exception Exception associated with the error.
81858438 * @param {string=} cause optional information about the context in which
81868439 * the error was thrown.
....@@ -8348,7 +8601,7 @@
83488601 * @description
83498602 *
83508603 * Configure $http service to combine processing of multiple http responses received at around
8351
- * the same time via {@link ng.$rootScope#applyAsync $rootScope.$applyAsync}. This can result in
8604
+ * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
83528605 * significant performance improvement for bigger applications that make many HTTP requests
83538606 * concurrently (common during application bootstrap).
83548607 *
....@@ -8424,7 +8677,8 @@
84248677 * with two $http specific methods: `success` and `error`.
84258678 *
84268679 * ```js
8427
- * $http({method: 'GET', url: '/someUrl'}).
8680
+ * // Simple GET request example :
8681
+ * $http.get('/someUrl').
84288682 * success(function(data, status, headers, config) {
84298683 * // this callback will be called asynchronously
84308684 * // when the response is available
....@@ -8434,6 +8688,20 @@
84348688 * // or server returns response with an error status.
84358689 * });
84368690 * ```
8691
+ *
8692
+ * ```js
8693
+ * // Simple POST request example (passing data) :
8694
+ * $http.post('/someUrl', {msg:'hello word!'}).
8695
+ * success(function(data, status, headers, config) {
8696
+ * // this callback will be called asynchronously
8697
+ * // when the response is available
8698
+ * }).
8699
+ * error(function(data, status, headers, config) {
8700
+ * // called asynchronously if an error occurs
8701
+ * // or server returns response with an error status.
8702
+ * });
8703
+ * ```
8704
+ *
84378705 *
84388706 * Since the returned value of calling the $http function is a `promise`, you can also use
84398707 * the `then` method to register callbacks, and these callbacks will receive a single argument –
....@@ -8587,7 +8855,7 @@
85878855 *
85888856 * You can change the default cache to a new object (built with
85898857 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the
8590
- * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set
8858
+ * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set
85918859 * their `cache` property to `true` will now use this cache object.
85928860 *
85938861 * If you set the default cache to `false` then only requests that specify their own custom
....@@ -8949,9 +9217,12 @@
89499217
89509218 function transformResponse(response) {
89519219 // make a copy since the response must be cacheable
8952
- var resp = extend({}, response, {
8953
- data: transformData(response.data, response.headers, config.transformResponse)
8954
- });
9220
+ var resp = extend({}, response);
9221
+ if (!response.data) {
9222
+ resp.data = response.data;
9223
+ } else {
9224
+ resp.data = transformData(response.data, response.headers, config.transformResponse);
9225
+ }
89559226 return (isSuccess(response.status))
89569227 ? resp
89579228 : $q.reject(resp);
....@@ -9694,16 +9965,13 @@
96949965 return '';
96959966 }
96969967 switch (typeof value) {
9697
- case 'string': {
9968
+ case 'string':
96989969 break;
9699
- }
9700
- case 'number': {
9970
+ case 'number':
97019971 value = '' + value;
97029972 break;
9703
- }
9704
- default: {
9973
+ default:
97059974 value = toJson(value);
9706
- }
97079975 }
97089976
97099977 return value;
....@@ -10525,6 +10793,7 @@
1052510793 search = search.toString();
1052610794 this.$$search = parseKeyValue(search);
1052710795 } else if (isObject(search)) {
10796
+ search = copy(search, {});
1052810797 // remove object undefined or null properties
1052910798 forEach(search, function(value, key) {
1053010799 if (value == null) delete search[key];
....@@ -10710,8 +10979,8 @@
1071010979 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
1071110980 * true, and a base tag is not present, an error will be thrown when `$location` is injected.
1071210981 * 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.
10982
+ * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
10983
+ * enables/disables url rewriting for relative links.
1071510984 *
1071610985 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
1071710986 */
....@@ -10722,7 +10991,7 @@
1072210991 } else if (isObject(mode)) {
1072310992
1072410993 if (isBoolean(mode.enabled)) {
10725
- html5Mode.enabled = mode.enabled;
10994
+ html5Mode.enabled = mode.enabled;
1072610995 }
1072710996
1072810997 if (isBoolean(mode.requireBase)) {
....@@ -10730,7 +10999,7 @@
1073010999 }
1073111000
1073211001 if (isBoolean(mode.rewriteLinks)) {
10733
- html5Mode.rewriteLinks = mode.rewriteLinks;
11002
+ html5Mode.rewriteLinks = mode.rewriteLinks;
1073411003 }
1073511004
1073611005 return this;
....@@ -10749,7 +11018,7 @@
1074911018 * This change can be prevented by calling
1075011019 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
1075111020 * details about event object. Upon successful change
10752
- * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
11021
+ * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
1075311022 *
1075411023 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
1075511024 * the browser supports the HTML5 History API.
....@@ -10901,9 +11170,10 @@
1090111170 var oldUrl = $browser.url();
1090211171 var oldState = $browser.state();
1090311172 var currentReplace = $location.$$replace;
11173
+ var urlOrStateChanged = oldUrl !== $location.absUrl() ||
11174
+ ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
1090411175
10905
- if (initializing || oldUrl !== $location.absUrl() ||
10906
- ($location.$$html5 && $sniffer.history && oldState !== $location.$$state)) {
11176
+ if (initializing || urlOrStateChanged) {
1090711177 initializing = false;
1090811178
1090911179 $rootScope.$evalAsync(function() {
....@@ -10912,8 +11182,10 @@
1091211182 $location.$$parse(oldUrl);
1091311183 $location.$$state = oldState;
1091411184 } else {
10915
- setBrowserUrlWithFallback($location.absUrl(), currentReplace,
10916
- oldState === $location.$$state ? null : $location.$$state);
11185
+ if (urlOrStateChanged) {
11186
+ setBrowserUrlWithFallback($location.absUrl(), currentReplace,
11187
+ oldState === $location.$$state ? null : $location.$$state);
11188
+ }
1091711189 afterLocationChange(oldUrl, oldState);
1091811190 }
1091911191 });
....@@ -11195,7 +11467,6 @@
1119511467
1119611468 //Operators - will be wrapped by binaryFn/unaryFn/assignment/filter
1119711469 var OPERATORS = extend(createMap(), {
11198
- /* jshint bitwise : false */
1119911470 '+':function(self, locals, a,b){
1120011471 a=a(self, locals); b=b(self, locals);
1120111472 if (isDefined(a)) {
....@@ -11212,7 +11483,6 @@
1121211483 '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
1121311484 '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
1121411485 '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
11215
- '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
1121611486 '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
1121711487 '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
1121811488 '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
....@@ -11223,14 +11493,12 @@
1122311493 '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
1122411494 '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
1122511495 '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
11226
- '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
1122711496 '!':function(self, locals, a){return !a(self, locals);},
1122811497
1122911498 //Tokenized as operators but parsed as assignment/filters
1123011499 '=':true,
1123111500 '|':true
1123211501 });
11233
-/* jshint bitwise: true */
1123411502 var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
1123511503
1123611504
....@@ -12268,16 +12536,17 @@
1226812536 }
1226912537
1227012538 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
12271
- var unwatch;
12539
+ var unwatch, lastValue;
1227212540 return unwatch = scope.$watch(function oneTimeWatch(scope) {
1227312541 return parsedExpression(scope);
1227412542 }, function oneTimeListener(value, old, scope) {
12543
+ lastValue = value;
1227512544 if (isFunction(listener)) {
1227612545 listener.call(this, value, old, scope);
1227712546 }
1227812547 if (isAllDefined(value)) {
1227912548 scope.$$postDigest(function () {
12280
- if(isAllDefined(value)) unwatch();
12549
+ if(isAllDefined(lastValue)) unwatch();
1228112550 });
1228212551 }
1228312552 }, objectEquality);
....@@ -12353,24 +12622,27 @@
1235312622 * It can be used like so:
1235412623 *
1235512624 * ```js
12356
- * return $q(function(resolve, reject) {
12357
- * // perform some asynchronous operation, resolve or reject the promise when appropriate.
12358
- * setInterval(function() {
12359
- * if (pollStatus > 0) {
12360
- * resolve(polledValue);
12361
- * } else if (pollStatus < 0) {
12362
- * reject(polledValue);
12363
- * } else {
12364
- * pollStatus = pollAgain(function(value) {
12365
- * polledValue = value;
12366
- * });
12367
- * }
12368
- * }, 10000);
12369
- * }).
12370
- * then(function(value) {
12371
- * // handle success
12625
+ * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
12626
+ * // are available in the current lexical scope (they could have been injected or passed in).
12627
+ *
12628
+ * function asyncGreet(name) {
12629
+ * // perform some asynchronous operation, resolve or reject the promise when appropriate.
12630
+ * return $q(function(resolve, reject) {
12631
+ * setTimeout(function() {
12632
+ * if (okToGreet(name)) {
12633
+ * resolve('Hello, ' + name + '!');
12634
+ * } else {
12635
+ * reject('Greeting ' + name + ' is not allowed.');
12636
+ * }
12637
+ * }, 1000);
12638
+ * });
12639
+ * }
12640
+ *
12641
+ * var promise = asyncGreet('Robin Hood');
12642
+ * promise.then(function(greeting) {
12643
+ * alert('Success: ' + greeting);
1237212644 * }, function(reason) {
12373
- * // handle failure
12645
+ * alert('Failed: ' + reason);
1237412646 * });
1237512647 * ```
1237612648 *
....@@ -12386,7 +12658,7 @@
1238612658 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
1238712659 *
1238812660 * ```js
12389
- * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet`
12661
+ * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
1239012662 * // are available in the current lexical scope (they could have been injected or passed in).
1239112663 *
1239212664 * function asyncGreet(name) {
....@@ -14726,7 +14998,7 @@
1472614998 *
1472714999 * As of version 1.2, Angular ships with SCE enabled by default.
1472815000 *
14729
- * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows
15001
+ * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow
1473015002 * one to execute arbitrary javascript by the use of the expression() syntax. Refer
1473115003 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
1473215004 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>`
....@@ -14773,7 +15045,7 @@
1477315045 *
1477415046 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
1477515047 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link
14776
- * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
15048
+ * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
1477715049 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
1477815050 *
1477915051 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
....@@ -15043,13 +15315,13 @@
1504315315 * sce.js and sceSpecs.js would need to be aware of this detail.
1504415316 */
1504515317
15046
- this.$get = ['$parse', '$sniffer', '$sceDelegate', function(
15047
- $parse, $sniffer, $sceDelegate) {
15048
- // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows
15318
+ this.$get = ['$document', '$parse', '$sceDelegate', function(
15319
+ $document, $parse, $sceDelegate) {
15320
+ // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
1504915321 // the "expression(javascript expression)" syntax which is insecure.
15050
- if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) {
15322
+ if (enabled && $document[0].documentMode < 8) {
1505115323 throw $sceMinErr('iequirks',
15052
- 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' +
15324
+ 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
1505315325 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
1505415326 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
1505515327 }
....@@ -15272,7 +15544,7 @@
1527215544 *
1527315545 * @description
1527415546 * Shorthand method. `$sce.parseAsHtml(expression string)` →
15275
- * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`}
15547
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
1527615548 *
1527715549 * @param {string} expression String expression to compile.
1527815550 * @returns {function(context, locals)} a function which represents the compiled expression:
....@@ -15289,7 +15561,7 @@
1528915561 *
1529015562 * @description
1529115563 * Shorthand method. `$sce.parseAsCss(value)` →
15292
- * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`}
15564
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
1529315565 *
1529415566 * @param {string} expression String expression to compile.
1529515567 * @returns {function(context, locals)} a function which represents the compiled expression:
....@@ -15306,7 +15578,7 @@
1530615578 *
1530715579 * @description
1530815580 * Shorthand method. `$sce.parseAsUrl(value)` →
15309
- * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`}
15581
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
1531015582 *
1531115583 * @param {string} expression String expression to compile.
1531215584 * @returns {function(context, locals)} a function which represents the compiled expression:
....@@ -15323,7 +15595,7 @@
1532315595 *
1532415596 * @description
1532515597 * Shorthand method. `$sce.parseAsResourceUrl(value)` →
15326
- * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`}
15598
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
1532715599 *
1532815600 * @param {string} expression String expression to compile.
1532915601 * @returns {function(context, locals)} a function which represents the compiled expression:
....@@ -15340,7 +15612,7 @@
1534015612 *
1534115613 * @description
1534215614 * Shorthand method. `$sce.parseAsJs(value)` →
15343
- * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`}
15615
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
1534415616 *
1534515617 * @param {string} expression String expression to compile.
1534615618 * @returns {function(context, locals)} a function which represents the compiled expression:
....@@ -15394,7 +15666,6 @@
1539415666 int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
1539515667 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
1539615668 document = $document[0] || {},
15397
- documentMode = document.documentMode,
1539815669 vendorPrefix,
1539915670 vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
1540015671 bodyStyle = document.body && document.body.style,
....@@ -15454,9 +15725,7 @@
1545415725 vendorPrefix: vendorPrefix,
1545515726 transitions : transitions,
1545615727 animations : animations,
15457
- android: android,
15458
- msie : msie,
15459
- msieDocumentMode: documentMode
15728
+ android: android
1546015729 };
1546115730 }];
1546215731 }
....@@ -16161,17 +16430,17 @@
1616116430 }
1616216431
1616316432 var search = function(obj, text){
16164
- if (typeof text == 'string' && text.charAt(0) === '!') {
16433
+ if (typeof text === 'string' && text.charAt(0) === '!') {
1616516434 return !search(obj, text.substr(1));
1616616435 }
1616716436 switch (typeof obj) {
16168
- case "boolean":
16169
- case "number":
16170
- case "string":
16437
+ case 'boolean':
16438
+ case 'number':
16439
+ case 'string':
1617116440 return comparator(obj, text);
16172
- case "object":
16441
+ case 'object':
1617316442 switch (typeof text) {
16174
- case "object":
16443
+ case 'object':
1617516444 return comparator(obj, text);
1617616445 default:
1617716446 for ( var objKey in obj) {
....@@ -16182,7 +16451,7 @@
1618216451 break;
1618316452 }
1618416453 return false;
16185
- case "array":
16454
+ case 'array':
1618616455 for ( var i = 0; i < obj.length; i++) {
1618716456 if (search(obj[i], text)) {
1618816457 return true;
....@@ -16194,13 +16463,13 @@
1619416463 }
1619516464 };
1619616465 switch (typeof expression) {
16197
- case "boolean":
16198
- case "number":
16199
- case "string":
16466
+ case 'boolean':
16467
+ case 'number':
16468
+ case 'string':
1620016469 // Set up expression object and fall through
1620116470 expression = {$:expression};
1620216471 // jshint -W086
16203
- case "object":
16472
+ case 'object':
1620416473 // jshint +W086
1620516474 for (var key in expression) {
1620616475 (function(path) {
....@@ -16239,6 +16508,7 @@
1623916508 *
1624016509 * @param {number} amount Input to filter.
1624116510 * @param {string=} symbol Currency symbol or identifier to be displayed.
16511
+ * @param {number=} fractionSize Number of decimal places to round the amount to.
1624216512 * @returns {string} Formatted number.
1624316513 *
1624416514 *
....@@ -16254,13 +16524,15 @@
1625416524 <div ng-controller="ExampleController">
1625516525 <input type="number" ng-model="amount"> <br>
1625616526 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
16257
- custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span>
16527
+ custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
16528
+ no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
1625816529 </div>
1625916530 </file>
1626016531 <file name="protractor.js" type="protractor">
1626116532 it('should init with 1234.56', function() {
1626216533 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
16263
- expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56');
16534
+ expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
16535
+ expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
1626416536 });
1626516537 it('should update', function() {
1626616538 if (browser.params.browser == 'safari') {
....@@ -16271,7 +16543,8 @@
1627116543 element(by.model('amount')).clear();
1627216544 element(by.model('amount')).sendKeys('-1234');
1627316545 expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)');
16274
- expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)');
16546
+ expect(element(by.id('currency-custom')).getText()).toBe('(USD$1,234.00)');
16547
+ expect(element(by.id('currency-no-fractions')).getText()).toBe('(USD$1,234)');
1627516548 });
1627616549 </file>
1627716550 </example>
....@@ -16279,13 +16552,20 @@
1627916552 currencyFilter.$inject = ['$locale'];
1628016553 function currencyFilter($locale) {
1628116554 var formats = $locale.NUMBER_FORMATS;
16282
- return function(amount, currencySymbol){
16283
- if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
16555
+ return function(amount, currencySymbol, fractionSize){
16556
+ if (isUndefined(currencySymbol)) {
16557
+ currencySymbol = formats.CURRENCY_SYM;
16558
+ }
16559
+
16560
+ if (isUndefined(fractionSize)) {
16561
+ // TODO: read the default value from the locale file
16562
+ fractionSize = 2;
16563
+ }
1628416564
1628516565 // if null or undefined pass it through
1628616566 return (amount == null)
1628716567 ? amount
16288
- : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
16568
+ : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
1628916569 replace(/\u00A4/g, currencySymbol);
1629016570 };
1629116571 }
....@@ -16802,7 +17082,7 @@
1680217082 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
1680317083 Limit {{letters}} to: <input type="number" step="1" ng-model="letterLimit">
1680417084 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
16805
- Limit {{longNumber}} to: <input type="integer" ng-model="longNumberLimit">
17085
+ Limit {{longNumber}} to: <input type="number" step="1" ng-model="longNumberLimit">
1680617086 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
1680717087 </div>
1680817088 </file>
....@@ -18039,15 +18319,13 @@
1803918319 parentFormCtrl.$$renameControl(controller, alias);
1804018320 });
1804118321 }
18042
- if (parentFormCtrl !== nullFormCtrl) {
18043
- formElement.on('$destroy', function() {
18044
- parentFormCtrl.$removeControl(controller);
18045
- if (alias) {
18046
- setter(scope, alias, undefined, alias);
18047
- }
18048
- extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
18049
- });
18050
- }
18322
+ formElement.on('$destroy', function() {
18323
+ parentFormCtrl.$removeControl(controller);
18324
+ if (alias) {
18325
+ setter(scope, alias, undefined, alias);
18326
+ }
18327
+ extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
18328
+ });
1805118329 }
1805218330 };
1805318331 }
....@@ -21294,7 +21572,7 @@
2129421572 *
2129521573 * You may also bypass sanitization for values you know are safe. To do so, bind to
2129621574 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
21297
- * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}.
21575
+ * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
2129821576 *
2129921577 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
2130021578 * will have an exception (instead of an exploit.)
....@@ -21606,8 +21884,8 @@
2160621884 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
2160721885 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
2160821886 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
21609
- to view the step by step details of {@link ngAnimate.$animate#addclass $animate.addClass} and
21610
- {@link ngAnimate.$animate#removeclass $animate.removeClass}.
21887
+ to view the step by step details of {@link ng.$animate#addClass $animate.addClass} and
21888
+ {@link ng.$animate#removeClass $animate.removeClass}.
2161121889 */
2161221890 var ngClassDirective = classDirective('', true);
2161321891
....@@ -22013,7 +22291,7 @@
2201322291 * @description
2201422292 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support.
2201522293 *
22016
- * This is necessary when developing things like Google Chrome Extensions.
22294
+ * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
2201722295 *
2201822296 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things).
2201922297 * For Angular to be CSP compatible there are only two things that we need to do differently:
....@@ -22709,7 +22987,7 @@
2270922987 Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/>
2271022988 Show when checked:
2271122989 <span ng-if="checked" class="animate-if">
22712
- I'm removed when the checkbox is unchecked.
22990
+ This is removed when the checkbox is unchecked.
2271322991 </span>
2271422992 </file>
2271522993 <file name="animations.css">
....@@ -22763,15 +23041,15 @@
2276323041 });
2276423042 }
2276523043 } else {
22766
- if(previousElements) {
23044
+ if (previousElements) {
2276723045 previousElements.remove();
2276823046 previousElements = null;
2276923047 }
22770
- if(childScope) {
23048
+ if (childScope) {
2277123049 childScope.$destroy();
2277223050 childScope = null;
2277323051 }
22774
- if(block) {
23052
+ if (block) {
2277523053 previousElements = getBlockNodes(block.clone);
2277623054 $animate.leave(previousElements).then(function() {
2277723055 previousElements = null;
....@@ -22793,10 +23071,10 @@
2279323071 * Fetches, compiles and includes an external HTML fragment.
2279423072 *
2279523073 * By default, the template URL is restricted to the same domain and protocol as the
22796
- * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
23074
+ * application document. This is done by calling {@link $sce#getTrustedResourceUrl
2279723075 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
2279823076 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
22799
- * [wrap them](ng.$sce#trustAsResourceUrl) as trusted values. Refer to Angular's {@link
23077
+ * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link
2280023078 * ng.$sce Strict Contextual Escaping}.
2280123079 *
2280223080 * In addition, the browser's
....@@ -23495,13 +23773,6 @@
2349523773 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
2349623774 * will be associated by item identity in the array.
2349723775 *
23498
- * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
23499
- * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
23500
- * when a filter is active on the repeater, but the filtered result set is empty.
23501
- *
23502
- * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
23503
- * the items have been processed through the filter.
23504
- *
2350523776 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
2350623777 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
2350723778 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
....@@ -23513,6 +23784,13 @@
2351323784 *
2351423785 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
2351523786 * to items in conjunction with a tracking expression.
23787
+ *
23788
+ * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
23789
+ * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
23790
+ * when a filter is active on the repeater, but the filtered result set is empty.
23791
+ *
23792
+ * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
23793
+ * the items have been processed through the filter.
2351623794 *
2351723795 * @example
2351823796 * This example initializes the scope to a list of names and
....@@ -24001,7 +24279,9 @@
2400124279 // we can control when the element is actually displayed on screen without having
2400224280 // to have a global/greedy CSS selector that breaks when other animations are run.
2400324281 // 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);
24282
+ $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
24283
+ tempClasses : NG_HIDE_IN_PROGRESS_CLASS
24284
+ });
2400524285 });
2400624286 }
2400724287 };
....@@ -24158,7 +24438,9 @@
2415824438 scope.$watch(attr.ngHide, function ngHideWatchAction(value){
2415924439 // The comment inside of the ngShowDirective explains why we add and
2416024440 // remove a temporary class for the show/hide animation
24161
- $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
24441
+ $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
24442
+ tempClasses : NG_HIDE_IN_PROGRESS_CLASS
24443
+ });
2416224444 });
2416324445 }
2416424446 };
....@@ -24583,8 +24865,22 @@
2458324865 * <div class="alert alert-info">
2458424866 * **Note:** Using `select as` will bind the result of the `select as` expression to the model, but
2458524867 * 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.
24868
+ * or property name (for object data sources) of the value within the collection.
2458724869 * </div>
24870
+ *
24871
+ * **Note:** Using `select as` together with `trackexpr` is not recommended.
24872
+ * Reasoning:
24873
+ * - Example: &lt;select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"&gt;
24874
+ * values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}],
24875
+ * $scope.selected = {name: 'aSubItem'};
24876
+ * - track by is always applied to `value`, with the purpose of preserving the selection,
24877
+ * (to `item` in this case)
24878
+ * - to calculate whether an item is selected we do the following:
24879
+ * 1. apply `track by` to the values in the array, e.g.
24880
+ * In the example: [1,2]
24881
+ * 2. apply `track by` to the already selected value in `ngModel`:
24882
+ * In the example: this is not possible, as `track by` refers to `item.id`, but the selected
24883
+ * value from `ngModel` is `{name: aSubItem}`.
2458824884 *
2458924885 * @param {string} ngModel Assignable angular expression to data-bind to.
2459024886 * @param {string=} name Property name of the form under which the control is published.
....@@ -24622,23 +24918,6 @@
2462224918 * used to identify the objects in the array. The `trackexpr` will most likely refer to the
2462324919 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
2462424920 * 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>
2464224921 *
2464324922 * @example
2464424923 <example module="selectExample">
....@@ -24748,7 +25027,7 @@
2474825027 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
2474925028 // Adding an <option selected="selected"> element to a <select required="required"> should
2475025029 // automatically select the new element
24751
- if (element[0].hasAttribute('selected')) {
25030
+ if (element && element[0].hasAttribute('selected')) {
2475225031 element[0].selected = true;
2475325032 }
2475425033 };
....@@ -24911,13 +25190,6 @@
2491125190 //re-usable object to represent option's locals
2491225191 locals = {};
2491325192
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
- }
24920
-
2492125193 if (nullOption) {
2492225194 // compile the element since there might be bindings in it
2492325195 $compile(nullOption)(scope);
....@@ -25008,7 +25280,7 @@
2500825280 function createIsSelectedFn(viewValue) {
2500925281 var selectedSet;
2501025282 if (multiple) {
25011
- if (!selectAs && trackFn && isArray(viewValue)) {
25283
+ if (trackFn && isArray(viewValue)) {
2501225284
2501325285 selectedSet = new HashMap([]);
2501425286 for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
....@@ -25018,15 +25290,16 @@
2501825290 } else {
2501925291 selectedSet = new HashMap(viewValue);
2502025292 }
25021
- } else if (!selectAsFn && trackFn) {
25293
+ } else if (trackFn) {
2502225294 viewValue = callExpression(trackFn, null, viewValue);
2502325295 }
25296
+
2502425297 return function isSelected(key, value) {
2502525298 var compareValueFn;
25026
- if (selectAsFn) {
25027
- compareValueFn = selectAsFn;
25028
- } else if (trackFn) {
25299
+ if (trackFn) {
2502925300 compareValueFn = trackFn;
25301
+ } else if (selectAsFn) {
25302
+ compareValueFn = selectAsFn;
2503025303 } else {
2503125304 compareValueFn = valueFn;
2503225305 }
....@@ -25046,6 +25319,23 @@
2504625319 }
2504725320 }
2504825321
25322
+ /**
25323
+ * A new labelMap is created with each render.
25324
+ * This function is called for each existing option with added=false,
25325
+ * and each new option with added=true.
25326
+ * - Labels that are passed to this method twice,
25327
+ * (once with added=true and once with added=false) will end up with a value of 0, and
25328
+ * will cause no change to happen to the corresponding option.
25329
+ * - Labels that are passed to this method only once with added=false will end up with a
25330
+ * value of -1 and will eventually be passed to selectCtrl.removeOption()
25331
+ * - Labels that are passed to this method only once with added=true will end up with a
25332
+ * value of 1 and will eventually be passed to selectCtrl.addOption()
25333
+ */
25334
+ function updateLabelMap(labelMap, label, added) {
25335
+ labelMap[label] = labelMap[label] || 0;
25336
+ labelMap[label] += (added ? 1 : -1);
25337
+ }
25338
+
2504925339 function render() {
2505025340 renderScheduled = false;
2505125341
....@@ -25063,6 +25353,7 @@
2506325353 value,
2506425354 groupLength, length,
2506525355 groupIndex, index,
25356
+ labelMap = {},
2506625357 selected,
2506725358 isSelected = createIsSelectedFn(viewValue),
2506825359 anySelected = false,
....@@ -25145,6 +25436,8 @@
2514525436 // reuse elements
2514625437 lastElement = existingOption.element;
2514725438 if (existingOption.label !== option.label) {
25439
+ updateLabelMap(labelMap, existingOption.label, false);
25440
+ updateLabelMap(labelMap, option.label, true);
2514825441 lastElement.text(existingOption.label = option.label);
2514925442 }
2515025443 if (existingOption.id !== option.id) {
....@@ -25184,7 +25477,7 @@
2518425477 id: option.id,
2518525478 selected: option.selected
2518625479 });
25187
- selectCtrl.addOption(option.label, element);
25480
+ updateLabelMap(labelMap, option.label, true);
2518825481 if (lastElement) {
2518925482 lastElement.after(element);
2519025483 } else {
....@@ -25197,9 +25490,16 @@
2519725490 index++; // increment since the existingOptions[0] is parent element not OPTION
2519825491 while(existingOptions.length > index) {
2519925492 option = existingOptions.pop();
25200
- selectCtrl.removeOption(option.label);
25493
+ updateLabelMap(labelMap, option.label, false);
2520125494 option.element.remove();
2520225495 }
25496
+ forEach(labelMap, function (count, label) {
25497
+ if (count > 0) {
25498
+ selectCtrl.addOption(label);
25499
+ } else if (count < 0) {
25500
+ selectCtrl.removeOption(label);
25501
+ }
25502
+ });
2520325503 }
2520425504 // remove any excessive OPTGROUPs from select
2520525505 while(optionGroupsCache.length > groupIndex) {