| .. | .. |
|---|
| 1 | 1 | /** |
|---|
| 2 | | - * @license AngularJS v1.3.0-rc.5 |
|---|
| 2 | + * @license AngularJS v1.3.0 |
|---|
| 3 | 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org |
|---|
| 4 | 4 | * License: MIT |
|---|
| 5 | 5 | */ |
|---|
| .. | .. |
|---|
| 71 | 71 | return match; |
|---|
| 72 | 72 | }); |
|---|
| 73 | 73 | |
|---|
| 74 | | - message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.5/' + |
|---|
| 74 | + message = message + '\nhttp://errors.angularjs.org/1.3.0/' + |
|---|
| 75 | 75 | (module ? module + '/' : '') + code; |
|---|
| 76 | 76 | for (i = 2; i < arguments.length; i++) { |
|---|
| 77 | 77 | message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + |
|---|
| .. | .. |
|---|
| 413 | 413 | * |
|---|
| 414 | 414 | * @description |
|---|
| 415 | 415 | * 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)`. |
|---|
| 417 | 418 | * |
|---|
| 418 | 419 | * @param {Object} dst Destination object. |
|---|
| 419 | 420 | * @param {...Object} src Source object(s). |
|---|
| .. | .. |
|---|
| 1924 | 1925 | * }) |
|---|
| 1925 | 1926 | * ``` |
|---|
| 1926 | 1927 | * |
|---|
| 1927 | | - * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and |
|---|
| 1928 | + * See {@link ng.$animateProvider#register $animateProvider.register()} and |
|---|
| 1928 | 1929 | * {@link ngAnimate ngAnimate module} for more information. |
|---|
| 1929 | 1930 | */ |
|---|
| 1930 | 1931 | animation: invokeLater('$animateProvider', 'register'), |
|---|
| .. | .. |
|---|
| 1974 | 1975 | * @description |
|---|
| 1975 | 1976 | * Use this method to register work which needs to be performed on module loading. |
|---|
| 1976 | 1977 | * For more about how to configure services, see |
|---|
| 1977 | | - * {@link providers#providers_provider-recipe Provider Recipe}. |
|---|
| 1978 | + * {@link providers#provider-recipe Provider Recipe}. |
|---|
| 1978 | 1979 | */ |
|---|
| 1979 | 1980 | config: config, |
|---|
| 1980 | 1981 | |
|---|
| .. | .. |
|---|
| 2121 | 2122 | * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". |
|---|
| 2122 | 2123 | */ |
|---|
| 2123 | 2124 | 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 |
|---|
| 2125 | 2126 | major: 1, // package task |
|---|
| 2126 | 2127 | minor: 3, |
|---|
| 2127 | 2128 | dot: 0, |
|---|
| 2128 | | - codeName: 'impossible-choreography' |
|---|
| 2129 | + codeName: 'superluminal-nudge' |
|---|
| 2129 | 2130 | }; |
|---|
| 2130 | 2131 | |
|---|
| 2131 | 2132 | |
|---|
| .. | .. |
|---|
| 2300 | 2301 | * - [`addClass()`](http://api.jquery.com/addClass/) |
|---|
| 2301 | 2302 | * - [`after()`](http://api.jquery.com/after/) |
|---|
| 2302 | 2303 | * - [`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 |
|---|
| 2304 | 2305 | * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData |
|---|
| 2305 | 2306 | * - [`children()`](http://api.jquery.com/children/) - Does not support selectors |
|---|
| 2306 | 2307 | * - [`clone()`](http://api.jquery.com/clone/) |
|---|
| .. | .. |
|---|
| 2535 | 2536 | if (!type) { |
|---|
| 2536 | 2537 | for (type in events) { |
|---|
| 2537 | 2538 | if (type !== '$destroy') { |
|---|
| 2538 | | - removeEventListenerFn(element, type, events[type]); |
|---|
| 2539 | + removeEventListenerFn(element, type, handle); |
|---|
| 2539 | 2540 | } |
|---|
| 2540 | 2541 | delete events[type]; |
|---|
| 2541 | 2542 | } |
|---|
| 2542 | 2543 | } else { |
|---|
| 2543 | 2544 | 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 | + } |
|---|
| 2549 | 2551 | } |
|---|
| 2552 | + |
|---|
| 2553 | + removeEventListenerFn(element, type, handle); |
|---|
| 2554 | + delete events[type]; |
|---|
| 2550 | 2555 | }); |
|---|
| 2551 | 2556 | } |
|---|
| 2552 | 2557 | } |
|---|
| .. | .. |
|---|
| 2708 | 2713 | if (!keepData) jqLiteDealoc(element); |
|---|
| 2709 | 2714 | var parent = element.parentNode; |
|---|
| 2710 | 2715 | 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 | + } |
|---|
| 2711 | 2730 | } |
|---|
| 2712 | 2731 | |
|---|
| 2713 | 2732 | ////////////////////////////////////////// |
|---|
| .. | .. |
|---|
| 3332 | 3351 | |
|---|
| 3333 | 3352 | * @param {Array.<string|Function>} modules A list of module functions or their aliases. See |
|---|
| 3334 | 3353 | * {@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}. |
|---|
| 3336 | 3355 | * |
|---|
| 3337 | 3356 | * @example |
|---|
| 3338 | 3357 | * Typical usage |
|---|
| .. | .. |
|---|
| 3983 | 4002 | |
|---|
| 3984 | 4003 | function enforceReturnValue(name, factory) { |
|---|
| 3985 | 4004 | return function enforcedReturnValue() { |
|---|
| 3986 | | - var result = instanceInjector.invoke(factory); |
|---|
| 4005 | + var result = instanceInjector.invoke(factory, this, undefined, name); |
|---|
| 3987 | 4006 | if (isUndefined(result)) { |
|---|
| 3988 | 4007 | throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); |
|---|
| 3989 | 4008 | } |
|---|
| .. | .. |
|---|
| 4161 | 4180 | createInjector.$$annotate = annotate; |
|---|
| 4162 | 4181 | |
|---|
| 4163 | 4182 | /** |
|---|
| 4164 | | - * @ngdoc service |
|---|
| 4165 | | - * @name $anchorScroll |
|---|
| 4166 | | - * @kind function |
|---|
| 4167 | | - * @requires $window |
|---|
| 4168 | | - * @requires $location |
|---|
| 4169 | | - * @requires $rootScope |
|---|
| 4183 | + * @ngdoc provider |
|---|
| 4184 | + * @name $anchorScrollProvider |
|---|
| 4170 | 4185 | * |
|---|
| 4171 | 4186 | * @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. |
|---|
| 4213 | 4189 | */ |
|---|
| 4214 | 4190 | function $AnchorScrollProvider() { |
|---|
| 4215 | 4191 | |
|---|
| 4216 | 4192 | var autoScrollingEnabled = true; |
|---|
| 4217 | 4193 | |
|---|
| 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 | + */ |
|---|
| 4218 | 4207 | this.disableAutoScrolling = function() { |
|---|
| 4219 | 4208 | autoScrollingEnabled = false; |
|---|
| 4220 | 4209 | }; |
|---|
| 4221 | 4210 | |
|---|
| 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 | + */ |
|---|
| 4222 | 4348 | this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { |
|---|
| 4223 | 4349 | var document = $window.document; |
|---|
| 4350 | + var scrollScheduled = false; |
|---|
| 4224 | 4351 | |
|---|
| 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.) |
|---|
| 4229 | 4355 | function getFirstAnchor(list) { |
|---|
| 4230 | 4356 | 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 | + } |
|---|
| 4233 | 4362 | }); |
|---|
| 4234 | 4363 | 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 | + } |
|---|
| 4235 | 4413 | } |
|---|
| 4236 | 4414 | |
|---|
| 4237 | 4415 | function scroll() { |
|---|
| 4238 | 4416 | var hash = $location.hash(), elm; |
|---|
| 4239 | 4417 | |
|---|
| 4240 | 4418 | // empty hash, scroll to the top of the page |
|---|
| 4241 | | - if (!hash) $window.scrollTo(0, 0); |
|---|
| 4419 | + if (!hash) scrollTo(null); |
|---|
| 4242 | 4420 | |
|---|
| 4243 | 4421 | // element with given id |
|---|
| 4244 | | - else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); |
|---|
| 4422 | + else if ((elm = document.getElementById(hash))) scrollTo(elm); |
|---|
| 4245 | 4423 | |
|---|
| 4246 | 4424 | // 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); |
|---|
| 4248 | 4426 | |
|---|
| 4249 | 4427 | // 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); |
|---|
| 4251 | 4429 | } |
|---|
| 4252 | 4430 | |
|---|
| 4253 | 4431 | // does not scroll when user clicks on anchor link that is currently on |
|---|
| .. | .. |
|---|
| 4258 | 4436 | // skip the initial scroll if $location.hash is empty |
|---|
| 4259 | 4437 | if (newVal === oldVal && newVal === '') return; |
|---|
| 4260 | 4438 | |
|---|
| 4261 | | - $rootScope.$evalAsync(scroll); |
|---|
| 4439 | + jqLiteDocumentLoaded(function() { |
|---|
| 4440 | + $rootScope.$evalAsync(scroll); |
|---|
| 4441 | + }); |
|---|
| 4262 | 4442 | }); |
|---|
| 4263 | 4443 | } |
|---|
| 4264 | 4444 | |
|---|
| .. | .. |
|---|
| 4366 | 4546 | return defer.promise; |
|---|
| 4367 | 4547 | } |
|---|
| 4368 | 4548 | |
|---|
| 4369 | | - function resolveElementClasses(element, cache) { |
|---|
| 4549 | + function resolveElementClasses(element, classes) { |
|---|
| 4370 | 4550 | var toAdd = [], toRemove = []; |
|---|
| 4371 | 4551 | |
|---|
| 4372 | 4552 | var hasClasses = createMap(); |
|---|
| .. | .. |
|---|
| 4374 | 4554 | hasClasses[className] = true; |
|---|
| 4375 | 4555 | }); |
|---|
| 4376 | 4556 | |
|---|
| 4377 | | - forEach(cache.classes, function(status, className) { |
|---|
| 4557 | + forEach(classes, function(status, className) { |
|---|
| 4378 | 4558 | var hasClass = hasClasses[className]; |
|---|
| 4379 | 4559 | |
|---|
| 4380 | 4560 | // If the most recent class manipulation (via $animate) was to remove the class, and the |
|---|
| .. | .. |
|---|
| 4388 | 4568 | } |
|---|
| 4389 | 4569 | }); |
|---|
| 4390 | 4570 | |
|---|
| 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]; |
|---|
| 4392 | 4573 | } |
|---|
| 4393 | 4574 | |
|---|
| 4394 | 4575 | function cachedClassManipulation(cache, classes, op) { |
|---|
| .. | .. |
|---|
| 4410 | 4591 | return currentDefer.promise; |
|---|
| 4411 | 4592 | } |
|---|
| 4412 | 4593 | |
|---|
| 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 | + |
|---|
| 4413 | 4601 | /** |
|---|
| 4414 | 4602 | * |
|---|
| 4415 | 4603 | * @ngdoc service |
|---|
| .. | .. |
|---|
| 4428 | 4616 | * page}. |
|---|
| 4429 | 4617 | */ |
|---|
| 4430 | 4618 | return { |
|---|
| 4619 | + animate : function(element, from, to) { |
|---|
| 4620 | + applyStyles(element, { from: from, to: to }); |
|---|
| 4621 | + return asyncPromise(); |
|---|
| 4622 | + }, |
|---|
| 4431 | 4623 | |
|---|
| 4432 | 4624 | /** |
|---|
| 4433 | 4625 | * |
|---|
| .. | .. |
|---|
| 4442 | 4634 | * a child (if the after element is not present) |
|---|
| 4443 | 4635 | * @param {DOMElement} after the sibling element which will append the element |
|---|
| 4444 | 4636 | * after itself |
|---|
| 4637 | + * @param {object=} options an optional collection of styles that will be applied to the element. |
|---|
| 4445 | 4638 | * @return {Promise} the animation callback promise |
|---|
| 4446 | 4639 | */ |
|---|
| 4447 | | - enter : function(element, parent, after) { |
|---|
| 4640 | + enter : function(element, parent, after, options) { |
|---|
| 4641 | + applyStyles(element, options); |
|---|
| 4448 | 4642 | after ? after.after(element) |
|---|
| 4449 | 4643 | : parent.prepend(element); |
|---|
| 4450 | 4644 | return asyncPromise(); |
|---|
| .. | .. |
|---|
| 4458 | 4652 | * @description Removes the element from the DOM. When the function is called a promise |
|---|
| 4459 | 4653 | * is returned that will be resolved at a later time. |
|---|
| 4460 | 4654 | * @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. |
|---|
| 4461 | 4656 | * @return {Promise} the animation callback promise |
|---|
| 4462 | 4657 | */ |
|---|
| 4463 | | - leave : function(element) { |
|---|
| 4658 | + leave : function(element, options) { |
|---|
| 4464 | 4659 | element.remove(); |
|---|
| 4465 | 4660 | return asyncPromise(); |
|---|
| 4466 | 4661 | }, |
|---|
| .. | .. |
|---|
| 4480 | 4675 | * inserted into (if the after element is not present) |
|---|
| 4481 | 4676 | * @param {DOMElement} after the sibling element where the element will be |
|---|
| 4482 | 4677 | * positioned next to |
|---|
| 4678 | + * @param {object=} options an optional collection of options that will be applied to the element. |
|---|
| 4483 | 4679 | * @return {Promise} the animation callback promise |
|---|
| 4484 | 4680 | */ |
|---|
| 4485 | | - move : function(element, parent, after) { |
|---|
| 4681 | + move : function(element, parent, after, options) { |
|---|
| 4486 | 4682 | // Do not remove element before insert. Removing will cause data associated with the |
|---|
| 4487 | 4683 | // 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); |
|---|
| 4489 | 4685 | }, |
|---|
| 4490 | 4686 | |
|---|
| 4491 | 4687 | /** |
|---|
| .. | .. |
|---|
| 4498 | 4694 | * @param {DOMElement} element the element which will have the className value |
|---|
| 4499 | 4695 | * added to it |
|---|
| 4500 | 4696 | * @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. |
|---|
| 4501 | 4698 | * @return {Promise} the animation callback promise |
|---|
| 4502 | 4699 | */ |
|---|
| 4503 | | - addClass : function(element, className) { |
|---|
| 4504 | | - return this.setClass(element, className, []); |
|---|
| 4700 | + addClass : function(element, className, options) { |
|---|
| 4701 | + return this.setClass(element, className, [], options); |
|---|
| 4505 | 4702 | }, |
|---|
| 4506 | 4703 | |
|---|
| 4507 | | - $$addClassImmediately : function addClassImmediately(element, className) { |
|---|
| 4704 | + $$addClassImmediately : function(element, className, options) { |
|---|
| 4508 | 4705 | element = jqLite(element); |
|---|
| 4509 | 4706 | className = !isString(className) |
|---|
| 4510 | 4707 | ? (isArray(className) ? className.join(' ') : '') |
|---|
| .. | .. |
|---|
| 4512 | 4709 | forEach(element, function (element) { |
|---|
| 4513 | 4710 | jqLiteAddClass(element, className); |
|---|
| 4514 | 4711 | }); |
|---|
| 4712 | + applyStyles(element, options); |
|---|
| 4713 | + return asyncPromise(); |
|---|
| 4515 | 4714 | }, |
|---|
| 4516 | 4715 | |
|---|
| 4517 | 4716 | /** |
|---|
| .. | .. |
|---|
| 4524 | 4723 | * @param {DOMElement} element the element which will have the className value |
|---|
| 4525 | 4724 | * removed from it |
|---|
| 4526 | 4725 | * @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. |
|---|
| 4527 | 4727 | * @return {Promise} the animation callback promise |
|---|
| 4528 | 4728 | */ |
|---|
| 4529 | | - removeClass : function(element, className) { |
|---|
| 4530 | | - return this.setClass(element, [], className); |
|---|
| 4729 | + removeClass : function(element, className, options) { |
|---|
| 4730 | + return this.setClass(element, [], className, options); |
|---|
| 4531 | 4731 | }, |
|---|
| 4532 | 4732 | |
|---|
| 4533 | | - $$removeClassImmediately : function removeClassImmediately(element, className) { |
|---|
| 4733 | + $$removeClassImmediately : function(element, className, options) { |
|---|
| 4534 | 4734 | element = jqLite(element); |
|---|
| 4535 | 4735 | className = !isString(className) |
|---|
| 4536 | 4736 | ? (isArray(className) ? className.join(' ') : '') |
|---|
| .. | .. |
|---|
| 4538 | 4738 | forEach(element, function (element) { |
|---|
| 4539 | 4739 | jqLiteRemoveClass(element, className); |
|---|
| 4540 | 4740 | }); |
|---|
| 4741 | + applyStyles(element, options); |
|---|
| 4541 | 4742 | return asyncPromise(); |
|---|
| 4542 | 4743 | }, |
|---|
| 4543 | 4744 | |
|---|
| .. | .. |
|---|
| 4552 | 4753 | * removed from it |
|---|
| 4553 | 4754 | * @param {string} add the CSS classes which will be added to the element |
|---|
| 4554 | 4755 | * @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. |
|---|
| 4555 | 4757 | * @return {Promise} the animation callback promise |
|---|
| 4556 | 4758 | */ |
|---|
| 4557 | | - setClass : function(element, add, remove, runSynchronously) { |
|---|
| 4759 | + setClass : function(element, add, remove, options) { |
|---|
| 4558 | 4760 | var self = this; |
|---|
| 4559 | 4761 | var STORAGE_KEY = '$$animateClasses'; |
|---|
| 4560 | 4762 | var createdCache = false; |
|---|
| 4561 | 4763 | element = jqLite(element); |
|---|
| 4562 | 4764 | |
|---|
| 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 | 4765 | var cache = element.data(STORAGE_KEY); |
|---|
| 4572 | 4766 | if (!cache) { |
|---|
| 4573 | 4767 | cache = { |
|---|
| 4574 | | - classes: {} |
|---|
| 4768 | + classes: {}, |
|---|
| 4769 | + options : options |
|---|
| 4575 | 4770 | }; |
|---|
| 4576 | 4771 | createdCache = true; |
|---|
| 4772 | + } else if (options && cache.options) { |
|---|
| 4773 | + cache.options = angular.extend(cache.options || {}, options); |
|---|
| 4577 | 4774 | } |
|---|
| 4578 | 4775 | |
|---|
| 4579 | 4776 | var classes = cache.classes; |
|---|
| .. | .. |
|---|
| 4588 | 4785 | var cache = element.data(STORAGE_KEY); |
|---|
| 4589 | 4786 | element.removeData(STORAGE_KEY); |
|---|
| 4590 | 4787 | |
|---|
| 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 | + } |
|---|
| 4596 | 4796 | } |
|---|
| 4597 | 4797 | |
|---|
| 4598 | 4798 | done(); |
|---|
| .. | .. |
|---|
| 4601 | 4801 | } |
|---|
| 4602 | 4802 | |
|---|
| 4603 | 4803 | 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(); |
|---|
| 4604 | 4811 | }, |
|---|
| 4605 | 4812 | |
|---|
| 4606 | 4813 | enabled : noop, |
|---|
| .. | .. |
|---|
| 4743 | 4950 | // URL API |
|---|
| 4744 | 4951 | ////////////////////////////////////////////////////////////// |
|---|
| 4745 | 4952 | |
|---|
| 4746 | | - var lastBrowserUrl = location.href, |
|---|
| 4747 | | - lastHistoryState = history.state, |
|---|
| 4953 | + var cachedState, lastHistoryState, |
|---|
| 4954 | + lastBrowserUrl = location.href, |
|---|
| 4748 | 4955 | baseElement = document.find('base'), |
|---|
| 4749 | 4956 | reloadLocation = null; |
|---|
| 4957 | + |
|---|
| 4958 | + cacheState(); |
|---|
| 4959 | + lastHistoryState = cachedState; |
|---|
| 4750 | 4960 | |
|---|
| 4751 | 4961 | /** |
|---|
| 4752 | 4962 | * @name $browser#url |
|---|
| .. | .. |
|---|
| 4782 | 4992 | |
|---|
| 4783 | 4993 | // setter |
|---|
| 4784 | 4994 | if (url) { |
|---|
| 4995 | + var sameState = lastHistoryState === state; |
|---|
| 4996 | + |
|---|
| 4785 | 4997 | // Don't change anything if previous and current URLs and states match. This also prevents |
|---|
| 4786 | 4998 | // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. |
|---|
| 4787 | 4999 | // 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)) { |
|---|
| 4789 | 5001 | return; |
|---|
| 4790 | 5002 | } |
|---|
| 4791 | 5003 | var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); |
|---|
| 4792 | 5004 | lastBrowserUrl = url; |
|---|
| 5005 | + lastHistoryState = state; |
|---|
| 4793 | 5006 | // Don't use history API if only the hash changed |
|---|
| 4794 | 5007 | // due to a bug in IE10/IE11 which leads |
|---|
| 4795 | 5008 | // to not firing a `hashchange` nor `popstate` event |
|---|
| 4796 | 5009 | // in some cases (see #9143). |
|---|
| 4797 | | - if ($sniffer.history && (!sameBase || history.state !== state)) { |
|---|
| 5010 | + if ($sniffer.history && (!sameBase || !sameState)) { |
|---|
| 4798 | 5011 | 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; |
|---|
| 4800 | 5015 | } else { |
|---|
| 4801 | 5016 | if (!sameBase) { |
|---|
| 4802 | 5017 | reloadLocation = url; |
|---|
| .. | .. |
|---|
| 4828 | 5043 | * @returns {object} state |
|---|
| 4829 | 5044 | */ |
|---|
| 4830 | 5045 | self.state = function() { |
|---|
| 4831 | | - return isUndefined(history.state) ? null : history.state; |
|---|
| 5046 | + return cachedState; |
|---|
| 4832 | 5047 | }; |
|---|
| 4833 | 5048 | |
|---|
| 4834 | 5049 | var urlChangeListeners = [], |
|---|
| 4835 | 5050 | urlChangeInit = false; |
|---|
| 4836 | 5051 | |
|---|
| 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 | + |
|---|
| 4837 | 5071 | function fireUrlChange() { |
|---|
| 4838 | | - if (lastBrowserUrl === self.url() && lastHistoryState === history.state) { |
|---|
| 5072 | + if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { |
|---|
| 4839 | 5073 | return; |
|---|
| 4840 | 5074 | } |
|---|
| 4841 | 5075 | |
|---|
| 4842 | 5076 | lastBrowserUrl = self.url(); |
|---|
| 5077 | + lastHistoryState = cachedState; |
|---|
| 4843 | 5078 | forEach(urlChangeListeners, function(listener) { |
|---|
| 4844 | | - listener(self.url(), history.state); |
|---|
| 5079 | + listener(self.url(), cachedState); |
|---|
| 4845 | 5080 | }); |
|---|
| 4846 | 5081 | } |
|---|
| 4847 | 5082 | |
|---|
| .. | .. |
|---|
| 4874 | 5109 | // changed by push/replaceState |
|---|
| 4875 | 5110 | |
|---|
| 4876 | 5111 | // html5 history api - popstate event |
|---|
| 4877 | | - if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); |
|---|
| 5112 | + if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); |
|---|
| 4878 | 5113 | // hashchange event |
|---|
| 4879 | | - jqLite(window).on('hashchange', fireUrlChange); |
|---|
| 5114 | + jqLite(window).on('hashchange', cacheStateAndFireUrlChange); |
|---|
| 4880 | 5115 | |
|---|
| 4881 | 5116 | urlChangeInit = true; |
|---|
| 4882 | 5117 | } |
|---|
| .. | .. |
|---|
| 4916 | 5151 | var lastCookies = {}; |
|---|
| 4917 | 5152 | var lastCookieString = ''; |
|---|
| 4918 | 5153 | var cookiePath = self.baseHref(); |
|---|
| 5154 | + |
|---|
| 5155 | + function safeDecodeURIComponent(str) { |
|---|
| 5156 | + try { |
|---|
| 5157 | + return decodeURIComponent(str); |
|---|
| 5158 | + } catch (e) { |
|---|
| 5159 | + return str; |
|---|
| 5160 | + } |
|---|
| 5161 | + } |
|---|
| 4919 | 5162 | |
|---|
| 4920 | 5163 | /** |
|---|
| 4921 | 5164 | * @name $browser#cookies |
|---|
| .. | .. |
|---|
| 4970 | 5213 | cookie = cookieArray[i]; |
|---|
| 4971 | 5214 | index = cookie.indexOf('='); |
|---|
| 4972 | 5215 | if (index > 0) { //ignore nameless cookies |
|---|
| 4973 | | - name = decodeURIComponent(cookie.substring(0, index)); |
|---|
| 5216 | + name = safeDecodeURIComponent(cookie.substring(0, index)); |
|---|
| 4974 | 5217 | // the first value that is seen for a cookie is the most |
|---|
| 4975 | 5218 | // specific one. values for the same cookie name that |
|---|
| 4976 | 5219 | // follow are for less specific paths. |
|---|
| 4977 | 5220 | if (lastCookies[name] === undefined) { |
|---|
| 4978 | | - lastCookies[name] = decodeURIComponent(cookie.substring(index + 1)); |
|---|
| 5221 | + lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); |
|---|
| 4979 | 5222 | } |
|---|
| 4980 | 5223 | } |
|---|
| 4981 | 5224 | } |
|---|
| .. | .. |
|---|
| 5500 | 5743 | * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, |
|---|
| 5501 | 5744 | * transclude: false, |
|---|
| 5502 | 5745 | * restrict: 'A', |
|---|
| 5746 | + * templateNamespace: 'html', |
|---|
| 5503 | 5747 | * scope: false, |
|---|
| 5504 | 5748 | * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, |
|---|
| 5505 | 5749 | * controllerAs: 'stringAlias', |
|---|
| .. | .. |
|---|
| 5554 | 5798 | * When this property is set to true, the HTML compiler will collect DOM nodes between |
|---|
| 5555 | 5799 | * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them |
|---|
| 5556 | 5800 | * 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}). |
|---|
| 5559 | 5803 | * |
|---|
| 5560 | 5804 | * #### `priority` |
|---|
| 5561 | 5805 | * When there are multiple directives defined on a single DOM element, sometimes it |
|---|
| .. | .. |
|---|
| 5568 | 5812 | * #### `terminal` |
|---|
| 5569 | 5813 | * If set to true then the current `priority` will be the last set of directives |
|---|
| 5570 | 5814 | * 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. |
|---|
| 5572 | 5817 | * |
|---|
| 5573 | 5818 | * #### `scope` |
|---|
| 5574 | 5819 | * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the |
|---|
| .. | .. |
|---|
| 5715 | 5960 | * You can specify `templateUrl` as a string representing the URL or as a function which takes two |
|---|
| 5716 | 5961 | * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns |
|---|
| 5717 | 5962 | * 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}. |
|---|
| 5719 | 5964 | * |
|---|
| 5720 | 5965 | * |
|---|
| 5721 | 5966 | * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) |
|---|
| .. | .. |
|---|
| 5725 | 5970 | * * `false` - the template will replace the contents of the directive's element. |
|---|
| 5726 | 5971 | * |
|---|
| 5727 | 5972 | * 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 |
|---|
| 5729 | 5974 | * Directives Guide} for an example. |
|---|
| 5730 | 5975 | * |
|---|
| 5731 | 5976 | * There are very few scenarios where element replacement is required for the application function, |
|---|
| .. | .. |
|---|
| 5880 | 6125 | * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery |
|---|
| 5881 | 6126 | * object that contains the compiled DOM, which is linked to the correct transclusion scope. |
|---|
| 5882 | 6127 | * |
|---|
| 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 |
|---|
| 5884 | 6129 | * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded |
|---|
| 5885 | 6130 | * content and the `scope` is the newly created transclusion scope, to which the clone is bound. |
|---|
| 5886 | 6131 | * |
|---|
| .. | .. |
|---|
| 6500 | 6745 | * @param {string} key Normalized key. (ie ngAttribute) . |
|---|
| 6501 | 6746 | * @param {function(interpolatedValue)} fn Function that will be called whenever |
|---|
| 6502 | 6747 | 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. |
|---|
| 6504 | 6749 | * @returns {function()} Returns a deregistration function for this observer. |
|---|
| 6505 | 6750 | */ |
|---|
| 6506 | 6751 | $observe: function(key, fn) { |
|---|
| 6507 | 6752 | var attrs = this, |
|---|
| 6508 | | - $$observers = (attrs.$$observers || (attrs.$$observers = Object.create(null))), |
|---|
| 6753 | + $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), |
|---|
| 6509 | 6754 | listeners = ($$observers[key] || ($$observers[key] = [])); |
|---|
| 6510 | 6755 | |
|---|
| 6511 | 6756 | listeners.push(fn); |
|---|
| .. | .. |
|---|
| 8181 | 8426 | * This example will override the normal action of `$exceptionHandler`, to make angular |
|---|
| 8182 | 8427 | * exceptions fail hard when they happen, instead of just logging to the console. |
|---|
| 8183 | 8428 | * |
|---|
| 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 | + * |
|---|
| 8184 | 8437 | * @param {Error} exception Exception associated with the error. |
|---|
| 8185 | 8438 | * @param {string=} cause optional information about the context in which |
|---|
| 8186 | 8439 | * the error was thrown. |
|---|
| .. | .. |
|---|
| 8348 | 8601 | * @description |
|---|
| 8349 | 8602 | * |
|---|
| 8350 | 8603 | * 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 |
|---|
| 8352 | 8605 | * significant performance improvement for bigger applications that make many HTTP requests |
|---|
| 8353 | 8606 | * concurrently (common during application bootstrap). |
|---|
| 8354 | 8607 | * |
|---|
| .. | .. |
|---|
| 8424 | 8677 | * with two $http specific methods: `success` and `error`. |
|---|
| 8425 | 8678 | * |
|---|
| 8426 | 8679 | * ```js |
|---|
| 8427 | | - * $http({method: 'GET', url: '/someUrl'}). |
|---|
| 8680 | + * // Simple GET request example : |
|---|
| 8681 | + * $http.get('/someUrl'). |
|---|
| 8428 | 8682 | * success(function(data, status, headers, config) { |
|---|
| 8429 | 8683 | * // this callback will be called asynchronously |
|---|
| 8430 | 8684 | * // when the response is available |
|---|
| .. | .. |
|---|
| 8434 | 8688 | * // or server returns response with an error status. |
|---|
| 8435 | 8689 | * }); |
|---|
| 8436 | 8690 | * ``` |
|---|
| 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 | + * |
|---|
| 8437 | 8705 | * |
|---|
| 8438 | 8706 | * Since the returned value of calling the $http function is a `promise`, you can also use |
|---|
| 8439 | 8707 | * the `then` method to register callbacks, and these callbacks will receive a single argument – |
|---|
| .. | .. |
|---|
| 8587 | 8855 | * |
|---|
| 8588 | 8856 | * You can change the default cache to a new object (built with |
|---|
| 8589 | 8857 | * {@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 |
|---|
| 8591 | 8859 | * their `cache` property to `true` will now use this cache object. |
|---|
| 8592 | 8860 | * |
|---|
| 8593 | 8861 | * If you set the default cache to `false` then only requests that specify their own custom |
|---|
| .. | .. |
|---|
| 8949 | 9217 | |
|---|
| 8950 | 9218 | function transformResponse(response) { |
|---|
| 8951 | 9219 | // 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 | + } |
|---|
| 8955 | 9226 | return (isSuccess(response.status)) |
|---|
| 8956 | 9227 | ? resp |
|---|
| 8957 | 9228 | : $q.reject(resp); |
|---|
| .. | .. |
|---|
| 9694 | 9965 | return ''; |
|---|
| 9695 | 9966 | } |
|---|
| 9696 | 9967 | switch (typeof value) { |
|---|
| 9697 | | - case 'string': { |
|---|
| 9968 | + case 'string': |
|---|
| 9698 | 9969 | break; |
|---|
| 9699 | | - } |
|---|
| 9700 | | - case 'number': { |
|---|
| 9970 | + case 'number': |
|---|
| 9701 | 9971 | value = '' + value; |
|---|
| 9702 | 9972 | break; |
|---|
| 9703 | | - } |
|---|
| 9704 | | - default: { |
|---|
| 9973 | + default: |
|---|
| 9705 | 9974 | value = toJson(value); |
|---|
| 9706 | | - } |
|---|
| 9707 | 9975 | } |
|---|
| 9708 | 9976 | |
|---|
| 9709 | 9977 | return value; |
|---|
| .. | .. |
|---|
| 10525 | 10793 | search = search.toString(); |
|---|
| 10526 | 10794 | this.$$search = parseKeyValue(search); |
|---|
| 10527 | 10795 | } else if (isObject(search)) { |
|---|
| 10796 | + search = copy(search, {}); |
|---|
| 10528 | 10797 | // remove object undefined or null properties |
|---|
| 10529 | 10798 | forEach(search, function(value, key) { |
|---|
| 10530 | 10799 | if (value == null) delete search[key]; |
|---|
| .. | .. |
|---|
| 10710 | 10979 | * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are |
|---|
| 10711 | 10980 | * true, and a base tag is not present, an error will be thrown when `$location` is injected. |
|---|
| 10712 | 10981 | * 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. |
|---|
| 10715 | 10984 | * |
|---|
| 10716 | 10985 | * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter |
|---|
| 10717 | 10986 | */ |
|---|
| .. | .. |
|---|
| 10722 | 10991 | } else if (isObject(mode)) { |
|---|
| 10723 | 10992 | |
|---|
| 10724 | 10993 | if (isBoolean(mode.enabled)) { |
|---|
| 10725 | | - html5Mode.enabled = mode.enabled; |
|---|
| 10994 | + html5Mode.enabled = mode.enabled; |
|---|
| 10726 | 10995 | } |
|---|
| 10727 | 10996 | |
|---|
| 10728 | 10997 | if (isBoolean(mode.requireBase)) { |
|---|
| .. | .. |
|---|
| 10730 | 10999 | } |
|---|
| 10731 | 11000 | |
|---|
| 10732 | 11001 | if (isBoolean(mode.rewriteLinks)) { |
|---|
| 10733 | | - html5Mode.rewriteLinks = mode.rewriteLinks; |
|---|
| 11002 | + html5Mode.rewriteLinks = mode.rewriteLinks; |
|---|
| 10734 | 11003 | } |
|---|
| 10735 | 11004 | |
|---|
| 10736 | 11005 | return this; |
|---|
| .. | .. |
|---|
| 10749 | 11018 | * This change can be prevented by calling |
|---|
| 10750 | 11019 | * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more |
|---|
| 10751 | 11020 | * details about event object. Upon successful change |
|---|
| 10752 | | - * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. |
|---|
| 11021 | + * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. |
|---|
| 10753 | 11022 | * |
|---|
| 10754 | 11023 | * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when |
|---|
| 10755 | 11024 | * the browser supports the HTML5 History API. |
|---|
| .. | .. |
|---|
| 10901 | 11170 | var oldUrl = $browser.url(); |
|---|
| 10902 | 11171 | var oldState = $browser.state(); |
|---|
| 10903 | 11172 | var currentReplace = $location.$$replace; |
|---|
| 11173 | + var urlOrStateChanged = oldUrl !== $location.absUrl() || |
|---|
| 11174 | + ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); |
|---|
| 10904 | 11175 | |
|---|
| 10905 | | - if (initializing || oldUrl !== $location.absUrl() || |
|---|
| 10906 | | - ($location.$$html5 && $sniffer.history && oldState !== $location.$$state)) { |
|---|
| 11176 | + if (initializing || urlOrStateChanged) { |
|---|
| 10907 | 11177 | initializing = false; |
|---|
| 10908 | 11178 | |
|---|
| 10909 | 11179 | $rootScope.$evalAsync(function() { |
|---|
| .. | .. |
|---|
| 10912 | 11182 | $location.$$parse(oldUrl); |
|---|
| 10913 | 11183 | $location.$$state = oldState; |
|---|
| 10914 | 11184 | } 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 | + } |
|---|
| 10917 | 11189 | afterLocationChange(oldUrl, oldState); |
|---|
| 10918 | 11190 | } |
|---|
| 10919 | 11191 | }); |
|---|
| .. | .. |
|---|
| 11195 | 11467 | |
|---|
| 11196 | 11468 | //Operators - will be wrapped by binaryFn/unaryFn/assignment/filter |
|---|
| 11197 | 11469 | var OPERATORS = extend(createMap(), { |
|---|
| 11198 | | - /* jshint bitwise : false */ |
|---|
| 11199 | 11470 | '+':function(self, locals, a,b){ |
|---|
| 11200 | 11471 | a=a(self, locals); b=b(self, locals); |
|---|
| 11201 | 11472 | if (isDefined(a)) { |
|---|
| .. | .. |
|---|
| 11212 | 11483 | '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, |
|---|
| 11213 | 11484 | '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, |
|---|
| 11214 | 11485 | '%':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);}, |
|---|
| 11216 | 11486 | '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, |
|---|
| 11217 | 11487 | '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, |
|---|
| 11218 | 11488 | '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, |
|---|
| .. | .. |
|---|
| 11223 | 11493 | '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, |
|---|
| 11224 | 11494 | '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, |
|---|
| 11225 | 11495 | '||':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);}, |
|---|
| 11227 | 11496 | '!':function(self, locals, a){return !a(self, locals);}, |
|---|
| 11228 | 11497 | |
|---|
| 11229 | 11498 | //Tokenized as operators but parsed as assignment/filters |
|---|
| 11230 | 11499 | '=':true, |
|---|
| 11231 | 11500 | '|':true |
|---|
| 11232 | 11501 | }); |
|---|
| 11233 | | -/* jshint bitwise: true */ |
|---|
| 11234 | 11502 | var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; |
|---|
| 11235 | 11503 | |
|---|
| 11236 | 11504 | |
|---|
| .. | .. |
|---|
| 12268 | 12536 | } |
|---|
| 12269 | 12537 | |
|---|
| 12270 | 12538 | function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { |
|---|
| 12271 | | - var unwatch; |
|---|
| 12539 | + var unwatch, lastValue; |
|---|
| 12272 | 12540 | return unwatch = scope.$watch(function oneTimeWatch(scope) { |
|---|
| 12273 | 12541 | return parsedExpression(scope); |
|---|
| 12274 | 12542 | }, function oneTimeListener(value, old, scope) { |
|---|
| 12543 | + lastValue = value; |
|---|
| 12275 | 12544 | if (isFunction(listener)) { |
|---|
| 12276 | 12545 | listener.call(this, value, old, scope); |
|---|
| 12277 | 12546 | } |
|---|
| 12278 | 12547 | if (isAllDefined(value)) { |
|---|
| 12279 | 12548 | scope.$$postDigest(function () { |
|---|
| 12280 | | - if(isAllDefined(value)) unwatch(); |
|---|
| 12549 | + if(isAllDefined(lastValue)) unwatch(); |
|---|
| 12281 | 12550 | }); |
|---|
| 12282 | 12551 | } |
|---|
| 12283 | 12552 | }, objectEquality); |
|---|
| .. | .. |
|---|
| 12353 | 12622 | * It can be used like so: |
|---|
| 12354 | 12623 | * |
|---|
| 12355 | 12624 | * ```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); |
|---|
| 12372 | 12644 | * }, function(reason) { |
|---|
| 12373 | | - * // handle failure |
|---|
| 12645 | + * alert('Failed: ' + reason); |
|---|
| 12374 | 12646 | * }); |
|---|
| 12375 | 12647 | * ``` |
|---|
| 12376 | 12648 | * |
|---|
| .. | .. |
|---|
| 12386 | 12658 | * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. |
|---|
| 12387 | 12659 | * |
|---|
| 12388 | 12660 | * ```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` |
|---|
| 12390 | 12662 | * // are available in the current lexical scope (they could have been injected or passed in). |
|---|
| 12391 | 12663 | * |
|---|
| 12392 | 12664 | * function asyncGreet(name) { |
|---|
| .. | .. |
|---|
| 14726 | 14998 | * |
|---|
| 14727 | 14999 | * As of version 1.2, Angular ships with SCE enabled by default. |
|---|
| 14728 | 15000 | * |
|---|
| 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 |
|---|
| 14730 | 15002 | * one to execute arbitrary javascript by the use of the expression() syntax. Refer |
|---|
| 14731 | 15003 | * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them. |
|---|
| 14732 | 15004 | * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` |
|---|
| .. | .. |
|---|
| 14773 | 15045 | * |
|---|
| 14774 | 15046 | * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted |
|---|
| 14775 | 15047 | * $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 |
|---|
| 14777 | 15049 | * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. |
|---|
| 14778 | 15050 | * |
|---|
| 14779 | 15051 | * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link |
|---|
| .. | .. |
|---|
| 15043 | 15315 | * sce.js and sceSpecs.js would need to be aware of this detail. |
|---|
| 15044 | 15316 | */ |
|---|
| 15045 | 15317 | |
|---|
| 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 |
|---|
| 15049 | 15321 | // the "expression(javascript expression)" syntax which is insecure. |
|---|
| 15050 | | - if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { |
|---|
| 15322 | + if (enabled && $document[0].documentMode < 8) { |
|---|
| 15051 | 15323 | 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 ' + |
|---|
| 15053 | 15325 | 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + |
|---|
| 15054 | 15326 | 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); |
|---|
| 15055 | 15327 | } |
|---|
| .. | .. |
|---|
| 15272 | 15544 | * |
|---|
| 15273 | 15545 | * @description |
|---|
| 15274 | 15546 | * 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)`} |
|---|
| 15276 | 15548 | * |
|---|
| 15277 | 15549 | * @param {string} expression String expression to compile. |
|---|
| 15278 | 15550 | * @returns {function(context, locals)} a function which represents the compiled expression: |
|---|
| .. | .. |
|---|
| 15289 | 15561 | * |
|---|
| 15290 | 15562 | * @description |
|---|
| 15291 | 15563 | * 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)`} |
|---|
| 15293 | 15565 | * |
|---|
| 15294 | 15566 | * @param {string} expression String expression to compile. |
|---|
| 15295 | 15567 | * @returns {function(context, locals)} a function which represents the compiled expression: |
|---|
| .. | .. |
|---|
| 15306 | 15578 | * |
|---|
| 15307 | 15579 | * @description |
|---|
| 15308 | 15580 | * 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)`} |
|---|
| 15310 | 15582 | * |
|---|
| 15311 | 15583 | * @param {string} expression String expression to compile. |
|---|
| 15312 | 15584 | * @returns {function(context, locals)} a function which represents the compiled expression: |
|---|
| .. | .. |
|---|
| 15323 | 15595 | * |
|---|
| 15324 | 15596 | * @description |
|---|
| 15325 | 15597 | * 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)`} |
|---|
| 15327 | 15599 | * |
|---|
| 15328 | 15600 | * @param {string} expression String expression to compile. |
|---|
| 15329 | 15601 | * @returns {function(context, locals)} a function which represents the compiled expression: |
|---|
| .. | .. |
|---|
| 15340 | 15612 | * |
|---|
| 15341 | 15613 | * @description |
|---|
| 15342 | 15614 | * 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)`} |
|---|
| 15344 | 15616 | * |
|---|
| 15345 | 15617 | * @param {string} expression String expression to compile. |
|---|
| 15346 | 15618 | * @returns {function(context, locals)} a function which represents the compiled expression: |
|---|
| .. | .. |
|---|
| 15394 | 15666 | int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), |
|---|
| 15395 | 15667 | boxee = /Boxee/i.test(($window.navigator || {}).userAgent), |
|---|
| 15396 | 15668 | document = $document[0] || {}, |
|---|
| 15397 | | - documentMode = document.documentMode, |
|---|
| 15398 | 15669 | vendorPrefix, |
|---|
| 15399 | 15670 | vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, |
|---|
| 15400 | 15671 | bodyStyle = document.body && document.body.style, |
|---|
| .. | .. |
|---|
| 15454 | 15725 | vendorPrefix: vendorPrefix, |
|---|
| 15455 | 15726 | transitions : transitions, |
|---|
| 15456 | 15727 | animations : animations, |
|---|
| 15457 | | - android: android, |
|---|
| 15458 | | - msie : msie, |
|---|
| 15459 | | - msieDocumentMode: documentMode |
|---|
| 15728 | + android: android |
|---|
| 15460 | 15729 | }; |
|---|
| 15461 | 15730 | }]; |
|---|
| 15462 | 15731 | } |
|---|
| .. | .. |
|---|
| 16161 | 16430 | } |
|---|
| 16162 | 16431 | |
|---|
| 16163 | 16432 | var search = function(obj, text){ |
|---|
| 16164 | | - if (typeof text == 'string' && text.charAt(0) === '!') { |
|---|
| 16433 | + if (typeof text === 'string' && text.charAt(0) === '!') { |
|---|
| 16165 | 16434 | return !search(obj, text.substr(1)); |
|---|
| 16166 | 16435 | } |
|---|
| 16167 | 16436 | switch (typeof obj) { |
|---|
| 16168 | | - case "boolean": |
|---|
| 16169 | | - case "number": |
|---|
| 16170 | | - case "string": |
|---|
| 16437 | + case 'boolean': |
|---|
| 16438 | + case 'number': |
|---|
| 16439 | + case 'string': |
|---|
| 16171 | 16440 | return comparator(obj, text); |
|---|
| 16172 | | - case "object": |
|---|
| 16441 | + case 'object': |
|---|
| 16173 | 16442 | switch (typeof text) { |
|---|
| 16174 | | - case "object": |
|---|
| 16443 | + case 'object': |
|---|
| 16175 | 16444 | return comparator(obj, text); |
|---|
| 16176 | 16445 | default: |
|---|
| 16177 | 16446 | for ( var objKey in obj) { |
|---|
| .. | .. |
|---|
| 16182 | 16451 | break; |
|---|
| 16183 | 16452 | } |
|---|
| 16184 | 16453 | return false; |
|---|
| 16185 | | - case "array": |
|---|
| 16454 | + case 'array': |
|---|
| 16186 | 16455 | for ( var i = 0; i < obj.length; i++) { |
|---|
| 16187 | 16456 | if (search(obj[i], text)) { |
|---|
| 16188 | 16457 | return true; |
|---|
| .. | .. |
|---|
| 16194 | 16463 | } |
|---|
| 16195 | 16464 | }; |
|---|
| 16196 | 16465 | switch (typeof expression) { |
|---|
| 16197 | | - case "boolean": |
|---|
| 16198 | | - case "number": |
|---|
| 16199 | | - case "string": |
|---|
| 16466 | + case 'boolean': |
|---|
| 16467 | + case 'number': |
|---|
| 16468 | + case 'string': |
|---|
| 16200 | 16469 | // Set up expression object and fall through |
|---|
| 16201 | 16470 | expression = {$:expression}; |
|---|
| 16202 | 16471 | // jshint -W086 |
|---|
| 16203 | | - case "object": |
|---|
| 16472 | + case 'object': |
|---|
| 16204 | 16473 | // jshint +W086 |
|---|
| 16205 | 16474 | for (var key in expression) { |
|---|
| 16206 | 16475 | (function(path) { |
|---|
| .. | .. |
|---|
| 16239 | 16508 | * |
|---|
| 16240 | 16509 | * @param {number} amount Input to filter. |
|---|
| 16241 | 16510 | * @param {string=} symbol Currency symbol or identifier to be displayed. |
|---|
| 16511 | + * @param {number=} fractionSize Number of decimal places to round the amount to. |
|---|
| 16242 | 16512 | * @returns {string} Formatted number. |
|---|
| 16243 | 16513 | * |
|---|
| 16244 | 16514 | * |
|---|
| .. | .. |
|---|
| 16254 | 16524 | <div ng-controller="ExampleController"> |
|---|
| 16255 | 16525 | <input type="number" ng-model="amount"> <br> |
|---|
| 16256 | 16526 | 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> |
|---|
| 16258 | 16529 | </div> |
|---|
| 16259 | 16530 | </file> |
|---|
| 16260 | 16531 | <file name="protractor.js" type="protractor"> |
|---|
| 16261 | 16532 | it('should init with 1234.56', function() { |
|---|
| 16262 | 16533 | 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'); |
|---|
| 16264 | 16536 | }); |
|---|
| 16265 | 16537 | it('should update', function() { |
|---|
| 16266 | 16538 | if (browser.params.browser == 'safari') { |
|---|
| .. | .. |
|---|
| 16271 | 16543 | element(by.model('amount')).clear(); |
|---|
| 16272 | 16544 | element(by.model('amount')).sendKeys('-1234'); |
|---|
| 16273 | 16545 | 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)'); |
|---|
| 16275 | 16548 | }); |
|---|
| 16276 | 16549 | </file> |
|---|
| 16277 | 16550 | </example> |
|---|
| .. | .. |
|---|
| 16279 | 16552 | currencyFilter.$inject = ['$locale']; |
|---|
| 16280 | 16553 | function currencyFilter($locale) { |
|---|
| 16281 | 16554 | 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 | + } |
|---|
| 16284 | 16564 | |
|---|
| 16285 | 16565 | // if null or undefined pass it through |
|---|
| 16286 | 16566 | return (amount == null) |
|---|
| 16287 | 16567 | ? 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). |
|---|
| 16289 | 16569 | replace(/\u00A4/g, currencySymbol); |
|---|
| 16290 | 16570 | }; |
|---|
| 16291 | 16571 | } |
|---|
| .. | .. |
|---|
| 16802 | 17082 | <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> |
|---|
| 16803 | 17083 | Limit {{letters}} to: <input type="number" step="1" ng-model="letterLimit"> |
|---|
| 16804 | 17084 | <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"> |
|---|
| 16806 | 17086 | <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> |
|---|
| 16807 | 17087 | </div> |
|---|
| 16808 | 17088 | </file> |
|---|
| .. | .. |
|---|
| 18039 | 18319 | parentFormCtrl.$$renameControl(controller, alias); |
|---|
| 18040 | 18320 | }); |
|---|
| 18041 | 18321 | } |
|---|
| 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 | + }); |
|---|
| 18051 | 18329 | } |
|---|
| 18052 | 18330 | }; |
|---|
| 18053 | 18331 | } |
|---|
| .. | .. |
|---|
| 21294 | 21572 | * |
|---|
| 21295 | 21573 | * You may also bypass sanitization for values you know are safe. To do so, bind to |
|---|
| 21296 | 21574 | * 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)}. |
|---|
| 21298 | 21576 | * |
|---|
| 21299 | 21577 | * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you |
|---|
| 21300 | 21578 | * will have an exception (instead of an exploit.) |
|---|
| .. | .. |
|---|
| 21606 | 21884 | The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. |
|---|
| 21607 | 21885 | Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder |
|---|
| 21608 | 21886 | 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}. |
|---|
| 21611 | 21889 | */ |
|---|
| 21612 | 21890 | var ngClassDirective = classDirective('', true); |
|---|
| 21613 | 21891 | |
|---|
| .. | .. |
|---|
| 22013 | 22291 | * @description |
|---|
| 22014 | 22292 | * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. |
|---|
| 22015 | 22293 | * |
|---|
| 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. |
|---|
| 22017 | 22295 | * |
|---|
| 22018 | 22296 | * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). |
|---|
| 22019 | 22297 | * For Angular to be CSP compatible there are only two things that we need to do differently: |
|---|
| .. | .. |
|---|
| 22709 | 22987 | Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/> |
|---|
| 22710 | 22988 | Show when checked: |
|---|
| 22711 | 22989 | <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. |
|---|
| 22713 | 22991 | </span> |
|---|
| 22714 | 22992 | </file> |
|---|
| 22715 | 22993 | <file name="animations.css"> |
|---|
| .. | .. |
|---|
| 22763 | 23041 | }); |
|---|
| 22764 | 23042 | } |
|---|
| 22765 | 23043 | } else { |
|---|
| 22766 | | - if(previousElements) { |
|---|
| 23044 | + if (previousElements) { |
|---|
| 22767 | 23045 | previousElements.remove(); |
|---|
| 22768 | 23046 | previousElements = null; |
|---|
| 22769 | 23047 | } |
|---|
| 22770 | | - if(childScope) { |
|---|
| 23048 | + if (childScope) { |
|---|
| 22771 | 23049 | childScope.$destroy(); |
|---|
| 22772 | 23050 | childScope = null; |
|---|
| 22773 | 23051 | } |
|---|
| 22774 | | - if(block) { |
|---|
| 23052 | + if (block) { |
|---|
| 22775 | 23053 | previousElements = getBlockNodes(block.clone); |
|---|
| 22776 | 23054 | $animate.leave(previousElements).then(function() { |
|---|
| 22777 | 23055 | previousElements = null; |
|---|
| .. | .. |
|---|
| 22793 | 23071 | * Fetches, compiles and includes an external HTML fragment. |
|---|
| 22794 | 23072 | * |
|---|
| 22795 | 23073 | * 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 |
|---|
| 22797 | 23075 | * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols |
|---|
| 22798 | 23076 | * 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 |
|---|
| 22800 | 23078 | * ng.$sce Strict Contextual Escaping}. |
|---|
| 22801 | 23079 | * |
|---|
| 22802 | 23080 | * In addition, the browser's |
|---|
| .. | .. |
|---|
| 23495 | 23773 | * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements |
|---|
| 23496 | 23774 | * will be associated by item identity in the array. |
|---|
| 23497 | 23775 | * |
|---|
| 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 | | - * |
|---|
| 23505 | 23776 | * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique |
|---|
| 23506 | 23777 | * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements |
|---|
| 23507 | 23778 | * with the corresponding item in the array by identity. Moving the same object in array would move the DOM |
|---|
| .. | .. |
|---|
| 23513 | 23784 | * |
|---|
| 23514 | 23785 | * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter |
|---|
| 23515 | 23786 | * 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. |
|---|
| 23516 | 23794 | * |
|---|
| 23517 | 23795 | * @example |
|---|
| 23518 | 23796 | * This example initializes the scope to a list of names and |
|---|
| .. | .. |
|---|
| 24001 | 24279 | // we can control when the element is actually displayed on screen without having |
|---|
| 24002 | 24280 | // to have a global/greedy CSS selector that breaks when other animations are run. |
|---|
| 24003 | 24281 | // 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 | + }); |
|---|
| 24005 | 24285 | }); |
|---|
| 24006 | 24286 | } |
|---|
| 24007 | 24287 | }; |
|---|
| .. | .. |
|---|
| 24158 | 24438 | scope.$watch(attr.ngHide, function ngHideWatchAction(value){ |
|---|
| 24159 | 24439 | // The comment inside of the ngShowDirective explains why we add and |
|---|
| 24160 | 24440 | // 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 | + }); |
|---|
| 24162 | 24444 | }); |
|---|
| 24163 | 24445 | } |
|---|
| 24164 | 24446 | }; |
|---|
| .. | .. |
|---|
| 24583 | 24865 | * <div class="alert alert-info"> |
|---|
| 24584 | 24866 | * **Note:** Using `select as` will bind the result of the `select as` expression to the model, but |
|---|
| 24585 | 24867 | * 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. |
|---|
| 24587 | 24869 | * </div> |
|---|
| 24870 | + * |
|---|
| 24871 | + * **Note:** Using `select as` together with `trackexpr` is not recommended. |
|---|
| 24872 | + * Reasoning: |
|---|
| 24873 | + * - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"> |
|---|
| 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}`. |
|---|
| 24588 | 24884 | * |
|---|
| 24589 | 24885 | * @param {string} ngModel Assignable angular expression to data-bind to. |
|---|
| 24590 | 24886 | * @param {string=} name Property name of the form under which the control is published. |
|---|
| .. | .. |
|---|
| 24622 | 24918 | * used to identify the objects in the array. The `trackexpr` will most likely refer to the |
|---|
| 24623 | 24919 | * `value` variable (e.g. `value.propertyName`). With this the selection is preserved |
|---|
| 24624 | 24920 | * 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> |
|---|
| 24642 | 24921 | * |
|---|
| 24643 | 24922 | * @example |
|---|
| 24644 | 24923 | <example module="selectExample"> |
|---|
| .. | .. |
|---|
| 24748 | 25027 | // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 |
|---|
| 24749 | 25028 | // Adding an <option selected="selected"> element to a <select required="required"> should |
|---|
| 24750 | 25029 | // automatically select the new element |
|---|
| 24751 | | - if (element[0].hasAttribute('selected')) { |
|---|
| 25030 | + if (element && element[0].hasAttribute('selected')) { |
|---|
| 24752 | 25031 | element[0].selected = true; |
|---|
| 24753 | 25032 | } |
|---|
| 24754 | 25033 | }; |
|---|
| .. | .. |
|---|
| 24911 | 25190 | //re-usable object to represent option's locals |
|---|
| 24912 | 25191 | locals = {}; |
|---|
| 24913 | 25192 | |
|---|
| 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 | | - |
|---|
| 24921 | 25193 | if (nullOption) { |
|---|
| 24922 | 25194 | // compile the element since there might be bindings in it |
|---|
| 24923 | 25195 | $compile(nullOption)(scope); |
|---|
| .. | .. |
|---|
| 25008 | 25280 | function createIsSelectedFn(viewValue) { |
|---|
| 25009 | 25281 | var selectedSet; |
|---|
| 25010 | 25282 | if (multiple) { |
|---|
| 25011 | | - if (!selectAs && trackFn && isArray(viewValue)) { |
|---|
| 25283 | + if (trackFn && isArray(viewValue)) { |
|---|
| 25012 | 25284 | |
|---|
| 25013 | 25285 | selectedSet = new HashMap([]); |
|---|
| 25014 | 25286 | for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) { |
|---|
| .. | .. |
|---|
| 25018 | 25290 | } else { |
|---|
| 25019 | 25291 | selectedSet = new HashMap(viewValue); |
|---|
| 25020 | 25292 | } |
|---|
| 25021 | | - } else if (!selectAsFn && trackFn) { |
|---|
| 25293 | + } else if (trackFn) { |
|---|
| 25022 | 25294 | viewValue = callExpression(trackFn, null, viewValue); |
|---|
| 25023 | 25295 | } |
|---|
| 25296 | + |
|---|
| 25024 | 25297 | return function isSelected(key, value) { |
|---|
| 25025 | 25298 | var compareValueFn; |
|---|
| 25026 | | - if (selectAsFn) { |
|---|
| 25027 | | - compareValueFn = selectAsFn; |
|---|
| 25028 | | - } else if (trackFn) { |
|---|
| 25299 | + if (trackFn) { |
|---|
| 25029 | 25300 | compareValueFn = trackFn; |
|---|
| 25301 | + } else if (selectAsFn) { |
|---|
| 25302 | + compareValueFn = selectAsFn; |
|---|
| 25030 | 25303 | } else { |
|---|
| 25031 | 25304 | compareValueFn = valueFn; |
|---|
| 25032 | 25305 | } |
|---|
| .. | .. |
|---|
| 25046 | 25319 | } |
|---|
| 25047 | 25320 | } |
|---|
| 25048 | 25321 | |
|---|
| 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 | + |
|---|
| 25049 | 25339 | function render() { |
|---|
| 25050 | 25340 | renderScheduled = false; |
|---|
| 25051 | 25341 | |
|---|
| .. | .. |
|---|
| 25063 | 25353 | value, |
|---|
| 25064 | 25354 | groupLength, length, |
|---|
| 25065 | 25355 | groupIndex, index, |
|---|
| 25356 | + labelMap = {}, |
|---|
| 25066 | 25357 | selected, |
|---|
| 25067 | 25358 | isSelected = createIsSelectedFn(viewValue), |
|---|
| 25068 | 25359 | anySelected = false, |
|---|
| .. | .. |
|---|
| 25145 | 25436 | // reuse elements |
|---|
| 25146 | 25437 | lastElement = existingOption.element; |
|---|
| 25147 | 25438 | if (existingOption.label !== option.label) { |
|---|
| 25439 | + updateLabelMap(labelMap, existingOption.label, false); |
|---|
| 25440 | + updateLabelMap(labelMap, option.label, true); |
|---|
| 25148 | 25441 | lastElement.text(existingOption.label = option.label); |
|---|
| 25149 | 25442 | } |
|---|
| 25150 | 25443 | if (existingOption.id !== option.id) { |
|---|
| .. | .. |
|---|
| 25184 | 25477 | id: option.id, |
|---|
| 25185 | 25478 | selected: option.selected |
|---|
| 25186 | 25479 | }); |
|---|
| 25187 | | - selectCtrl.addOption(option.label, element); |
|---|
| 25480 | + updateLabelMap(labelMap, option.label, true); |
|---|
| 25188 | 25481 | if (lastElement) { |
|---|
| 25189 | 25482 | lastElement.after(element); |
|---|
| 25190 | 25483 | } else { |
|---|
| .. | .. |
|---|
| 25197 | 25490 | index++; // increment since the existingOptions[0] is parent element not OPTION |
|---|
| 25198 | 25491 | while(existingOptions.length > index) { |
|---|
| 25199 | 25492 | option = existingOptions.pop(); |
|---|
| 25200 | | - selectCtrl.removeOption(option.label); |
|---|
| 25493 | + updateLabelMap(labelMap, option.label, false); |
|---|
| 25201 | 25494 | option.element.remove(); |
|---|
| 25202 | 25495 | } |
|---|
| 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 | + }); |
|---|
| 25203 | 25503 | } |
|---|
| 25204 | 25504 | // remove any excessive OPTGROUPs from select |
|---|
| 25205 | 25505 | while(optionGroupsCache.length > groupIndex) { |
|---|