Compare commits
157 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8822a4ff69 | |||
| 772440cdaf | |||
| 1fbddf950f | |||
| 369ca3e13e | |||
| 00d38e3358 | |||
| a0ed1b73ff | |||
| e44c3ac327 | |||
| c6551231b2 | |||
| bf073be69d | |||
| aed34c75be | |||
| 78e17f5729 | |||
| ba6f477ac8 | |||
| 6482297185 | |||
| 41deaf1d21 | |||
| 23e4138c07 | |||
| 0ffb30f585 | |||
| 6f52013668 | |||
| 07e1ba29f0 | |||
| 04c64b3d10 | |||
| 4d9a95ffdb | |||
| 27486bd15e | |||
| cf919a6fb7 | |||
| d4d1031bcd | |||
| 798114075f | |||
| 838dd12ee1 | |||
| 6926223963 | |||
| 5f8372ee2d | |||
| 289374a43c | |||
| 3b333f3b9f | |||
| 7cbb1044fc | |||
| a8bfeff5d8 | |||
| fc0e100c45 | |||
| eb49f6b755 | |||
| f9e651d113 | |||
| 2f72a69ded | |||
| 8a67ac57fc | |||
| 90a41d415c | |||
| eefaa76a90 | |||
| bc3432365c | |||
| f4ac5c8487 | |||
| 9f2f567167 | |||
| 3e6f49a227 | |||
| 795398fab0 | |||
| f1a34bad20 | |||
| 84a6ea9eb4 | |||
| ff0ef2aeaf | |||
| c81e3556e8 | |||
| 8cc69b9edb | |||
| ae8ce3ec60 | |||
| df0d5d245e | |||
| 67ec5e8ceb | |||
| 4df2188e96 | |||
| 816a587578 | |||
| 0e1bd7822e | |||
| b27080d525 | |||
| 1a509873bf | |||
| fead62e958 | |||
| e2011bd0b3 | |||
| 3cdffcecba | |||
| f87d6648d7 | |||
| b1d0a83d01 | |||
| d6098eeb1c | |||
| c903b76f6c | |||
| f3a565872d | |||
| 582b03b983 | |||
| be240b1176 | |||
| 61b33543ff | |||
| 1dcba9cd88 | |||
| 8cd54d7794 | |||
| 3105b2c26a | |||
| bc5a48d4a4 | |||
| 2bbc7c464f | |||
| e85f91d582 | |||
| 862a78dfd2 | |||
| bd772abf34 | |||
| b074d719ae | |||
| a43a40b778 | |||
| 2ceeb739f3 | |||
| fa715abf45 | |||
| f943e377e8 | |||
| 30084c1369 | |||
| 668a33da34 | |||
| 19e2347759 | |||
| f17292e1b5 | |||
| 77b4330011 | |||
| 7717de4c51 | |||
| e68697e2e3 | |||
| a02ed88279 | |||
| a5914c94a8 | |||
| 63c9c9e8d7 | |||
| 4adbf82a84 | |||
| 1144b1eccb | |||
| 8970087e58 | |||
| 131e62a819 | |||
| 2fad638237 | |||
| a0940895a2 | |||
| 0a1db2ad5f | |||
| 4bd4246906 | |||
| 05ac702bc7 | |||
| 6882113bc1 | |||
| 535ee32a0b | |||
| 7cf4a2933c | |||
| 7dd6c87eec | |||
| 17f963c5d8 | |||
| ba09ba5344 | |||
| 8c36a43e91 | |||
| af14d67b84 | |||
| c8acff1cdc | |||
| 876e9842a2 | |||
| 5cb9465093 | |||
| 58f9413ad3 | |||
| 6f7674a7d0 | |||
| 8dc153db75 | |||
| 4a6f0996f6 | |||
| 522d581fc9 | |||
| 17b139f107 | |||
| 10973c3366 | |||
| fc64e68076 | |||
| ac5e92de9b | |||
| 0936353e9a | |||
| ed22d2fe7b | |||
| a5cfa88630 | |||
| bbf74f9994 | |||
| 62ad450d60 | |||
| faa4b17c86 | |||
| 4d980a8771 | |||
| 369469b4f3 | |||
| be417f2854 | |||
| 3a517c25f6 | |||
| 29b8dcf387 | |||
| c9d1e690aa | |||
| a47247b5e0 | |||
| b682213d72 | |||
| 223cf2b5bb | |||
| c387e0d79d | |||
| cbf74d5d64 | |||
| a812327acd | |||
| 35fce310e9 | |||
| 93a754a490 | |||
| 7d9d387195 | |||
| feac52d840 | |||
| f4f571efdf | |||
| a8c263c194 | |||
| 3d6c45d76e | |||
| bf841d3512 | |||
| a1d88457de | |||
| b011ae9544 | |||
| 63fdee6b9c | |||
| 257ebbb514 | |||
| 2b6c986736 | |||
| 2da4950406 | |||
| 789db83a8a | |||
| ce443792c4 | |||
| dd47867bfb | |||
| f2ebb82ba5 | |||
| 12698755be | |||
| f7d7954904 |
+1
-1
@@ -1,4 +1,4 @@
|
||||
# http://editorconfig.org
|
||||
# https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ https://plnkr.co or similar (you can use this template as a starting point: http
|
||||
<!-- Check whether this is still an issue in the most recent stable or in the snapshot AngularJS
|
||||
version (https://code.angularjs.org/snapshot/) -->
|
||||
|
||||
**Browser:** [all | Chrome XX | Firefox XX | Edge XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
|
||||
**Browser:** [all | Chrome XX | Firefox XX | Edge XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView | Opera XX ]
|
||||
<!-- All browsers where this could be reproduced (and Operating System if relevant) -->
|
||||
|
||||
**Anything else:**
|
||||
|
||||
+2
-1
@@ -15,6 +15,7 @@ env:
|
||||
- JOB=ci-checks
|
||||
- JOB=unit-core BROWSER_PROVIDER=saucelabs
|
||||
- JOB=unit-jquery BROWSER_PROVIDER=saucelabs
|
||||
- JOB=unit-modules BROWSER_PROVIDER=saucelabs
|
||||
- JOB=docs-app BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
|
||||
@@ -26,7 +27,7 @@ env:
|
||||
- secure: oTBjhnOKhs0qDSKTf7fE4f6DYiNDPycvB7qfSF5QRIbJK/LK/J4UtFwetXuXj79HhUZG9qnoT+5e7lPaiaMlpsIKn9ann7ffqFWN1E8TMtpJF+AGigx3djYElwfgf5nEnFUFhwjFzvbfpZNnxVGgX5YbIZpe/WUbHkP4ffU0Wks=
|
||||
|
||||
before_install:
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.10.1
|
||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
||||
|
||||
before_script:
|
||||
|
||||
+291
-4
@@ -1,3 +1,261 @@
|
||||
<a name="1.7.6"></a>
|
||||
# 1.7.6 gravity-manipulation (2019-01-17)
|
||||
|
||||
## Bug Fixes
|
||||
- **$compile:** fix ng-prop-* with undefined values
|
||||
([772440](https://github.com/angular/angular.js/commit/772440cdaf9a9bfa40de1675e20a5f0e356089ed),
|
||||
[#16797](https://github.com/angular/angular.js/issues/16797),
|
||||
[#16798](https://github.com/angular/angular.js/issues/16798))
|
||||
- **compile:** properly handle false value for boolean attrs with jQuery
|
||||
([27486b](https://github.com/angular/angular.js/commit/27486bd15e70946ece2ba713e4e8654b7f9bddad),
|
||||
[#16778](https://github.com/angular/angular.js/issues/16778),
|
||||
[#16779](https://github.com/angular/angular.js/issues/16779))
|
||||
- **ngRepeat:**
|
||||
- fix reference to last collection value remaining across linkages
|
||||
([cf919a](https://github.com/angular/angular.js/commit/cf919a6fb7fc655f3fa37a74899a797ea5b8073e))
|
||||
- fix trackBy function being invoked with incorrect scope
|
||||
([d4d103](https://github.com/angular/angular.js/commit/d4d1031bcd9b30ae6a58bd60a79bcc9d20f0f2b7),
|
||||
[#16776](https://github.com/angular/angular.js/issues/16776),
|
||||
[#16777](https://github.com/angular/angular.js/issues/16777))
|
||||
- **aria/ngClick:** check if element is `contenteditable` before blocking spacebar
|
||||
([289374](https://github.com/angular/angular.js/commit/289374a43c1b2fd715ddf7455db225b17afebbaf),
|
||||
[#16762](https://github.com/angular/angular.js/issues/16762))
|
||||
- **input:** prevent browsers from autofilling hidden inputs
|
||||
([7cbb10](https://github.com/angular/angular.js/commit/7cbb1044fcb3576cdad791bd22ebea3dfd533ff8))
|
||||
- **Angular:** add workaround for Safari / Webdriver problem
|
||||
([eb49f6](https://github.com/angular/angular.js/commit/eb49f6b7555cfd7ab03fd35581adb6b4bd49044e))
|
||||
- **$browser:** normalize inputted URLs
|
||||
([2f72a6](https://github.com/angular/angular.js/commit/2f72a69ded53a122afad3ec28d91f9bd2f41eb4f),
|
||||
[#16606](https://github.com/angular/angular.js/issues/16606))
|
||||
- **interpolate:** do not create directives for constant media URL attributes
|
||||
([90a41d](https://github.com/angular/angular.js/commit/90a41d415c83abdbf28317f49df0fd0a7e07db86),
|
||||
[#16734](https://github.com/angular/angular.js/issues/16734))
|
||||
- **$q:** allow third-party promise libraries
|
||||
([eefaa7](https://github.com/angular/angular.js/commit/eefaa76a90dbef08fdc7d734a205cc2de50d9f91),
|
||||
[#16164](https://github.com/angular/angular.js/issues/16164),
|
||||
[#16471](https://github.com/angular/angular.js/issues/16471))
|
||||
- **urlUtils:** make IPv6 URL's hostname wrapped in square brackets in IE/Edge
|
||||
([0e1bd7](https://github.com/angular/angular.js/commit/0e1bd7822e61822a48b8fd7ba5913a8702e6dabf),
|
||||
[#16692](https://github.com/angular/angular.js/issues/16692),
|
||||
[#16715](https://github.com/angular/angular.js/issues/16715))
|
||||
- **ngAnimateSwap:** make it compatible with `ngIf` on the same element
|
||||
([b27080](https://github.com/angular/angular.js/commit/b27080d52546409fb4e483f212f03616e2ca8037),
|
||||
[#16616](https://github.com/angular/angular.js/issues/16616),
|
||||
[#16729](https://github.com/angular/angular.js/issues/16729))
|
||||
- **ngMock:** make matchLatestDefinitionEnabled work
|
||||
([3cdffc](https://github.com/angular/angular.js/commit/3cdffcecbae71189b4db69b57fadda6608a23b61),
|
||||
[#16702](https://github.com/angular/angular.js/issues/16702))
|
||||
- **ngStyle:** skip setting empty value when new style has the property
|
||||
([d6098e](https://github.com/angular/angular.js/commit/d6098eeb1c9510d599e9bd3cfdba7dd21e7a55a5),
|
||||
[#16709](https://github.com/angular/angular.js/issues/16709))
|
||||
|
||||
## Performance Improvements
|
||||
- **input:** prevent multiple validations on initialization
|
||||
([692622](https://github.com/angular/angular.js/commit/69262239632027b373258e75c670b89132ad9edb),
|
||||
[#14691](https://github.com/angular/angular.js/issues/14691),
|
||||
[#16760](https://github.com/angular/angular.js/issues/16760))
|
||||
|
||||
|
||||
|
||||
<a name="1.7.5"></a>
|
||||
# 1.7.5 anti-prettification (2018-10-04)
|
||||
|
||||
## Bug Fixes
|
||||
- **ngClass:** do not break on invalid values
|
||||
([f3a565](https://github.com/angular/angular.js/commit/f3a565872d802c94bb213944791b11b483d52f73),
|
||||
[#16697](https://github.com/angular/angular.js/issues/16697),
|
||||
[#16699](https://github.com/angular/angular.js/issues/16699))
|
||||
|
||||
|
||||
<a name="1.7.4"></a>
|
||||
# 1.7.4 interstellar-exploration (2018-09-07)
|
||||
|
||||
## Bug Fixes
|
||||
- **ngAria.ngClick:** prevent default event on space/enter only for non-interactive elements
|
||||
([61b335](https://github.com/angular/angular.js/commit/61b33543ff8e7f32464dec98a46bf0a35e9b03a4),
|
||||
[#16664](https://github.com/angular/angular.js/issues/16664),
|
||||
[#16680](https://github.com/angular/angular.js/issues/16680))
|
||||
- **ngAnimate:** remove the "prepare" classes with multiple structural animations
|
||||
([3105b2](https://github.com/angular/angular.js/commit/3105b2c26a71594c4e7904efc18f4b2e9da25b1b),
|
||||
[#16681](https://github.com/angular/angular.js/issues/16681),
|
||||
[#16677](https://github.com/angular/angular.js/issues/16677))
|
||||
- **$route:** correctly extract path params if the path contains a question mark or a hash
|
||||
([2ceeb7](https://github.com/angular/angular.js/commit/2ceeb739f35e01fcebcabac4beeeb7684ae9f86d))
|
||||
- **ngHref:** allow numbers and other objects in interpolation
|
||||
([30084c](https://github.com/angular/angular.js/commit/30084c13699c814ff6703d7aa2d3947a9b2f7067),
|
||||
[#16652](https://github.com/angular/angular.js/issues/16652),
|
||||
[#16626](https://github.com/angular/angular.js/issues/16626))
|
||||
- **select:** allow to select first option with value `undefined`
|
||||
([668a33](https://github.com/angular/angular.js/commit/668a33da3439f17e61dfa8f6d9b114ebde8c9d87),
|
||||
[#16653](https://github.com/angular/angular.js/issues/16653),
|
||||
[#16656](https://github.com/angular/angular.js/issues/16656))
|
||||
|
||||
|
||||
<a name="1.7.3"></a>
|
||||
# 1.7.3 eventful-proposal (2018-08-03)
|
||||
|
||||
## Bug Fixes
|
||||
- **$location:**
|
||||
- fix infinite recursion/digest on URLs with special characters
|
||||
([e68697](https://github.com/angular/angular.js/commit/e68697e2e30695f509e6c2c1e43c2c02b7af41f0),
|
||||
[#16592](https://github.com/angular/angular.js/issues/16592),
|
||||
[#16611](https://github.com/angular/angular.js/issues/16611))
|
||||
- avoid unnecessary `$locationChange*` events due to empty hash
|
||||
([1144b1](https://github.com/angular/angular.js/commit/1144b1eccb886ea0e4a80bcb07d38a305c3263b4),
|
||||
[#16632](https://github.com/angular/angular.js/issues/16632),
|
||||
[#16636](https://github.com/angular/angular.js/issues/16636))
|
||||
- **ngMock.$httpBackend:**
|
||||
- pass failed HTTP expectations to `$exceptionHandler`
|
||||
([4adbf8](https://github.com/angular/angular.js/commit/4adbf82a84a564a8d3f0982c17a64c6163200bcd),
|
||||
[#16644](https://github.com/angular/angular.js/issues/16644))
|
||||
- correctly ignore query params in {expect,when}Route
|
||||
([be417f](https://github.com/angular/angular.js/commit/be417f28549e184fbc3c7f74251ac21fca965ae8),
|
||||
[#14173](https://github.com/angular/angular.js/issues/14173),
|
||||
[#16589](https://github.com/angular/angular.js/issues/16589))
|
||||
- **Angular:** add workaround for Safari / Webdriver problem
|
||||
([0a1db2](https://github.com/angular/angular.js/commit/0a1db2ad5f8da6902b1711a738ae4177ce9685fa),
|
||||
[#16645](https://github.com/angular/angular.js/issues/16645))
|
||||
- **$animate:** avoid memory leak with `$animate.enabled(element, enabled)`
|
||||
([4bd424](https://github.com/angular/angular.js/commit/4bd424690612885ca06028e9b27de585edc3d3c3),
|
||||
[#16649](https://github.com/angular/angular.js/issues/16649))
|
||||
- **$compile:**
|
||||
- use correct parent element when requiring on html element
|
||||
([05ac70](https://github.com/angular/angular.js/commit/05ac702bc7edae5f89c363ea661774910735ea8b),
|
||||
[#16535](https://github.com/angular/angular.js/issues/16535),
|
||||
[#16647](https://github.com/angular/angular.js/issues/16647))
|
||||
- work around Firefox `DocumentFragment` bug
|
||||
([10973c](https://github.com/angular/angular.js/commit/10973c3366676ac8e5b2728b1e006cdef4ea197e),
|
||||
[#16607](https://github.com/angular/angular.js/issues/16607),
|
||||
[#16615](https://github.com/angular/angular.js/issues/16615))
|
||||
- **ngEventDirs:**
|
||||
- pass error in handler to $exceptionHandler when event was triggered in a digest
|
||||
([688211](https://github.com/angular/angular.js/commit/6882113bc194fb10081db9bab3dd7d69dd59f311))
|
||||
- don't wrap the event handler in $apply if already in $digest
|
||||
([535ee3](https://github.com/angular/angular.js/commit/535ee32a0b4881c9fd526fb5e0ffc10919ba1800),
|
||||
[#14673](https://github.com/angular/angular.js/issues/14673),
|
||||
[#14674](https://github.com/angular/angular.js/issues/14674))
|
||||
- **angular.element:** do not break on `cleanData()` if `_data()` returns undefined
|
||||
([7cf4a2](https://github.com/angular/angular.js/commit/7cf4a2933cb017e45b0c97b0a836cbbd905ee31a),
|
||||
[#16641](https://github.com/angular/angular.js/issues/16641),
|
||||
[#16642](https://github.com/angular/angular.js/issues/16642))
|
||||
- **ngAria:** do not scroll when pressing spacebar on custom buttons
|
||||
([3a517c](https://github.com/angular/angular.js/commit/3a517c25f677294a7a9eca1660654a3edcc9e103),
|
||||
[#14665](https://github.com/angular/angular.js/issues/14665),
|
||||
[#16604](https://github.com/angular/angular.js/issues/16604))
|
||||
|
||||
|
||||
## New Features
|
||||
- **$compile:** add support for arbitrary DOM property and event bindings
|
||||
([a5914c](https://github.com/angular/angular.js/commit/a5914c94a8fa5b1eceeab9e4e6849cbf467bc26d),
|
||||
[#16428](https://github.com/angular/angular.js/issues/16428),
|
||||
[#16235](https://github.com/angular/angular.js/issues/16235),
|
||||
[#16614](https://github.com/angular/angular.js/issues/16614))
|
||||
- **ngMock:** add `$flushPendingTasks()` and `$verifyNoPendingTasks()`
|
||||
([6f7674](https://github.com/angular/angular.js/commit/6f7674a7d063d434205f75f5b861f167e8125999),
|
||||
[#14336](https://github.com/angular/angular.js/issues/14336))
|
||||
- **core:** implement more granular pending task tracking
|
||||
([17b139](https://github.com/angular/angular.js/commit/17b139f107e5471a9351af638093a8e13a69e42a))
|
||||
- **$animate:** add option data to event callbacks
|
||||
([fc64e6](https://github.com/angular/angular.js/commit/fc64e6807642512b567deb52b497bd2bff570a1f),
|
||||
[#12697](https://github.com/angular/angular.js/issues/12697),
|
||||
[#13059](https://github.com/angular/angular.js/issues/13059))
|
||||
- **form.FormController:** add $getControls()
|
||||
([c9d1e6](https://github.com/angular/angular.js/commit/c9d1e690aa597283373b78e646676fa8f1ba1b4d),
|
||||
[#16601](https://github.com/angular/angular.js/issues/16601),
|
||||
[#14749](https://github.com/angular/angular.js/issues/14749),
|
||||
[#14517](https://github.com/angular/angular.js/issues/14517),
|
||||
[#13202](https://github.com/angular/angular.js/issues/13202))
|
||||
- **ngModelOptions:** add `timeStripZeroSeconds` and `timeSecondsFormat`
|
||||
([b68221](https://github.com/angular/angular.js/commit/b682213d72d65c996a6a31ea57b79d4c4f4e3c98),
|
||||
[#10721](https://github.com/angular/angular.js/issues/10721),
|
||||
[#16510](https://github.com/angular/angular.js/issues/16510),
|
||||
[#16584](https://github.com/angular/angular.js/issues/16584))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
- **ngAnimate:** avoid repeated calls to addClass/removeClass when animation has no duration
|
||||
([093635](https://github.com/angular/angular.js/commit/0936353e9a03f072bc3c4056888fd154a96530ef),
|
||||
[#14165](https://github.com/angular/angular.js/issues/14165),
|
||||
[#14166](https://github.com/angular/angular.js/issues/14166),
|
||||
[#16613](https://github.com/angular/angular.js/issues/16613))
|
||||
|
||||
|
||||
<a name="1.7.2"></a>
|
||||
# 1.7.2 extreme-compatiplication (2018-06-12)
|
||||
|
||||
In the previous release, we removed a private, undocumented API that was no longer used by
|
||||
AngularJS. It turned out that several popular UI libraries (such as
|
||||
[AngularJS Material](https://material.angularjs.org/),
|
||||
[UI Bootstrap](https://angular-ui.github.io/bootstrap/),
|
||||
[ngDialog](http://likeastore.github.io/ngDialog/) and probably others) relied on that API.
|
||||
|
||||
In order to avoid unnecessary pain for developers, this release reverts the removal of the private
|
||||
API and restores compatibility of the aforementioned libraries with the latest AngularJS.
|
||||
|
||||
## Reverts
|
||||
- **$compile:** remove `preAssignBindingsEnabled` leftovers
|
||||
([2da495](https://github.com/angular/angular.js/commit/2da49504065e9e2b71a7a5622e45118d8abbe87e),
|
||||
[#16580](https://github.com/angular/angular.js/pull/16580),
|
||||
[a81232](https://github.com/angular/angular.js/commit/a812327acda8bc890a4c4e809f0debb761c29625),
|
||||
[#16595](https://github.com/angular/angular.js/pull/16595))
|
||||
|
||||
|
||||
<a name="1.7.1"></a>
|
||||
# 1.7.1 momentum-defiance (2018-06-08)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
- **$compile:** support transcluding multi-element directives
|
||||
([789db8](https://github.com/angular/angular.js/commit/789db83a8ae0e2db5db13289b2c29e56093d967a),
|
||||
[#15554](https://github.com/angular/angular.js/issues/15554),
|
||||
[#15555](https://github.com/angular/angular.js/issues/15555))
|
||||
- **ngModel:** do not throw if view value changes on destroyed scope
|
||||
([2b6c98](https://github.com/angular/angular.js/commit/2b6c9867369fd3ef1ddb687af1153478ab62ee1b),
|
||||
[#16583](https://github.com/angular/angular.js/issues/16583),
|
||||
[#16585](https://github.com/angular/angular.js/issues/16585))
|
||||
|
||||
|
||||
## New Features
|
||||
- **$compile:** add one-way collection bindings
|
||||
([f9d1ca](https://github.com/angular/angular.js/commit/f9d1ca20c38f065f15769fbe23aee5314cb58bd4),
|
||||
[#14039](https://github.com/angular/angular.js/issues/14039),
|
||||
[#16553](https://github.com/angular/angular.js/issues/16553),
|
||||
[#15874](https://github.com/angular/angular.js/issues/15874))
|
||||
- **ngRef:** add directive to publish controller, or element into scope
|
||||
([bf841d](https://github.com/angular/angular.js/commit/bf841d35120bf3c4655fde46af4105c85a0f1cdc),
|
||||
[#16511](https://github.com/angular/angular.js/issues/16511))
|
||||
- **errorHandlingConfig:** add option to exclude error params from url
|
||||
([3d6c45](https://github.com/angular/angular.js/commit/3d6c45d76e30b1b3c4eb9672cf4a93e5251c06b3),
|
||||
[#14744](https://github.com/angular/angular.js/issues/14744),
|
||||
[#15707](https://github.com/angular/angular.js/issues/15707),
|
||||
[#16283](https://github.com/angular/angular.js/issues/16283),
|
||||
[#16299](https://github.com/angular/angular.js/issues/16299),
|
||||
[#16591](https://github.com/angular/angular.js/issues/16591))
|
||||
- **ngAria:** add support for ignoring a specific element
|
||||
([7d9d38](https://github.com/angular/angular.js/commit/7d9d387195292cb5e04984602b752d31853cfea6),
|
||||
[#14602](https://github.com/angular/angular.js/issues/14602),
|
||||
[#14672](https://github.com/angular/angular.js/issues/14672),
|
||||
[#14833](https://github.com/angular/angular.js/issues/14833))
|
||||
- **ngCookies:** support samesite option
|
||||
([10a229](https://github.com/angular/angular.js/commit/10a229ce1befdeaf6295d1635dc11391c252a91a),
|
||||
[#16543](https://github.com/angular/angular.js/issues/16543),
|
||||
[#16544](https://github.com/angular/angular.js/issues/16544))
|
||||
- **ngMessages:** add support for default message
|
||||
([a8c263](https://github.com/angular/angular.js/commit/a8c263c1947cc85ee60b4732f7e4bcdc7ba463e8),
|
||||
[#12008](https://github.com/angular/angular.js/issues/12008),
|
||||
[#12213](https://github.com/angular/angular.js/issues/12213),
|
||||
[#16587](https://github.com/angular/angular.js/issues/16587))
|
||||
- **ngMock, ngMockE2E:** add option to match latest definition for `$httpBackend` request
|
||||
([773f39](https://github.com/angular/angular.js/commit/773f39c9345479f5f8b6321236ce6ad96f77aa92),
|
||||
[#16251](https://github.com/angular/angular.js/issues/16251),
|
||||
[#11637](https://github.com/angular/angular.js/issues/11637),
|
||||
[#16560](https://github.com/angular/angular.js/issues/16560))
|
||||
- **$route:** add support for the `reloadOnUrl` configuration option
|
||||
([f4f571](https://github.com/angular/angular.js/commit/f4f571efdf86d6acbcd5c6b1de66b4b33a259125),
|
||||
[#7925](https://github.com/angular/angular.js/issues/7925),
|
||||
[#15002](https://github.com/angular/angular.js/issues/15002))
|
||||
|
||||
|
||||
<a name="1.7.0"></a>
|
||||
# 1.7.0 nonexistent-physiology (2018-05-11)
|
||||
|
||||
@@ -372,8 +630,8 @@ This in turn affects how dirty checking treats objects that prototypally
|
||||
inherit from `Array` (e.g. MobX observable arrays). AngularJS will now
|
||||
be able to handle these objects better when copying or watching.
|
||||
|
||||
### **$sce** due to:
|
||||
- **[1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**: handle URL sanitization through the `$sce` service
|
||||
### **$sce** :
|
||||
- due to **[1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**: handle URL sanitization through the `$sce` service
|
||||
|
||||
If you use `attrs.$set` for URL attributes (a[href] and img[src]) there will no
|
||||
longer be any automated sanitization of the value. This is in line with other
|
||||
@@ -387,6 +645,35 @@ Note that values that have been passed through the `$interpolate` service within
|
||||
`URL` or `MEDIA_URL` will have already been sanitized, so you would not need to sanitize
|
||||
these values again.
|
||||
|
||||
- due to **[1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**: handle URL sanitization through the `$sce` service
|
||||
|
||||
binding `trustAs()` and the short versions (`trustAsResourceUrl()` et al.) to
|
||||
`ngSrc`, `ngSrcset`, and `ngHref` will now raise an infinite digest error:
|
||||
|
||||
```js
|
||||
$scope.imgThumbFn = function(id) {
|
||||
return $sce.trustAsResourceUrl(someService.someUrl(id));
|
||||
};
|
||||
```
|
||||
|
||||
```html
|
||||
<img ng-src="{{imgThumbFn(imgId)}}">
|
||||
```
|
||||
This is because the `$interpolate` service is now responsible for sanitizing
|
||||
the attribute value, and its watcher receives a new object from `trustAs()`
|
||||
on every digest.
|
||||
To migrate, compute the trusted value only when the input value changes:
|
||||
|
||||
```js
|
||||
$scope.$watch('imgId', function(id) {
|
||||
$scope.imgThumb = $sce.trustAsResourceUrl(someService.someUrl(id));
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<img ng-src="{{imgThumb}}">
|
||||
```
|
||||
|
||||
### **orderBy** due to:
|
||||
- **[1d8046](https://github.com/angular/angular.js/commit/1d804645f7656d592c90216a0355b4948807f6b8)**: consider `null` and `undefined` greater than other values
|
||||
|
||||
@@ -518,7 +805,7 @@ If you rely on the $modelValue validation, you can overwrite the `min`/`max` val
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
var maxValidator = ctrl.$validators.max;
|
||||
|
||||
ctrk.$validators.max = function(modelValue, viewValue) {
|
||||
ctrl.$validators.max = function(modelValue, viewValue) {
|
||||
return maxValidator(modelValue, modelValue);
|
||||
};
|
||||
}
|
||||
@@ -1351,7 +1638,7 @@ If you rely on the $modelValue validation, you can overwrite the `min`/`max` val
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
var maxValidator = ctrl.$validators.max;
|
||||
|
||||
ctrk.$validators.max = function(modelValue, viewValue) {
|
||||
ctrl.$validators.max = function(modelValue, viewValue) {
|
||||
return maxValidator(modelValue, modelValue);
|
||||
};
|
||||
}
|
||||
|
||||
+56
-3
@@ -4,6 +4,7 @@ var serveFavicon = require('serve-favicon');
|
||||
var serveStatic = require('serve-static');
|
||||
var serveIndex = require('serve-index');
|
||||
var files = require('./angularFiles').files;
|
||||
var mergeFilesFor = require('./angularFiles').mergeFilesFor;
|
||||
var util = require('./lib/grunt/utils.js');
|
||||
var versionInfo = require('./lib/versions/version-info');
|
||||
var path = require('path');
|
||||
@@ -30,7 +31,7 @@ if (!semver.satisfies(currentYarnVersion, expectedYarnVersion)) {
|
||||
}
|
||||
|
||||
// Grunt CLI version checks
|
||||
var expectedGruntVersion = pkg.engines.grunt;
|
||||
var expectedGruntVersion = pkg.engines['grunt-cli'];
|
||||
var currentGruntVersions = exec('grunt --version', {silent: true}).stdout;
|
||||
var match = /^grunt-cli v(.+)$/m.exec(currentGruntVersions);
|
||||
if (!match) {
|
||||
@@ -141,7 +142,9 @@ module.exports = function(grunt) {
|
||||
'jquery-2.2': 'karma-jquery-2.2.conf.js',
|
||||
'jquery-2.1': 'karma-jquery-2.1.conf.js',
|
||||
docs: 'karma-docs.conf.js',
|
||||
modules: 'karma-modules.conf.js'
|
||||
modules: 'karma-modules.conf.js',
|
||||
'modules-ngAnimate': 'karma-modules-ngAnimate.conf.js',
|
||||
'modules-ngMock': 'karma-modules-ngMock.conf.js'
|
||||
},
|
||||
|
||||
|
||||
@@ -211,6 +214,12 @@ module.exports = function(grunt) {
|
||||
dest: 'build/angular-touch.js',
|
||||
src: util.wrap(files['angularModules']['ngTouch'], 'module')
|
||||
},
|
||||
touchModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-touch.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngTouch'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
mocks: {
|
||||
dest: 'build/angular-mocks.js',
|
||||
src: util.wrap(files['angularModules']['ngMock'], 'module'),
|
||||
@@ -220,18 +229,42 @@ module.exports = function(grunt) {
|
||||
dest: 'build/angular-sanitize.js',
|
||||
src: util.wrap(files['angularModules']['ngSanitize'], 'module')
|
||||
},
|
||||
sanitizeModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-sanitize.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngSanitize'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
resource: {
|
||||
dest: 'build/angular-resource.js',
|
||||
src: util.wrap(files['angularModules']['ngResource'], 'module')
|
||||
},
|
||||
resourceModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-resource.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngResource'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
messageformat: {
|
||||
dest: 'build/angular-message-format.js',
|
||||
src: util.wrap(files['angularModules']['ngMessageFormat'], 'module')
|
||||
},
|
||||
messageformatModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-message-format.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngMessageFormat'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
messages: {
|
||||
dest: 'build/angular-messages.js',
|
||||
src: util.wrap(files['angularModules']['ngMessages'], 'module')
|
||||
},
|
||||
messagesModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-messages.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngMessages'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
animate: {
|
||||
dest: 'build/angular-animate.js',
|
||||
src: util.wrap(files['angularModules']['ngAnimate'], 'module')
|
||||
@@ -240,14 +273,32 @@ module.exports = function(grunt) {
|
||||
dest: 'build/angular-route.js',
|
||||
src: util.wrap(files['angularModules']['ngRoute'], 'module')
|
||||
},
|
||||
routeModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-route.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngRoute'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
cookies: {
|
||||
dest: 'build/angular-cookies.js',
|
||||
src: util.wrap(files['angularModules']['ngCookies'], 'module')
|
||||
},
|
||||
cookiesModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-cookies.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngCookies'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
aria: {
|
||||
dest: 'build/angular-aria.js',
|
||||
src: util.wrap(files['angularModules']['ngAria'], 'module')
|
||||
},
|
||||
ariaModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-aria.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngAria'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
parseext: {
|
||||
dest: 'build/angular-parse-ext.js',
|
||||
src: util.wrap(files['angularModules']['ngParseExt'], 'module')
|
||||
@@ -430,7 +481,9 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('test:jquery-2.1', 'Run the jQuery 2.1 unit tests with Karma', ['tests:jquery-2.1']);
|
||||
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', [
|
||||
'build',
|
||||
'tests:modules'
|
||||
'tests:modules',
|
||||
'tests:modules-ngAnimate',
|
||||
'tests:modules-ngMock'
|
||||
]);
|
||||
grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']);
|
||||
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', [
|
||||
|
||||
@@ -14,9 +14,9 @@ piece of cake. Best of all? It makes development fun!
|
||||
|
||||
--------------------
|
||||
|
||||
##### AngularJS will be moving to Long Term Support (LTS) mode on July 1st 2018: [Find out more](https://docs.angularjs.org/misc/version-support-status)
|
||||
**On July 1, 2018 AngularJS entered a 3 year Long Term Support period:** [Find out more](https://docs.angularjs.org/misc/version-support-status)
|
||||
|
||||
##### Looking for the new Angular? Go here: https://github.com/angular/angular
|
||||
**Looking for the new Angular? Go here:** https://github.com/angular/angular
|
||||
|
||||
--------------------
|
||||
|
||||
|
||||
Vendored
+80
-13
@@ -28,6 +28,7 @@ var angularFiles = {
|
||||
'src/ng/httpBackend.js',
|
||||
'src/ng/interpolate.js',
|
||||
'src/ng/interval.js',
|
||||
'src/ng/intervalFactory.js',
|
||||
'src/ng/jsonpCallbacks.js',
|
||||
'src/ng/locale.js',
|
||||
'src/ng/location.js',
|
||||
@@ -40,6 +41,7 @@ var angularFiles = {
|
||||
'src/ng/sanitizeUri.js',
|
||||
'src/ng/sce.js',
|
||||
'src/ng/sniffer.js',
|
||||
'src/ng/taskTrackerFactory.js',
|
||||
'src/ng/templateRequest.js',
|
||||
'src/ng/testability.js',
|
||||
'src/ng/timeout.js',
|
||||
@@ -74,6 +76,7 @@ var angularFiles = {
|
||||
'src/ng/directive/ngNonBindable.js',
|
||||
'src/ng/directive/ngOptions.js',
|
||||
'src/ng/directive/ngPluralize.js',
|
||||
'src/ng/directive/ngRef.js',
|
||||
'src/ng/directive/ngRepeat.js',
|
||||
'src/ng/directive/ngShowHide.js',
|
||||
'src/ng/directive/ngStyle.js',
|
||||
@@ -103,6 +106,7 @@ var angularFiles = {
|
||||
'src/ngAnimate/animateJs.js',
|
||||
'src/ngAnimate/animateJsDriver.js',
|
||||
'src/ngAnimate/animateQueue.js',
|
||||
'src/ngAnimate/animateCache.js',
|
||||
'src/ngAnimate/animation.js',
|
||||
'src/ngAnimate/ngAnimateSwap.js',
|
||||
'src/ngAnimate/module.js'
|
||||
@@ -130,6 +134,7 @@ var angularFiles = {
|
||||
],
|
||||
'ngRoute': [
|
||||
'src/shallowCopy.js',
|
||||
'src/routeToRegExp.js',
|
||||
'src/ngRoute/route.js',
|
||||
'src/ngRoute/routeParams.js',
|
||||
'src/ngRoute/directive/ngView.js'
|
||||
@@ -139,6 +144,7 @@ var angularFiles = {
|
||||
'src/ngSanitize/filter/linky.js'
|
||||
],
|
||||
'ngMock': [
|
||||
'src/routeToRegExp.js',
|
||||
'src/ngMock/angular-mocks.js',
|
||||
'src/ngMock/browserTrigger.js'
|
||||
],
|
||||
@@ -183,21 +189,72 @@ var angularFiles = {
|
||||
'src/angular.bind.js'
|
||||
],
|
||||
|
||||
'karmaModules': [
|
||||
'karmaModules-ngAnimate': [
|
||||
'build/angular.js',
|
||||
'@angularSrcModules',
|
||||
'build/angular-mocks.js',
|
||||
'test/modules/no_bootstrap.js',
|
||||
'test/helpers/*.js',
|
||||
'test/ngAnimate/*.js',
|
||||
'test/ngMessageFormat/*.js',
|
||||
'test/ngMessages/*.js',
|
||||
'test/ngMock/*.js',
|
||||
'test/ngCookies/*.js',
|
||||
'test/ngRoute/**/*.js',
|
||||
'test/ngResource/*.js',
|
||||
'test/ngSanitize/**/*.js',
|
||||
'test/ngTouch/**/*.js',
|
||||
'test/ngAria/*.js'
|
||||
'test/helpers/matchers.js',
|
||||
'test/helpers/privateMocks.js',
|
||||
'test/helpers/support.js',
|
||||
'test/helpers/testabilityPatch.js',
|
||||
'@angularSrcModuleNgAnimate',
|
||||
'test/ngAnimate/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngAria': [
|
||||
'@angularSrcModuleNgAria',
|
||||
'test/ngAria/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngCookies': [
|
||||
'@angularSrcModuleNgCookies',
|
||||
'test/ngCookies/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngMessageFormat': [
|
||||
'@angularSrcModuleNgMessageFormat',
|
||||
'test/ngMessageFormat/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngMessages': [
|
||||
'build/angular-animate.js',
|
||||
'@angularSrcModuleNgMessages',
|
||||
'test/ngMessages/**/*.js'
|
||||
],
|
||||
|
||||
// ngMock doesn't include the base because it must use the ngMock src files
|
||||
'karmaModules-ngMock': [
|
||||
'build/angular.js',
|
||||
'src/ngMock/*.js',
|
||||
'test/modules/no_bootstrap.js',
|
||||
'test/helpers/matchers.js',
|
||||
'test/helpers/privateMocks.js',
|
||||
'test/helpers/support.js',
|
||||
'test/helpers/testabilityPatch.js',
|
||||
'src/routeToRegExp.js',
|
||||
'build/angular-animate.js',
|
||||
'test/ngMock/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngResource': [
|
||||
'@angularSrcModuleNgResource',
|
||||
'test/ngResource/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngRoute': [
|
||||
'build/angular-animate.js',
|
||||
'@angularSrcModuleNgRoute',
|
||||
'test/ngRoute/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngSanitize': [
|
||||
'@angularSrcModuleNgSanitize',
|
||||
'test/ngSanitize/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngTouch': [
|
||||
'@angularSrcModuleNgTouch',
|
||||
'test/ngTouch/**/*.js'
|
||||
],
|
||||
|
||||
'karmaJquery': [
|
||||
@@ -226,6 +283,16 @@ var angularFiles = {
|
||||
});
|
||||
});
|
||||
|
||||
angularFiles['angularSrcModuleNgAnimate'] = angularFiles['angularModules']['ngAnimate'];
|
||||
angularFiles['angularSrcModuleNgAria'] = angularFiles['angularModules']['ngAria'];
|
||||
angularFiles['angularSrcModuleNgCookies'] = angularFiles['angularModules']['ngCookies'];
|
||||
angularFiles['angularSrcModuleNgMessageFormat'] = angularFiles['angularModules']['ngMessageFormat'];
|
||||
angularFiles['angularSrcModuleNgMessages'] = angularFiles['angularModules']['ngMessages'];
|
||||
angularFiles['angularSrcModuleNgResource'] = angularFiles['angularModules']['ngResource'];
|
||||
angularFiles['angularSrcModuleNgRoute'] = angularFiles['angularModules']['ngRoute'];
|
||||
angularFiles['angularSrcModuleNgSanitize'] = angularFiles['angularModules']['ngSanitize'];
|
||||
angularFiles['angularSrcModuleNgTouch'] = angularFiles['angularModules']['ngTouch'];
|
||||
|
||||
angularFiles['angularSrcModules'] = [].concat(
|
||||
angularFiles['angularModules']['ngAnimate'],
|
||||
angularFiles['angularModules']['ngMessageFormat'],
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', ['ngAnimate'])
|
||||
.config(function($animateProvider) {
|
||||
$animateProvider.classNameFilter(/animate-/);
|
||||
})
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'classfilter';
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', [])
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'noanimate';
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', ['ngAnimate'])
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'default';
|
||||
});
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/* eslint-env node */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
scripts: [
|
||||
{
|
||||
id: 'angular',
|
||||
src: '/build/angular.js'
|
||||
},
|
||||
{
|
||||
id: 'angular-animate',
|
||||
src: '/build/angular-animate.js'
|
||||
},
|
||||
{
|
||||
id: 'app',
|
||||
src: 'app.js'
|
||||
},
|
||||
{
|
||||
src: 'common.js'
|
||||
}]
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
var app = angular.module('repeatAnimateBenchmark');
|
||||
|
||||
app.config(function($compileProvider, $animateProvider) {
|
||||
if ($compileProvider.debugInfoEnabled) {
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.run(function($animate) {
|
||||
if ($animate.enabled) {
|
||||
$animate.enabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
app.controller('DataController', function($scope, $rootScope, $animate) {
|
||||
var totalRows = 500;
|
||||
var totalColumns = 20;
|
||||
|
||||
var data = $scope.data = [];
|
||||
|
||||
function fillData() {
|
||||
if ($animate.enabled) {
|
||||
$animate.enabled($scope.benchmarkType !== 'globallyDisabled');
|
||||
}
|
||||
|
||||
for (var i = 0; i < totalRows; i++) {
|
||||
data[i] = [];
|
||||
for (var j = 0; j < totalColumns; j++) {
|
||||
data[i][j] = {
|
||||
i: i
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'enter',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
fillData();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'leave',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
data = $scope.data = [];
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.directive('disableAnimations', function($animate) {
|
||||
return {
|
||||
link: {
|
||||
pre: function(s, e) {
|
||||
$animate.enabled(e, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.directive('noop', function($animate) {
|
||||
return {
|
||||
link: {
|
||||
pre: angular.noop
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.directive('baseline', function($document) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, $element) {
|
||||
var document = $document[0];
|
||||
|
||||
var i, j, row, cell, comment;
|
||||
var template = document.createElement('span');
|
||||
template.setAttribute('ng-repeat', 'foo in foos');
|
||||
template.classList.add('ng-scope');
|
||||
template.appendChild(document.createElement('span'));
|
||||
template.appendChild(document.createTextNode(':'));
|
||||
|
||||
function createList() {
|
||||
for (i = 0; i < $scope.data.length; i++) {
|
||||
row = document.createElement('div');
|
||||
$element[0].appendChild(row);
|
||||
for (j = 0; j < $scope.data[i].length; j++) {
|
||||
cell = template.cloneNode(true);
|
||||
row.appendChild(cell);
|
||||
cell.childNodes[0].textContent = i;
|
||||
cell.ng339 = 'xxx';
|
||||
comment = document.createComment('ngRepeat end: bar in foo');
|
||||
row.appendChild(comment);
|
||||
}
|
||||
|
||||
comment = document.createComment('ngRepeat end: foo in foos');
|
||||
$element[0].appendChild(comment);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch('data.length', function(newVal) {
|
||||
if (newVal === 0) {
|
||||
while ($element[0].firstChild) {
|
||||
$element[0].removeChild($element[0].firstChild);
|
||||
}
|
||||
} else {
|
||||
createList();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,70 @@
|
||||
<div ng-app="repeatAnimateBenchmark" ng-cloak>
|
||||
<div ng-controller="DataController">
|
||||
<div class="container-fluid">
|
||||
<p>
|
||||
Tests rendering of an ngRepeat with 500 elements.<br>
|
||||
Animations can be enabled / disabled in different ways.<br>
|
||||
Two tests require reloading the app with different module / app configurations.
|
||||
</p>
|
||||
|
||||
<div><label><input type="radio" ng-model="benchmarkType" value="none">none: </label></div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" value="baseline">baseline (vanilla Javascript): </label></div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'default'" value="enabled">enabled : </label> (requires <a href="./">app.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'default' && fileType !== 'classfilter'" value="globallyDisabled">globally disabled:</label> (requires <a href="./">app.js</a> or <a href="?app=app-classfilter.js">app-classfilter.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'default'" value="disabledParentElement">disabled by $animate.enabled() on parent element: </label> (requires <a href="./">app.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'noanimate'" value="noanimate">Without ngAnimate:</label> (requires <a href="?app=app-noanimate.js">app-noanimate.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'classfilter'" value="disabledClassFilter">disabled by classNameFilter on element:</label> (requires <a href="?app=app-classfilter.js">app-classfilter.js</a>)</div>
|
||||
|
||||
<ng-switch on="benchmarkType">
|
||||
<baseline ng-switch-when="baseline">
|
||||
</baseline>
|
||||
<div ng-switch-when="noanimate">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="enabled">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="globallyDisabled">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="disabledClassFilter">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span class="disable-animations" ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="disabledParentElement">
|
||||
<div disable-animations>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-switch>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,13 +91,16 @@ directivesModule
|
||||
.component('tocTree', {
|
||||
template: '<ul>' +
|
||||
'<li ng-repeat="item in $ctrl.items">' +
|
||||
'<a ng-href="#{{item.fragment}}">{{item.title}}</a>' +
|
||||
'<a ng-href="{{ $ctrl.path }}#{{item.fragment}}">{{item.title}}</a>' +
|
||||
'<toc-tree ng-if="::item.children.length > 0" items="item.children"></toc-tree>' +
|
||||
'</li>' +
|
||||
'</ul>',
|
||||
bindings: {
|
||||
items: '<'
|
||||
}
|
||||
},
|
||||
controller: ['$location', /** @this */ function($location) {
|
||||
this.path = $location.path().replace(/^\/?(.+?)(\/index)?\/?$/, '$1');
|
||||
}]
|
||||
})
|
||||
.directive('tocContainer', function() {
|
||||
return {
|
||||
|
||||
@@ -148,6 +148,7 @@ module.exports = new Package('angularjs', [
|
||||
|
||||
.config(function(checkAnchorLinksProcessor) {
|
||||
checkAnchorLinksProcessor.base = '/';
|
||||
checkAnchorLinksProcessor.errorOnUnmatchedLinks = true;
|
||||
// We are only interested in docs that have an area (i.e. they are pages)
|
||||
checkAnchorLinksProcessor.checkDoc = function(doc) { return doc.area; };
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@ var cdnUrl = googleCdnUrl + versionInfo.cdnVersion;
|
||||
// docs.angularjs.org and code.angularjs.org need them.
|
||||
var versionPath = versionInfo.currentVersion.isSnapshot ?
|
||||
'snapshot' :
|
||||
(versionInfo.currentVersion.version || versionInfo.currentVersion.version);
|
||||
versionInfo.currentVersion.version;
|
||||
var examplesDependencyPath = angularCodeUrl + versionPath + '/';
|
||||
|
||||
module.exports = function productionDeployment(getVersion) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if method.this %}
|
||||
<h4>Method's {% code %}this{% endcode %}</h4>
|
||||
<h4>Method's `this`</h4>
|
||||
{$ method.this | marked $}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% if doc.this %}
|
||||
<h3>Method's {% code %}this{% endcode %}</h3>
|
||||
<h3>Method's `this`</h3>
|
||||
{$ doc.this | marked $}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# AngularJS API Docs
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**AngularJS will be moving to Long Term Support (LTS) mode on July 1st 2018.**: [Find out more](misc/version-support-status).
|
||||
**On July 1, 2018 AngularJS entered a 3 year Long Term Support period:** [Find out more](misc/version-support-status).
|
||||
</div>
|
||||
|
||||
## Welcome to the AngularJS API docs page.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
@ngdoc error
|
||||
@name $compile:ctxoverride
|
||||
@fullName DOM Property Security Context Override
|
||||
@description
|
||||
|
||||
This error occurs when the security context for a property is defined via {@link ng.$compileProvider#addPropertySecurityContext addPropertySecurityContext()} multiple times under different security contexts.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
$compileProvider.addPropertySecurityContext("my-element", "src", $sce.MEDIA_URL);
|
||||
$compileProvider.addPropertySecurityContext("my-element", "src", $sce.RESOURCE_URL); //throws
|
||||
```
|
||||
@@ -1,12 +1,12 @@
|
||||
@ngdoc error
|
||||
@name $compile:nodomevents
|
||||
@fullName Interpolated Event Attributes
|
||||
@fullName Event Attribute/Property Binding
|
||||
@description
|
||||
|
||||
This error occurs when one tries to create a binding for event handler attributes like `onclick`, `onload`, `onsubmit`, etc.
|
||||
This error occurs when one tries to create a binding for event handler attributes or properties like `onclick`, `onload`, `onsubmit`, etc.
|
||||
|
||||
There is no practical value in binding to these attributes and doing so only exposes your application to security vulnerabilities like XSS.
|
||||
For these reasons binding to event handler attributes (all attributes that start with `on` and `formaction` attribute) is not supported.
|
||||
There is no practical value in binding to these attributes/properties and doing so only exposes your application to security vulnerabilities like XSS.
|
||||
For these reasons binding to event handler attributes and properties (`formaction` and all starting with `on`) is not supported.
|
||||
|
||||
|
||||
An example code that would allow XSS vulnerability by evaluating user input in the window context could look like this:
|
||||
@@ -17,4 +17,4 @@ An example code that would allow XSS vulnerability by evaluating user input in t
|
||||
|
||||
Since the `onclick` evaluates the value as JavaScript code in the window context, setting the `username` model to a value like `javascript:alert('PWND')` would result in script injection when the `div` is clicked.
|
||||
|
||||
|
||||
Please use the `ng-*` or `ng-on-*` versions instead (such as `ng-click` or `ng-on-click` rather than `onclick`).
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
@ngdoc error
|
||||
@name ngRef:noctrl
|
||||
@fullName A controller for the value of `ngRefRead` could not be found on the element.
|
||||
@description
|
||||
|
||||
This error occurs when the {@link ng.ngRef ngRef directive} specifies
|
||||
a value in `ngRefRead` that cannot be resolved to a directive / component controller.
|
||||
|
||||
Causes for this error can be:
|
||||
|
||||
1. Your `ngRefRead` value has a typo.
|
||||
2. You have a typo in the *registered* directive / component name.
|
||||
3. The directive / component does not have a controller.
|
||||
|
||||
Note that `ngRefRead` takes the name of the component / directive, not the name of controller, and
|
||||
also not the combination of directive and 'Controller'. For example, for a directive called 'myDirective',
|
||||
the correct declaration is `<div ng-ref="$ctrl.ref" ng-ref-read="myDirective">`.
|
||||
@@ -0,0 +1,27 @@
|
||||
@ngdoc error
|
||||
@name ngRef:nonassign
|
||||
@fullName Non-Assignable Expression
|
||||
@description
|
||||
|
||||
This error occurs when ngRef defines an expression that is not-assignable.
|
||||
|
||||
In order for ngRef to work, it must be possible to write the reference into the path defined with the expression.
|
||||
|
||||
For example, the following expressions are non-assignable:
|
||||
|
||||
```
|
||||
<my-directive ng-ref="{}"></my-directive>
|
||||
|
||||
<my-directive ng-ref="myFn()"></my-directive>
|
||||
|
||||
<!-- missing attribute value is also invalid -->
|
||||
<my-directive ng-ref></my-directive>
|
||||
|
||||
```
|
||||
|
||||
To resolve this error, use a path expression that is assignable:
|
||||
|
||||
```
|
||||
<my-directive ng-ref="$ctrl.reference"></my-directive>
|
||||
|
||||
```
|
||||
@@ -3,7 +3,7 @@
|
||||
@sortOrder 500
|
||||
@description
|
||||
|
||||
# What does it do?
|
||||
# Using the `$location` service
|
||||
|
||||
The `$location` service parses the URL in the browser address bar (based on [`window.location`](https://developer.mozilla.org/en/window.location)) and makes the URL available to
|
||||
your application. Changes to the URL in the address bar are reflected into the `$location` service and
|
||||
@@ -76,7 +76,7 @@ the current URL in the browser.
|
||||
It does not cause a full page reload when the browser URL is changed. To reload the page after
|
||||
changing the URL, use the lower-level API, `$window.location.href`.
|
||||
|
||||
# General overview of the API
|
||||
## General overview of the API
|
||||
|
||||
The `$location` service can behave differently, depending on the configuration that was provided to
|
||||
it when it was instantiated. The default configuration is suitable for many applications, for
|
||||
@@ -85,7 +85,7 @@ others customizing the configuration can enable new features.
|
||||
Once the `$location` service is instantiated, you can interact with it via jQuery-style getter and
|
||||
setter methods that allow you to get or change the current URL in the browser.
|
||||
|
||||
## `$location` service configuration
|
||||
### `$location` service configuration
|
||||
|
||||
To configure the `$location` service, retrieve the
|
||||
{@link ng.$locationProvider $locationProvider} and set the parameters as follows:
|
||||
@@ -113,12 +113,12 @@ To configure the `$location` service, retrieve the
|
||||
Prefix used for Hashbang URLs (used in Hashbang mode or in legacy browsers in HTML5 mode).<br />
|
||||
Default: `'!'`
|
||||
|
||||
### Example configuration
|
||||
#### Example configuration
|
||||
```js
|
||||
$locationProvider.html5Mode(true).hashPrefix('*');
|
||||
```
|
||||
|
||||
## Getter and setter methods
|
||||
### Getter and setter methods
|
||||
|
||||
`$location` service provides getter methods for read-only parts of the URL (absUrl, protocol, host,
|
||||
port) and getter / setter methods for url, path, search, hash:
|
||||
@@ -137,7 +137,7 @@ change multiple segments in one go, chain setters like this:
|
||||
$location.path('/newValue').search({key: value});
|
||||
```
|
||||
|
||||
## Replace method
|
||||
### Replace method
|
||||
|
||||
There is a special `replace` method which can be used to tell the $location service that the next
|
||||
time the $location service is synced with the browser, the last history record should be replaced
|
||||
@@ -173,7 +173,7 @@ encoded.
|
||||
`/path?search=a&b=c#hash`. The segments are encoded as well.
|
||||
|
||||
|
||||
# Hashbang and HTML5 Modes
|
||||
## Hashbang and HTML5 Modes
|
||||
|
||||
`$location` service has two configuration modes which control the format of the URL in the browser
|
||||
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
|
||||
@@ -221,7 +221,7 @@ facilitate the browser URL change and history management.
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Hashbang mode (default mode)
|
||||
### Hashbang mode (default mode)
|
||||
|
||||
In this mode, `$location` uses Hashbang URLs in all browsers.
|
||||
AngularJS also does not intercept and rewrite links in this mode. I.e. links work
|
||||
@@ -229,7 +229,7 @@ as expected and also perform full page reloads when other parts of the url
|
||||
than the hash fragment was changed.
|
||||
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
```js
|
||||
it('should show example', function() {
|
||||
@@ -255,7 +255,7 @@ it('should show example', function() {
|
||||
});
|
||||
```
|
||||
|
||||
## HTML5 mode
|
||||
### HTML5 mode
|
||||
|
||||
In HTML5 mode, the `$location` service getters and setters interact with the browser URL address
|
||||
through the HTML5 history API. This allows for use of regular URL path and search segments,
|
||||
@@ -271,7 +271,7 @@ Note that in this mode, AngularJS intercepts all links (subject to the "Html lin
|
||||
and updates the url in a way that never performs a full page reload.
|
||||
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
```js
|
||||
it('should show example', function() {
|
||||
@@ -320,14 +320,14 @@ it('should show example (when browser doesn\'t support HTML5 mode', function() {
|
||||
});
|
||||
```
|
||||
|
||||
### Fallback for legacy browsers
|
||||
#### Fallback for legacy browsers
|
||||
|
||||
For browsers that support the HTML5 history API, `$location` uses the HTML5 history API to write
|
||||
path and search. If the history API is not supported by a browser, `$location` supplies a Hashbang
|
||||
URL. This frees you from having to worry about whether the browser viewing your app supports the
|
||||
history API or not; the `$location` service makes this transparent to you.
|
||||
|
||||
### HTML link rewriting
|
||||
#### HTML link rewriting
|
||||
|
||||
When you use HTML5 history API mode, you will not need special hashbang links. All you have to do
|
||||
is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
|
||||
@@ -361,7 +361,7 @@ Note that [attribute name normalization](guide/directive#normalization) does not
|
||||
`'internalLink'` will **not** match `'internal-link'`.
|
||||
|
||||
|
||||
### Relative links
|
||||
#### Relative links
|
||||
|
||||
Be sure to check all relative links, images, scripts etc. AngularJS requires you to specify the url
|
||||
base in the head of your main html file (`<base href="/my-base/index.html">`) unless `html5Mode.requireBase`
|
||||
@@ -374,14 +374,14 @@ will only change `$location.hash()` and not modify the url otherwise. This is us
|
||||
to anchors on the same page without needing to know on which page the user currently is.
|
||||
|
||||
|
||||
### Server side
|
||||
#### Server side
|
||||
|
||||
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
|
||||
to entry point of your application (e.g. index.html). Requiring a `<base>` tag is also important for
|
||||
this case, as it allows AngularJS to differentiate between the part of the url that is the application
|
||||
base and the path that should be handled by the application.
|
||||
|
||||
### Base href constraints
|
||||
#### Base href constraints
|
||||
|
||||
The `$location` service is not able to function properly if the current URL is outside the URL given
|
||||
as the base href. This can have subtle confusing consequences...
|
||||
@@ -403,7 +403,7 @@ legacy browsers and hashbang links in modern browser:
|
||||
- Modern browser will rewrite hashbang URLs to regular URLs.
|
||||
- Older browsers will redirect regular URLs to hashbang URLs.
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
Here you can see two `$location` instances that show the difference between **Html5 mode** and **Html5 Fallback mode**.
|
||||
Note that to simulate different levels of browser support, the `$location` instances are connected to
|
||||
@@ -415,7 +415,7 @@ redirect to regular / hashbang url, as this conversion happens only during parsi
|
||||
|
||||
In these examples we use `<base href="/base/index.html" />`. The inputs represent the address bar of the browser.
|
||||
|
||||
#### Browser in HTML5 mode
|
||||
##### Browser in HTML5 mode
|
||||
<example module="html5-mode" name="location-html5-mode">
|
||||
<file name="index.html">
|
||||
<div ng-controller="LocationController">
|
||||
@@ -565,7 +565,7 @@ In these examples we use `<base href="/base/index.html" />`. The inputs represen
|
||||
|
||||
</example>
|
||||
|
||||
#### Browser in HTML5 Fallback mode (Hashbang mode)
|
||||
##### Browser in HTML5 Fallback mode (Hashbang mode)
|
||||
<example module="hashbang-mode" name="location-hashbang-mode">
|
||||
<file name="index.html">
|
||||
<div ng-controller="LocationController">
|
||||
@@ -718,15 +718,15 @@ In these examples we use `<base href="/base/index.html" />`. The inputs represen
|
||||
|
||||
</example>
|
||||
|
||||
# Caveats
|
||||
## Caveats
|
||||
|
||||
## Page reload navigation
|
||||
### Page reload navigation
|
||||
|
||||
The `$location` service allows you to change only the URL; it does not allow you to reload the
|
||||
page. When you need to change the URL and reload the page or navigate to a different page, please
|
||||
use a lower level API, {@link ng.$window $window.location.href}.
|
||||
|
||||
## Using $location outside of the scope life-cycle
|
||||
### Using $location outside of the scope life-cycle
|
||||
|
||||
`$location` knows about AngularJS's {@link ng.$rootScope.Scope scope} life-cycle. When a URL changes in
|
||||
the browser it updates the `$location` and calls `$apply` so that all
|
||||
@@ -738,7 +738,7 @@ propagate this change into browser and will notify all the {@link ng.$rootScope.
|
||||
When you want to change the `$location` from outside AngularJS (for example, through a DOM Event or
|
||||
during testing) - you must call `$apply` to propagate the changes.
|
||||
|
||||
## $location.path() and ! or / prefixes
|
||||
### $location.path() and ! or / prefixes
|
||||
|
||||
A path should always begin with forward slash (`/`); the `$location.path()` setter will add the
|
||||
forward slash if it is missing.
|
||||
@@ -746,22 +746,17 @@ forward slash if it is missing.
|
||||
Note that the `!` prefix in the hashbang mode is not part of `$location.path()`; it is actually
|
||||
`hashPrefix`.
|
||||
|
||||
## Crawling your app
|
||||
### Crawling your app
|
||||
|
||||
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
|
||||
your document:
|
||||
Most modern search engines are able to crawl AJAX applications with dynamic content, provided all
|
||||
included resources are available to the crawler bots.
|
||||
|
||||
```html
|
||||
<meta name="fragment" content="!" />
|
||||
```
|
||||
There also exists a special
|
||||
[AJAX crawling scheme](http://code.google.com/web/ajaxcrawling/docs/specification.html) developed by
|
||||
Google that allows bots to crawl the static equivalent of a dynamically generated page,
|
||||
but this schema has been deprecated, and support for it may vary by search engine.
|
||||
|
||||
This will cause crawler bot to request links with `_escaped_fragment_` param so that your server
|
||||
can recognize the crawler and serve a HTML snapshots. For more information about this technique,
|
||||
see [Making AJAX Applications
|
||||
Crawlable](http://code.google.com/web/ajaxcrawling/docs/specification.html).
|
||||
|
||||
|
||||
# Testing with the $location service
|
||||
## Testing with the $location service
|
||||
|
||||
When using `$location` service during testing, you are outside of the angular's {@link
|
||||
ng.$rootScope.Scope scope} life-cycle. This means it's your responsibility to call `scope.$apply()`.
|
||||
@@ -784,85 +779,6 @@ describe('serviceUnderTest', function() {
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
# Migrating from earlier AngularJS releases
|
||||
|
||||
In earlier releases of AngularJS, `$location` used `hashPath` or `hashSearch` to process path and
|
||||
search methods. With this release, the `$location` service processes path and search methods and
|
||||
then uses the information it obtains to compose hashbang URLs (such as
|
||||
`http://server.com/#!/path?search=a`), when necessary.
|
||||
|
||||
## Changes to your code
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr class="head">
|
||||
<th>Navigation inside the app</th>
|
||||
<th>Change to</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>$location.href = value<br />$location.hash = value<br />$location.update(value)<br
|
||||
/>$location.updateHash(value)</td>
|
||||
<td>$location.path(path).search(search)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashPath = path</td>
|
||||
<td>$location.path(path)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashSearch = search</td>
|
||||
<td>$location.search(search)</td>
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<th>Navigation outside the app</td>
|
||||
<th>Use lower level API</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href = value<br />$location.update(value)</td>
|
||||
<td>$window.location.href = value</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location[protocol | host | port | path | search]</td>
|
||||
<td>$window.location[protocol | host | port | path | search]</td>
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<th>Read access</td>
|
||||
<th>Change to</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashPath</td>
|
||||
<td>$location.path()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashSearch</td>
|
||||
<td>$location.search()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href<br />$location.protocol<br />$location.host<br />$location.port<br
|
||||
/>$location.hash</td>
|
||||
<td>$location.absUrl()<br />$location.protocol()<br />$location.host()<br />$location.port()<br
|
||||
/>$location.path() + $location.search()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.path<br />$location.search</td>
|
||||
<td>$window.location.path<br />$window.location.search</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Two-way binding to $location
|
||||
|
||||
Because `$location` uses getters/setters, you can use `ng-model-options="{ getterSetter: true }"`
|
||||
@@ -884,6 +800,6 @@ angular.module('locationExample', [])
|
||||
</file>
|
||||
</example>
|
||||
|
||||
# Related API
|
||||
## Related API
|
||||
|
||||
* {@link ng.$location `$location` API}
|
||||
|
||||
@@ -222,23 +222,26 @@ triggered:
|
||||
|
||||
| Directive | Supported Animations |
|
||||
|-------------------------------------------------------------------------------|---------------------------------------------------------------------------|
|
||||
| {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
|
||||
| {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
| {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
| {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
| {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
| {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
|
||||
| {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
|
||||
| {@link ngAnimate.directive:ngAnimateSwap#animations ngAnimateSwap} | enter and leave |
|
||||
| {@link ng.directive:ngClass#animations ngClass / {{class}​}} | add and remove |
|
||||
| {@link ng.directive:ngClassEven#animations ngClassEven} | add and remove |
|
||||
| {@link ng.directive:ngClassOdd#animations ngClassOdd} | add and remove |
|
||||
| {@link ng.directive:ngHide#animations ngHide} | add and remove (the `ng-hide` class) |
|
||||
| {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
|
||||
| {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
|
||||
| {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
|
||||
| {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
| {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
| {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
|
||||
| {@link module:ngMessages#animations ngMessages} | add and remove (the `ng-active`/`ng-inactive` classes) |
|
||||
| {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
|
||||
| {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
|
||||
| {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
|
||||
| {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
| {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
|
||||
(More information can be found by visiting the documentation associated with each directive.)
|
||||
|
||||
For a full breakdown of the steps involved during each animation event, refer to the
|
||||
{@link ng.$animate API docs}.
|
||||
{@link ng.$animate `$animate` API docs}.
|
||||
|
||||
## How do I use animations in my own directives?
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ components should follow a few simple conventions:
|
||||
}
|
||||
```
|
||||
|
||||
- **Components have a well-defined lifecycle**
|
||||
- **Components have a well-defined lifecycle:**
|
||||
Each component can implement "lifecycle hooks". These are methods that will be called at certain points in the life
|
||||
of the component. The following hook methods can be implemented:
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ Right now, the `InvoiceController` contains all logic of our example. When the a
|
||||
is a good practice to move view-independent logic from the controller into a
|
||||
<a name="service">{@link services service}</a>, so it can be reused by other parts
|
||||
of the application as well. Later on, we could also change that service to load the exchange rates
|
||||
from the web, e.g. by calling the [Fixer.io](http://fixer.io) exchange rate API, without changing the controller.
|
||||
from the web, e.g. by calling the [exchangeratesapi.io](https://exchangeratesapi.io) exchange rate API, without changing the controller.
|
||||
|
||||
Let's refactor our example and move the currency conversion into a service in another file:
|
||||
|
||||
@@ -300,7 +300,7 @@ to something shorter like `a`.
|
||||
|
||||
## Accessing the backend
|
||||
|
||||
Let's finish our example by fetching the exchange rates from the [Fixer.io](http://fixer.io) exchange rate API.
|
||||
Let's finish our example by fetching the exchange rates from the [exchangeratesapi.io](https://exchangeratesapi.io) exchange rate API.
|
||||
The following example shows how this is done with AngularJS:
|
||||
|
||||
<example name="guide-concepts-3" ng-app-included="true">
|
||||
@@ -331,7 +331,7 @@ The following example shows how this is done with AngularJS:
|
||||
};
|
||||
|
||||
var refresh = function() {
|
||||
var url = 'https://api.fixer.io/latest?base=USD&symbols=' + currencies.join(",");
|
||||
var url = 'https://api.exchangeratesapi.io/latest?base=USD&symbols=' + currencies.join(",");
|
||||
return $http.get(url).then(function(response) {
|
||||
usdToForeignRates = response.data.rates;
|
||||
usdToForeignRates['USD'] = 1;
|
||||
|
||||
@@ -43,7 +43,7 @@ In AngularJS applications, you move the job of filling page templates with data
|
||||
|
||||
* **Animation:** {@link guide/animations Core concepts}, {@link ngAnimate ngAnimate API}
|
||||
* **Security:** {@link guide/security Security Docs}, {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
|
||||
* **Internationalization and Localization:** {@link guide/i18n AngularJS Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
|
||||
* **Internationalization and Localization:** {@link guide/i18n AngularJS Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](https://blog.novanet.no/creating-multilingual-support-using-angularjs/)
|
||||
* **Touch events:** {@link ngTouch Touch events}
|
||||
* **Accessibility:** {@link guide/accessibility ngAria}
|
||||
|
||||
|
||||
+147
-101
@@ -25,7 +25,7 @@ Additionally, we have removed some long-deprecated modules and APIs.
|
||||
|
||||
The most notable changes are:
|
||||
|
||||
- $resource has now support for request and requestError interceptors
|
||||
- `$resource` has now support for request and requestError interceptors
|
||||
|
||||
- Several deprecated features have been removed:
|
||||
- the `$controllerProvider.allowGlobals()` flag
|
||||
@@ -36,8 +36,8 @@ The most notable changes are:
|
||||
- the complete `ngScenario` module
|
||||
|
||||
Please note that feature development (without breaking changes) has happened in parallel on the
|
||||
1.6.x branch, so 1.7 doesn't contain many new features, but you may still benefit from those features
|
||||
that were added (with possible BCs), bugfixes, and a few smaller performance improvements.
|
||||
1.6.x branch, so 1.7 doesn't contain many new features, but you may still benefit from those
|
||||
features that were added (with possible BCs), bugfixes, and a few smaller performance improvements.
|
||||
|
||||
|
||||
<br />
|
||||
@@ -48,11 +48,11 @@ Below is the full list of breaking changes:
|
||||
<a name="migrate1.6to1.7-ng-directives"></a>
|
||||
### Core: _Directives_
|
||||
|
||||
<a name="migrate1.6to1.7-ng-directives-form"></a>
|
||||
|
||||
#### **form**
|
||||
|
||||
**Due to [223de5](https://github.com/angular/angular.js/commit/223de59e988dc0cc8b4ec3a045b7c0735eba1c77)**,
|
||||
forms will now set $submitted on child forms when they are submitted.
|
||||
forms will now set `$submitted` on child forms when they are submitted.
|
||||
For example:
|
||||
```
|
||||
<form name="parentform" ng-submit="$ctrl.submit()">
|
||||
@@ -63,15 +63,16 @@ For example:
|
||||
</form>
|
||||
```
|
||||
|
||||
Submitting this form will set $submitted on "parentform" and "childform".
|
||||
Submitting this form will set `$submitted` on "parentform" and "childform".
|
||||
Previously, it was only set on "parentform".
|
||||
|
||||
This change was introduced because mixing form and ngForm does not create
|
||||
This change was introduced because mixing `form` and `ngForm` does not create
|
||||
logically separate forms, but rather something like input groups.
|
||||
Therefore, child forms should inherit the submission state from their parent form.
|
||||
|
||||
|
||||
#### **input[radio]** and **input[checkbox]**
|
||||
|
||||
**Due to [656c8f](https://github.com/angular/angular.js/commit/656c8fa8f23b1277cc5c214c4d0237f3393afa1e)**,
|
||||
`input[radio]` and `input[checkbox]` now listen to the "change" event instead of the "click" event.
|
||||
Most apps should not be affected, as "change" is automatically fired by browsers after "click"
|
||||
@@ -84,10 +85,10 @@ Two scenarios might need migration:
|
||||
Before this change, custom click event listeners on radio / checkbox would be called after the
|
||||
input element and `ngModel` had been updated, unless they were specifically registered before
|
||||
the built-in click handlers.
|
||||
After this change, they are called before the input is updated, and can call event.preventDefault()
|
||||
to prevent the input from updating.
|
||||
After this change, they are called before the input is updated, and can call
|
||||
`event.preventDefault()` to prevent the input from updating.
|
||||
|
||||
If an app uses a click event listener that expects ngModel to be updated when it is called, it now
|
||||
If an app uses a click event listener that expects `ngModel` to be updated when it is called, it now
|
||||
needs to register a change event listener instead.
|
||||
|
||||
- Triggering click events:
|
||||
@@ -95,57 +96,59 @@ needs to register a change event listener instead.
|
||||
Conventional trigger functions:
|
||||
|
||||
The change event might not be fired when the input element is not attached to the document. This
|
||||
can happen in **tests** that compile input elements and
|
||||
trigger click events on them. Depending on the browser (Chrome and Safari) and the trigger method,
|
||||
the change event will not be fired when the input isn't attached to the document.
|
||||
can happen in **tests** that compile input elements and trigger click events on them. Depending on
|
||||
the browser (Chrome and Safari) and the trigger method, the change event will not be fired when the
|
||||
input isn't attached to the document.
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
it('should update the model', inject(function($compile, $rootScope) {
|
||||
var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
it('should update the model', inject(function($compile, $rootScope) {
|
||||
var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
|
||||
inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger()
|
||||
expect($rootScope.checkbox).toBe(true);
|
||||
});
|
||||
inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger()
|
||||
expect($rootScope.checkbox).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
With this patch, `$rootScope.checkbox` might not be true, because the click event
|
||||
hasn't triggered the change event. To make the test, work append the inputElm to the app's
|
||||
`$rootElement`, and the `$rootElement` to the `$document`.
|
||||
With this patch, `$rootScope.checkbox` might not be true, because the click event hasn't triggered
|
||||
the change event. To make the test, work append `inputElm` to the app's `$rootElement`, and the
|
||||
`$rootElement` to the `$document`.
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
it('should update the model', inject(function($compile, $rootScope, $rootElement, $document) {
|
||||
var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
it('should update the model', inject(function($compile, $rootScope, $rootElement, $document) {
|
||||
var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
|
||||
$rootElement.append(inputElm);
|
||||
$document.append($rootElement);
|
||||
$rootElement.append(inputElm);
|
||||
$document.append($rootElement);
|
||||
|
||||
inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger()
|
||||
expect($rootScope.checkbox).toBe(true);
|
||||
});
|
||||
inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger()
|
||||
expect($rootScope.checkbox).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
#### **input\[number\]**
|
||||
|
||||
**Due to [aa3f95](https://github.com/angular/angular.js/commit/aa3f951330ec7b10b43ea884d9b5754e296770ec)**,
|
||||
`input[type=number]` with `ngModel` now validates the input for the `max`/`min` restriction against
|
||||
the `ngModelController.$viewValue` instead of against the `ngModelController.$modelValue`.
|
||||
|
||||
This affects apps that use `$parsers` or `$formatters` to transform the input / model value.
|
||||
|
||||
If you rely on the $modelValue validation, you can overwrite the `min`/`max` validator from a custom directive, as seen in the following example directive definition object:
|
||||
If you rely on the `$modelValue` validation, you can overwrite the `min`/`max` validator from a
|
||||
custom directive, as seen in the following example directive definition object:
|
||||
|
||||
```
|
||||
```js
|
||||
{
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
var maxValidator = ctrl.$validators.max;
|
||||
|
||||
ctrk.$validators.max = function(modelValue, viewValue) {
|
||||
ctrl.$validators.max = function(modelValue, viewValue) {
|
||||
return maxValidator(modelValue, modelValue);
|
||||
};
|
||||
}
|
||||
@@ -154,11 +157,11 @@ If you rely on the $modelValue validation, you can overwrite the `min`/`max` val
|
||||
|
||||
|
||||
#### **ngModel, input**
|
||||
|
||||
**Due to [74b04c](https://github.com/angular/angular.js/commit/74b04c9403af4fc7df5b6420f22c9f45a3e84140)**,
|
||||
*Custom* parsers that fail to parse on input types "email", "url", "number", "date", "month",
|
||||
"time", "datetime-local", "week", no longer set `ngModelController.$error[inputType]`, and
|
||||
the `ng-invalid-[inputType]` class. Also, custom parsers on input type "range" do no
|
||||
longer set `ngModelController.$error.number` and the `ng-invalid-number` class.
|
||||
the `ng-invalid-[inputType]` class. Also, custom parsers on input type "range" no longer set `ngModelController.$error.number` and the `ng-invalid-number` class.
|
||||
|
||||
Instead, any custom parsers on these inputs set `ngModelController.$error.parse` and
|
||||
`ng-invalid-parse`. This change was made to make distinguishing errors from built-in parsers
|
||||
@@ -166,6 +169,7 @@ and custom parsers easier.
|
||||
|
||||
|
||||
#### **ngModelOptions**
|
||||
|
||||
**Due to [55ba44](https://github.com/angular/angular.js/commit/55ba44913e02650b56410aa9ab5eeea5d3492b68)**,
|
||||
the 'default' key in 'debounce' now only debounces the default event, i.e. the event that is added
|
||||
as an update trigger by the different input directives automatically.
|
||||
@@ -179,24 +183,24 @@ See the following example:
|
||||
|
||||
Pre-1.7:
|
||||
'mouseup' is also debounced by 500 milliseconds because 'default' is applied:
|
||||
```
|
||||
```html
|
||||
ng-model-options="{
|
||||
updateOn: 'default blur mouseup',
|
||||
debounce: { 'default': 500, 'blur': 0 }
|
||||
}
|
||||
}"
|
||||
```
|
||||
|
||||
1.7:
|
||||
The pre-1.7 behavior can be re-created by setting '*' as a catch-all debounce value:
|
||||
```
|
||||
```html
|
||||
ng-model-options="{
|
||||
updateOn: 'default blur mouseup',
|
||||
debounce: { '*': 500, 'blur': 0 }
|
||||
}
|
||||
}"
|
||||
```
|
||||
|
||||
In contrast, when only 'default' is used, 'blur' and 'mouseup' are not debounced:
|
||||
```
|
||||
```html
|
||||
ng-model-options="{
|
||||
updateOn: 'default blur mouseup',
|
||||
debounce: { 'default': 500 }
|
||||
@@ -207,14 +211,15 @@ ng-model-options="{
|
||||
#### **ngStyle**
|
||||
|
||||
**Due to [15bbd3](https://github.com/angular/angular.js/commit/15bbd3e18cd89b91f7206a06c73d40e54a8a48a0)**,
|
||||
previously the use of deep watch by ng-style would trigger styles to be
|
||||
re-applied when nested state changed. Now only changes to direct
|
||||
properties of the watched object will trigger changes.
|
||||
the use of deep-watching in `ngStyle` has changed. Previously, `ngStyle` would trigger styles to be
|
||||
re-applied whenever nested state changed. Now, only changes to direct properties of the watched
|
||||
object will trigger changes.
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ng-services"></a>
|
||||
### Core: _Services_
|
||||
|
||||
|
||||
#### **$compile**
|
||||
|
||||
**Due to [38f8c9](https://github.com/angular/angular.js/commit/38f8c97af74649ce224b6dd45f433cc665acfbfb)**,
|
||||
@@ -238,16 +243,16 @@ migrating to AngularJS 1.7.0 shouldn't require any further action.
|
||||
3. If you specified `$compileProvider.preAssignBindingsEnabled(true)` you need
|
||||
to first migrate your code so that the flag can be flipped to `false`. The
|
||||
instructions on how to do that are available in the "Migrating from 1.5 to 1.6"
|
||||
guide:
|
||||
https://docs.angularjs.org/guide/migration#migrating-from-1-5-to-1-6
|
||||
guide: https://docs.angularjs.org/guide/migration#migrating-from-1-5-to-1-6
|
||||
Afterwards, remove the `$compileProvider.preAssignBindingsEnabled(true)`
|
||||
statement.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [6ccbfa](https://github.com/angular/angular.js/commit/6ccbfa65d60a3dc396d0cf6da21b993ad74653fd)**,
|
||||
the `xlink:href` security context for SVG's `a` and `image` elements has been lowered.
|
||||
|
||||
In the unlikely case that an app relied on RESOURCE_URL whitelisting for the
|
||||
In the unlikely case that an app relied on `RESOURCE_URL` whitelisting for the
|
||||
purpose of binding to the `xlink:href` property of SVG's `<a>` or `<image>`
|
||||
elements and if the values do not pass the regular URL sanitization, they will
|
||||
break.
|
||||
@@ -258,37 +263,39 @@ To fix this you need to ensure that the values used for binding to the affected
|
||||
`imgSrcSanitizationWhitelist` (for `<image>` elements).
|
||||
|
||||
<hr />
|
||||
**Due to [fd4f01](https://github.com/angular/angular.js/commit/fd4f0111188b62773b99ab6eab38b4d2b5d8d727)**,
|
||||
deepWatch is no longer used in in literal one-way bindings.
|
||||
|
||||
Previously when a literal value was passed into a directive/component via
|
||||
**Due to [fd4f01](https://github.com/angular/angular.js/commit/fd4f0111188b62773b99ab6eab38b4d2b5d8d727)**,
|
||||
deep-watching is no longer used in literal one-way bindings.
|
||||
|
||||
Previously, when a literal value was passed into a directive/component via
|
||||
one-way binding it would be watched with a deep watcher.
|
||||
|
||||
For example, for `<my-component input="[a]">`, a new instance of the array
|
||||
would be passed into the directive/component (and trigger $onChanges) not
|
||||
would be passed into the directive/component (and trigger `$onChanges`) not
|
||||
only if `a` changed but also if any sub property of `a` changed such as
|
||||
`a.b` or `a.b.c.d.e` etc.
|
||||
|
||||
This also means a new but equal value for `a` would NOT trigger such a
|
||||
change.
|
||||
|
||||
Now literal values use an input-based watch similar to other directive/component
|
||||
Now, literal values use an input-based watch similar to other directive/component
|
||||
one-way bindings. In this context inputs are the non-constant parts of the
|
||||
literal. In the example above the input would be `a`. Changes are only
|
||||
triggered when the inputs to the literal change.
|
||||
literal. In the example above, the input would be `a`. Changes are only
|
||||
triggered, when the inputs to the literal change.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [1cf728](https://github.com/angular/angular.js/commit/1cf728e209a9e0016068fac2769827e8f747760e)**,
|
||||
`base[href]` was added to the list of RESOURCE_URL context attributes.
|
||||
`base[href]` was added to the list of `RESOURCE_URL` context attributes.
|
||||
|
||||
Previously, `<base href="{{ $ctrl.baseUrl }}" />` would not require `baseUrl` to
|
||||
be trusted as a RESOURCE_URL. Now, `baseUrl` will be sent to `$sce`'s
|
||||
RESOURCE_URL checks. By default, it will break unless `baseUrl` is of the same
|
||||
be trusted as a `RESOURCE_URL`. Now, `baseUrl` will be sent to `$sce`'s
|
||||
`RESOURCE_URL` checks. By default, it will break unless `baseUrl` is of the same
|
||||
origin as the application document.
|
||||
|
||||
Refer to the
|
||||
[`$sce` API docs](https://code.angularjs.org/snapshot/docs/api/ng/service/$sce)
|
||||
for more info on how to trust a value in a RESOURCE_URL context.
|
||||
for more info on how to trust a value in a `RESOURCE_URL` context.
|
||||
|
||||
Also, concatenation in trusted contexts is not allowed, which means that the
|
||||
following won't work: `<base href="/something/{{ $ctrl.partialPath }}" />`.
|
||||
@@ -315,10 +322,10 @@ except for the simplest of cases):
|
||||
**Due to ([c2b8fa](https://github.com/angular/angular.js/commit/c2b8fab0a480204374d561d6b9b3d47347ac5570))**,
|
||||
the arguments of `$watchGroup` callbacks have changed.
|
||||
|
||||
Previously when using `$watchGroup` the entries in `newValues` and
|
||||
Previously, when using `$watchGroup`, the entries in `newValues` and
|
||||
`oldValues` represented the *most recent change of each entry*.
|
||||
|
||||
Now the entries in `oldValues` will always equal the `newValues` of the previous
|
||||
Now, the entries in `oldValues` will always equal the `newValues` of the previous
|
||||
call of the listener. This means comparing the entries in `newValues` and
|
||||
`oldValues` can be used to determine which individual expressions changed.
|
||||
|
||||
@@ -343,7 +350,7 @@ Now the `oldValue` will always equal the previous `newValue`:
|
||||
|
||||
Note the last call now shows `a === 2` in the `oldValues` array.
|
||||
|
||||
This also makes the `oldValue` of one-time watchers more clear. Previously
|
||||
This also makes the `oldValue` of one-time watchers more clear. Previously,
|
||||
the `oldValue` of a one-time watcher would remain `undefined` forever. For
|
||||
example `$scope.$watchGroup(['a', '::b'], fn)` would previously:
|
||||
|
||||
@@ -367,7 +374,7 @@ Where now the `oldValue` will always equal the previous `newValue`:
|
||||
#### **$interval**
|
||||
|
||||
**Due to [a8bef9](https://github.com/angular/angular.js/commit/a8bef95127775d83d80daa4617c33227c4b443d4)**,
|
||||
`$interval.cancel() will throw an error if called with a promise that was not generated by
|
||||
`$interval.cancel()` will throw an error if called with a promise that was not generated by
|
||||
`$interval()`. Previously, it would silently do nothing.
|
||||
|
||||
Before:
|
||||
@@ -393,7 +400,7 @@ $interval.cancel(promise); // Interval canceled.
|
||||
#### **$timeout**
|
||||
|
||||
**Due to [336525](https://github.com/angular/angular.js/commit/3365256502344970f86355d3ace1cb4251ae9828)**,
|
||||
`$timeout.cancel() will throw an error if called with a promise that was not generated by
|
||||
`$timeout.cancel()` will throw an error if called with a promise that was not generated by
|
||||
`$timeout()`. Previously, it would silently do nothing.
|
||||
|
||||
Before:
|
||||
@@ -417,10 +424,11 @@ $timeout.cancel(promise); // Timeout canceled.
|
||||
|
||||
|
||||
#### **$cookies**
|
||||
|
||||
**Due to [73c646](https://github.com/angular/angular.js/commit/73c6467f1468353215dc689c019ed83aa4993c77)**,
|
||||
the `$cookieStore`service has been removed. Migrate to the $cookies service. Note that
|
||||
for object values you need to use the `putObject` & `getObject` methods as
|
||||
`get`/`put` will not correctly save/retrieve them.
|
||||
the `$cookieStore`service has been removed. Migrate to the `$cookies` service. Note that
|
||||
for object values you need to use the `putObject` & `getObject` methods, as
|
||||
`get`/`put` will not correctly save/retrieve the object values.
|
||||
|
||||
Before:
|
||||
```js
|
||||
@@ -433,29 +441,31 @@ $cookieStore.remove('name');
|
||||
#### **$templateRequest**
|
||||
|
||||
**Due to [c617d6](https://github.com/angular/angular.js/commit/c617d6dceee5b000bfceda44ced22fc16b48b18b)**,
|
||||
give tpload error namespace has changed. Previously the `tpload` error was namespaced to `$compile`.
|
||||
If you have code that matches errors of the form `[$compile:tpload]` it will no
|
||||
longer run. You should change the code to match
|
||||
`[$templateRequest:tpload]`.
|
||||
the `tpload` error namespace has changed. Previously, the `tpload` error was namespaced to
|
||||
`$compile`. If you have code that matches errors of the form `[$compile:tpload]` it will no longer
|
||||
run. You should change the code to match `[$templateRequest:tpload]`.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to ([fb0099](https://github.com/angular/angular.js/commit/fb00991460cf69ae8bc7f1f826363d09c73c0d5e)**,
|
||||
the service now returns the result of `$templateCache.put()` when making a server request to the
|
||||
template. Previously it would return the content of the response directly.
|
||||
This now means if you are decorating `$templateCache.put()` to manipulate the template, you will
|
||||
now get this manipulated result also on the first `$templateRequest` rather than only on subsequent
|
||||
calls (when the template is retrived from the cache).
|
||||
In practice this should not affect any apps, as it is unlikely that they rely on the template being
|
||||
`$templateRequest()` now returns the result of `$templateCache.put()` when making a server request
|
||||
for a template. Previously, it would return the content of the response directly.
|
||||
|
||||
This means that if you are decorating `$templateCache.put()` to manipulate the template, you will
|
||||
now get this manipulated result also on the first `$templateRequest()` call rather than only on
|
||||
subsequent calls (when the template is retrieved from the cache).
|
||||
|
||||
In practice, this should not affect any apps, as it is unlikely that they rely on the template being
|
||||
different in the first and subsequent calls.
|
||||
|
||||
|
||||
#### **$animate**
|
||||
|
||||
**Due to [16b82c](https://github.com/angular/angular.js/commit/16b82c6afe0ab916fef1d6ca78053b00bf5ada83)**,
|
||||
$animate.cancel(runner) now rejects the underlying
|
||||
promise and calls the catch() handler on the runner
|
||||
returned by $animate functions (enter, leave, move,
|
||||
addClass, removeClass, setClass, animate).
|
||||
Previously it would resolve the promise as if the animation
|
||||
had ended successfully.
|
||||
`$animate.cancel(runner)` now rejects the underlying promise and calls the `catch()` handler on the
|
||||
runner returned by `$animate` functions (`enter`, `leave`, `move`, `addClass`, `removeClass`,
|
||||
`setClass`, `animate`).
|
||||
Previously, it would resolve the promise as if the animation had ended successfully.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -468,7 +478,7 @@ runner.cancel();
|
||||
```
|
||||
|
||||
Pre-1.7.0, this logs 'success', 1.7.0 and later it logs 'cancelled'.
|
||||
To migrate, add a catch() handler to your animation runners.
|
||||
To migrate, add a `catch()` handler to your animation runners.
|
||||
|
||||
|
||||
#### **$controller**
|
||||
@@ -479,38 +489,74 @@ has been removed. Likewise, the deprecated `$controllerProvider.allowGlobals()`
|
||||
method that could enable this behavior, has been removed.
|
||||
|
||||
This behavior had been deprecated since AngularJS v1.3.0, because polluting the global scope
|
||||
is bad. To migrate, remove the call to $controllerProvider.allowGlobals() in the config, and
|
||||
register your controller via the Module API or the $controllerProvider, e.g.
|
||||
is considered bad practice. To migrate, remove the call to `$controllerProvider.allowGlobals()` in
|
||||
the config, and register your controller via the Module API or the `$controllerProvider`, e.g.:
|
||||
|
||||
```
|
||||
```js
|
||||
angular.module('myModule', []).controller('myController', function() {...});
|
||||
|
||||
// or
|
||||
|
||||
angular.module('myModule', []).config(function($controllerProvider) {
|
||||
$controllerProvider.register('myController', function() {...});
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### **$sce**
|
||||
|
||||
**Due to [1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**,
|
||||
if you use `attrs.$set` for URL attributes (a[href] and img[src]) there will no
|
||||
if you use `attrs.$set` for URL attributes (`a[href]` and `img[src]`) there will no
|
||||
longer be any automated sanitization of the value. This is in line with other
|
||||
programmatic operations, such as writing to the innerHTML of an element.
|
||||
programmatic operations, such as writing to the `innerHTML` of an element.
|
||||
|
||||
If you are programmatically writing URL values to attributes from untrusted
|
||||
input then you must sanitize it yourself. You could write your own sanitizer or copy
|
||||
input, then you must sanitize it yourself. You could write your own sanitizer or copy
|
||||
the private `$$sanitizeUri` service.
|
||||
|
||||
Note that values that have been passed through the `$interpolate` service within the
|
||||
`URL` or `MEDIA_URL` will have already been sanitized, so you would not need to sanitize
|
||||
these values again.
|
||||
|
||||
<hr/>
|
||||
|
||||
**Due to [1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**,
|
||||
binding {@link ng.$sce#trustAs trustAs()} and the short versions
|
||||
({@link ng.$sce#trustAsResourceUrl trustAsResourceUrl()} et al.) to {@link ng.ngSrc},
|
||||
{@link ng.ngSrcset}, and {@link ng.ngHref} will now raise an infinite digest error:
|
||||
|
||||
```js
|
||||
$scope.imgThumbFn = function(id) {
|
||||
return $sce.trustAsResourceUrl(someService.someUrl(id));
|
||||
};
|
||||
```
|
||||
|
||||
```html
|
||||
<img ng-src="{{ imgThumbFn(imgId) }}" />
|
||||
```
|
||||
|
||||
This is because {@link ng.$interpolate} is now responsible for sanitizing
|
||||
the attribute value, and its watcher receives a new object from `trustAs()`
|
||||
on every digest.
|
||||
To migrate, compute the trusted value only when the input value changes:
|
||||
|
||||
```js
|
||||
$scope.$watch('imgId', function(id) {
|
||||
$scope.imgThumb = $sce.trustAsResourceUrl(someService.someUrl(id));
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<img ng-src="{{ imgThumb }}" />
|
||||
```
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ng-filters"></a>
|
||||
### Core: _Filters_
|
||||
|
||||
|
||||
#### **orderBy**
|
||||
|
||||
**Due to [1d8046](https://github.com/angular/angular.js/commit/1d804645f7656d592c90216a0355b4948807f6b8)**,
|
||||
when using `orderBy` to sort arrays containing `null` values, the `null` values
|
||||
will be considered "greater than" all other values, except for `undefined`.
|
||||
@@ -535,8 +581,9 @@ orderByFilter(['a', undefined, 'o', null, 'z']);
|
||||
|
||||
|
||||
#### **jqLite**
|
||||
|
||||
**Due to [b7d396](https://github.com/angular/angular.js/commit/b7d396b8b6e8f27a1f4556d58fc903321e8d532a)**,
|
||||
removeData() no longer removes event handlers.
|
||||
`removeData()` no longer removes event handlers.
|
||||
|
||||
Before this commit `removeData()` invoked on an element removed its event
|
||||
handlers as well. If you want to trigger a full cleanup of an element, change:
|
||||
@@ -561,22 +608,20 @@ elem.remove();
|
||||
will remove event handlers as well.
|
||||
|
||||
|
||||
|
||||
#### **Angular**
|
||||
#### **Helpers**
|
||||
|
||||
**Due to [1daa4f](https://github.com/angular/angular.js/commit/1daa4f2231a89ee88345689f001805ffffa9e7de)**,
|
||||
the helper functions `angular.lowercase` `and angular.uppercase` have been removed.
|
||||
the helper functions `angular.lowercase` and `angular.uppercase` have been removed.
|
||||
|
||||
These functions have been deprecated since 1.5.0. They are internally
|
||||
used, but should not be exposed as they contain special locale handling
|
||||
(for Turkish) to maintain internal consistency regardless of user-set locale.
|
||||
|
||||
Developers should generally use the built-ins `toLowerCase` and `toUpperCase`
|
||||
Developers should generally use the built-in methods `toLowerCase` and `toUpperCase`
|
||||
or `toLocaleLowerCase` and `toLocaleUpperCase` for special cases.
|
||||
|
||||
Further, we generally discourage using the angular.x helpers in application code.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [e3ece2](https://github.com/angular/angular.js/commit/e3ece2fad9e1e6d47b5f06815ff186d7e6f44948)**,
|
||||
`angular.isArray()` now supports Array subclasses.
|
||||
|
||||
@@ -597,18 +642,19 @@ be able to handle these objects better when copying or watching.
|
||||
### ngAria
|
||||
|
||||
**Due to [6d5ef3](https://github.com/angular/angular.js/commit/6d5ef34fc6a974cde73157ba94f9706723dd8f5b)**,
|
||||
the ngAria directive no longer sets aria-* attributes on input[type="hidden"] with ngModel.
|
||||
This can affect apps that test for the presence of aria attributes on hidden inputs.
|
||||
`ngAria` no longer sets `aria-*` attributes on `input[type="hidden"]` with `ngModel`.
|
||||
This can affect apps that test for the presence of ARIA attributes on hidden inputs.
|
||||
To migrate, remove these assertions.
|
||||
In actual apps, this should not have a user-facing effect, as the previous behavior
|
||||
was incorrect, and the new behavior is correct for accessibility.
|
||||
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ngResource"></a>
|
||||
### ngResource
|
||||
|
||||
|
||||
#### **$resource**
|
||||
|
||||
**Due to [ea0585](https://github.com/angular/angular.js/commit/ea0585773bb93fd891576e2271254a17e15f1ddd)**,
|
||||
the behavior of interceptors and success/error callbacks has changed.
|
||||
|
||||
@@ -690,6 +736,7 @@ User.get({id: 2}, onSuccess, onError);
|
||||
```
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [240a3d](https://github.com/angular/angular.js/commit/240a3ddbf12a9bb79754031be95dae4b6bd2dded)**,
|
||||
`$http` will be called asynchronously from `$resource` methods
|
||||
(regardless if a `request`/`requestError` interceptor has been defined).
|
||||
@@ -728,7 +775,6 @@ it('...', function() {
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ngScenario"></a>
|
||||
### ngScenario
|
||||
|
||||
@@ -736,7 +782,7 @@ it('...', function() {
|
||||
the angular scenario runner end-to-end test framework has been
|
||||
removed from the project and will no longer be available on npm
|
||||
or bower starting with 1.7.0.
|
||||
It was deprecated and removed from the documentation in 2014.
|
||||
It has been deprecated and removed from the documentation since 2014.
|
||||
Applications that still use it should migrate to
|
||||
[Protractor](http://www.protractortest.org).
|
||||
Technically, it should also be possible to continue using an
|
||||
@@ -748,10 +794,10 @@ not changed. However, we do not guarantee future compatibility.
|
||||
### ngTouch
|
||||
|
||||
**Due to [11d9ad](https://github.com/angular/angular.js/commit/11d9ad1eb25eaf5967195e424108207427835d50)**,
|
||||
the `ngClick` directive from the ngTouch module has been removed, and with it the
|
||||
the `ngClick` directive of the `ngTouch` module has been removed, and with it the
|
||||
corresponding `$touchProvider` and `$touch` service.
|
||||
|
||||
If you have included ngTouch v1.5.0 or higher in your application, and have not
|
||||
If you have included `ngTouch` v1.5.0 or higher in your application, and have not
|
||||
changed the value of `$touchProvider.ngClickOverrideEnabled()`, or injected and used the `$touch`
|
||||
service, then there are no migration steps for your code. Otherwise you must remove references to
|
||||
the provider and service.
|
||||
|
||||
@@ -102,7 +102,7 @@ For more information please visit {@link $http#json-vulnerability-protection JSO
|
||||
|
||||
Bear in mind that calling `$http.jsonp` gives the remote server (and, if the request is not secured, any Man-in-the-Middle attackers)
|
||||
instant remote code execution in your application: the result of these requests is handed off
|
||||
to the browser as regular `<script>` tag.
|
||||
to the browser as a regular `<script>` tag.
|
||||
|
||||
## Strict Contextual Escaping
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
@description
|
||||
|
||||
# Including AngularJS scripts from the Google CDN
|
||||
The quickest way to get started is to point your html `<script>` tag to a
|
||||
[Google CDN](https://developers.google.com/speed/libraries/#angularjs) URL.
|
||||
The quickest way to get started is to point your html `<script>` tag to a Google CDN URL.
|
||||
This way, you don't have to download anything or maintain a local copy.
|
||||
|
||||
There are two types of AngularJS script URLs you can point to, one for development and one for
|
||||
|
||||
@@ -7,48 +7,41 @@
|
||||
This page describes the support status of the significant versions of AngularJS.
|
||||
|
||||
<div class="alert alert-info">
|
||||
AngularJS is planning one more significant release, version 1.7, and on July 1, 2018 it will enter a 3 year Long Term Support period.
|
||||
On July 1, 2018 AngularJS entered a 3 year Long Term Support period.
|
||||
</div>
|
||||
|
||||
### Until July 1st 2018
|
||||
|
||||
Any version branch not shown in the following table (e.g. 1.5.x) is no longer being developed.
|
||||
|
||||
<table class="dev-status table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Version</th><th>Status</th><th>Comments</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="security"><td><span>1.2.x</span></td><td>Security patches only</td><td>Last version to provide IE 8 support</td></tr>
|
||||
<tr class="stable"><td><span>1.6.x</span></td><td>Patch Releases</td><td>Minor features, bug fixes, security patches - no breaking changes</td></tr>
|
||||
<tr class="current"><td><span>1.7.x</span></td><td>Active Development</td><td>1.7.0 (not yet released) will be the last release of AngularJS to contain breaking changes</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### After July 1st 2018
|
||||
|
||||
Any version branch not shown in the following table (e.g. 1.6.x) is no longer being developed.
|
||||
|
||||
<table class="dev-status table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Version</th><th>Status</th><th>Comments</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="security"><td><span>1.2.x</span></td><td>Long Term Support</td><td>Last version to provide IE 8 support</td></tr>
|
||||
<tr class="stable"><td><span>1.7.x</span></td><td>Long Term Support</td><td>See [Long Term Support](#long-term-support) section below.</td></tr>
|
||||
</tbody>
|
||||
<thead>
|
||||
<tr><th>Version</th><th>Status</th><th>Comments</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="security">
|
||||
<td><span>1.2.x</span></td>
|
||||
<td>Security patches only</td>
|
||||
<td>Last version to provide IE 8 support</td>
|
||||
</tr>
|
||||
<tr class="stable">
|
||||
<td><span>1.7.x</span></td>
|
||||
<td>Long Term Support</td>
|
||||
<td>See {@link version-support-status#long-term-support Long Term Support} section below.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Long Term Support
|
||||
|
||||
On July 1st 2018, we will enter a Long Term Support period for AngularJS.
|
||||
On July 1st 2018, AngularJS entered a Long Term Support period for AngularJS.
|
||||
|
||||
At this time we will focus exclusively on providing fixes to bugs that satisfy at least one of the following criteria:
|
||||
We now focus exclusively on providing fixes to bugs that satisfy at least one of the following criteria:
|
||||
|
||||
* A security flaw is detected in the 1.7.x branch of the framework
|
||||
* One of the major browsers releases a version that will cause current production applications using AngularJS 1.7.x to stop working
|
||||
* The jQuery library releases a version that will cause current production applications using AngularJS 1.7.x to stop working.
|
||||
|
||||
AngularJS 1.2.x will get a new version if and only if a new severe security issue is discovered.
|
||||
|
||||
### Blog Post
|
||||
|
||||
You can read more about these plans in our [blog post announcement](https://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c).
|
||||
You can read more about these plans in our [blog post announcement](https://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c).
|
||||
|
||||
@@ -64,7 +64,7 @@ a few git commands.
|
||||
|
||||
### Install Git
|
||||
|
||||
You can download and install Git from http://git-scm.com/download. Once installed, you should have
|
||||
You can download and install Git from https://git-scm.com/download. Once installed, you should have
|
||||
access to the `git` command line tool. The main commands that you will need to use are:
|
||||
|
||||
* `git clone ...`: Clone a remote repository onto your local machine.
|
||||
@@ -99,8 +99,8 @@ The tutorial instructions, from now on, assume you are running all commands from
|
||||
|
||||
### Install Node.js
|
||||
|
||||
If you want to run the preconfigured local web server and the test tools then you will also need
|
||||
[Node.js v4+][node].
|
||||
In order to install dependencies (such as the test tools and AngularJS itself) and run the
|
||||
preconfigured local web server, you will also need [Node.js v6+][node].
|
||||
|
||||
You can download a Node.js installer for your operating system from https://nodejs.org/en/download/.
|
||||
|
||||
@@ -125,22 +125,25 @@ npm --version
|
||||
[Node Version Manager (nvm)][nvm] or [Node Version Manager (nvm) for Windows][nvm-windows].
|
||||
</div>
|
||||
|
||||
Once you have Node.js installed on your machine, you can download the tool dependencies by running:
|
||||
By installing Node.js, you also get [npm][npm], which is a command line executable for downloading
|
||||
and managing Node.js packages. We use it to download the AngularJS framework as well as development
|
||||
and testing tools.
|
||||
|
||||
Once you have Node.js installed on your machine, you can download these dependencies by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
This command reads angular-phonecat's `package.json` file and downloads the following tools into the
|
||||
`node_modules` directory:
|
||||
This command reads angular-phonecat's `package.json` file and downloads the following dependencies
|
||||
into the `node_modules` directory:
|
||||
|
||||
* [Bower][bower] - client-side code package manager
|
||||
* [Http-Server][http-server] - simple local static web server
|
||||
* [Karma][karma] - unit test runner
|
||||
* [Protractor][protractor] - end-to-end (E2E) test runner
|
||||
|
||||
Running `npm install` will also automatically use bower to download the AngularJS framework into the
|
||||
`app/bower_components` directory.
|
||||
Running `npm install` will also automatically copy the AngularJS framework and other dependencies
|
||||
necessary for our app to work into the `app/lib/` directory.
|
||||
|
||||
<div class="alert alert-info">
|
||||
Note the angular-phonecat project is setup to install and run these utilities via npm scripts.
|
||||
@@ -160,23 +163,23 @@ tasks that you will need while developing:
|
||||
|
||||
### Install Helper Tools (optional)
|
||||
|
||||
The Bower, Http-Server, Karma and Protractor modules are also executables, which can be installed
|
||||
globally and run directly from a terminal/command prompt. You don't need to do this to follow the
|
||||
tutorial, but if you decide you do want to run them directly, you can install these modules globally
|
||||
using, `sudo npm install -g ...`.
|
||||
The Http-Server, Karma and Protractor modules are also executables, which can be installed globally
|
||||
and run directly from a terminal/command prompt. You don't need to do this to follow the tutorial,
|
||||
but if you decide you do want to run them directly, you can install these modules globally using,
|
||||
`sudo npm install --global ...`.
|
||||
|
||||
For instance, to install the Bower command line executable you would do:
|
||||
For instance, to install the `http-server` command line executable you would do:
|
||||
|
||||
```
|
||||
sudo npm install -g bower
|
||||
sudo npm install --global http-server
|
||||
```
|
||||
|
||||
_(Omit the sudo if running on Windows)_
|
||||
_(Omit the sudo if running on Windows.)_
|
||||
|
||||
Then you can run the bower tool directly, such as:
|
||||
Then you can run the `http-server` tool directly, such as:
|
||||
|
||||
```
|
||||
bower install
|
||||
http-server ./app
|
||||
```
|
||||
|
||||
|
||||
@@ -278,6 +281,45 @@ It is good to run the E2E tests whenever you make changes to the HTML views or w
|
||||
the application as a whole is executing correctly. It is very common to run E2E tests before pushing
|
||||
a new commit of changes to a remote repository.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<p>
|
||||
Each version of Protractor is compatible with specific browser versions. If you are reading this
|
||||
some time in the future, it is possible that the specified Protractor version is no longer
|
||||
compatible with the latest version of Chrome that you are using.
|
||||
</p>
|
||||
<p>
|
||||
If that is the case, you can try upgrading Protractor to newer version. For instructions on how
|
||||
to upgrade dependencies see [Updating dependencies](tutorial/#updating-dependencies).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
### Updating dependencies
|
||||
|
||||
In order to avoid surprises, all dependencies listed in `package.json` are pinned to specific
|
||||
versions (this is what the [package-lock.json][package-lock] file is about). This ensures that the
|
||||
same version of a dependency is installed every time.
|
||||
|
||||
Since all dependencies are acquired via npm, you can use the same tool to easily update them as
|
||||
well (although you probably don't need to for the purpose of this tutorial). Simply run the
|
||||
preconfigured script:
|
||||
|
||||
```
|
||||
npm run update-deps
|
||||
```
|
||||
|
||||
This will update all packages to the latest version that satisfy their version ranges (as specified
|
||||
in `package.json`) and also copy the necessary files into `app/lib/`. For example, if `package.json`
|
||||
contains `"some-package": "1.2.x"`, it will be updated to the latest 1.2.x version (e.g. 1.2.99),
|
||||
but not to 1.3.x (e.g. 1.3.0).
|
||||
|
||||
If you want to update a dependency to a version newer than what the specificed range would permit,
|
||||
you can change the version range in `package.json` and then run `npm run update-deps` as usual.
|
||||
|
||||
<div class="alert alert-info">
|
||||
See [here][semver-ranges] for more info on the various version range formats.
|
||||
</div>
|
||||
|
||||
|
||||
### Common Issues
|
||||
|
||||
@@ -324,14 +366,16 @@ Now that you have set up your local machine, let's get started with the tutorial
|
||||
|
||||
|
||||
[angular-phonecat]: https://github.com/angular/angular-phonecat
|
||||
[bower]: http://bower.io/
|
||||
[git]: http://git-scm.com/
|
||||
[git]: https://git-scm.com/
|
||||
[http-server]: https://github.com/nodeapps/http-server
|
||||
[jdk]: https://en.wikipedia.org/wiki/Java_Development_Kit
|
||||
[jdk-download]: http://www.oracle.com/technetwork/java/javase/downloads/index.html
|
||||
[jdk-download]: https://www.oracle.com/technetwork/java/javase/downloads/index.html
|
||||
[karma]: https://karma-runner.github.io/
|
||||
[node]: http://nodejs.org/
|
||||
[node]: https://nodejs.org/
|
||||
[npm]: https://www.npmjs.com/
|
||||
[nvm]: https://github.com/creationix/nvm
|
||||
[nvm-windows]: https://github.com/coreybutler/nvm-windows
|
||||
[package-lock]: https://docs.npmjs.com/files/package-lock.json
|
||||
[protractor]: https://github.com/angular/protractor
|
||||
[selenium]: http://docs.seleniumhq.org/
|
||||
[selenium]: https://docs.seleniumhq.org/
|
||||
[semver-ranges]: https://docs.npmjs.com/misc/semver#ranges
|
||||
|
||||
@@ -51,8 +51,8 @@ The code contains some key AngularJS elements that we will need as we progress.
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>My HTML File</title>
|
||||
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.css" />
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -84,7 +84,7 @@ For more info on `ngApp`, check out the {@link ngApp API Reference}.
|
||||
**`angular.js` script tag:**
|
||||
|
||||
```html
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
```
|
||||
|
||||
This code downloads the `angular.js` script which registers a callback that will be executed by the
|
||||
@@ -154,8 +154,8 @@ and one static binding, and our model is empty. That will soon change!
|
||||
|
||||
Most of the files in your working directory come from the [angular-seed project][angular-seed],
|
||||
which is typically used to bootstrap new AngularJS projects. The seed project is pre-configured to
|
||||
install the AngularJS framework (via `bower` into the `app/bower_components/` directory) and tools
|
||||
for developing and testing a typical web application (via `npm`).
|
||||
install the AngularJS framework (via `npm` into the `app/lib/` directory) and tools for developing
|
||||
and testing a typical web application (via `npm`).
|
||||
|
||||
For the purposes of this tutorial, we modified the angular-seed with the following changes:
|
||||
|
||||
@@ -163,7 +163,7 @@ For the purposes of this tutorial, we modified the angular-seed with the followi
|
||||
* Removed unused dependencies.
|
||||
* Added phone images to `app/img/phones/`.
|
||||
* Added phone data files (JSON) to `app/phones/`.
|
||||
* Added a dependency on [Bootstrap](http://getbootstrap.com) in the `bower.json` file.
|
||||
* Added a dependency on [Bootstrap][bootstrap-3.3] in the `package.json` file.
|
||||
|
||||
|
||||
## Experiments
|
||||
@@ -186,3 +186,4 @@ Now let's go to {@link step_01 step 1} and add some content to the web app.
|
||||
|
||||
|
||||
[angular-seed]: https://github.com/angular/angular-seed
|
||||
[bootstrap-3.3]: https://getbootstrap.com/docs/3.3
|
||||
|
||||
@@ -33,7 +33,7 @@ The view is constructed by AngularJS from this template.
|
||||
<html ng-app="phonecatApp">
|
||||
<head>
|
||||
...
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</head>
|
||||
<body ng-controller="PhoneListController">
|
||||
@@ -317,7 +317,7 @@ by utilizing components.
|
||||
<ul doc-tutorial-nav="2"></ul>
|
||||
|
||||
|
||||
[jasmine-docs]: http://jasmine.github.io/2.4/introduction.html
|
||||
[jasmine-home]: http://jasmine.github.io/
|
||||
[jasmine-docs]: https://jasmine.github.io/api/3.3/global
|
||||
[jasmine-home]: https://jasmine.github.io/
|
||||
[karma]: https://karma-runner.github.io/
|
||||
[mvc-pattern]: http://en.wikipedia.org/wiki/Model–View–Controller
|
||||
[mvc-pattern]: https://en.wikipedia.org/wiki/Model–View–Controller
|
||||
|
||||
@@ -88,6 +88,13 @@ Let's see an example:
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<body>
|
||||
<!-- The following line is how to use the `greetUser` component above in your html doc. -->
|
||||
<greet-user></greet-user>
|
||||
</body>
|
||||
```
|
||||
|
||||
Now, every time we include `<greet-user></greet-user>` in our view, AngularJS will expand it into a
|
||||
DOM sub-tree constructed using the provided `template` and managed by an instance of the specified
|
||||
controller.
|
||||
@@ -120,14 +127,14 @@ acquired skill.
|
||||
<html ng-app="phonecatApp">
|
||||
<head>
|
||||
...
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="app.js"></script>
|
||||
<script src="phone-list.component.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Use a custom component to render a list of phones -->
|
||||
<phone-list></phone-list>
|
||||
<phone-list></phone-list> <!-- This tells AngularJS to instantiate a `phoneList` component here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -148,7 +155,7 @@ angular.module('phonecatApp', []);
|
||||
// Register `phoneList` component, along with its associated controller and template
|
||||
angular.
|
||||
module('phonecatApp').
|
||||
component('phoneList', {
|
||||
component('phoneList', { // This name is what AngularJS uses to match to the `<phone-list>` element.
|
||||
template:
|
||||
'<ul>' +
|
||||
'<li ng-repeat="phone in $ctrl.phones">' +
|
||||
@@ -277,7 +284,7 @@ files, so it remains easy to locate as our application grows.
|
||||
|
||||
|
||||
[case-styles]: https://en.wikipedia.org/wiki/Letter_case#Special_case_styles
|
||||
[jasmine-docs]: http://jasmine.github.io/2.4/introduction.html
|
||||
[jasmine-home]: http://jasmine.github.io/
|
||||
[jasmine-docs]: https://jasmine.github.io/api/3.3/global
|
||||
[jasmine-home]: https://jasmine.github.io/
|
||||
[karma]: https://karma-runner.github.io/
|
||||
[mvc-pattern]: http://en.wikipedia.org/wiki/Model–View–Controller
|
||||
[mvc-pattern]: https://en.wikipedia.org/wiki/Model–View–Controller
|
||||
|
||||
@@ -230,6 +230,8 @@ You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
* Reverse the sort order by adding a `-` symbol before the sorting value:
|
||||
`<option value="-age">Oldest</option>`
|
||||
After making this change, you'll notice that the drop-down list has a blank option selected and does not default to age anymore.
|
||||
Fix this by updating the `orderProp` value in `phone-list.component.js` to match the new value on the `<option>` element.
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset
|
||||
from our server using one of AngularJS's built-in {@link guide/services services} called
|
||||
{@link ng.$http $http}. We will use AngularJS's {@link guide/di dependency injection (DI)} to provide
|
||||
the service to the `phoneList` component's controller.
|
||||
{@link ng.$http $http}. We will use AngularJS's {@link guide/di dependency injection (DI)} to
|
||||
provide the service to the `phoneList` component's controller.
|
||||
|
||||
* There is now a list of 20 phones, loaded from the server.
|
||||
|
||||
|
||||
@@ -47,10 +47,10 @@ URLs point to the `app/img/phones/` directory.
|
||||
...
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.orderProp" class="thumbnail">
|
||||
<a href="#/phones/{{phone.id}}" class="thumb">
|
||||
<a href="#!/phones/{{phone.id}}" class="thumb">
|
||||
<img ng-src="{{phone.imageUrl}}" alt="{{phone.name}}" />
|
||||
</a>
|
||||
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
|
||||
<a href="#!/phones/{{phone.id}}">{{phone.name}}</a>
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -83,7 +83,7 @@ HTTP request to an invalid location.
|
||||
query.sendKeys('nexus');
|
||||
|
||||
element.all(by.css('.phones li a')).first().click();
|
||||
expect(browser.getLocationAbsUrl()).toBe('/phones/nexus-s');
|
||||
expect(browser.getCurrentUrl()).toContain('index.html#!/phones/nexus-s');
|
||||
});
|
||||
|
||||
...
|
||||
@@ -110,8 +110,9 @@ You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
## Summary
|
||||
|
||||
Now that you have added phone images and links, go to {@link step_09 step 9} to learn about AngularJS
|
||||
layout templates and how AngularJS makes it easy to create applications that have multiple views.
|
||||
Now that you have added phone images and links, go to {@link step_09 step 9} to learn about
|
||||
AngularJS layout templates and how AngularJS makes it easy to create applications that have
|
||||
multiple views.
|
||||
|
||||
|
||||
<ul doc-tutorial-nav="8"></ul>
|
||||
|
||||
@@ -23,49 +23,33 @@ has multiple views by adding routing, using an AngularJS module called {@link ng
|
||||
The routing functionality added in this step is provided by AngularJS in the `ngRoute` module, which
|
||||
is distributed separately from the core AngularJS framework.
|
||||
|
||||
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
|
||||
`bower.json` configuration file to include the new dependency:
|
||||
Since we are using [npm][npm] to install client-side dependencies, this step updates the
|
||||
`package.json` configuration file to include the new dependency:
|
||||
|
||||
<br />
|
||||
**`bower.json`:**
|
||||
**`package.json`:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "angular-phonecat",
|
||||
"description": "A starter project for AngularJS",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/angular/angular-phonecat",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
...
|
||||
"dependencies": {
|
||||
"angular": "1.5.x",
|
||||
"angular-mocks": "1.5.x",
|
||||
"angular-route": "1.5.x",
|
||||
"angular": "1.7.x",
|
||||
"angular-route": "1.7.x",
|
||||
"bootstrap": "3.3.x"
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-route": "1.5.x"` tells bower to install a version of the angular-route
|
||||
module that is compatible with version 1.5.x of AngularJS. We must tell bower to download and install
|
||||
The new dependency `"angular-route": "1.7.x"` tells npm to install a version of the angular-route
|
||||
module that is compatible with version 1.7.x of AngularJS. We must tell npm to download and install
|
||||
this dependency.
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
|
||||
we have preconfigured `npm install` to run bower for us.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Warning:** If a new version of AngularJS has been released since you last ran `npm install`, then
|
||||
you may have a problem with the `bower install` due to a conflict between the versions of
|
||||
angular.js that need to be installed. If you run into this issue, simply delete your
|
||||
`app/bower_components` directory and then run `npm install`.
|
||||
</div>
|
||||
|
||||
|
||||
## Multiple Views, Routing and Layout Templates
|
||||
|
||||
@@ -127,8 +111,8 @@ service, the `$routeProvider` exposes APIs that allow you to define routes for y
|
||||
</div>
|
||||
|
||||
AngularJS modules solve the problem of removing global variables from the application and provide a
|
||||
way of configuring the injector. As opposed to AMD or require.js modules, AngularJS modules don't try
|
||||
to solve the problem of script load ordering or lazy script fetching. These goals are totally
|
||||
way of configuring the injector. As opposed to AMD or require.js modules, AngularJS modules don't
|
||||
try to solve the problem of script load ordering or lazy script fetching. These goals are totally
|
||||
independent and both module systems can live side-by-side and fulfill their goals.
|
||||
|
||||
To deepen your understanding on AngularJS's DI, see [Understanding Dependency Injection][wiki-di].
|
||||
@@ -146,8 +130,8 @@ into the layout template. This makes it a perfect fit for our `index.html` templ
|
||||
```html
|
||||
<head>
|
||||
...
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="bower_components/angular-route/angular-route.js"></script>
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="lib/angular-route/angular-route.js"></script>
|
||||
<script src="app.module.js"></script>
|
||||
<script src="app.config.js"></script>
|
||||
...
|
||||
@@ -203,10 +187,8 @@ code, we put it into a separate file and used the `.config` suffix.
|
||||
```js
|
||||
angular.
|
||||
module('phonecatApp').
|
||||
config(['$locationProvider', '$routeProvider',
|
||||
function config($locationProvider, $routeProvider) {
|
||||
$locationProvider.hashPrefix('!');
|
||||
|
||||
config(['$routeProvider',
|
||||
function config($routeProvider) {
|
||||
$routeProvider.
|
||||
when('/phones', {
|
||||
template: '<phone-list></phone-list>'
|
||||
@@ -226,18 +208,6 @@ the corresponding services. Here, we use the
|
||||
{@link ngRoute.$routeProvider#otherwise $routeProvider.otherwise()} methods to define our
|
||||
application routes.
|
||||
|
||||
<div class="alert alert-success">
|
||||
<p>
|
||||
We also used {@link $locationProvider#hashPrefix $locationProvider.hashPrefix()} to set the
|
||||
hash-prefix to `!`. This prefix will appear in the links to our client-side routes, right after
|
||||
the hash (`#`) symbol and before the actual path (e.g. `index.html#!/some/path`).
|
||||
</p>
|
||||
<p>
|
||||
Setting a prefix is not necessary, but it is considered a good practice (for reasons that are
|
||||
outside the scope of this tutorial). `!` is the most commonly used prefix.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Our routes are defined as follows:
|
||||
|
||||
* `when('/phones')`: Determines the view that will be shown, when the URL hash fragment is
|
||||
@@ -261,6 +231,25 @@ the route declaration — `'/phones/:phoneId'` — as a template that is matched
|
||||
URL. All variables defined with the `:` prefix are extracted into the (injectable)
|
||||
{@link ngRoute.$routeParams $routeParams} object.
|
||||
|
||||
<div class="alert alert-info">
|
||||
<p>
|
||||
You may have noticed, that — while the configured route paths start with `/` (e.g.
|
||||
`/phones`) — the URLs used in templates start with `#!/` (e.g. `#!/phones`).
|
||||
</p>
|
||||
<p>
|
||||
Without getting into much detail, AngularJS (by default) uses the hash part of the URL (i.e.
|
||||
what comes after the hash (`#`) symbol) to determine the current route. In addition to that, you
|
||||
can also specify a {@link $locationProvider#hashPrefix hash-prefix} (`!` by default) that needs
|
||||
to appear after the hash symbol in order for AngularJS to consider the value an "AngularJS path"
|
||||
and process it (for example, try to match it to a route).
|
||||
</p>
|
||||
<p>
|
||||
You can find out more about how all this works in the [Using $location](guide/$location) section
|
||||
of the Developer Guide. But all you need to know for now, is that the URLs to our various routes
|
||||
should be prefixed with `#!`.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
## The `phoneDetail` Component
|
||||
|
||||
@@ -345,8 +334,8 @@ any modification.
|
||||
|
||||
```js
|
||||
files: [
|
||||
'bower_components/angular/angular.js',
|
||||
'bower_components/angular-route/angular-route.js',
|
||||
'lib/angular/angular.js',
|
||||
'lib/angular-route/angular-route.js',
|
||||
...
|
||||
],
|
||||
```
|
||||
@@ -363,7 +352,7 @@ various URLs and verifying that the correct view was rendered.
|
||||
|
||||
it('should redirect `index.html` to `index.html#!/phones', function() {
|
||||
browser.get('index.html');
|
||||
expect(browser.getLocationAbsUrl()).toBe('/phones');
|
||||
expect(browser.getCurrentUrl()).toContain('index.html#!/phones');
|
||||
});
|
||||
|
||||
...
|
||||
@@ -424,6 +413,6 @@ With the routing set up and the phone list view implemented, we are ready to go
|
||||
<ul doc-tutorial-nav="9"></ul>
|
||||
|
||||
|
||||
[bower]: http://bower.io
|
||||
[deep-linking]: https://en.wikipedia.org/wiki/Deep_linking
|
||||
[npm]: https://www.npmjs.com/
|
||||
[wiki-di]: https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection
|
||||
|
||||
@@ -21,50 +21,34 @@ In this step, we will change the way our application fetches data.
|
||||
The RESTful functionality is provided by AngularJS in the {@link ngResource ngResource} module, which
|
||||
is distributed separately from the core AngularJS framework.
|
||||
|
||||
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
|
||||
`bower.json` configuration file to include the new dependency:
|
||||
Since we are using [npm][npm] to install client-side dependencies, this step updates the
|
||||
`package.json` configuration file to include the new dependency:
|
||||
|
||||
<br />
|
||||
**`bower.json`:**
|
||||
**`package.json`:**
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "angular-phonecat",
|
||||
"description": "A starter project for AngularJS",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/angular/angular-phonecat",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
...
|
||||
"dependencies": {
|
||||
"angular": "1.5.x",
|
||||
"angular-mocks": "1.5.x",
|
||||
"angular-resource": "1.5.x",
|
||||
"angular-route": "1.5.x",
|
||||
"angular": "1.7.x",
|
||||
"angular-resource": "1.7.x",
|
||||
"angular-route": "1.7.x",
|
||||
"bootstrap": "3.3.x"
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-resource": "1.5.x"` tells bower to install a version of the
|
||||
angular-resource module that is compatible with version 1.5.x of AngularJS. We must tell bower to
|
||||
The new dependency `"angular-resource": "1.7.x"` tells npm to install a version of the
|
||||
angular-resource module that is compatible with version 1.7.x of AngularJS. We must tell npm to
|
||||
download and install this dependency.
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
|
||||
we have preconfigured `npm install` to run bower for us.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Warning:** If a new version of AngularJS has been released since you last ran `npm install`, then
|
||||
you may have a problem with the `bower install` due to a conflict between the versions of
|
||||
angular.js that need to be installed. If you run into this issue, simply delete your
|
||||
`app/bower_components` directory and then run `npm install`.
|
||||
</div>
|
||||
|
||||
|
||||
## Service
|
||||
|
||||
@@ -129,7 +113,7 @@ need to load the `angular-resource.js` file, which contains the `ngResource` mod
|
||||
```html
|
||||
<head>
|
||||
...
|
||||
<script src="bower_components/angular-resource/angular-resource.js"></script>
|
||||
<script src="lib/angular-resource/angular-resource.js"></script>
|
||||
...
|
||||
<script src="core/phone/phone.module.js"></script>
|
||||
<script src="core/phone/phone.service.js"></script>
|
||||
@@ -141,9 +125,10 @@ need to load the `angular-resource.js` file, which contains the `ngResource` mod
|
||||
## Component Controllers
|
||||
|
||||
We can now simplify our component controllers (`PhoneListController` and `PhoneDetailController`) by
|
||||
factoring out the lower-level `$http` service, replacing it with the new `Phone` service. AngularJS's
|
||||
`$resource` service is easier to use than `$http` for interacting with data sources exposed as
|
||||
RESTful resources. It is also easier now to understand what the code in our controllers is doing.
|
||||
factoring out the lower-level `$http` service, replacing it with the new `Phone` service.
|
||||
AngularJS's `$resource` service is easier to use than `$http` for interacting with data sources
|
||||
exposed as RESTful resources. It is also easier now to understand what the code in our controllers
|
||||
is doing.
|
||||
|
||||
<br />
|
||||
**`app/phone-list/phone-list.module.js`:**
|
||||
@@ -240,8 +225,8 @@ Karma configuration file with angular-resource.
|
||||
|
||||
```js
|
||||
files: [
|
||||
'bower_components/angular/angular.js',
|
||||
'bower_components/angular-resource/angular-resource.js',
|
||||
'lib/angular/angular.js',
|
||||
'lib/angular-resource/angular-resource.js',
|
||||
...
|
||||
],
|
||||
```
|
||||
@@ -319,6 +304,6 @@ Now that we have seen how to build a custom service as a RESTful client, we are
|
||||
<ul doc-tutorial-nav="13"></ul>
|
||||
|
||||
|
||||
[bower]: http://bower.io/
|
||||
[jasmine-equality]: https://jasmine.github.io/2.4/custom_equality.html
|
||||
[npm]: https://www.npmjs.com/
|
||||
[restful]: https://en.wikipedia.org/wiki/Representational_State_Transfer
|
||||
|
||||
@@ -22,59 +22,43 @@ the template code we created earlier.
|
||||
## Dependencies
|
||||
|
||||
The animation functionality is provided by AngularJS in the `ngAnimate` module, which is distributed
|
||||
separately from the core AngularJS framework. In addition we will use [jQuery][jquery] in this project
|
||||
to do extra JavaScript animations.
|
||||
separately from the core AngularJS framework. In addition we will use [jQuery][jquery] in this
|
||||
project to do extra JavaScript animations.
|
||||
|
||||
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
|
||||
`bower.json` configuration file to include the new dependencies:
|
||||
Since we are using [npm][npm] to install client-side dependencies, this step updates the
|
||||
`package.json` configuration file to include the new dependencies:
|
||||
|
||||
<br />
|
||||
**`bower.json`:**
|
||||
**`package.json`:**
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "angular-phonecat",
|
||||
"description": "A starter project for AngularJS",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/angular/angular-phonecat",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
...
|
||||
"dependencies": {
|
||||
"angular": "1.5.x",
|
||||
"angular-animate": "1.5.x",
|
||||
"angular-mocks": "1.5.x",
|
||||
"angular-resource": "1.5.x",
|
||||
"angular-route": "1.5.x",
|
||||
"angular": "1.7.x",
|
||||
"angular-animate": "1.7.x",
|
||||
"angular-resource": "1.7.x",
|
||||
"angular-route": "1.7.x",
|
||||
"bootstrap": "3.3.x",
|
||||
"jquery": "3.2.x"
|
||||
}
|
||||
"jquery": "3.3.x"
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
* `"angular-animate": "1.5.x"` tells bower to install a version of the angular-animate module that
|
||||
is compatible with version 1.5.x of AngularJS.
|
||||
* `"jquery": "3.2.x"` tells bower to install the latest patch release of the 3.2 version of jQuery.
|
||||
Note that this is not an AngularJS library; it is the standard jQuery library. We can use bower to
|
||||
* `"angular-animate": "1.7.x"` tells npm to install a version of the angular-animate module that
|
||||
is compatible with version 1.7.x of AngularJS.
|
||||
* `"jquery": "3.3.x"` tells npm to install the latest patch release of the 3.3 version of jQuery.
|
||||
Note that this is not an AngularJS library; it is the standard jQuery library. We can use npm to
|
||||
install a wide range of 3rd party libraries.
|
||||
|
||||
Now, we must tell bower to download and install these dependencies.
|
||||
Now, we must tell npm to download and install these dependencies.
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
|
||||
we have preconfigured `npm install` to run bower for us.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Warning:** If a new version of AngularJS has been released since you last ran `npm install`, then
|
||||
you may have a problem with the `bower install` due to a conflict between the versions of
|
||||
angular.js that need to be installed. If you run into this issue, simply delete your
|
||||
`app/bower_components` directory and then run `npm install`.
|
||||
</div>
|
||||
|
||||
|
||||
## How Animations work with `ngAnimate`
|
||||
|
||||
@@ -101,12 +85,12 @@ code necessary to make your application "animation aware".
|
||||
...
|
||||
|
||||
<!-- Used for JavaScript animations (include this before angular.js) -->
|
||||
<script src="bower_components/jquery/dist/jquery.js"></script>
|
||||
<script src="lib/jquery/dist/jquery.js"></script>
|
||||
|
||||
...
|
||||
|
||||
<!-- Adds animation support in AngularJS -->
|
||||
<script src="bower_components/angular-animate/angular-animate.js"></script>
|
||||
<script src="lib/angular-animate/angular-animate.js"></script>
|
||||
|
||||
<!-- Defines JavaScript animations -->
|
||||
<script src="app.animations.js"></script>
|
||||
@@ -115,8 +99,8 @@ code necessary to make your application "animation aware".
|
||||
```
|
||||
|
||||
<div class="alert alert-error">
|
||||
**Important:** Be sure to use jQuery version 2.1 or newer, when using AngularJS 1.5 or newer; jQuery 1.x is
|
||||
not officially supported.
|
||||
**Important:** Be sure to use jQuery version 2.1 or newer, when using AngularJS 1.5 or newer;
|
||||
jQuery 1.x is not officially supported.
|
||||
In order for AngularJS to detect jQuery and take advantage of it, make sure to include `jquery.js`
|
||||
before `angular.js`.
|
||||
</div>
|
||||
@@ -556,9 +540,9 @@ There you have it! We have created a web application in a relatively short amoun
|
||||
<ul doc-tutorial-nav="14"></ul>
|
||||
|
||||
|
||||
[bower]: http://bower.io/
|
||||
[caniuse-css-animation]: http://caniuse.com/#feat=css-animation
|
||||
[caniuse-css-transitions]: http://caniuse.com/#feat=css-transitions
|
||||
[caniuse-css-animation]: https://caniuse.com/#feat=css-animation
|
||||
[caniuse-css-transitions]: https://caniuse.com/#feat=css-transitions
|
||||
[jquery]: https://jquery.com/
|
||||
[jquery-animate]: https://api.jquery.com/animate/
|
||||
[jquery-animate]: https://api.jquery.com/animate
|
||||
[mdn-animations]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations
|
||||
[npm]: https://www.npmjs.com/
|
||||
|
||||
@@ -22,5 +22,5 @@ If you have questions or feedback or just want to say "hi", please post a messag
|
||||
|
||||
[angular-seed]: https://github.com/angular/angular-seed
|
||||
[gitter]: https://gitter.im/angular/angular.js
|
||||
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
|
||||
[irc]: https://webchat.freenode.net/?channels=angularjs&uio=d4
|
||||
[mailing-list]: https://groups.google.com/forum/#!forum/angular
|
||||
|
||||
@@ -20,7 +20,7 @@ function main() {
|
||||
} catch (e) {
|
||||
fs.mkdirSync(__dirname + '/../../../src/ngParseExt');
|
||||
}
|
||||
fs.writeFile(__dirname + '/../../../src/ngParseExt/ucd.js', code);
|
||||
fs.writeFileSync(__dirname + '/../../../src/ngParseExt/ucd.js', code);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var angularFiles = require('./angularFiles');
|
||||
var sharedConfig = require('./karma-shared.conf');
|
||||
|
||||
module.exports = function(config) {
|
||||
sharedConfig(config, {testName: 'AngularJS: isolated module tests (ngAnimate)', logFile: 'karma-ngAnimate-isolated.log'});
|
||||
|
||||
config.set({
|
||||
files: angularFiles.mergeFilesFor('karmaModules-ngAnimate')
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var angularFiles = require('./angularFiles');
|
||||
var sharedConfig = require('./karma-shared.conf');
|
||||
|
||||
module.exports = function(config) {
|
||||
sharedConfig(config, {testName: 'AngularJS: isolated module tests (ngMock)', logFile: 'karma-ngMock-isolated.log'});
|
||||
|
||||
config.set({
|
||||
files: angularFiles.mergeFilesFor('karmaModules-ngMock')
|
||||
});
|
||||
};
|
||||
+11
-8
@@ -1,17 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
var angularFiles = require('./angularFiles');
|
||||
var sharedConfig = require('./karma-shared.conf');
|
||||
|
||||
module.exports = function(config) {
|
||||
sharedConfig(config, {testName: 'AngularJS: modules', logFile: 'karma-modules.log'});
|
||||
sharedConfig(config, {testName: 'AngularJS: isolated module tests', logFile: 'karma-modules-isolated.log'});
|
||||
|
||||
config.set({
|
||||
files: angularFiles.mergeFilesFor('karmaModules'),
|
||||
|
||||
junitReporter: {
|
||||
outputFile: 'test_out/modules.xml',
|
||||
suite: 'modules'
|
||||
}
|
||||
files: [
|
||||
'build/angular.js',
|
||||
'build/angular-mocks.js',
|
||||
'test/modules/no_bootstrap.js',
|
||||
'test/helpers/matchers.js',
|
||||
'test/helpers/privateMocks.js',
|
||||
'test/helpers/support.js',
|
||||
'test/helpers/testabilityPatch.js',
|
||||
'build/test-bundles/angular-*.js'
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
+8
-12
@@ -23,12 +23,7 @@ module.exports = function(config, specificOptions) {
|
||||
// SauceLabs config for local development.
|
||||
sauceLabs: {
|
||||
testName: specificOptions.testName || 'AngularJS',
|
||||
startConnect: true,
|
||||
options: {
|
||||
// We need selenium version +2.46 for Firefox 39 and the last selenium version for OS X is 2.45.
|
||||
// TODO: Uncomment when there is a selenium 2.46 available for OS X.
|
||||
// 'selenium-version': '2.46.0'
|
||||
}
|
||||
startConnect: true
|
||||
},
|
||||
|
||||
// BrowserStack config for local development.
|
||||
@@ -65,13 +60,11 @@ module.exports = function(config, specificOptions) {
|
||||
'SL_Safari-1': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.12',
|
||||
version: 'latest-1'
|
||||
},
|
||||
'SL_Safari': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.12',
|
||||
version: 'latest'
|
||||
},
|
||||
'SL_IE_9': {
|
||||
@@ -104,15 +97,15 @@ module.exports = function(config, specificOptions) {
|
||||
platform: 'Windows 10',
|
||||
version: 'latest-1'
|
||||
},
|
||||
'SL_iOS_10': {
|
||||
'SL_iOS': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
version: '10.3'
|
||||
version: 'latest'
|
||||
},
|
||||
'SL_iOS_11': {
|
||||
'SL_iOS-1': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
version: '11'
|
||||
version: 'latest-1'
|
||||
},
|
||||
|
||||
'BS_Chrome': {
|
||||
@@ -191,6 +184,9 @@ module.exports = function(config, specificOptions) {
|
||||
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
|
||||
config.sauceLabs.recordScreenshots = true;
|
||||
|
||||
// Try 'websocket' for a faster transmission first. Fallback to 'polling' if necessary.
|
||||
config.transports = ['websocket', 'polling'];
|
||||
|
||||
// Debug logging into a file, that we print out at the end of the build.
|
||||
config.loggers.push({
|
||||
type: 'file',
|
||||
|
||||
+13
-5
@@ -75,10 +75,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
|
||||
wrap: function(src, name) {
|
||||
src.unshift('src/' + name + '.prefix');
|
||||
src.push('src/' + name + '.suffix');
|
||||
return src;
|
||||
wrap(src, name) {
|
||||
return [`src/${name}.prefix`, ...src, `src/${name}.suffix`];
|
||||
},
|
||||
|
||||
|
||||
@@ -135,6 +133,16 @@ module.exports = {
|
||||
|
||||
build: function(config, fn) {
|
||||
var files = grunt.file.expand(config.src);
|
||||
// grunt.file.expand might reorder the list of files
|
||||
// when it is expanding globs, so we use prefix and suffix
|
||||
// fields to ensure that files are at the start of end of
|
||||
// the list (primarily for wrapping in an IIFE).
|
||||
if (config.prefix) {
|
||||
files = grunt.file.expand(config.prefix).concat(files);
|
||||
}
|
||||
if (config.suffix) {
|
||||
files = files.concat(grunt.file.expand(config.suffix));
|
||||
}
|
||||
var styles = config.styles;
|
||||
var processedStyles;
|
||||
//concat
|
||||
@@ -221,7 +229,7 @@ module.exports = {
|
||||
//returns the 32-bit mode force flags for java compiler if supported, this makes the build much faster
|
||||
java32flags: function() {
|
||||
if (process.platform === 'win32') return '';
|
||||
if (shell.exec('java -version -d32 2>&1', {silent: true}).code !== 0) return '';
|
||||
if (shell.exec('java -d32 -version 2>&1', {silent: true}).code !== 0) return '';
|
||||
return ' -d32 -client';
|
||||
},
|
||||
|
||||
|
||||
@@ -11,14 +11,14 @@ set -e
|
||||
# Curl and run this script as part of your .travis.yml before_script section:
|
||||
# before_script:
|
||||
# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash
|
||||
SC_VERSION="4.4.1"
|
||||
SC_VERSION="4.5.2"
|
||||
CONNECT_URL="https://saucelabs.com/downloads/sc-$SC_VERSION-linux.tar.gz"
|
||||
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
|
||||
CONNECT_DOWNLOAD="sc-$SC_VERSION-linux.tar.gz"
|
||||
|
||||
CONNECT_LOG="$LOGS_DIR/sauce-connect"
|
||||
CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout"
|
||||
CONNECT_STDERR="$LOGS_DIR/sauce-connect.stderr"
|
||||
# We don't want to create a log file because sauceconnect always logs in verbose mode. This seems
|
||||
# to be overwhelming Travis and causing flakes when we are cat-ing the log in "print_logs.sh"
|
||||
CONNECT_LOG="/dev/null"
|
||||
|
||||
# Get Connect and start it
|
||||
mkdir -p $CONNECT_DIR
|
||||
@@ -42,9 +42,6 @@ if [ ! -z "$BROWSER_PROVIDER_READY_FILE" ]; then
|
||||
fi
|
||||
|
||||
|
||||
echo "Starting Sauce Connect in the background, logging into:"
|
||||
echo " $CONNECT_LOG"
|
||||
echo " $CONNECT_STDOUT"
|
||||
echo " $CONNECT_STDERR"
|
||||
echo "Starting Sauce Connect in the background"
|
||||
sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY $ARGS \
|
||||
--logfile $CONNECT_LOG 2> $CONNECT_STDERR 1> $CONNECT_STDOUT &
|
||||
--logfile $CONNECT_LOG &
|
||||
|
||||
+21
-17
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"name": "angular",
|
||||
"license": "MIT",
|
||||
"branchVersion": "^1.7.0",
|
||||
"branchPattern": "1.7.*",
|
||||
@@ -9,15 +9,14 @@
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^8.9.1",
|
||||
"yarn": ">=1.3.2",
|
||||
"grunt": "^1.2.0"
|
||||
"node": ">=8.12.0",
|
||||
"yarn": ">=1.10.1",
|
||||
"grunt-cli": "^1.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"commit": "git-cz",
|
||||
"test-i18n": "jasmine-node i18n/spec",
|
||||
"test-i18n-ucd": "jasmine-node i18n/ucd/spec",
|
||||
"grunt": "grunt"
|
||||
"test-i18n-ucd": "jasmine-node i18n/ucd/spec"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-benchpress": "0.x.x",
|
||||
@@ -32,8 +31,8 @@
|
||||
"commitplease": "^2.7.10",
|
||||
"cross-spawn": "^4.0.0",
|
||||
"cz-conventional-changelog": "1.1.4",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.16.4",
|
||||
"dgeni": "^0.4.9",
|
||||
"dgeni-packages": "^0.26.5",
|
||||
"eslint-plugin-promise": "^3.6.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"glob": "^6.0.1",
|
||||
@@ -63,18 +62,18 @@
|
||||
"jquery": "3.2.1",
|
||||
"jquery-2.1": "npm:jquery@2.1.4",
|
||||
"jquery-2.2": "npm:jquery@2.2.4",
|
||||
"karma": "^2.0.0",
|
||||
"karma-browserstack-launcher": "^1.2.0",
|
||||
"karma-chrome-launcher": "^2.1.1",
|
||||
"karma": "^3.1.4",
|
||||
"karma-browserstack-launcher": "^1.3.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-edge-launcher": "^0.4.2",
|
||||
"karma-firefox-launcher": "^1.0.1",
|
||||
"karma-firefox-launcher": "^1.1.0",
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"karma-jasmine": "^1.1.0",
|
||||
"karma-jasmine": "^1.1.2",
|
||||
"karma-junit-reporter": "^1.2.0",
|
||||
"karma-safari-launcher": "^1.0.0",
|
||||
"karma-sauce-launcher": "^1.2.0",
|
||||
"karma-sauce-launcher": "^2.0.2",
|
||||
"karma-script-launcher": "^1.0.0",
|
||||
"karma-spec-reporter": "^0.0.31",
|
||||
"karma-spec-reporter": "^0.0.32",
|
||||
"load-grunt-tasks": "^3.5.0",
|
||||
"lodash": "~2.4.1",
|
||||
"log4js": "^0.6.27",
|
||||
@@ -84,13 +83,13 @@
|
||||
"npm-run": "^4.1.0",
|
||||
"open-sans-fontface": "^1.4.0",
|
||||
"promises-aplus-tests": "~2.1.0",
|
||||
"protractor": "^5.1.2",
|
||||
"protractor": "^5.4.1",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
"rewire": "~2.1.0",
|
||||
"sax": "^1.1.1",
|
||||
"selenium-webdriver": "^2.53.1",
|
||||
"selenium-webdriver": "^4.0.0-alpha.1",
|
||||
"semver": "^5.4.1",
|
||||
"serve-favicon": "^2.3.0",
|
||||
"serve-index": "^1.8.0",
|
||||
@@ -100,6 +99,11 @@
|
||||
"stringmap": "^0.2.2"
|
||||
},
|
||||
"dependencies": {},
|
||||
"resolutions": {
|
||||
"//1": "`natives@1.1.0` does not work with Node.js 10.x on Windows 10",
|
||||
"//2": "(E.g. see https://github.com/gulpjs/gulp/issues/2162.)",
|
||||
"natives": "1.1.3"
|
||||
},
|
||||
"commitplease": {
|
||||
"style": "angular",
|
||||
"nohook": true
|
||||
|
||||
@@ -79,6 +79,8 @@ function capabilitiesForSauceLabs(capabilities) {
|
||||
'browserName': capabilities.browserName,
|
||||
'platform': capabilities.platform,
|
||||
'version': capabilities.version,
|
||||
'elementScrollBehavior': 1
|
||||
'elementScrollBehavior': 1,
|
||||
// Allow e2e test sessions to run for a maximum of 35 minutes, instead of the default 30 minutes.
|
||||
'maxDuration': 2100
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ const BROWSER_CACHE_DURATION = 60 * 10;
|
||||
const CDN_CACHE_DURATION = 60 * 60 * 12;
|
||||
|
||||
function sendStoredFile(request, response) {
|
||||
let filePathSegments = request.path.split('/').filter((segment) => {
|
||||
const requestPath = request.path || '/';
|
||||
let filePathSegments = requestPath.split('/').filter((segment) => {
|
||||
// Remove empty leading or trailing path parts
|
||||
return segment !== '';
|
||||
});
|
||||
@@ -159,7 +160,11 @@ function sendStoredFile(request, response) {
|
||||
const nextQuery = data[1];
|
||||
const apiResponse = data[2];
|
||||
|
||||
if (!files.length && (!apiResponse || !apiResponse.prefixes)) {
|
||||
if (
|
||||
// we got no files or directories from previous query pages
|
||||
!fileList.length && !directoryList.length &&
|
||||
// this query page has no file or directories
|
||||
!files.length && (!apiResponse || !apiResponse.prefixes)) {
|
||||
return Promise.reject({
|
||||
code: 404
|
||||
});
|
||||
@@ -190,22 +195,16 @@ const snapshotRegex = /^snapshot(-stable)?\//;
|
||||
* When a new zip file is uploaded into snapshot or snapshot-stable,
|
||||
* delete the previous zip file.
|
||||
*/
|
||||
function deleteOldSnapshotZip(event) {
|
||||
const object = event.data;
|
||||
|
||||
function deleteOldSnapshotZip(object, context) {
|
||||
const bucketId = object.bucket;
|
||||
const filePath = object.name;
|
||||
const contentType = object.contentType;
|
||||
const resourceState = object.resourceState;
|
||||
|
||||
const bucket = gcs.bucket(bucketId);
|
||||
|
||||
const snapshotFolderMatch = filePath.match(snapshotRegex);
|
||||
|
||||
if (!snapshotFolderMatch ||
|
||||
contentType !== 'application/zip' ||
|
||||
resourceState === 'not_exists' // Deletion event
|
||||
) {
|
||||
if (!snapshotFolderMatch || contentType !== 'application/zip') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -230,4 +229,4 @@ function deleteOldSnapshotZip(event) {
|
||||
}
|
||||
|
||||
exports.sendStoredFile = functions.https.onRequest(sendStoredFile);
|
||||
exports.deleteOldSnapshotZip = functions.storage.object().onChange(deleteOldSnapshotZip);
|
||||
exports.deleteOldSnapshotZip = functions.storage.object().onFinalize(deleteOldSnapshotZip);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,8 @@
|
||||
"description": "Cloud Functions to serve files from gcs to code.angularjs.org",
|
||||
"dependencies": {
|
||||
"@google-cloud/storage": "^1.1.1",
|
||||
"firebase-admin": "^4.2.1",
|
||||
"firebase-functions": "^0.5.9"
|
||||
"firebase-admin": "^5.11.0",
|
||||
"firebase-functions": "^1.0.4"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -21,21 +21,21 @@ rm -f angular.js.size
|
||||
|
||||
|
||||
# BUILD #
|
||||
yarn run grunt -- ci-checks package --no-color
|
||||
yarn grunt ci-checks package --no-color
|
||||
|
||||
mkdir -p test_out
|
||||
|
||||
# UNIT TESTS #
|
||||
yarn run grunt -- test:unit --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
|
||||
yarn grunt test:unit --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
|
||||
|
||||
# END TO END TESTS #
|
||||
yarn run grunt -- test:ci-protractor
|
||||
yarn grunt test:ci-protractor
|
||||
|
||||
# DOCS APP TESTS #
|
||||
yarn run grunt -- test:docs --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
|
||||
yarn grunt test:docs --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
|
||||
|
||||
# Promises/A+ TESTS #
|
||||
yarn run grunt -- test:promises-aplus --no-color
|
||||
yarn grunt test:promises-aplus --no-color
|
||||
|
||||
|
||||
# CHECK SIZE #
|
||||
|
||||
@@ -8,11 +8,11 @@ nvm install
|
||||
|
||||
# clean out and install yarn
|
||||
rm -rf ~/.yarn
|
||||
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2
|
||||
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.10.1
|
||||
export PATH="$HOME/.yarn/bin:$PATH"
|
||||
|
||||
# Ensure that we have the local dependencies installed
|
||||
yarn install
|
||||
|
||||
echo testing grunt version
|
||||
yarn run grunt -- --version
|
||||
yarn grunt --version
|
||||
|
||||
@@ -37,7 +37,7 @@ function init {
|
||||
function build {
|
||||
cd ../..
|
||||
source scripts/jenkins/init-node.sh
|
||||
yarn run grunt -- ci-checks package --no-color
|
||||
yarn grunt ci-checks package --no-color
|
||||
|
||||
cd $SCRIPT_DIR
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
set -e
|
||||
|
||||
yarn global add grunt-cli@1.2.0
|
||||
|
||||
mkdir -p "$LOGS_DIR"
|
||||
|
||||
if [ "$JOB" != "ci-checks" ]; then
|
||||
@@ -13,13 +11,13 @@ fi
|
||||
|
||||
# ci-checks and unit tests do not run against the packaged code
|
||||
if [[ "$JOB" != "ci-checks" ]] && [[ "$JOB" != unit-* ]]; then
|
||||
grunt package
|
||||
yarn grunt package
|
||||
fi
|
||||
|
||||
# unit runs the docs tests too which need a built version of the code
|
||||
if [[ "$JOB" = unit-* ]]; then
|
||||
grunt validate-angular-files
|
||||
grunt build
|
||||
yarn grunt validate-angular-files
|
||||
yarn grunt build
|
||||
fi
|
||||
|
||||
# check this after the package, because at this point the browser_provider
|
||||
|
||||
+10
-7
@@ -11,14 +11,14 @@ export SAUCE_ACCESS_KEY
|
||||
BROWSER_STACK_ACCESS_KEY=$(echo "$BROWSER_STACK_ACCESS_KEY" | rev)
|
||||
SAUCE_ACCESS_KEY=$(echo "$SAUCE_ACCESS_KEY" | rev)
|
||||
|
||||
# TODO: restore "SL_EDGE-1" once Sauce Labs adds Edge 17 and "SL_EDGE-1" refers
|
||||
# to version 16. Edge 15 disconnects from Karma frequently causing extreme build instability.
|
||||
# The currently latest version of Safari on Saucelabs (v12) is unstable and disconnects frequently.
|
||||
# TODO: Add `SL_Safari` back, once/if it becomes more stable again.
|
||||
BROWSERS="SL_Chrome,SL_Chrome-1,\
|
||||
SL_Firefox,SL_Firefox-1,\
|
||||
SL_Safari,SL_Safari-1,\
|
||||
SL_iOS_10,SL_iOS_11,\
|
||||
SL_Safari-1,\
|
||||
SL_iOS,SL_iOS-1,\
|
||||
SL_IE_9,SL_IE_10,SL_IE_11,\
|
||||
SL_EDGE"
|
||||
SL_EDGE,SL_EDGE-1"
|
||||
|
||||
case "$JOB" in
|
||||
"ci-checks")
|
||||
@@ -29,19 +29,21 @@ case "$JOB" in
|
||||
# convert commit range to 2 dots, as commitplease uses `git log`.
|
||||
# See https://github.com/travis-ci/travis-ci/issues/4596 for more info
|
||||
echo "Validate commit messages in PR:"
|
||||
yarn run commitplease -- "${TRAVIS_COMMIT_RANGE/.../..}"
|
||||
yarn run commitplease "${TRAVIS_COMMIT_RANGE/.../..}"
|
||||
fi
|
||||
;;
|
||||
"unit-core")
|
||||
grunt test:promises-aplus
|
||||
grunt test:jqlite --browsers="$BROWSERS" --reporters=spec
|
||||
grunt test:modules --browsers="$BROWSERS" --reporters=spec
|
||||
;;
|
||||
"unit-jquery")
|
||||
grunt test:jquery --browsers="$BROWSERS" --reporters=spec
|
||||
grunt test:jquery-2.2 --browsers="$BROWSERS" --reporters=spec
|
||||
grunt test:jquery-2.1 --browsers="$BROWSERS" --reporters=spec
|
||||
;;
|
||||
"unit-modules")
|
||||
grunt test:modules --browsers="$BROWSERS" --reporters=spec
|
||||
;;
|
||||
"docs-app")
|
||||
grunt tests:docs --browsers="$BROWSERS" --reporters=spec
|
||||
grunt test:travis-protractor --specs="docs/app/e2e/**/*.scenario.js"
|
||||
@@ -102,6 +104,7 @@ case "$JOB" in
|
||||
'ci-checks',\
|
||||
'unit-core',\
|
||||
'unit-jquery',\
|
||||
'unit-modules',\
|
||||
'docs-app',\
|
||||
'e2e',\
|
||||
or\
|
||||
|
||||
@@ -7,5 +7,5 @@ for FILE in $LOG_FILES; do
|
||||
echo "================================================================================"
|
||||
echo " $FILE"
|
||||
echo "================================================================================"
|
||||
cat $FILE
|
||||
cat $FILE || true
|
||||
done
|
||||
|
||||
@@ -171,9 +171,15 @@
|
||||
/* ng/q.js */
|
||||
"markQExceptionHandled": false,
|
||||
|
||||
/* sce.js */
|
||||
"SCE_CONTEXTS": false,
|
||||
|
||||
/* ng/directive/directives.js */
|
||||
"ngDirective": false,
|
||||
|
||||
/* ng/directive/ngEventDirs.js */
|
||||
"createEventDirective": false,
|
||||
|
||||
/* ng/directive/input.js */
|
||||
"VALID_CLASS": false,
|
||||
"INVALID_CLASS": false,
|
||||
|
||||
+6
-5
@@ -792,15 +792,16 @@ function arrayRemove(array, value) {
|
||||
* * If `source` is identical to `destination` an exception will be thrown.
|
||||
*
|
||||
* <br />
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* Only enumerable properties are taken into account. Non-enumerable properties (both on `source`
|
||||
* and on `destination`) will be ignored.
|
||||
* </div>
|
||||
*
|
||||
* @param {*} source The source that will be used to make a copy.
|
||||
* Can be any type, including primitives, `null`, and `undefined`.
|
||||
* @param {(Object|Array)=} destination Destination into which the source is copied. If
|
||||
* provided, must be of the same type as `source`.
|
||||
* @param {*} source The source that will be used to make a copy. Can be any type, including
|
||||
* primitives, `null`, and `undefined`.
|
||||
* @param {(Object|Array)=} destination Destination into which the source is copied. If provided,
|
||||
* must be of the same type as `source`.
|
||||
* @returns {*} The copy or updated `destination`, if `destination` was specified.
|
||||
*
|
||||
* @example
|
||||
@@ -1909,7 +1910,7 @@ function bindJQuery() {
|
||||
jqLite.cleanData = function(elems) {
|
||||
var events;
|
||||
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
|
||||
events = jqLite._data(elem).events;
|
||||
events = (jqLite._data(elem) || {}).events;
|
||||
if (events && events.$destroy) {
|
||||
jqLite(elem).triggerHandler('$destroy');
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
htmlAnchorDirective,
|
||||
inputDirective,
|
||||
inputDirective,
|
||||
hiddenInputBrowserCacheDirective,
|
||||
formDirective,
|
||||
scriptDirective,
|
||||
selectDirective,
|
||||
@@ -28,6 +28,7 @@
|
||||
ngInitDirective,
|
||||
ngNonBindableDirective,
|
||||
ngPluralizeDirective,
|
||||
ngRefDirective,
|
||||
ngRepeatDirective,
|
||||
ngShowDirective,
|
||||
ngStyleDirective,
|
||||
@@ -69,6 +70,7 @@
|
||||
$FilterProvider,
|
||||
$$ForceReflowProvider,
|
||||
$InterpolateProvider,
|
||||
$$IntervalFactoryProvider,
|
||||
$IntervalProvider,
|
||||
$HttpProvider,
|
||||
$HttpParamSerializerProvider,
|
||||
@@ -87,6 +89,7 @@
|
||||
$SceProvider,
|
||||
$SceDelegateProvider,
|
||||
$SnifferProvider,
|
||||
$$TaskTrackerFactoryProvider,
|
||||
$TemplateCacheProvider,
|
||||
$TemplateRequestProvider,
|
||||
$$TestabilityProvider,
|
||||
@@ -194,6 +197,7 @@ function publishExternalAPI(angular) {
|
||||
ngInit: ngInitDirective,
|
||||
ngNonBindable: ngNonBindableDirective,
|
||||
ngPluralize: ngPluralizeDirective,
|
||||
ngRef: ngRefDirective,
|
||||
ngRepeat: ngRepeatDirective,
|
||||
ngShow: ngShowDirective,
|
||||
ngStyle: ngStyleDirective,
|
||||
@@ -217,7 +221,8 @@ function publishExternalAPI(angular) {
|
||||
ngModelOptions: ngModelOptionsDirective
|
||||
}).
|
||||
directive({
|
||||
ngInclude: ngIncludeFillContentDirective
|
||||
ngInclude: ngIncludeFillContentDirective,
|
||||
input: hiddenInputBrowserCacheDirective
|
||||
}).
|
||||
directive(ngAttributeAliasDirectives).
|
||||
directive(ngEventDirectives);
|
||||
@@ -239,6 +244,7 @@ function publishExternalAPI(angular) {
|
||||
$$forceReflow: $$ForceReflowProvider,
|
||||
$interpolate: $InterpolateProvider,
|
||||
$interval: $IntervalProvider,
|
||||
$$intervalFactory: $$IntervalFactoryProvider,
|
||||
$http: $HttpProvider,
|
||||
$httpParamSerializer: $HttpParamSerializerProvider,
|
||||
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
|
||||
@@ -254,6 +260,7 @@ function publishExternalAPI(angular) {
|
||||
$sce: $SceProvider,
|
||||
$sceDelegate: $SceDelegateProvider,
|
||||
$sniffer: $SnifferProvider,
|
||||
$$taskTrackerFactory: $$TaskTrackerFactoryProvider,
|
||||
$templateCache: $TemplateCacheProvider,
|
||||
$templateRequest: $TemplateRequestProvider,
|
||||
$$testability: $$TestabilityProvider,
|
||||
|
||||
+8
-4
@@ -45,11 +45,10 @@ function NgMapShim() {
|
||||
}
|
||||
NgMapShim.prototype = {
|
||||
_idx: function(key) {
|
||||
if (key === this._lastKey) {
|
||||
return this._lastIndex;
|
||||
if (key !== this._lastKey) {
|
||||
this._lastKey = key;
|
||||
this._lastIndex = this._keys.indexOf(key);
|
||||
}
|
||||
this._lastKey = key;
|
||||
this._lastIndex = this._keys.indexOf(key);
|
||||
return this._lastIndex;
|
||||
},
|
||||
_transformKey: function(key) {
|
||||
@@ -62,6 +61,11 @@ NgMapShim.prototype = {
|
||||
return this._values[idx];
|
||||
}
|
||||
},
|
||||
has: function(key) {
|
||||
key = this._transformKey(key);
|
||||
var idx = this._idx(key);
|
||||
return idx !== -1;
|
||||
},
|
||||
set: function(key, value) {
|
||||
key = this._transformKey(key);
|
||||
var idx = this._idx(key);
|
||||
|
||||
+16
-3
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
|
||||
var minErrConfig = {
|
||||
objectMaxDepth: 5
|
||||
objectMaxDepth: 5,
|
||||
urlErrorParamsEnabled: true
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -30,12 +31,21 @@ var minErrConfig = {
|
||||
* * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a
|
||||
* non-positive or non-numeric value, removes the max depth limit.
|
||||
* Default: 5
|
||||
*
|
||||
* * `urlErrorParamsEnabled` **{Boolean}** - Specifies wether the generated error url will
|
||||
* contain the parameters of the thrown error. Disabling the parameters can be useful if the
|
||||
* generated error url is very long.
|
||||
*
|
||||
* Default: true. When used without argument, it returns the current value.
|
||||
*/
|
||||
function errorHandlingConfig(config) {
|
||||
if (isObject(config)) {
|
||||
if (isDefined(config.objectMaxDepth)) {
|
||||
minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN;
|
||||
}
|
||||
if (isDefined(config.urlErrorParamsEnabled) && isBoolean(config.urlErrorParamsEnabled)) {
|
||||
minErrConfig.urlErrorParamsEnabled = config.urlErrorParamsEnabled;
|
||||
}
|
||||
} else {
|
||||
return minErrConfig;
|
||||
}
|
||||
@@ -50,6 +60,7 @@ function isValidObjectMaxDepth(maxDepth) {
|
||||
return isNumber(maxDepth) && maxDepth > 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description
|
||||
*
|
||||
@@ -113,8 +124,10 @@ function minErr(module, ErrorConstructor) {
|
||||
|
||||
message += '\n' + url + (module ? module + '/' : '') + code;
|
||||
|
||||
for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
|
||||
message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
|
||||
if (minErrConfig.urlErrorParamsEnabled) {
|
||||
for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
|
||||
message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return new ErrorConstructor(message);
|
||||
|
||||
+28
-5
@@ -369,14 +369,39 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note**: Generally, the events that are fired correspond 1:1 to `$animate` method names,
|
||||
* e.g. {@link ng.$animate#addClass addClass()} will fire `addClass`, and {@link ng.ngClass}
|
||||
* will fire `addClass` if classes are added, and `removeClass` if classes are removed.
|
||||
* However, there are two exceptions:
|
||||
*
|
||||
* <ul>
|
||||
* <li>if both an {@link ng.$animate#addClass addClass()} and a
|
||||
* {@link ng.$animate#removeClass removeClass()} action are performed during the same
|
||||
* animation, the event fired will be `setClass`. This is true even for `ngClass`.</li>
|
||||
* <li>an {@link ng.$animate#animate animate()} call that adds and removes classes will fire
|
||||
* the `setClass` event, but if it either removes or adds classes,
|
||||
* it will fire `animate` instead.</li>
|
||||
* </ul>
|
||||
*
|
||||
* </div>
|
||||
*
|
||||
* @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
|
||||
* @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
|
||||
* as well as among its children
|
||||
* @param {Function} callback the callback function that will be fired when the listener is triggered
|
||||
* @param {Function} callback the callback function that will be fired when the listener is triggered.
|
||||
*
|
||||
* The arguments present in the callback function are:
|
||||
* * `element` - The captured DOM element that the animation was fired on.
|
||||
* * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
|
||||
* * `data` - an object with these properties:
|
||||
* * addClass - `{string|null}` - space-separated CSS classes to add to the element
|
||||
* * removeClass - `{string|null}` - space-separated CSS classes to remove from the element
|
||||
* * from - `{Object|null}` - CSS properties & values at the beginning of the animation
|
||||
* * to - `{Object|null}` - CSS properties & values at the end of the animation
|
||||
*
|
||||
* Note that the callback does not trigger a scope digest. Wrap your call into a
|
||||
* {@link $rootScope.Scope#$apply scope.$apply} to propagate changes to the scope.
|
||||
*/
|
||||
on: $$animateQueue.on,
|
||||
|
||||
@@ -644,9 +669,8 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* @param {object=} options an optional collection of options/styles that will be applied to the element.
|
||||
* The object can have the following properties:
|
||||
*
|
||||
* - **addClass** - `{string}` - space-separated CSS classes to add to element
|
||||
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
|
||||
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
|
||||
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
|
||||
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
|
||||
*
|
||||
* @return {Runner} animationRunner the animation runner
|
||||
@@ -676,7 +700,6 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
*
|
||||
* - **addClass** - `{string}` - space-separated CSS classes to add to element
|
||||
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
|
||||
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
|
||||
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
|
||||
*
|
||||
* @return {Runner} the animation runner
|
||||
@@ -706,8 +729,8 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* The object can have the following properties:
|
||||
*
|
||||
* - **addClass** - `{string}` - space-separated CSS classes to add to element
|
||||
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
|
||||
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
|
||||
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
|
||||
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
|
||||
*
|
||||
* @return {Runner} the animation runner
|
||||
|
||||
+50
-64
@@ -1,5 +1,14 @@
|
||||
'use strict';
|
||||
/* global stripHash: true */
|
||||
/* global getHash: true, stripHash: false */
|
||||
|
||||
function getHash(url) {
|
||||
var index = url.indexOf('#');
|
||||
return index === -1 ? '' : url.substr(index);
|
||||
}
|
||||
|
||||
function trimEmptyHash(url) {
|
||||
return url.replace(/#$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* ! This is a private undocumented service !
|
||||
@@ -22,61 +31,27 @@
|
||||
* @param {object} $log window.console or an object with the same interface.
|
||||
* @param {object} $sniffer $sniffer service
|
||||
*/
|
||||
function Browser(window, document, $log, $sniffer) {
|
||||
function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
|
||||
var self = this,
|
||||
location = window.location,
|
||||
history = window.history,
|
||||
setTimeout = window.setTimeout,
|
||||
clearTimeout = window.clearTimeout,
|
||||
pendingDeferIds = {};
|
||||
pendingDeferIds = {},
|
||||
taskTracker = $$taskTrackerFactory($log);
|
||||
|
||||
self.isMock = false;
|
||||
|
||||
var outstandingRequestCount = 0;
|
||||
var outstandingRequestCallbacks = [];
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Task-tracking API
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO(vojta): remove this temporary api
|
||||
self.$$completeOutstandingRequest = completeOutstandingRequest;
|
||||
self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
|
||||
self.$$completeOutstandingRequest = taskTracker.completeTask;
|
||||
self.$$incOutstandingRequestCount = taskTracker.incTaskCount;
|
||||
|
||||
/**
|
||||
* Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
|
||||
* counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
|
||||
*/
|
||||
function completeOutstandingRequest(fn) {
|
||||
try {
|
||||
fn.apply(null, sliceArgs(arguments, 1));
|
||||
} finally {
|
||||
outstandingRequestCount--;
|
||||
if (outstandingRequestCount === 0) {
|
||||
while (outstandingRequestCallbacks.length) {
|
||||
try {
|
||||
outstandingRequestCallbacks.pop()();
|
||||
} catch (e) {
|
||||
$log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getHash(url) {
|
||||
var index = url.indexOf('#');
|
||||
return index === -1 ? '' : url.substr(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* TODO(vojta): prefix this method with $$ ?
|
||||
* @param {function()} callback Function that will be called when no outstanding request
|
||||
*/
|
||||
self.notifyWhenNoOutstandingRequests = function(callback) {
|
||||
if (outstandingRequestCount === 0) {
|
||||
callback();
|
||||
} else {
|
||||
outstandingRequestCallbacks.push(callback);
|
||||
}
|
||||
};
|
||||
// TODO(vojta): prefix this method with $$ ?
|
||||
self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// URL API
|
||||
@@ -101,20 +76,21 @@ function Browser(window, document, $log, $sniffer) {
|
||||
*
|
||||
* @description
|
||||
* GETTER:
|
||||
* Without any argument, this method just returns current value of location.href.
|
||||
* Without any argument, this method just returns current value of `location.href` (with a
|
||||
* trailing `#` stripped of if the hash is empty).
|
||||
*
|
||||
* SETTER:
|
||||
* With at least one argument, this method sets url to new value.
|
||||
* If html5 history api supported, pushState/replaceState is used, otherwise
|
||||
* location.href/location.replace is used.
|
||||
* Returns its own instance to allow chaining
|
||||
* If html5 history api supported, `pushState`/`replaceState` is used, otherwise
|
||||
* `location.href`/`location.replace` is used.
|
||||
* Returns its own instance to allow chaining.
|
||||
*
|
||||
* NOTE: this api is intended for use only by the $location service. Please use the
|
||||
* NOTE: this api is intended for use only by the `$location` service. Please use the
|
||||
* {@link ng.$location $location service} to change url.
|
||||
*
|
||||
* @param {string} url New url (when used as setter)
|
||||
* @param {boolean=} replace Should new url replace current history record?
|
||||
* @param {object=} state object to use with pushState/replaceState
|
||||
* @param {object=} state State object to use with `pushState`/`replaceState`
|
||||
*/
|
||||
self.url = function(url, replace, state) {
|
||||
// In modern browsers `history.state` is `null` by default; treating it separately
|
||||
@@ -132,6 +108,9 @@ function Browser(window, document, $log, $sniffer) {
|
||||
if (url) {
|
||||
var sameState = lastHistoryState === state;
|
||||
|
||||
// Normalize the inputted URL
|
||||
url = urlResolve(url).href;
|
||||
|
||||
// Don't change anything if previous and current URLs and states match. This also prevents
|
||||
// IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
|
||||
// See https://github.com/angular/angular.js/commit/ffb2701
|
||||
@@ -172,7 +151,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
// - pendingLocation is needed as browsers don't allow to read out
|
||||
// the new location.href if a reload happened or if there is a bug like in iOS 9 (see
|
||||
// https://openradar.appspot.com/22186109).
|
||||
return pendingLocation || location.href;
|
||||
return trimEmptyHash(pendingLocation || location.href);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -307,7 +286,8 @@ function Browser(window, document, $log, $sniffer) {
|
||||
/**
|
||||
* @name $browser#defer
|
||||
* @param {function()} fn A function, who's execution should be deferred.
|
||||
* @param {number=} [delay=0] of milliseconds to defer the function execution.
|
||||
* @param {number=} [delay=0] Number of milliseconds to defer the function execution.
|
||||
* @param {string=} [taskType=DEFAULT_TASK_TYPE] The type of task that is deferred.
|
||||
* @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
|
||||
*
|
||||
* @description
|
||||
@@ -318,14 +298,19 @@ function Browser(window, document, $log, $sniffer) {
|
||||
* via `$browser.defer.flush()`.
|
||||
*
|
||||
*/
|
||||
self.defer = function(fn, delay) {
|
||||
self.defer = function(fn, delay, taskType) {
|
||||
var timeoutId;
|
||||
outstandingRequestCount++;
|
||||
|
||||
delay = delay || 0;
|
||||
taskType = taskType || taskTracker.DEFAULT_TASK_TYPE;
|
||||
|
||||
taskTracker.incTaskCount(taskType);
|
||||
timeoutId = setTimeout(function() {
|
||||
delete pendingDeferIds[timeoutId];
|
||||
completeOutstandingRequest(fn);
|
||||
}, delay || 0);
|
||||
pendingDeferIds[timeoutId] = true;
|
||||
taskTracker.completeTask(fn, taskType);
|
||||
}, delay);
|
||||
pendingDeferIds[timeoutId] = taskType;
|
||||
|
||||
return timeoutId;
|
||||
};
|
||||
|
||||
@@ -341,10 +326,11 @@ function Browser(window, document, $log, $sniffer) {
|
||||
* canceled.
|
||||
*/
|
||||
self.defer.cancel = function(deferId) {
|
||||
if (pendingDeferIds[deferId]) {
|
||||
if (pendingDeferIds.hasOwnProperty(deferId)) {
|
||||
var taskType = pendingDeferIds[deferId];
|
||||
delete pendingDeferIds[deferId];
|
||||
clearTimeout(deferId);
|
||||
completeOutstandingRequest(noop);
|
||||
taskTracker.completeTask(noop, taskType);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -354,8 +340,8 @@ function Browser(window, document, $log, $sniffer) {
|
||||
|
||||
/** @this */
|
||||
function $BrowserProvider() {
|
||||
this.$get = ['$window', '$log', '$sniffer', '$document',
|
||||
function($window, $log, $sniffer, $document) {
|
||||
return new Browser($window, $document, $log, $sniffer);
|
||||
}];
|
||||
this.$get = ['$window', '$log', '$sniffer', '$document', '$$taskTrackerFactory',
|
||||
function($window, $log, $sniffer, $document, $$taskTrackerFactory) {
|
||||
return new Browser($window, $document, $log, $sniffer, $$taskTrackerFactory);
|
||||
}];
|
||||
}
|
||||
|
||||
+590
-86
@@ -1030,8 +1030,7 @@
|
||||
*
|
||||
* See issue [#2573](https://github.com/angular/angular.js/issues/2573).
|
||||
*
|
||||
* #### `transclude: element` in the replace template root can have
|
||||
* unexpected effects
|
||||
* #### `transclude: element` in the replace template root can have unexpected effects
|
||||
*
|
||||
* When the replace template has a directive at the root node that uses
|
||||
* {@link $compile#-transclude- `transclude: element`}, e.g.
|
||||
@@ -1045,6 +1044,325 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngProp
|
||||
* @restrict A
|
||||
* @element ANY
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
* ```html
|
||||
* <ANY ng-prop-propname="expression">
|
||||
* </ANY>
|
||||
* ```
|
||||
*
|
||||
* or with uppercase letters in property (e.g. "propName"):
|
||||
*
|
||||
*
|
||||
* ```html
|
||||
* <ANY ng-prop-prop_name="expression">
|
||||
* </ANY>
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* @description
|
||||
* The `ngProp` directive binds an expression to a DOM element property.
|
||||
* `ngProp` allows writing to arbitrary properties by including
|
||||
* the property name in the attribute, e.g. `ng-prop-value="'my value'"` binds 'my value' to
|
||||
* the `value` property.
|
||||
*
|
||||
* Usually, it's not necessary to write to properties in AngularJS, as the built-in directives
|
||||
* handle the most common use cases (instead of the above example, you would use {@link ngValue}).
|
||||
*
|
||||
* However, [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements)
|
||||
* often use custom properties to hold data, and `ngProp` can be used to provide input to these
|
||||
* custom elements.
|
||||
*
|
||||
* ## Binding to camelCase properties
|
||||
*
|
||||
* Since HTML attributes are case-insensitive, camelCase properties like `innerHTML` must be escaped.
|
||||
* AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so
|
||||
* `innerHTML` must be written as `ng-prop-inner_h_t_m_l="expression"` (Note that this is just an
|
||||
* example, and for binding HTML {@link ngBindHtml} should be used.
|
||||
*
|
||||
* ## Security
|
||||
*
|
||||
* Binding expressions to arbitrary properties poses a security risk, as properties like `innerHTML`
|
||||
* can insert potentially dangerous HTML into the application, e.g. script tags that execute
|
||||
* malicious code.
|
||||
* For this reason, `ngProp` applies Strict Contextual Escaping with the {@link ng.$sce $sce service}.
|
||||
* This means vulnerable properties require their content to be "trusted", based on the
|
||||
* context of the property. For example, the `innerHTML` is in the `HTML` context, and the
|
||||
* `iframe.src` property is in the `RESOURCE_URL` context, which requires that values written to
|
||||
* this property are trusted as a `RESOURCE_URL`.
|
||||
*
|
||||
* This can be set explicitly by calling $sce.trustAs(type, value) on the value that is
|
||||
* trusted before passing it to the `ng-prop-*` directive. There are exist shorthand methods for
|
||||
* each context type in the form of {@link ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl()} et al.
|
||||
*
|
||||
* In some cases you can also rely upon automatic sanitization of untrusted values - see below.
|
||||
*
|
||||
* Based on the context, other options may exist to mark a value as trusted / configure the behavior
|
||||
* of {@link ng.$sce}. For example, to restrict the `RESOURCE_URL` context to specific origins, use
|
||||
* the {@link $sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist()}
|
||||
* and {@link $sceDelegateProvider#resourceUrlBlacklist resourceUrlBlacklist()}.
|
||||
*
|
||||
* {@link ng.$sce#what-trusted-context-types-are-supported- Find out more about the different context types}.
|
||||
*
|
||||
* ### HTML Sanitization
|
||||
*
|
||||
* By default, `$sce` will throw an error if it detects untrusted HTML content, and will not bind the
|
||||
* content.
|
||||
* However, if you include the {@link ngSanitize ngSanitize module}, it will try to sanitize the
|
||||
* potentially dangerous HTML, e.g. strip non-whitelisted tags and attributes when binding to
|
||||
* `innerHTML`.
|
||||
*
|
||||
* @example
|
||||
* ### Binding to different contexts
|
||||
*
|
||||
* <example name="ngProp" module="exampleNgProp">
|
||||
* <file name="app.js">
|
||||
* angular.module('exampleNgProp', [])
|
||||
* .component('main', {
|
||||
* templateUrl: 'main.html',
|
||||
* controller: function($sce) {
|
||||
* this.safeContent = '<strong>Safe content</strong>';
|
||||
* this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>';
|
||||
* this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent);
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="main.html">
|
||||
* <div>
|
||||
* <div class="prop-unit">
|
||||
* Binding to a property without security context:
|
||||
* <div class="prop-binding" ng-prop-inner_text="$ctrl.safeContent"></div>
|
||||
* <span class="prop-note">innerText</span> (safeContent)
|
||||
* </div>
|
||||
*
|
||||
* <div class="prop-unit">
|
||||
* "Safe" content that requires a security context will throw because the contents could potentially be dangerous ...
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (safeContent)
|
||||
* </div>
|
||||
*
|
||||
* <div class="prop-unit">
|
||||
* ... so that actually dangerous content cannot be executed:
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (unsafeContent)
|
||||
* </div>
|
||||
*
|
||||
* <div class="prop-unit">
|
||||
* ... but unsafe Content that has been trusted explicitly works - only do this if you are 100% sure!
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (trustedUnsafeContent)
|
||||
* </div>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <main></main>
|
||||
* </file>
|
||||
* <file name="styles.css">
|
||||
* .prop-unit {
|
||||
* margin-bottom: 10px;
|
||||
* }
|
||||
*
|
||||
* .prop-binding {
|
||||
* min-height: 30px;
|
||||
* border: 1px solid blue;
|
||||
* }
|
||||
*
|
||||
* .prop-note {
|
||||
* font-family: Monospace;
|
||||
* }
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ### Binding to innerHTML with ngSanitize
|
||||
*
|
||||
* <example name="ngProp" module="exampleNgProp" deps="angular-sanitize.js">
|
||||
* <file name="app.js">
|
||||
* angular.module('exampleNgProp', ['ngSanitize'])
|
||||
* .component('main', {
|
||||
* templateUrl: 'main.html',
|
||||
* controller: function($sce) {
|
||||
* this.safeContent = '<strong>Safe content</strong>';
|
||||
* this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>';
|
||||
* this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent);
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="main.html">
|
||||
* <div>
|
||||
* <div class="prop-unit">
|
||||
* "Safe" content will be sanitized ...
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (safeContent)
|
||||
* </div>
|
||||
*
|
||||
* <div class="prop-unit">
|
||||
* ... as will dangerous content:
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (unsafeContent)
|
||||
* </div>
|
||||
*
|
||||
* <div class="prop-unit">
|
||||
* ... and content that has been trusted explicitly works the same as without ngSanitize:
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (trustedUnsafeContent)
|
||||
* </div>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <main></main>
|
||||
* </file>
|
||||
* <file name="styles.css">
|
||||
* .prop-unit {
|
||||
* margin-bottom: 10px;
|
||||
* }
|
||||
*
|
||||
* .prop-binding {
|
||||
* min-height: 30px;
|
||||
* border: 1px solid blue;
|
||||
* }
|
||||
*
|
||||
* .prop-note {
|
||||
* font-family: Monospace;
|
||||
* }
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
*/
|
||||
|
||||
/** @ngdoc directive
|
||||
* @name ngOn
|
||||
* @restrict A
|
||||
* @element ANY
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
* ```html
|
||||
* <ANY ng-on-eventname="expression">
|
||||
* </ANY>
|
||||
* ```
|
||||
*
|
||||
* or with uppercase letters in property (e.g. "eventName"):
|
||||
*
|
||||
*
|
||||
* ```html
|
||||
* <ANY ng-on-event_name="expression">
|
||||
* </ANY>
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
* The `ngOn` directive adds an event listener to a DOM element via
|
||||
* {@link angular.element angular.element().on()}, and evaluates an expression when the event is
|
||||
* fired.
|
||||
* `ngOn` allows adding listeners for arbitrary events by including
|
||||
* the event name in the attribute, e.g. `ng-on-drop="onDrop()"` executes the 'onDrop()' expression
|
||||
* when the `drop` event is fired.
|
||||
*
|
||||
* AngularJS provides specific directives for many events, such as {@link ngClick}, so in most
|
||||
* cases it is not necessary to use `ngOn`. However, AngularJS does not support all events
|
||||
* (e.g. the `drop` event in the example above), and new events might be introduced in later DOM
|
||||
* standards.
|
||||
*
|
||||
* Another use-case for `ngOn` is listening to
|
||||
* [custom events](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events)
|
||||
* fired by
|
||||
* [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements).
|
||||
*
|
||||
* ## Binding to camelCase properties
|
||||
*
|
||||
* Since HTML attributes are case-insensitive, camelCase properties like `myEvent` must be escaped.
|
||||
* AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so
|
||||
* `myEvent` must be written as `ng-on-my_event="expression"`.
|
||||
*
|
||||
* @example
|
||||
* ### Bind to built-in DOM events
|
||||
*
|
||||
* <example name="ngOn" module="exampleNgOn">
|
||||
* <file name="app.js">
|
||||
* angular.module('exampleNgOn', [])
|
||||
* .component('main', {
|
||||
* templateUrl: 'main.html',
|
||||
* controller: function() {
|
||||
* this.clickCount = 0;
|
||||
* this.mouseoverCount = 0;
|
||||
*
|
||||
* this.loadingState = 0;
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="main.html">
|
||||
* <div>
|
||||
* This is equivalent to `ngClick` and `ngMouseover`:<br>
|
||||
* <button
|
||||
* ng-on-click="$ctrl.clickCount = $ctrl.clickCount + 1"
|
||||
* ng-on-mouseover="$ctrl.mouseoverCount = $ctrl.mouseoverCount + 1">Click or mouseover</button><br>
|
||||
* clickCount: {{$ctrl.clickCount}}<br>
|
||||
* mouseover: {{$ctrl.mouseoverCount}}
|
||||
*
|
||||
* <hr>
|
||||
*
|
||||
* For the `error` and `load` event on images no built-in AngularJS directives exist:<br>
|
||||
* <img src="thisimagedoesnotexist.png" ng-on-error="$ctrl.loadingState = -1" ng-on-load="$ctrl.loadingState = 1"><br>
|
||||
* <div ng-switch="$ctrl.loadingState">
|
||||
* <span ng-switch-when="0">Image is loading</span>
|
||||
* <span ng-switch-when="-1">Image load error</span>
|
||||
* <span ng-switch-when="1">Image loaded successfully</span>
|
||||
* </div>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <main></main>
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ### Bind to custom DOM events
|
||||
*
|
||||
* <example name="ngOnCustom" module="exampleNgOn">
|
||||
* <file name="app.js">
|
||||
* angular.module('exampleNgOn', [])
|
||||
* .component('main', {
|
||||
* templateUrl: 'main.html',
|
||||
* controller: function() {
|
||||
* this.eventLog = '';
|
||||
*
|
||||
* this.listener = function($event) {
|
||||
* this.eventLog = 'Event with type "' + $event.type + '" fired at ' + $event.detail;
|
||||
* };
|
||||
* }
|
||||
* })
|
||||
* .component('childComponent', {
|
||||
* templateUrl: 'child.html',
|
||||
* controller: function($element) {
|
||||
* this.fireEvent = function() {
|
||||
* var event = new CustomEvent('customtype', { detail: new Date()});
|
||||
*
|
||||
* $element[0].dispatchEvent(event);
|
||||
* };
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="main.html">
|
||||
* <child-component ng-on-customtype="$ctrl.listener($event)"></child-component><br>
|
||||
* <span>Event log: {{$ctrl.eventLog}}</span>
|
||||
* </file>
|
||||
* <file name="child.html">
|
||||
<button ng-click="$ctrl.fireEvent()">Fire custom event</button>
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <main></main>
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
|
||||
var $compileMinErr = minErr('$compile');
|
||||
|
||||
function UNINITIALIZED_VALUE() {}
|
||||
@@ -1587,6 +1905,91 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return cssClassDirectivesEnabledConfig;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The security context of DOM Properties.
|
||||
* @private
|
||||
*/
|
||||
var PROP_CONTEXTS = createMap();
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compileProvider#addPropertySecurityContext
|
||||
* @description
|
||||
*
|
||||
* Defines the security context for DOM properties bound by ng-prop-*.
|
||||
*
|
||||
* @param {string} elementName The element name or '*' to match any element.
|
||||
* @param {string} propertyName The DOM property name.
|
||||
* @param {string} ctx The {@link $sce} security context in which this value is safe for use, e.g. `$sce.URL`
|
||||
* @returns {object} `this` for chaining
|
||||
*/
|
||||
this.addPropertySecurityContext = function(elementName, propertyName, ctx) {
|
||||
var key = (elementName.toLowerCase() + '|' + propertyName.toLowerCase());
|
||||
|
||||
if (key in PROP_CONTEXTS && PROP_CONTEXTS[key] !== ctx) {
|
||||
throw $compileMinErr('ctxoverride', 'Property context \'{0}.{1}\' already set to \'{2}\', cannot override to \'{3}\'.', elementName, propertyName, PROP_CONTEXTS[key], ctx);
|
||||
}
|
||||
|
||||
PROP_CONTEXTS[key] = ctx;
|
||||
return this;
|
||||
};
|
||||
|
||||
/* Default property contexts.
|
||||
*
|
||||
* Copy of https://github.com/angular/angular/blob/6.0.6/packages/compiler/src/schema/dom_security_schema.ts#L31-L58
|
||||
* Changing:
|
||||
* - SecurityContext.* => SCE_CONTEXTS/$sce.*
|
||||
* - STYLE => CSS
|
||||
* - various URL => MEDIA_URL
|
||||
* - *|formAction, form|action URL => RESOURCE_URL (like the attribute)
|
||||
*/
|
||||
(function registerNativePropertyContexts() {
|
||||
function registerContext(ctx, values) {
|
||||
forEach(values, function(v) { PROP_CONTEXTS[v.toLowerCase()] = ctx; });
|
||||
}
|
||||
|
||||
registerContext(SCE_CONTEXTS.HTML, [
|
||||
'iframe|srcdoc',
|
||||
'*|innerHTML',
|
||||
'*|outerHTML'
|
||||
]);
|
||||
registerContext(SCE_CONTEXTS.CSS, ['*|style']);
|
||||
registerContext(SCE_CONTEXTS.URL, [
|
||||
'area|href', 'area|ping',
|
||||
'a|href', 'a|ping',
|
||||
'blockquote|cite',
|
||||
'body|background',
|
||||
'del|cite',
|
||||
'input|src',
|
||||
'ins|cite',
|
||||
'q|cite'
|
||||
]);
|
||||
registerContext(SCE_CONTEXTS.MEDIA_URL, [
|
||||
'audio|src',
|
||||
'img|src', 'img|srcset',
|
||||
'source|src', 'source|srcset',
|
||||
'track|src',
|
||||
'video|src', 'video|poster'
|
||||
]);
|
||||
registerContext(SCE_CONTEXTS.RESOURCE_URL, [
|
||||
'*|formAction',
|
||||
'applet|code', 'applet|codebase',
|
||||
'base|href',
|
||||
'embed|src',
|
||||
'frame|src',
|
||||
'form|action',
|
||||
'head|profile',
|
||||
'html|manifest',
|
||||
'iframe|src',
|
||||
'link|href',
|
||||
'media|src',
|
||||
'object|codebase', 'object|data',
|
||||
'script|src'
|
||||
]);
|
||||
})();
|
||||
|
||||
|
||||
this.$get = [
|
||||
'$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
|
||||
'$controller', '$rootScope', '$sce', '$animate',
|
||||
@@ -1632,6 +2035,57 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
|
||||
function sanitizeSrcset(value, invokeType) {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
if (!isString(value)) {
|
||||
throw $compileMinErr('srcset', 'Can\'t pass trusted values to `{0}`: "{1}"', invokeType, value.toString());
|
||||
}
|
||||
|
||||
// Such values are a bit too complex to handle automatically inside $sce.
|
||||
// Instead, we sanitize each of the URIs individually, which works, even dynamically.
|
||||
|
||||
// It's not possible to work around this using `$sce.trustAsMediaUrl`.
|
||||
// If you want to programmatically set explicitly trusted unsafe URLs, you should use
|
||||
// `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the
|
||||
// `ng-bind-html` directive.
|
||||
|
||||
var result = '';
|
||||
|
||||
// first check if there are spaces because it's not the same pattern
|
||||
var trimmedSrcset = trim(value);
|
||||
// ( 999x ,| 999w ,| ,|, )
|
||||
var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
|
||||
var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
|
||||
|
||||
// split srcset into tuple of uri and descriptor except for the last item
|
||||
var rawUris = trimmedSrcset.split(pattern);
|
||||
|
||||
// for each tuples
|
||||
var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
|
||||
for (var i = 0; i < nbrUrisWith2parts; i++) {
|
||||
var innerIdx = i * 2;
|
||||
// sanitize the uri
|
||||
result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx]));
|
||||
// add the descriptor
|
||||
result += ' ' + trim(rawUris[innerIdx + 1]);
|
||||
}
|
||||
|
||||
// split the last item into uri and descriptor
|
||||
var lastTuple = trim(rawUris[i * 2]).split(/\s/);
|
||||
|
||||
// sanitize the last uri
|
||||
result += $sce.getTrustedMediaUrl(trim(lastTuple[0]));
|
||||
|
||||
// and add the last descriptor if any
|
||||
if (lastTuple.length === 2) {
|
||||
result += (' ' + trim(lastTuple[1]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function Attributes(element, attributesToCopy) {
|
||||
if (attributesToCopy) {
|
||||
var keys = Object.keys(attributesToCopy);
|
||||
@@ -1768,51 +2222,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
nodeName = nodeName_(this.$$element);
|
||||
|
||||
// Sanitize img[srcset] values.
|
||||
if (nodeName === 'img' && key === 'srcset' && value) {
|
||||
if (!isString(value)) {
|
||||
throw $compileMinErr('srcset', 'Can\'t pass trusted values to `$set(\'srcset\', value)`: "{0}"', value.toString());
|
||||
}
|
||||
|
||||
// Such values are a bit too complex to handle automatically inside $sce.
|
||||
// Instead, we sanitize each of the URIs individually, which works, even dynamically.
|
||||
|
||||
// It's not possible to work around this using `$sce.trustAsMediaUrl`.
|
||||
// If you want to programmatically set explicitly trusted unsafe URLs, you should use
|
||||
// `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the
|
||||
// `ng-bind-html` directive.
|
||||
|
||||
var result = '';
|
||||
|
||||
// first check if there are spaces because it's not the same pattern
|
||||
var trimmedSrcset = trim(value);
|
||||
// ( 999x ,| 999w ,| ,|, )
|
||||
var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
|
||||
var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
|
||||
|
||||
// split srcset into tuple of uri and descriptor except for the last item
|
||||
var rawUris = trimmedSrcset.split(pattern);
|
||||
|
||||
// for each tuples
|
||||
var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
|
||||
for (var i = 0; i < nbrUrisWith2parts; i++) {
|
||||
var innerIdx = i * 2;
|
||||
// sanitize the uri
|
||||
result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx]));
|
||||
// add the descriptor
|
||||
result += ' ' + trim(rawUris[innerIdx + 1]);
|
||||
}
|
||||
|
||||
// split the last item into uri and descriptor
|
||||
var lastTuple = trim(rawUris[i * 2]).split(/\s/);
|
||||
|
||||
// sanitize the last uri
|
||||
result += $sce.getTrustedMediaUrl(trim(lastTuple[0]));
|
||||
|
||||
// and add the last descriptor if any
|
||||
if (lastTuple.length === 2) {
|
||||
result += (' ' + trim(lastTuple[1]));
|
||||
}
|
||||
this[key] = value = result;
|
||||
if (nodeName === 'img' && key === 'srcset') {
|
||||
this[key] = value = sanitizeSrcset(value, '$set(\'srcset\', value)');
|
||||
}
|
||||
|
||||
if (writeAttr !== false) {
|
||||
@@ -1820,7 +2231,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
} else {
|
||||
if (SIMPLE_ATTR_NAME.test(attrName)) {
|
||||
this.$$element.attr(attrName, value);
|
||||
// jQuery skips special boolean attrs treatment in XML nodes for
|
||||
// historical reasons and hence AngularJS cannot freely call
|
||||
// `.attr(attrName, false) with such attributes. To avoid issues
|
||||
// in XHTML, call `removeAttr` in such cases instead.
|
||||
// See https://github.com/jquery/jquery/issues/4249
|
||||
if (booleanKey && value === false) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
} else {
|
||||
this.$$element.attr(attrName, value);
|
||||
}
|
||||
} else {
|
||||
setSpecialAttr(this.$$element[0], attrName, value);
|
||||
}
|
||||
@@ -1909,7 +2329,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
: function denormalizeTemplate(template) {
|
||||
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
|
||||
},
|
||||
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
|
||||
NG_PREFIX_BINDING = /^ng(Attr|Prop|On)([A-Z].*)$/;
|
||||
var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
|
||||
|
||||
compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
|
||||
@@ -2245,43 +2665,66 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective);
|
||||
|
||||
// iterate over the attributes
|
||||
for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
|
||||
for (var attr, name, nName, value, ngPrefixMatch, nAttrs = node.attributes,
|
||||
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
|
||||
var attrStartName = false;
|
||||
var attrEndName = false;
|
||||
|
||||
var isNgAttr = false, isNgProp = false, isNgEvent = false;
|
||||
var multiElementMatch;
|
||||
|
||||
attr = nAttrs[j];
|
||||
name = attr.name;
|
||||
value = attr.value;
|
||||
|
||||
// support ngAttr attribute binding
|
||||
ngAttrName = directiveNormalize(name);
|
||||
isNgAttr = NG_ATTR_BINDING.test(ngAttrName);
|
||||
if (isNgAttr) {
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
|
||||
// Support ng-attr-*, ng-prop-* and ng-on-*
|
||||
if ((ngPrefixMatch = nName.match(NG_PREFIX_BINDING))) {
|
||||
isNgAttr = ngPrefixMatch[1] === 'Attr';
|
||||
isNgProp = ngPrefixMatch[1] === 'Prop';
|
||||
isNgEvent = ngPrefixMatch[1] === 'On';
|
||||
|
||||
// Normalize the non-prefixed name
|
||||
name = name.replace(PREFIX_REGEXP, '')
|
||||
.substr(8).replace(/_(.)/g, function(match, letter) {
|
||||
.toLowerCase()
|
||||
.substr(4 + ngPrefixMatch[1].length).replace(/_(.)/g, function(match, letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
|
||||
if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
|
||||
// Support *-start / *-end multi element directives
|
||||
} else if ((multiElementMatch = nName.match(MULTI_ELEMENT_DIR_RE)) && directiveIsMultiElement(multiElementMatch[1])) {
|
||||
attrStartName = name;
|
||||
attrEndName = name.substr(0, name.length - 5) + 'end';
|
||||
name = name.substr(0, name.length - 6);
|
||||
}
|
||||
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
attrsMap[nName] = name;
|
||||
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
|
||||
if (isNgProp || isNgEvent) {
|
||||
attrs[nName] = value;
|
||||
attrsMap[nName] = attr.name;
|
||||
|
||||
if (isNgProp) {
|
||||
addPropertyDirective(node, directives, nName, name);
|
||||
} else {
|
||||
addEventDirective(directives, nName, name);
|
||||
}
|
||||
} else {
|
||||
// Update nName for cases where a prefix was removed
|
||||
// NOTE: the .toLowerCase() is unnecessary and causes https://github.com/angular/angular.js/issues/16624 for ng-attr-*
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
attrsMap[nName] = name;
|
||||
|
||||
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
|
||||
attrs[nName] = value;
|
||||
if (getBooleanAttrName(node, nName)) {
|
||||
attrs[nName] = true; // presence means true
|
||||
}
|
||||
}
|
||||
|
||||
addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
|
||||
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
|
||||
attrEndName);
|
||||
}
|
||||
addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
|
||||
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
|
||||
attrEndName);
|
||||
}
|
||||
|
||||
if (nodeName === 'input' && node.getAttribute('type') === 'hidden') {
|
||||
@@ -2575,7 +3018,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// We have transclusion slots,
|
||||
// collect them up, compile them and store their transclusion functions
|
||||
$template = [];
|
||||
$template = window.document.createDocumentFragment();
|
||||
|
||||
var slotMap = createMap();
|
||||
var filledSlots = createMap();
|
||||
@@ -2603,10 +3046,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var slotName = slotMap[directiveNormalize(nodeName_(node))];
|
||||
if (slotName) {
|
||||
filledSlots[slotName] = true;
|
||||
slots[slotName] = slots[slotName] || [];
|
||||
slots[slotName].push(node);
|
||||
slots[slotName] = slots[slotName] || window.document.createDocumentFragment();
|
||||
slots[slotName].appendChild(node);
|
||||
} else {
|
||||
$template.push(node);
|
||||
$template.appendChild(node);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2620,9 +3063,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
for (var slotName in slots) {
|
||||
if (slots[slotName]) {
|
||||
// Only define a transclusion function if the slot was filled
|
||||
slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn);
|
||||
var slotCompileNodes = jqLite(slots[slotName].childNodes);
|
||||
slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slotCompileNodes, transcludeFn);
|
||||
}
|
||||
}
|
||||
|
||||
$template = jqLite($template.childNodes);
|
||||
}
|
||||
|
||||
$compileNode.empty(); // clear contents
|
||||
@@ -2958,7 +3404,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
if (!value) {
|
||||
var dataName = '$' + name + 'Controller';
|
||||
value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
|
||||
|
||||
if (inheritType === '^^' && $element[0] && $element[0].nodeType === NODE_TYPE_DOCUMENT) {
|
||||
// inheritedData() uses the documentElement when it finds the document, so we would
|
||||
// require from the element itself.
|
||||
value = null;
|
||||
} else {
|
||||
value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!value && !optional) {
|
||||
@@ -3315,42 +3768,95 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
|
||||
function getTrustedContext(node, attrNormalizedName) {
|
||||
function getTrustedAttrContext(nodeName, attrNormalizedName) {
|
||||
if (attrNormalizedName === 'srcdoc') {
|
||||
return $sce.HTML;
|
||||
}
|
||||
var tag = nodeName_(node);
|
||||
// All tags with src attributes require a RESOURCE_URL value, except for
|
||||
// img and various html5 media tags, which require the MEDIA_URL context.
|
||||
// All nodes with src attributes require a RESOURCE_URL value, except for
|
||||
// img and various html5 media nodes, which require the MEDIA_URL context.
|
||||
if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') {
|
||||
if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) {
|
||||
if (['img', 'video', 'audio', 'source', 'track'].indexOf(nodeName) === -1) {
|
||||
return $sce.RESOURCE_URL;
|
||||
}
|
||||
return $sce.MEDIA_URL;
|
||||
} else if (attrNormalizedName === 'xlinkHref') {
|
||||
// Some xlink:href are okay, most aren't
|
||||
if (tag === 'image') return $sce.MEDIA_URL;
|
||||
if (tag === 'a') return $sce.URL;
|
||||
if (nodeName === 'image') return $sce.MEDIA_URL;
|
||||
if (nodeName === 'a') return $sce.URL;
|
||||
return $sce.RESOURCE_URL;
|
||||
} else if (
|
||||
// Formaction
|
||||
(tag === 'form' && attrNormalizedName === 'action') ||
|
||||
(nodeName === 'form' && attrNormalizedName === 'action') ||
|
||||
// If relative URLs can go where they are not expected to, then
|
||||
// all sorts of trust issues can arise.
|
||||
(tag === 'base' && attrNormalizedName === 'href') ||
|
||||
(nodeName === 'base' && attrNormalizedName === 'href') ||
|
||||
// links can be stylesheets or imports, which can run script in the current origin
|
||||
(tag === 'link' && attrNormalizedName === 'href')
|
||||
(nodeName === 'link' && attrNormalizedName === 'href')
|
||||
) {
|
||||
return $sce.RESOURCE_URL;
|
||||
} else if (tag === 'a' && (attrNormalizedName === 'href' ||
|
||||
} else if (nodeName === 'a' && (attrNormalizedName === 'href' ||
|
||||
attrNormalizedName === 'ngHref')) {
|
||||
return $sce.URL;
|
||||
}
|
||||
}
|
||||
|
||||
function getTrustedPropContext(nodeName, propNormalizedName) {
|
||||
var prop = propNormalizedName.toLowerCase();
|
||||
return PROP_CONTEXTS[nodeName + '|' + prop] || PROP_CONTEXTS['*|' + prop];
|
||||
}
|
||||
|
||||
function sanitizeSrcsetPropertyValue(value) {
|
||||
return sanitizeSrcset($sce.valueOf(value), 'ng-prop-srcset');
|
||||
}
|
||||
function addPropertyDirective(node, directives, attrName, propName) {
|
||||
if (EVENT_HANDLER_ATTR_REGEXP.test(propName)) {
|
||||
throw $compileMinErr('nodomevents', 'Property bindings for HTML DOM event properties are disallowed');
|
||||
}
|
||||
|
||||
var nodeName = nodeName_(node);
|
||||
var trustedContext = getTrustedPropContext(nodeName, propName);
|
||||
|
||||
var sanitizer = identity;
|
||||
// Sanitize img[srcset] + source[srcset] values.
|
||||
if (propName === 'srcset' && (nodeName === 'img' || nodeName === 'source')) {
|
||||
sanitizer = sanitizeSrcsetPropertyValue;
|
||||
} else if (trustedContext) {
|
||||
sanitizer = $sce.getTrusted.bind($sce, trustedContext);
|
||||
}
|
||||
|
||||
directives.push({
|
||||
priority: 100,
|
||||
compile: function ngPropCompileFn(_, attr) {
|
||||
var ngPropGetter = $parse(attr[attrName]);
|
||||
var ngPropWatch = $parse(attr[attrName], function sceValueOf(val) {
|
||||
// Unwrap the value to compare the actual inner safe value, not the wrapper object.
|
||||
return $sce.valueOf(val);
|
||||
});
|
||||
|
||||
return {
|
||||
pre: function ngPropPreLinkFn(scope, $element) {
|
||||
function applyPropValue() {
|
||||
var propValue = ngPropGetter(scope);
|
||||
$element[0][propName] = sanitizer(propValue);
|
||||
}
|
||||
|
||||
applyPropValue();
|
||||
scope.$watch(ngPropWatch, applyPropValue);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addEventDirective(directives, attrName, eventName) {
|
||||
directives.push(
|
||||
createEventDirective($parse, $rootScope, $exceptionHandler, attrName, eventName, /*forceAsync=*/false)
|
||||
);
|
||||
}
|
||||
|
||||
function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) {
|
||||
var trustedContext = getTrustedContext(node, name);
|
||||
var nodeName = nodeName_(node);
|
||||
var trustedContext = getTrustedAttrContext(nodeName, name);
|
||||
var mustHaveExpression = !isNgAttr;
|
||||
var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr;
|
||||
|
||||
@@ -3359,16 +3865,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// no interpolation found -> ignore
|
||||
if (!interpolateFn) return;
|
||||
|
||||
if (name === 'multiple' && nodeName_(node) === 'select') {
|
||||
if (name === 'multiple' && nodeName === 'select') {
|
||||
throw $compileMinErr('selmulti',
|
||||
'Binding to the \'multiple\' attribute is not supported. Element: {0}',
|
||||
startingTag(node));
|
||||
}
|
||||
|
||||
if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
|
||||
throw $compileMinErr('nodomevents',
|
||||
'Interpolations for HTML DOM event attributes are disallowed. Please use the ' +
|
||||
'ng- versions (such as ng-click instead of onclick) instead.');
|
||||
throw $compileMinErr('nodomevents', 'Interpolations for HTML DOM event attributes are disallowed');
|
||||
}
|
||||
|
||||
directives.push({
|
||||
|
||||
@@ -408,7 +408,7 @@ forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
|
||||
// ng-src, ng-srcset, ng-href are interpolated
|
||||
forEach(['src', 'srcset', 'href'], function(attrName) {
|
||||
var normalized = directiveNormalize('ng-' + attrName);
|
||||
ngAttributeAliasDirectives[normalized] = function() {
|
||||
ngAttributeAliasDirectives[normalized] = ['$sce', function($sce) {
|
||||
return {
|
||||
priority: 99, // it needs to run after the attributes are interpolated
|
||||
link: function(scope, element, attr) {
|
||||
@@ -422,6 +422,10 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
|
||||
propName = null;
|
||||
}
|
||||
|
||||
// We need to sanitize the url at least once, in case it is a constant
|
||||
// non-interpolated attribute.
|
||||
attr.$set(normalized, $sce.getTrustedMediaUrl(attr[normalized]));
|
||||
|
||||
attr.$observe(normalized, function(value) {
|
||||
if (!value) {
|
||||
if (attrName === 'href') {
|
||||
@@ -441,5 +445,5 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
}];
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
var nullFormCtrl = {
|
||||
$addControl: noop,
|
||||
$getControls: valueFn([]),
|
||||
$$renameControl: nullFormRenameControl,
|
||||
$removeControl: noop,
|
||||
$setValidity: noop,
|
||||
@@ -159,6 +160,30 @@ FormController.prototype = {
|
||||
control.$$parentForm = this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name form.FormController#$getControls
|
||||
* @returns {Array} the controls that are currently part of this form
|
||||
*
|
||||
* @description
|
||||
* This method returns a **shallow copy** of the controls that are currently part of this form.
|
||||
* The controls can be instances of {@link form.FormController `FormController`}
|
||||
* ({@link ngForm "child-forms"}) and of {@link ngModel.NgModelController `NgModelController`}.
|
||||
* If you need access to the controls of child-forms, you have to call `$getControls()`
|
||||
* recursively on them.
|
||||
* This can be used for example to iterate over all controls to validate them.
|
||||
*
|
||||
* The controls can be accessed normally, but adding to, or removing controls from the array has
|
||||
* no effect on the form. Instead, use {@link form.FormController#$addControl `$addControl()`} and
|
||||
* {@link form.FormController#$removeControl `$removeControl()`} for this use-case.
|
||||
* Likewise, adding a control to, or removing a control from the form is not reflected
|
||||
* in the shallow copy. That means you should get a fresh copy from `$getControls()` every time
|
||||
* you need access to the controls.
|
||||
*/
|
||||
$getControls: function() {
|
||||
return shallowCopy(this.$$controls);
|
||||
},
|
||||
|
||||
// Private API: rename a form control
|
||||
$$renameControl: function(control, newName) {
|
||||
var oldName = control.$name;
|
||||
|
||||
+144
-31
@@ -255,6 +255,10 @@ var inputType = {
|
||||
* The timezone to be used to read/write the `Date` instance in the model can be defined using
|
||||
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
|
||||
*
|
||||
* The format of the displayed time can be adjusted with the
|
||||
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
|
||||
* and `timeStripZeroSeconds`.
|
||||
*
|
||||
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
||||
@@ -356,7 +360,12 @@ var inputType = {
|
||||
* Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
|
||||
*
|
||||
* The timezone to be used to read/write the `Date` instance in the model can be defined using
|
||||
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
|
||||
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions}. By default,
|
||||
* this is the timezone of the browser.
|
||||
*
|
||||
* The format of the displayed time can be adjusted with the
|
||||
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
|
||||
* and `timeStripZeroSeconds`.
|
||||
*
|
||||
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
@@ -1488,9 +1497,11 @@ function createDateParser(regexp, mapping) {
|
||||
}
|
||||
|
||||
function createDateInputType(type, regexp, parseDate, format) {
|
||||
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
|
||||
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
|
||||
badInputChecker(scope, element, attr, ctrl, type);
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
|
||||
var isTimeType = type === 'time' || type === 'datetimelocal';
|
||||
var previousDate;
|
||||
var previousTimezone;
|
||||
|
||||
@@ -1514,11 +1525,13 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
if (isValidDate(value)) {
|
||||
previousDate = value;
|
||||
var timezone = ctrl.$options.getOption('timezone');
|
||||
|
||||
if (timezone) {
|
||||
previousTimezone = timezone;
|
||||
previousDate = convertTimezoneToLocal(previousDate, timezone, true);
|
||||
}
|
||||
return $filter('date')(value, format, timezone);
|
||||
|
||||
return formatter(value, timezone);
|
||||
} else {
|
||||
previousDate = null;
|
||||
previousTimezone = null;
|
||||
@@ -1527,24 +1540,34 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
});
|
||||
|
||||
if (isDefined(attr.min) || attr.ngMin) {
|
||||
var minVal;
|
||||
var minVal = attr.min || $parse(attr.ngMin)(scope);
|
||||
var parsedMinVal = parseObservedDateValue(minVal);
|
||||
|
||||
ctrl.$validators.min = function(value) {
|
||||
return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
|
||||
return !isValidDate(value) || isUndefined(parsedMinVal) || parseDate(value) >= parsedMinVal;
|
||||
};
|
||||
attr.$observe('min', function(val) {
|
||||
minVal = parseObservedDateValue(val);
|
||||
ctrl.$validate();
|
||||
if (val !== minVal) {
|
||||
parsedMinVal = parseObservedDateValue(val);
|
||||
minVal = val;
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(attr.max) || attr.ngMax) {
|
||||
var maxVal;
|
||||
var maxVal = attr.max || $parse(attr.ngMax)(scope);
|
||||
var parsedMaxVal = parseObservedDateValue(maxVal);
|
||||
|
||||
ctrl.$validators.max = function(value) {
|
||||
return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
|
||||
return !isValidDate(value) || isUndefined(parsedMaxVal) || parseDate(value) <= parsedMaxVal;
|
||||
};
|
||||
attr.$observe('max', function(val) {
|
||||
maxVal = parseObservedDateValue(val);
|
||||
ctrl.$validate();
|
||||
if (val !== maxVal) {
|
||||
parsedMaxVal = parseObservedDateValue(val);
|
||||
maxVal = val;
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1573,6 +1596,24 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
}
|
||||
return parsedDate;
|
||||
}
|
||||
|
||||
function formatter(value, timezone) {
|
||||
var targetFormat = format;
|
||||
|
||||
if (isTimeType && isString(ctrl.$options.getOption('timeSecondsFormat'))) {
|
||||
targetFormat = format
|
||||
.replace('ss.sss', ctrl.$options.getOption('timeSecondsFormat'))
|
||||
.replace(/:$/, '');
|
||||
}
|
||||
|
||||
var formatted = $filter('date')(value, targetFormat, timezone);
|
||||
|
||||
if (isTimeType && ctrl.$options.getOption('timeStripZeroSeconds')) {
|
||||
formatted = formatted.replace(/(?::00)?(?:\.000)?$/, '');
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1678,50 +1719,68 @@ function isValidForStep(viewValue, stepBase, step) {
|
||||
return (value - stepBase) % step === 0;
|
||||
}
|
||||
|
||||
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
|
||||
badInputChecker(scope, element, attr, ctrl, 'number');
|
||||
numberFormatterParser(ctrl);
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
|
||||
var minVal;
|
||||
var maxVal;
|
||||
var parsedMinVal;
|
||||
|
||||
if (isDefined(attr.min) || attr.ngMin) {
|
||||
var minVal = attr.min || $parse(attr.ngMin)(scope);
|
||||
parsedMinVal = parseNumberAttrVal(minVal);
|
||||
|
||||
ctrl.$validators.min = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(parsedMinVal) || viewValue >= parsedMinVal;
|
||||
};
|
||||
|
||||
attr.$observe('min', function(val) {
|
||||
minVal = parseNumberAttrVal(val);
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
if (val !== minVal) {
|
||||
parsedMinVal = parseNumberAttrVal(val);
|
||||
minVal = val;
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(attr.max) || attr.ngMax) {
|
||||
var maxVal = attr.max || $parse(attr.ngMax)(scope);
|
||||
var parsedMaxVal = parseNumberAttrVal(maxVal);
|
||||
|
||||
ctrl.$validators.max = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(parsedMaxVal) || viewValue <= parsedMaxVal;
|
||||
};
|
||||
|
||||
attr.$observe('max', function(val) {
|
||||
maxVal = parseNumberAttrVal(val);
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
if (val !== maxVal) {
|
||||
parsedMaxVal = parseNumberAttrVal(val);
|
||||
maxVal = val;
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(attr.step) || attr.ngStep) {
|
||||
var stepVal;
|
||||
var stepVal = attr.step || $parse(attr.ngStep)(scope);
|
||||
var parsedStepVal = parseNumberAttrVal(stepVal);
|
||||
|
||||
ctrl.$validators.step = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) ||
|
||||
isValidForStep(viewValue, minVal || 0, stepVal);
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(parsedStepVal) ||
|
||||
isValidForStep(viewValue, parsedMinVal || 0, parsedStepVal);
|
||||
};
|
||||
|
||||
attr.$observe('step', function(val) {
|
||||
stepVal = parseNumberAttrVal(val);
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
if (val !== stepVal) {
|
||||
parsedStepVal = parseNumberAttrVal(val);
|
||||
stepVal = val;
|
||||
ctrl.$validate();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1751,6 +1810,8 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
originalRender;
|
||||
|
||||
if (hasMinAttr) {
|
||||
minVal = parseNumberAttrVal(attr.min);
|
||||
|
||||
ctrl.$validators.min = supportsRange ?
|
||||
// Since all browsers set the input to a valid value, we don't need to check validity
|
||||
function noopMinValidator() { return true; } :
|
||||
@@ -1763,6 +1824,8 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
if (hasMaxAttr) {
|
||||
maxVal = parseNumberAttrVal(attr.max);
|
||||
|
||||
ctrl.$validators.max = supportsRange ?
|
||||
// Since all browsers set the input to a valid value, we don't need to check validity
|
||||
function noopMaxValidator() { return true; } :
|
||||
@@ -1775,6 +1838,8 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
if (hasStepAttr) {
|
||||
stepVal = parseNumberAttrVal(attr.step);
|
||||
|
||||
ctrl.$validators.step = supportsRange ?
|
||||
function nativeStepValidator() {
|
||||
// Currently, only FF implements the spec on step change correctly (i.e. adjusting the
|
||||
@@ -1796,7 +1861,13 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
// attribute value when the input is first rendered, so that the browser can adjust the
|
||||
// input value based on the min/max value
|
||||
element.attr(htmlAttrName, attr[htmlAttrName]);
|
||||
attr.$observe(htmlAttrName, changeFn);
|
||||
var oldVal = attr[htmlAttrName];
|
||||
attr.$observe(htmlAttrName, function wrappedObserver(val) {
|
||||
if (val !== oldVal) {
|
||||
oldVal = val;
|
||||
changeFn(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function minChange(val) {
|
||||
@@ -1850,11 +1921,11 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
// Some browsers don't adjust the input value correctly, but set the stepMismatch error
|
||||
if (supportsRange && ctrl.$viewValue !== element.val()) {
|
||||
ctrl.$setViewValue(element.val());
|
||||
} else {
|
||||
if (!supportsRange) {
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
} else if (ctrl.$viewValue !== element.val()) {
|
||||
ctrl.$setViewValue(element.val());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2162,6 +2233,48 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
|
||||
}];
|
||||
|
||||
|
||||
var hiddenInputBrowserCacheDirective = function() {
|
||||
var valueProperty = {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
get: function() {
|
||||
return this.getAttribute('value') || '';
|
||||
},
|
||||
set: function(val) {
|
||||
this.setAttribute('value', val);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
priority: 200,
|
||||
compile: function(_, attr) {
|
||||
if (lowercase(attr.type) !== 'hidden') {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
pre: function(scope, element, attr, ctrls) {
|
||||
var node = element[0];
|
||||
|
||||
// Support: Edge
|
||||
// Moving the DOM around prevents autofillling
|
||||
if (node.parentNode) {
|
||||
node.parentNode.insertBefore(node, node.nextSibling);
|
||||
}
|
||||
|
||||
// Support: FF, IE
|
||||
// Avoiding direct assignment to .value prevents autofillling
|
||||
if (Object.defineProperty) {
|
||||
Object.defineProperty(node, 'value', valueProperty);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
|
||||
/**
|
||||
|
||||
@@ -125,6 +125,8 @@ function classDirective(name, selector) {
|
||||
}
|
||||
|
||||
function toClassString(classValue) {
|
||||
if (!classValue) return classValue;
|
||||
|
||||
var classString = classValue;
|
||||
|
||||
if (isArray(classValue)) {
|
||||
@@ -133,6 +135,8 @@ function classDirective(name, selector) {
|
||||
classString = Object.keys(classValue).
|
||||
filter(function(key) { return classValue[key]; }).
|
||||
join(' ');
|
||||
} else if (!isString(classValue)) {
|
||||
classString = classValue + '';
|
||||
}
|
||||
|
||||
return classString;
|
||||
@@ -178,6 +182,7 @@ function classDirective(name, selector) {
|
||||
* |----------------------------------|-------------------------------------|
|
||||
* | {@link ng.$animate#addClass addClass} | just before the class is applied to the element |
|
||||
* | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
|
||||
* | {@link ng.$animate#setClass setClass} | just before classes are added and classes are removed from the element at the same time |
|
||||
*
|
||||
* ### ngClass and pre-existing CSS3 Transitions/Animations
|
||||
The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
|
||||
|
||||
@@ -50,33 +50,44 @@ forEach(
|
||||
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
|
||||
function(eventName) {
|
||||
var directiveName = directiveNormalize('ng-' + eventName);
|
||||
ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function($element, attr) {
|
||||
// NOTE:
|
||||
// We expose the powerful `$event` object on the scope that provides access to the Window,
|
||||
// etc. This is OK, because expressions are not sandboxed any more (and the expression
|
||||
// sandbox was never meant to be a security feature anyway).
|
||||
var fn = $parse(attr[directiveName]);
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(eventName, function(event) {
|
||||
var callback = function() {
|
||||
fn(scope, {$event: event});
|
||||
};
|
||||
if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
|
||||
scope.$evalAsync(callback);
|
||||
} else {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
ngEventDirectives[directiveName] = ['$parse', '$rootScope', '$exceptionHandler', function($parse, $rootScope, $exceptionHandler) {
|
||||
return createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsyncEvents[eventName]);
|
||||
}];
|
||||
}
|
||||
);
|
||||
|
||||
function createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsync) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function($element, attr) {
|
||||
// NOTE:
|
||||
// We expose the powerful `$event` object on the scope that provides access to the Window,
|
||||
// etc. This is OK, because expressions are not sandboxed any more (and the expression
|
||||
// sandbox was never meant to be a security feature anyway).
|
||||
var fn = $parse(attr[directiveName]);
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(eventName, function(event) {
|
||||
var callback = function() {
|
||||
fn(scope, {$event: event});
|
||||
};
|
||||
|
||||
if (!$rootScope.$$phase) {
|
||||
scope.$apply(callback);
|
||||
} else if (forceAsync) {
|
||||
scope.$evalAsync(callback);
|
||||
} else {
|
||||
try {
|
||||
callback();
|
||||
} catch (error) {
|
||||
$exceptionHandler(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngDblclick
|
||||
|
||||
@@ -287,6 +287,7 @@ function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $
|
||||
this.$$currentValidationRunId = 0;
|
||||
|
||||
this.$$scope = $scope;
|
||||
this.$$rootScope = $scope.$root;
|
||||
this.$$attr = $attr;
|
||||
this.$$element = $element;
|
||||
this.$$animate = $animate;
|
||||
@@ -561,6 +562,7 @@ NgModelController.prototype = {
|
||||
* `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
|
||||
*/
|
||||
$validate: function() {
|
||||
|
||||
// ignore $validate before model is initialized
|
||||
if (isNumberNaN(this.$modelValue)) {
|
||||
return;
|
||||
@@ -864,7 +866,7 @@ NgModelController.prototype = {
|
||||
this.$$pendingDebounce = this.$$timeout(function() {
|
||||
that.$commitViewValue();
|
||||
}, debounceDelay);
|
||||
} else if (this.$$scope.$root.$$phase) {
|
||||
} else if (this.$$rootScope.$$phase) {
|
||||
this.$commitViewValue();
|
||||
} else {
|
||||
this.$$scope.$apply(function() {
|
||||
|
||||
@@ -41,7 +41,7 @@ ModelOptions.prototype = {
|
||||
options = extend({}, options);
|
||||
|
||||
// Inherit options from the parent if specified by the value `"$inherit"`
|
||||
forEach(options, /* @this */ function(option, key) {
|
||||
forEach(options, /** @this */ function(option, key) {
|
||||
if (option === '$inherit') {
|
||||
if (key === '*') {
|
||||
inheritAll = true;
|
||||
@@ -406,12 +406,6 @@ defaultModelOptions = new ModelOptions({
|
||||
* </example>
|
||||
*
|
||||
*
|
||||
* ## Specifying timezones
|
||||
*
|
||||
* You can specify the timezone that date/time input directives expect by providing its name in the
|
||||
* `timezone` property.
|
||||
*
|
||||
*
|
||||
* ## Programmatically changing options
|
||||
*
|
||||
* The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not
|
||||
@@ -423,8 +417,70 @@ defaultModelOptions = new ModelOptions({
|
||||
* Default events, extra triggers, and catch-all debounce values}.
|
||||
*
|
||||
*
|
||||
* ## Specifying timezones
|
||||
*
|
||||
* You can specify the timezone that date/time input directives expect by providing its name in the
|
||||
* `timezone` property.
|
||||
*
|
||||
*
|
||||
* ## Formatting the value of `time` and `datetime-local`
|
||||
*
|
||||
* With the options `timeSecondsFormat` and `timeStripZeroSeconds` it is possible to adjust the value
|
||||
* that is displayed in the control. Note that browsers may apply their own formatting
|
||||
* in the user interface.
|
||||
*
|
||||
<example name="ngModelOptions-time-format" module="timeExample">
|
||||
<file name="index.html">
|
||||
<time-example></time-example>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
angular.module('timeExample', [])
|
||||
.component('timeExample', {
|
||||
templateUrl: 'timeExample.html',
|
||||
controller: function() {
|
||||
this.time = new Date(1970, 0, 1, 14, 57, 0);
|
||||
|
||||
this.options = {
|
||||
timeSecondsFormat: 'ss',
|
||||
timeStripZeroSeconds: true
|
||||
};
|
||||
|
||||
this.optionChange = function() {
|
||||
this.timeForm.timeFormatted.$overrideModelOptions(this.options);
|
||||
this.time = new Date(this.time);
|
||||
};
|
||||
}
|
||||
});
|
||||
</file>
|
||||
<file name="timeExample.html">
|
||||
<form name="$ctrl.timeForm">
|
||||
<strong>Default</strong>:
|
||||
<input type="time" ng-model="$ctrl.time" step="any" /><br>
|
||||
<strong>With options</strong>:
|
||||
<input type="time" name="timeFormatted" ng-model="$ctrl.time" step="any" ng-model-options="$ctrl.options" />
|
||||
<br>
|
||||
|
||||
Options:<br>
|
||||
<code>timeSecondsFormat</code>:
|
||||
<input
|
||||
type="text"
|
||||
ng-model="$ctrl.options.timeSecondsFormat"
|
||||
ng-change="$ctrl.optionChange()">
|
||||
<br>
|
||||
<code>timeStripZeroSeconds</code>:
|
||||
<input
|
||||
type="checkbox"
|
||||
ng-model="$ctrl.options.timeStripZeroSeconds"
|
||||
ng-change="$ctrl.optionChange()">
|
||||
</form>
|
||||
</file>
|
||||
* </example>
|
||||
*
|
||||
* @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and
|
||||
* and its descendents. Valid keys are:
|
||||
* and its descendents.
|
||||
*
|
||||
* **General options**:
|
||||
*
|
||||
* - `updateOn`: string specifying which event should the input be bound to. You can set several
|
||||
* events using an space delimited list. There is a special event called `default` that
|
||||
* matches the default events belonging to the control. These are the events that are bound to
|
||||
@@ -457,6 +513,10 @@ defaultModelOptions = new ModelOptions({
|
||||
* not validate correctly instead of the default behavior of setting the model to undefined.
|
||||
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
|
||||
* `ngModel` as getters/setters.
|
||||
*
|
||||
*
|
||||
* **Input-type specific options**:
|
||||
*
|
||||
* - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
|
||||
* `<input type="date" />`, `<input type="time" />`, ... . It understands UTC/GMT and the
|
||||
* continental US time zone abbreviations, but for general use, use a time zone offset, for
|
||||
@@ -465,6 +525,24 @@ defaultModelOptions = new ModelOptions({
|
||||
* Note that changing the timezone will have no effect on the current date, and is only applied after
|
||||
* the next input / model change.
|
||||
*
|
||||
* - `timeSecondsFormat`: Defines if the `time` and `datetime-local` types should show seconds and
|
||||
* milliseconds. The option follows the format string of {@link date date filter}.
|
||||
* By default, the options is `undefined` which is equal to `'ss.sss'` (seconds and milliseconds).
|
||||
* The other options are `'ss'` (strips milliseconds), and `''` (empty string), which strips both
|
||||
* seconds and milliseconds.
|
||||
* Note that browsers that support `time` and `datetime-local` require the hour and minutes
|
||||
* part of the time string, and may show the value differently in the user interface.
|
||||
* {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}.
|
||||
*
|
||||
* - `timeStripZeroSeconds`: Defines if the `time` and `datetime-local` types should strip the
|
||||
* seconds and milliseconds from the formatted value if they are zero. This option is applied
|
||||
* after `timeSecondsFormat`.
|
||||
* This option can be used to make the formatting consistent over different browsers, as some
|
||||
* browsers with support for `time` will natively hide the milliseconds and
|
||||
* seconds if they are zero, but others won't, and browsers that don't implement these input
|
||||
* types will always show the full string.
|
||||
* {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}.
|
||||
*
|
||||
*/
|
||||
var ngModelOptionsDirective = function() {
|
||||
NgModelOptionsController.$inject = ['$attrs', '$scope'];
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngRef
|
||||
* @restrict A
|
||||
*
|
||||
* @description
|
||||
* The `ngRef` attribute tells AngularJS to assign the controller of a component (or a directive)
|
||||
* to the given property in the current scope. It is also possible to add the jqlite-wrapped DOM
|
||||
* element to the scope.
|
||||
*
|
||||
* If the element with `ngRef` is destroyed `null` is assigned to the property.
|
||||
*
|
||||
* Note that if you want to assign from a child into the parent scope, you must initialize the
|
||||
* target property on the parent scope, otherwise `ngRef` will assign on the child scope.
|
||||
* This commonly happens when assigning elements or components wrapped in {@link ngIf} or
|
||||
* {@link ngRepeat}. See the second example below.
|
||||
*
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string} ngRef property name - A valid AngularJS expression identifier to which the
|
||||
* controller or jqlite-wrapped DOM element will be bound.
|
||||
* @param {string=} ngRefRead read value - The name of a directive (or component) on this element,
|
||||
* or the special string `$element`. If a name is provided, `ngRef` will
|
||||
* assign the matching controller. If `$element` is provided, the element
|
||||
* itself is assigned (even if a controller is available).
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ### Simple toggle
|
||||
* This example shows how the controller of the component toggle
|
||||
* is reused in the template through the scope to use its logic.
|
||||
* <example name="ng-ref-component" module="myApp">
|
||||
* <file name="index.html">
|
||||
* <my-toggle ng-ref="myToggle"></my-toggle>
|
||||
* <button ng-click="myToggle.toggle()">Toggle</button>
|
||||
* <div ng-show="myToggle.isOpen()">
|
||||
* You are using a component in the same template to show it.
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="index.js">
|
||||
* angular.module('myApp', [])
|
||||
* .component('myToggle', {
|
||||
* controller: function ToggleController() {
|
||||
* var opened = false;
|
||||
* this.isOpen = function() { return opened; };
|
||||
* this.toggle = function() { opened = !opened; };
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it('should publish the toggle into the scope', function() {
|
||||
* var toggle = element(by.buttonText('Toggle'));
|
||||
* expect(toggle.evaluate('myToggle.isOpen()')).toEqual(false);
|
||||
* toggle.click();
|
||||
* expect(toggle.evaluate('myToggle.isOpen()')).toEqual(true);
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @example
|
||||
* ### ngRef inside scopes
|
||||
* This example shows how `ngRef` works with child scopes. The `ngRepeat`-ed `myWrapper` components
|
||||
* are assigned to the scope of `myRoot`, because the `toggles` property has been initialized.
|
||||
* The repeated `myToggle` components are published to the child scopes created by `ngRepeat`.
|
||||
* `ngIf` behaves similarly - the assignment of `myToggle` happens in the `ngIf` child scope,
|
||||
* because the target property has not been initialized on the `myRoot` component controller.
|
||||
*
|
||||
* <example name="ng-ref-scopes" module="myApp">
|
||||
* <file name="index.html">
|
||||
* <my-root></my-root>
|
||||
* </file>
|
||||
* <file name="index.js">
|
||||
* angular.module('myApp', [])
|
||||
* .component('myRoot', {
|
||||
* templateUrl: 'root.html',
|
||||
* controller: function() {
|
||||
* this.wrappers = []; // initialize the array so that the wrappers are assigned into the parent scope
|
||||
* }
|
||||
* })
|
||||
* .component('myToggle', {
|
||||
* template: '<strong>myToggle</strong><button ng-click="$ctrl.toggle()" ng-transclude></button>',
|
||||
* transclude: true,
|
||||
* controller: function ToggleController() {
|
||||
* var opened = false;
|
||||
* this.isOpen = function() { return opened; };
|
||||
* this.toggle = function() { opened = !opened; };
|
||||
* }
|
||||
* })
|
||||
* .component('myWrapper', {
|
||||
* transclude: true,
|
||||
* template: '<strong>myWrapper</strong>' +
|
||||
* '<div>ngRepeatToggle.isOpen(): {{$ctrl.ngRepeatToggle.isOpen() | json}}</div>' +
|
||||
* '<my-toggle ng-ref="$ctrl.ngRepeatToggle"><ng-transclude></ng-transclude></my-toggle>'
|
||||
* });
|
||||
* </file>
|
||||
* <file name="root.html">
|
||||
* <strong>myRoot</strong>
|
||||
* <my-toggle ng-ref="$ctrl.outerToggle">Outer Toggle</my-toggle>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* <div><em>wrappers assigned to root</em><br>
|
||||
* <div ng-repeat="wrapper in $ctrl.wrappers">
|
||||
* wrapper.ngRepeatToggle.isOpen(): {{wrapper.ngRepeatToggle.isOpen() | json}}
|
||||
* </div>
|
||||
*
|
||||
* <ul>
|
||||
* <li ng-repeat="(index, value) in [1,2,3]">
|
||||
* <strong>ngRepeat</strong>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* <my-wrapper ng-ref="$ctrl.wrappers[index]">ngRepeat Toggle {{$index + 1}}</my-wrapper>
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen()}} // This is always undefined because it's
|
||||
* assigned to the child scope created by ngIf.
|
||||
* </div>
|
||||
* <div ng-if="true">
|
||||
<strong>ngIf</strong>
|
||||
* <my-toggle ng-ref="ngIfToggle">ngIf Toggle</my-toggle>
|
||||
* <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen() | json}}</div>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="styles.css">
|
||||
* ul {
|
||||
* list-style: none;
|
||||
* padding-left: 0;
|
||||
* }
|
||||
*
|
||||
* li[ng-repeat] {
|
||||
* background: lightgreen;
|
||||
* padding: 8px;
|
||||
* margin: 8px;
|
||||
* }
|
||||
*
|
||||
* [ng-if] {
|
||||
* background: lightgrey;
|
||||
* padding: 8px;
|
||||
* }
|
||||
*
|
||||
* my-root {
|
||||
* background: lightgoldenrodyellow;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
*
|
||||
* my-wrapper {
|
||||
* background: lightsalmon;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
*
|
||||
* my-toggle {
|
||||
* background: lightblue;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* var OuterToggle = function() {
|
||||
* this.toggle = function() {
|
||||
* element(by.buttonText('Outer Toggle')).click();
|
||||
* };
|
||||
* this.isOpen = function() {
|
||||
* return element.all(by.binding('outerToggle.isOpen()')).first().getText();
|
||||
* };
|
||||
* };
|
||||
* var NgRepeatToggle = function(i) {
|
||||
* var parent = element.all(by.repeater('(index, value) in [1,2,3]')).get(i - 1);
|
||||
* this.toggle = function() {
|
||||
* element(by.buttonText('ngRepeat Toggle ' + i)).click();
|
||||
* };
|
||||
* this.isOpen = function() {
|
||||
* return parent.element(by.binding('ngRepeatToggle.isOpen() | json')).getText();
|
||||
* };
|
||||
* this.isOuterOpen = function() {
|
||||
* return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
|
||||
* };
|
||||
* };
|
||||
* var NgRepeatToggles = function() {
|
||||
* var toggles = [1,2,3].map(function(i) { return new NgRepeatToggle(i); });
|
||||
* this.forEach = function(fn) {
|
||||
* toggles.forEach(fn);
|
||||
* };
|
||||
* this.isOuterOpen = function(i) {
|
||||
* return toggles[i - 1].isOuterOpen();
|
||||
* };
|
||||
* };
|
||||
* var NgIfToggle = function() {
|
||||
* var parent = element(by.css('[ng-if]'));
|
||||
* this.toggle = function() {
|
||||
* element(by.buttonText('ngIf Toggle')).click();
|
||||
* };
|
||||
* this.isOpen = function() {
|
||||
* return by.binding('ngIfToggle.isOpen() | json').getText();
|
||||
* };
|
||||
* this.isOuterOpen = function() {
|
||||
* return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* it('should toggle the outer toggle', function() {
|
||||
* var outerToggle = new OuterToggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* outerToggle.toggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
|
||||
* });
|
||||
*
|
||||
* it('should toggle all outer toggles', function() {
|
||||
* var outerToggle = new OuterToggle();
|
||||
* var repeatToggles = new NgRepeatToggles();
|
||||
* var ifToggle = new NgIfToggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* outerToggle.toggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): true');
|
||||
* });
|
||||
*
|
||||
* it('should toggle each repeat iteration separately', function() {
|
||||
* var repeatToggles = new NgRepeatToggles();
|
||||
*
|
||||
* repeatToggles.forEach(function(repeatToggle) {
|
||||
* expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): false');
|
||||
* expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* repeatToggle.toggle();
|
||||
* expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): true');
|
||||
* expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* });
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
*/
|
||||
|
||||
var ngRefMinErr = minErr('ngRef');
|
||||
|
||||
var ngRefDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
priority: -1, // Needed for compatibility with element transclusion on the same element
|
||||
restrict: 'A',
|
||||
compile: function(tElement, tAttrs) {
|
||||
// Get the expected controller name, converts <data-some-thing> into "someThing"
|
||||
var controllerName = directiveNormalize(nodeName_(tElement));
|
||||
|
||||
// Get the expression for value binding
|
||||
var getter = $parse(tAttrs.ngRef);
|
||||
var setter = getter.assign || function() {
|
||||
throw ngRefMinErr('nonassign', 'Expression in ngRef="{0}" is non-assignable!', tAttrs.ngRef);
|
||||
};
|
||||
|
||||
return function(scope, element, attrs) {
|
||||
var refValue;
|
||||
|
||||
if (attrs.hasOwnProperty('ngRefRead')) {
|
||||
if (attrs.ngRefRead === '$element') {
|
||||
refValue = element;
|
||||
} else {
|
||||
refValue = element.data('$' + attrs.ngRefRead + 'Controller');
|
||||
|
||||
if (!refValue) {
|
||||
throw ngRefMinErr(
|
||||
'noctrl',
|
||||
'The controller for ngRefRead="{0}" could not be found on ngRef="{1}"',
|
||||
attrs.ngRefRead,
|
||||
tAttrs.ngRef
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refValue = element.data('$' + controllerName + 'Controller');
|
||||
}
|
||||
|
||||
refValue = refValue || element;
|
||||
|
||||
setter(scope, refValue);
|
||||
|
||||
// when the element is removed, remove it (nullify it)
|
||||
element.on('$destroy', function() {
|
||||
// only remove it if value has not changed,
|
||||
// because animations (and other procedures) may duplicate elements
|
||||
if (getter(scope) === refValue) {
|
||||
setter(scope, null);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
@@ -74,7 +74,7 @@
|
||||
* For example, if an item is added to the collection, `ngRepeat` will know that all other items
|
||||
* already have DOM elements, and will not re-render them.
|
||||
*
|
||||
* All different types of tracking functions, their syntax, and and their support for duplicate
|
||||
* All different types of tracking functions, their syntax, and their support for duplicate
|
||||
* items in collections can be found in the
|
||||
* {@link ngRepeat#ngRepeat-arguments ngRepeat expression description}.
|
||||
*
|
||||
@@ -454,6 +454,13 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
|
||||
return block.clone[block.clone.length - 1];
|
||||
};
|
||||
|
||||
var trackByIdArrayFn = function($scope, key, value) {
|
||||
return hashKey(value);
|
||||
};
|
||||
|
||||
var trackByIdObjFn = function($scope, key) {
|
||||
return key;
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
@@ -493,32 +500,23 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
|
||||
aliasAs);
|
||||
}
|
||||
|
||||
var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
|
||||
var hashFnLocals = {$id: hashKey};
|
||||
var trackByIdExpFn;
|
||||
|
||||
if (trackByExp) {
|
||||
trackByExpGetter = $parse(trackByExp);
|
||||
} else {
|
||||
trackByIdArrayFn = function(key, value) {
|
||||
return hashKey(value);
|
||||
};
|
||||
trackByIdObjFn = function(key) {
|
||||
return key;
|
||||
var hashFnLocals = {$id: hashKey};
|
||||
var trackByExpGetter = $parse(trackByExp);
|
||||
|
||||
trackByIdExpFn = function($scope, key, value, index) {
|
||||
// assign key, value, and $index to the locals so that they can be used in hash functions
|
||||
if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
|
||||
hashFnLocals[valueIdentifier] = value;
|
||||
hashFnLocals.$index = index;
|
||||
return trackByExpGetter($scope, hashFnLocals);
|
||||
};
|
||||
}
|
||||
|
||||
return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
|
||||
|
||||
if (trackByExpGetter) {
|
||||
trackByIdExpFn = function(key, value, index) {
|
||||
// assign key, value, and $index to the locals so that they can be used in hash functions
|
||||
if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
|
||||
hashFnLocals[valueIdentifier] = value;
|
||||
hashFnLocals.$index = index;
|
||||
return trackByExpGetter($scope, hashFnLocals);
|
||||
};
|
||||
}
|
||||
|
||||
// Store a list of elements from previous run. This is a hash where key is the item from the
|
||||
// iterator, and the value is objects with following properties.
|
||||
// - scope: bound scope
|
||||
@@ -572,7 +570,7 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
|
||||
for (index = 0; index < collectionLength; index++) {
|
||||
key = (collection === collectionKeys) ? index : collectionKeys[index];
|
||||
value = collection[key];
|
||||
trackById = trackByIdFn(key, value, index);
|
||||
trackById = trackByIdFn($scope, key, value, index);
|
||||
if (lastBlockMap[trackById]) {
|
||||
// found previously seen block
|
||||
block = lastBlockMap[trackById];
|
||||
@@ -594,6 +592,12 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the value property from the hashFnLocals object to prevent a reference to the last value
|
||||
// being leaked into the ngRepeatCompile function scope
|
||||
if (hashFnLocals) {
|
||||
hashFnLocals[valueIdentifier] = undefined;
|
||||
}
|
||||
|
||||
// remove leftover items
|
||||
for (var blockKey in lastBlockMap) {
|
||||
block = lastBlockMap[blockKey];
|
||||
|
||||
@@ -54,7 +54,14 @@
|
||||
var ngStyleDirective = ngDirective(function(scope, element, attr) {
|
||||
scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
if (oldStyles && (newStyles !== oldStyles)) {
|
||||
forEach(oldStyles, function(val, style) { element.css(style, '');});
|
||||
if (!newStyles) {
|
||||
newStyles = {};
|
||||
}
|
||||
forEach(oldStyles, function(val, style) {
|
||||
if (newStyles[style] == null) {
|
||||
newStyles[style] = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
if (newStyles) element.css(newStyles);
|
||||
});
|
||||
|
||||
@@ -383,7 +383,7 @@ var SelectController =
|
||||
|
||||
if (optionAttrs.$attr.ngValue) {
|
||||
// The value attribute is set by ngValue
|
||||
var oldVal, hashedVal = NaN;
|
||||
var oldVal, hashedVal;
|
||||
optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
|
||||
|
||||
var removal;
|
||||
@@ -556,18 +556,6 @@ var SelectController =
|
||||
* {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive.
|
||||
*
|
||||
*
|
||||
* @knownIssue
|
||||
*
|
||||
* In Firefox, the select model is only updated when the select element is blurred. For example,
|
||||
* when switching between options with the keyboard, the select model is only set to the
|
||||
* currently selected option when the select is blurred, e.g via tab key or clicking the mouse
|
||||
* outside the select.
|
||||
*
|
||||
* This is due to an ambiguity in the select element specification. See the
|
||||
* [issue on the Firefox bug tracker](https://bugzilla.mozilla.org/show_bug.cgi?id=126379)
|
||||
* for more information, and this
|
||||
* [Github comment for a workaround](https://github.com/angular/angular.js/issues/9134#issuecomment-130800488)
|
||||
*
|
||||
* @example
|
||||
* ### Simple `select` elements with static options
|
||||
*
|
||||
|
||||
@@ -62,24 +62,29 @@
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var requiredDirective = function() {
|
||||
var requiredDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
var oldVal = attr.required || $parse(attr.ngRequired)(scope);
|
||||
|
||||
attr.required = true; // force truthy in case we are on non input element
|
||||
|
||||
ctrl.$validators.required = function(modelValue, viewValue) {
|
||||
return !attr.required || !ctrl.$isEmpty(viewValue);
|
||||
};
|
||||
|
||||
attr.$observe('required', function() {
|
||||
ctrl.$validate();
|
||||
attr.$observe('required', function(val) {
|
||||
if (oldVal !== val) {
|
||||
oldVal = val;
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
@@ -162,36 +167,59 @@ var requiredDirective = function() {
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var patternDirective = function() {
|
||||
var patternDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
compile: function(tElm, tAttr) {
|
||||
var patternExp;
|
||||
var parseFn;
|
||||
|
||||
var regexp, patternExp = attr.ngPattern || attr.pattern;
|
||||
attr.$observe('pattern', function(regex) {
|
||||
if (isString(regex) && regex.length > 0) {
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
if (tAttr.ngPattern) {
|
||||
patternExp = tAttr.ngPattern;
|
||||
|
||||
// ngPattern might be a scope expression, or an inlined regex, which is not parsable.
|
||||
// We get value of the attribute here, so we can compare the old and the new value
|
||||
// in the observer to avoid unnecessary validations
|
||||
if (tAttr.ngPattern.charAt(0) === '/' && REGEX_STRING_REGEXP.test(tAttr.ngPattern)) {
|
||||
parseFn = function() { return tAttr.ngPattern; };
|
||||
} else {
|
||||
parseFn = $parse(tAttr.ngPattern);
|
||||
}
|
||||
}
|
||||
|
||||
return function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var attrVal = attr.pattern;
|
||||
|
||||
if (attr.ngPattern) {
|
||||
attrVal = parseFn(scope);
|
||||
} else {
|
||||
patternExp = attr.pattern;
|
||||
}
|
||||
|
||||
if (regex && !regex.test) {
|
||||
throw minErr('ngPattern')('noregexp',
|
||||
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
|
||||
regex, startingTag(elm));
|
||||
}
|
||||
var regexp = parsePatternAttr(attrVal, patternExp, elm);
|
||||
|
||||
regexp = regex || undefined;
|
||||
ctrl.$validate();
|
||||
});
|
||||
attr.$observe('pattern', function(newVal) {
|
||||
var oldRegexp = regexp;
|
||||
|
||||
ctrl.$validators.pattern = function(modelValue, viewValue) {
|
||||
// HTML5 pattern constraint validates the input value, so we validate the viewValue
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
|
||||
regexp = parsePatternAttr(newVal, patternExp, elm);
|
||||
|
||||
if ((oldRegexp && oldRegexp.toString()) !== (regexp && regexp.toString())) {
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
|
||||
ctrl.$validators.pattern = function(modelValue, viewValue) {
|
||||
// HTML5 pattern constraint validates the input value, so we validate the viewValue
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
@@ -264,25 +292,29 @@ var patternDirective = function() {
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var maxlengthDirective = function() {
|
||||
var maxlengthDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var maxlength = -1;
|
||||
var maxlength = attr.maxlength || $parse(attr.ngMaxlength)(scope);
|
||||
var maxlengthParsed = parseLength(maxlength);
|
||||
|
||||
attr.$observe('maxlength', function(value) {
|
||||
var intVal = toInt(value);
|
||||
maxlength = isNumberNaN(intVal) ? -1 : intVal;
|
||||
ctrl.$validate();
|
||||
if (maxlength !== value) {
|
||||
maxlengthParsed = parseLength(value);
|
||||
maxlength = value;
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
ctrl.$validators.maxlength = function(modelValue, viewValue) {
|
||||
return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
|
||||
return (maxlengthParsed < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlengthParsed);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
@@ -353,21 +385,49 @@ var maxlengthDirective = function() {
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var minlengthDirective = function() {
|
||||
var minlengthDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var minlength = 0;
|
||||
var minlength = attr.minlength || $parse(attr.ngMinlength)(scope);
|
||||
var minlengthParsed = parseLength(minlength) || -1;
|
||||
|
||||
attr.$observe('minlength', function(value) {
|
||||
minlength = toInt(value) || 0;
|
||||
ctrl.$validate();
|
||||
if (minlength !== value) {
|
||||
minlengthParsed = parseLength(value) || -1;
|
||||
minlength = value;
|
||||
ctrl.$validate();
|
||||
}
|
||||
|
||||
});
|
||||
ctrl.$validators.minlength = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
|
||||
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlengthParsed;
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
|
||||
function parsePatternAttr(regex, patternExp, elm) {
|
||||
if (!regex) return undefined;
|
||||
|
||||
if (isString(regex)) {
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
}
|
||||
|
||||
if (!regex.test) {
|
||||
throw minErr('ngPattern')('noregexp',
|
||||
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
|
||||
regex, startingTag(elm));
|
||||
}
|
||||
|
||||
return regex;
|
||||
}
|
||||
|
||||
function parseLength(val) {
|
||||
var intVal = toInt(val);
|
||||
return isNumberNaN(intVal) ? -1 : intVal;
|
||||
}
|
||||
|
||||
+2
-2
@@ -1054,7 +1054,7 @@ function $HttpProvider() {
|
||||
config.paramSerializer = isString(config.paramSerializer) ?
|
||||
$injector.get(config.paramSerializer) : config.paramSerializer;
|
||||
|
||||
$browser.$$incOutstandingRequestCount();
|
||||
$browser.$$incOutstandingRequestCount('$http');
|
||||
|
||||
var requestInterceptors = [];
|
||||
var responseInterceptors = [];
|
||||
@@ -1092,7 +1092,7 @@ function $HttpProvider() {
|
||||
}
|
||||
|
||||
function completeOutstandingRequest() {
|
||||
$browser.$$completeOutstandingRequest(noop);
|
||||
$browser.$$completeOutstandingRequest(noop, '$http');
|
||||
}
|
||||
|
||||
function executeHeaderFns(headers, config) {
|
||||
|
||||
@@ -242,7 +242,7 @@ function $InterpolateProvider() {
|
||||
|
||||
// Provide a quick exit and simplified result function for text with no interpolation
|
||||
if (!text.length || text.indexOf(startSymbol) === -1) {
|
||||
if (mustHaveExpression && !contextAllowsConcatenation) return;
|
||||
if (mustHaveExpression) return;
|
||||
|
||||
var unescapedText = unescapeText(text);
|
||||
if (contextAllowsConcatenation) {
|
||||
|
||||
+13
-48
@@ -4,10 +4,18 @@ var $intervalMinErr = minErr('$interval');
|
||||
|
||||
/** @this */
|
||||
function $IntervalProvider() {
|
||||
this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser',
|
||||
function($rootScope, $window, $q, $$q, $browser) {
|
||||
this.$get = ['$$intervalFactory', '$window',
|
||||
function($$intervalFactory, $window) {
|
||||
var intervals = {};
|
||||
|
||||
var setIntervalFn = function(tick, delay, deferred) {
|
||||
var id = $window.setInterval(tick, delay);
|
||||
intervals[id] = deferred;
|
||||
return id;
|
||||
};
|
||||
var clearIntervalFn = function(id) {
|
||||
$window.clearInterval(id);
|
||||
delete intervals[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
@@ -135,49 +143,7 @@ function $IntervalProvider() {
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
function interval(fn, delay, count, invokeApply) {
|
||||
var hasParams = arguments.length > 4,
|
||||
args = hasParams ? sliceArgs(arguments, 4) : [],
|
||||
setInterval = $window.setInterval,
|
||||
clearInterval = $window.clearInterval,
|
||||
iteration = 0,
|
||||
skipApply = (isDefined(invokeApply) && !invokeApply),
|
||||
deferred = (skipApply ? $$q : $q).defer(),
|
||||
promise = deferred.promise;
|
||||
|
||||
count = isDefined(count) ? count : 0;
|
||||
|
||||
promise.$$intervalId = setInterval(function tick() {
|
||||
if (skipApply) {
|
||||
$browser.defer(callback);
|
||||
} else {
|
||||
$rootScope.$evalAsync(callback);
|
||||
}
|
||||
deferred.notify(iteration++);
|
||||
|
||||
if (count > 0 && iteration >= count) {
|
||||
deferred.resolve(iteration);
|
||||
clearInterval(promise.$$intervalId);
|
||||
delete intervals[promise.$$intervalId];
|
||||
}
|
||||
|
||||
if (!skipApply) $rootScope.$apply();
|
||||
|
||||
}, delay);
|
||||
|
||||
intervals[promise.$$intervalId] = deferred;
|
||||
|
||||
return promise;
|
||||
|
||||
function callback() {
|
||||
if (!hasParams) {
|
||||
fn(iteration);
|
||||
} else {
|
||||
fn.apply(null, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var interval = $$intervalFactory(setIntervalFn, clearIntervalFn);
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -205,8 +171,7 @@ function $IntervalProvider() {
|
||||
// Interval cancels should not report an unhandled promise.
|
||||
markQExceptionHandled(deferred.promise);
|
||||
deferred.reject('canceled');
|
||||
$window.clearInterval(id);
|
||||
delete intervals[id];
|
||||
clearIntervalFn(id);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
/** @this */
|
||||
function $$IntervalFactoryProvider() {
|
||||
this.$get = ['$browser', '$q', '$$q', '$rootScope',
|
||||
function($browser, $q, $$q, $rootScope) {
|
||||
return function intervalFactory(setIntervalFn, clearIntervalFn) {
|
||||
return function intervalFn(fn, delay, count, invokeApply) {
|
||||
var hasParams = arguments.length > 4,
|
||||
args = hasParams ? sliceArgs(arguments, 4) : [],
|
||||
iteration = 0,
|
||||
skipApply = isDefined(invokeApply) && !invokeApply,
|
||||
deferred = (skipApply ? $$q : $q).defer(),
|
||||
promise = deferred.promise;
|
||||
|
||||
count = isDefined(count) ? count : 0;
|
||||
|
||||
function callback() {
|
||||
if (!hasParams) {
|
||||
fn(iteration);
|
||||
} else {
|
||||
fn.apply(null, args);
|
||||
}
|
||||
}
|
||||
|
||||
function tick() {
|
||||
if (skipApply) {
|
||||
$browser.defer(callback);
|
||||
} else {
|
||||
$rootScope.$evalAsync(callback);
|
||||
}
|
||||
deferred.notify(iteration++);
|
||||
|
||||
if (count > 0 && iteration >= count) {
|
||||
deferred.resolve(iteration);
|
||||
clearIntervalFn(promise.$$intervalId);
|
||||
}
|
||||
|
||||
if (!skipApply) $rootScope.$apply();
|
||||
}
|
||||
|
||||
promise.$$intervalId = setIntervalFn(tick, delay, deferred, skipApply);
|
||||
|
||||
return promise;
|
||||
};
|
||||
};
|
||||
}];
|
||||
}
|
||||
+36
-44
@@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
/* global stripHash: true */
|
||||
|
||||
var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/,
|
||||
DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
|
||||
@@ -38,6 +39,14 @@ function decodePath(path, html5Mode) {
|
||||
return segments.join('/');
|
||||
}
|
||||
|
||||
function normalizePath(pathValue, searchValue, hashValue) {
|
||||
var search = toKeyValue(searchValue),
|
||||
hash = hashValue ? '#' + encodeUriSegment(hashValue) : '',
|
||||
path = encodePath(pathValue);
|
||||
|
||||
return path + (search ? '?' + search : '') + hash;
|
||||
}
|
||||
|
||||
function parseAbsoluteUrl(absoluteUrl, locationObj) {
|
||||
var parsedUrl = urlResolve(absoluteUrl);
|
||||
|
||||
@@ -86,17 +95,11 @@ function stripBaseUrl(base, url) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function stripHash(url) {
|
||||
var index = url.indexOf('#');
|
||||
return index === -1 ? url : url.substr(0, index);
|
||||
}
|
||||
|
||||
function trimEmptyHash(url) {
|
||||
return url.replace(/(#.+)|#$/, '$1');
|
||||
}
|
||||
|
||||
|
||||
function stripFile(url) {
|
||||
return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
|
||||
}
|
||||
@@ -143,18 +146,8 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
|
||||
this.$$compose();
|
||||
};
|
||||
|
||||
/**
|
||||
* Compose url and update `absUrl` property
|
||||
* @private
|
||||
*/
|
||||
this.$$compose = function() {
|
||||
var search = toKeyValue(this.$$search),
|
||||
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
|
||||
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
|
||||
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
this.$$normalizeUrl = function(url) {
|
||||
return appBaseNoFile + url.substr(1); // first char is always '/'
|
||||
};
|
||||
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
@@ -278,18 +271,8 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compose hashbang URL and update `absUrl` property
|
||||
* @private
|
||||
*/
|
||||
this.$$compose = function() {
|
||||
var search = toKeyValue(this.$$search),
|
||||
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
|
||||
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
|
||||
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
this.$$normalizeUrl = function(url) {
|
||||
return appBase + (url ? hashPrefix + url : '');
|
||||
};
|
||||
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
@@ -340,17 +323,10 @@ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
|
||||
return !!rewrittenUrl;
|
||||
};
|
||||
|
||||
this.$$compose = function() {
|
||||
var search = toKeyValue(this.$$search),
|
||||
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
|
||||
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
this.$$normalizeUrl = function(url) {
|
||||
// include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
|
||||
this.$$absUrl = appBase + hashPrefix + this.$$url;
|
||||
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
return appBase + hashPrefix + url;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -374,6 +350,16 @@ var locationPrototype = {
|
||||
*/
|
||||
$$replace: false,
|
||||
|
||||
/**
|
||||
* Compose url and update `url` and `absUrl` property
|
||||
* @private
|
||||
*/
|
||||
$$compose: function() {
|
||||
this.$$url = normalizePath(this.$$path, this.$$search, this.$$hash);
|
||||
this.$$absUrl = this.$$normalizeUrl(this.$$url);
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $location#absUrl
|
||||
@@ -879,6 +865,13 @@ function $LocationProvider() {
|
||||
|
||||
var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
|
||||
|
||||
// Determine if two URLs are equal despite potentially having different encoding/normalizing
|
||||
// such as $location.absUrl() vs $browser.url()
|
||||
// See https://github.com/angular/angular.js/issues/16592
|
||||
function urlsEqual(a, b) {
|
||||
return a === b || urlResolve(a).href === urlResolve(b).href;
|
||||
}
|
||||
|
||||
function setBrowserUrlWithFallback(url, replace, state) {
|
||||
var oldUrl = $location.url();
|
||||
var oldState = $location.$$state;
|
||||
@@ -945,7 +938,7 @@ function $LocationProvider() {
|
||||
|
||||
|
||||
// rewrite hashbang url <> html5 url
|
||||
if (trimEmptyHash($location.absUrl()) !== trimEmptyHash(initialUrl)) {
|
||||
if ($location.absUrl() !== initialUrl) {
|
||||
$browser.url($location.absUrl(), true);
|
||||
}
|
||||
|
||||
@@ -964,7 +957,6 @@ function $LocationProvider() {
|
||||
var oldUrl = $location.absUrl();
|
||||
var oldState = $location.$$state;
|
||||
var defaultPrevented;
|
||||
newUrl = trimEmptyHash(newUrl);
|
||||
$location.$$parse(newUrl);
|
||||
$location.$$state = newState;
|
||||
|
||||
@@ -992,11 +984,11 @@ function $LocationProvider() {
|
||||
if (initializing || $location.$$urlUpdatedByLocation) {
|
||||
$location.$$urlUpdatedByLocation = false;
|
||||
|
||||
var oldUrl = trimEmptyHash($browser.url());
|
||||
var newUrl = trimEmptyHash($location.absUrl());
|
||||
var oldUrl = $browser.url();
|
||||
var newUrl = $location.absUrl();
|
||||
var oldState = $browser.state();
|
||||
var currentReplace = $location.$$replace;
|
||||
var urlOrStateChanged = oldUrl !== newUrl ||
|
||||
var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) ||
|
||||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
|
||||
|
||||
if (initializing || urlOrStateChanged) {
|
||||
|
||||
+7
-1
@@ -683,5 +683,11 @@ function markQStateExceptionHandled(state) {
|
||||
state.pur = true;
|
||||
}
|
||||
function markQExceptionHandled(q) {
|
||||
markQStateExceptionHandled(q.$$state);
|
||||
// Built-in `$q` promises will always have a `$$state` property. This check is to allow
|
||||
// overwriting `$q` with a different promise library (e.g. Bluebird + angular-bluebird-promises).
|
||||
// (Currently, this is the only method that might be called with a promise, even if it is not
|
||||
// created by the built-in `$q`.)
|
||||
if (q.$$state) {
|
||||
markQStateExceptionHandled(q.$$state);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1122,7 +1122,7 @@ function $RootScopeProvider() {
|
||||
if (asyncQueue.length) {
|
||||
$rootScope.$digest();
|
||||
}
|
||||
});
|
||||
}, null, '$evalAsync');
|
||||
}
|
||||
|
||||
asyncQueue.push({scope: this, fn: $parse(expr), locals: locals});
|
||||
@@ -1493,7 +1493,7 @@ function $RootScopeProvider() {
|
||||
if (applyAsyncId === null) {
|
||||
applyAsyncId = $browser.defer(function() {
|
||||
$rootScope.$apply(flushApplyAsync);
|
||||
});
|
||||
}, null, '$applyAsync');
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
+2
-2
@@ -440,7 +440,7 @@ function $SceDelegateProvider() {
|
||||
// If we get here, then we will either sanitize the value or throw an exception.
|
||||
if (type === SCE_CONTEXTS.MEDIA_URL || type === SCE_CONTEXTS.URL) {
|
||||
// we attempt to sanitize non-resource URLs
|
||||
return $$sanitizeUri(maybeTrusted, type === SCE_CONTEXTS.MEDIA_URL);
|
||||
return $$sanitizeUri(maybeTrusted.toString(), type === SCE_CONTEXTS.MEDIA_URL);
|
||||
} else if (type === SCE_CONTEXTS.RESOURCE_URL) {
|
||||
if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
|
||||
return maybeTrusted;
|
||||
@@ -623,7 +623,7 @@ function $SceDelegateProvider() {
|
||||
* | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
|
||||
* | `$sce.MEDIA_URL` | For URLs that are safe to render as media. Is automatically converted from string by sanitizing when needed. |
|
||||
* | `$sce.URL` | For URLs that are safe to follow as links. Is automatically converted from string by sanitizing when needed. Note that `$sce.URL` makes a stronger statement about the URL than `$sce.MEDIA_URL` does and therefore contexts requiring values trusted for `$sce.URL` can be used anywhere that values trusted for `$sce.MEDIA_URL` are required.|
|
||||
* | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` or `$sce.MEDIA_URL` do and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` or `$sce.MEDIA_URL` are required. |
|
||||
* | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` or `$sce.MEDIA_URL` do and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` or `$sce.MEDIA_URL` are required. <br><br> The {@link $sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider#resourceUrlWhitelist()} and {@link $sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider#resourceUrlBlacklist()} can be used to restrict trusted origins for `RESOURCE_URL` |
|
||||
* | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. |
|
||||
*
|
||||
*
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* ! This is a private undocumented service !
|
||||
*
|
||||
* @name $$taskTrackerFactory
|
||||
* @description
|
||||
* A function to create `TaskTracker` instances.
|
||||
*
|
||||
* A `TaskTracker` can keep track of pending tasks (grouped by type) and can notify interested
|
||||
* parties when all pending tasks (or tasks of a specific type) have been completed.
|
||||
*
|
||||
* @param {$log} log - A logger instance (such as `$log`). Used to log error during callback
|
||||
* execution.
|
||||
*
|
||||
* @this
|
||||
*/
|
||||
function $$TaskTrackerFactoryProvider() {
|
||||
this.$get = valueFn(function(log) { return new TaskTracker(log); });
|
||||
}
|
||||
|
||||
function TaskTracker(log) {
|
||||
var self = this;
|
||||
var taskCounts = {};
|
||||
var taskCallbacks = [];
|
||||
|
||||
var ALL_TASKS_TYPE = self.ALL_TASKS_TYPE = '$$all$$';
|
||||
var DEFAULT_TASK_TYPE = self.DEFAULT_TASK_TYPE = '$$default$$';
|
||||
|
||||
/**
|
||||
* Execute the specified function and decrement the appropriate `taskCounts` counter.
|
||||
* If the counter reaches 0, all corresponding `taskCallbacks` are executed.
|
||||
*
|
||||
* @param {Function} fn - The function to execute.
|
||||
* @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task that is being completed.
|
||||
*/
|
||||
self.completeTask = completeTask;
|
||||
|
||||
/**
|
||||
* Increase the task count for the specified task type (or the default task type if non is
|
||||
* specified).
|
||||
*
|
||||
* @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task whose count will be increased.
|
||||
*/
|
||||
self.incTaskCount = incTaskCount;
|
||||
|
||||
/**
|
||||
* Execute the specified callback when all pending tasks have been completed.
|
||||
*
|
||||
* If there are no pending tasks, the callback is executed immediately. You can optionally limit
|
||||
* the tasks that will be waited for to a specific type, by passing a `taskType`.
|
||||
*
|
||||
* @param {function} callback - The function to call when there are no pending tasks.
|
||||
* @param {string=} [taskType=ALL_TASKS_TYPE] - The type of tasks that will be waited for.
|
||||
*/
|
||||
self.notifyWhenNoPendingTasks = notifyWhenNoPendingTasks;
|
||||
|
||||
function completeTask(fn, taskType) {
|
||||
taskType = taskType || DEFAULT_TASK_TYPE;
|
||||
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
decTaskCount(taskType);
|
||||
|
||||
var countForType = taskCounts[taskType];
|
||||
var countForAll = taskCounts[ALL_TASKS_TYPE];
|
||||
|
||||
// If at least one of the queues (`ALL_TASKS_TYPE` or `taskType`) is empty, run callbacks.
|
||||
if (!countForAll || !countForType) {
|
||||
var getNextCallback = !countForAll ? getLastCallback : getLastCallbackForType;
|
||||
var nextCb;
|
||||
|
||||
while ((nextCb = getNextCallback(taskType))) {
|
||||
try {
|
||||
nextCb();
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function decTaskCount(taskType) {
|
||||
taskType = taskType || DEFAULT_TASK_TYPE;
|
||||
if (taskCounts[taskType]) {
|
||||
taskCounts[taskType]--;
|
||||
taskCounts[ALL_TASKS_TYPE]--;
|
||||
}
|
||||
}
|
||||
|
||||
function getLastCallback() {
|
||||
var cbInfo = taskCallbacks.pop();
|
||||
return cbInfo && cbInfo.cb;
|
||||
}
|
||||
|
||||
function getLastCallbackForType(taskType) {
|
||||
for (var i = taskCallbacks.length - 1; i >= 0; --i) {
|
||||
var cbInfo = taskCallbacks[i];
|
||||
if (cbInfo.type === taskType) {
|
||||
taskCallbacks.splice(i, 1);
|
||||
return cbInfo.cb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function incTaskCount(taskType) {
|
||||
taskType = taskType || DEFAULT_TASK_TYPE;
|
||||
taskCounts[taskType] = (taskCounts[taskType] || 0) + 1;
|
||||
taskCounts[ALL_TASKS_TYPE] = (taskCounts[ALL_TASKS_TYPE] || 0) + 1;
|
||||
}
|
||||
|
||||
function notifyWhenNoPendingTasks(callback, taskType) {
|
||||
taskType = taskType || ALL_TASKS_TYPE;
|
||||
if (!taskCounts[taskType]) {
|
||||
callback();
|
||||
} else {
|
||||
taskCallbacks.push({type: taskType, cb: callback});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,15 @@ function $$TestabilityProvider() {
|
||||
* @name $$testability#whenStable
|
||||
*
|
||||
* @description
|
||||
* Calls the callback when $timeout and $http requests are completed.
|
||||
* Calls the callback when all pending tasks are completed.
|
||||
*
|
||||
* Types of tasks waited for include:
|
||||
* - Pending timeouts (via {@link $timeout}).
|
||||
* - Pending HTTP requests (via {@link $http}).
|
||||
* - In-progress route transitions (via {@link $route}).
|
||||
* - Pending tasks scheduled via {@link $rootScope#$applyAsync}.
|
||||
* - Pending tasks scheduled via {@link $rootScope#$evalAsync}.
|
||||
* These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
|
||||
*
|
||||
* @param {function} callback
|
||||
*/
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ function $TimeoutProvider() {
|
||||
}
|
||||
|
||||
if (!skipApply) $rootScope.$apply();
|
||||
}, delay);
|
||||
}, delay, '$timeout');
|
||||
|
||||
promise.$$timeoutId = timeoutId;
|
||||
deferreds[timeoutId] = deferred;
|
||||
|
||||
+13
-1
@@ -10,6 +10,12 @@ var urlParsingNode = window.document.createElement('a');
|
||||
var originUrl = urlResolve(window.location.href);
|
||||
var baseUrlParsingNode;
|
||||
|
||||
urlParsingNode.href = 'http://[::1]';
|
||||
|
||||
// Support: IE 9-11 only, Edge 16-17 only (fixed in 18 Preview)
|
||||
// IE/Edge don't wrap IPv6 addresses' hostnames in square brackets
|
||||
// when parsed out of an anchor element.
|
||||
var ipv6InBrackets = urlParsingNode.hostname === '[::1]';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -72,13 +78,19 @@ function urlResolve(url) {
|
||||
|
||||
urlParsingNode.setAttribute('href', href);
|
||||
|
||||
var hostname = urlParsingNode.hostname;
|
||||
|
||||
if (!ipv6InBrackets && hostname.indexOf(':') > -1) {
|
||||
hostname = '[' + hostname + ']';
|
||||
}
|
||||
|
||||
return {
|
||||
href: urlParsingNode.href,
|
||||
protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
|
||||
host: urlParsingNode.host,
|
||||
search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
|
||||
hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
|
||||
hostname: urlParsingNode.hostname,
|
||||
hostname: hostname,
|
||||
port: urlParsingNode.port,
|
||||
pathname: (urlParsingNode.pathname.charAt(0) === '/')
|
||||
? urlParsingNode.pathname
|
||||
|
||||
@@ -57,11 +57,11 @@
|
||||
"applyInlineStyle": false,
|
||||
"assertArg": false,
|
||||
"blockKeyframeAnimations": false,
|
||||
"blockTransitions": false,
|
||||
"clearGeneratedClasses": false,
|
||||
"concatWithSpace": false,
|
||||
"extractElementNode": false,
|
||||
"getDomNode": false,
|
||||
"helpers": false,
|
||||
"mergeAnimationDetails": false,
|
||||
"mergeClasses": false,
|
||||
"packageStyles": false,
|
||||
@@ -73,6 +73,7 @@
|
||||
/* ngAnimate directives/services */
|
||||
"ngAnimateSwapDirective": true,
|
||||
"$$rAFSchedulerFactory": true,
|
||||
"$$AnimateCacheProvider": true,
|
||||
"$$AnimateChildrenDirective": true,
|
||||
"$$AnimateQueueProvider": true,
|
||||
"$$AnimationProvider": true,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
/** @this */
|
||||
var $$AnimateCacheProvider = function() {
|
||||
|
||||
var KEY = '$$ngAnimateParentKey';
|
||||
var parentCounter = 0;
|
||||
var cache = Object.create(null);
|
||||
|
||||
this.$get = [function() {
|
||||
return {
|
||||
cacheKey: function(node, method, addClass, removeClass) {
|
||||
var parentNode = node.parentNode;
|
||||
var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
|
||||
var parts = [parentID, method, node.getAttribute('class')];
|
||||
if (addClass) {
|
||||
parts.push(addClass);
|
||||
}
|
||||
if (removeClass) {
|
||||
parts.push(removeClass);
|
||||
}
|
||||
return parts.join(' ');
|
||||
},
|
||||
|
||||
containsCachedAnimationWithoutDuration: function(key) {
|
||||
var entry = cache[key];
|
||||
|
||||
// nothing cached, so go ahead and animate
|
||||
// otherwise it should be a valid animation
|
||||
return (entry && !entry.isValid) || false;
|
||||
},
|
||||
|
||||
flush: function() {
|
||||
cache = Object.create(null);
|
||||
},
|
||||
|
||||
count: function(key) {
|
||||
var entry = cache[key];
|
||||
return entry ? entry.total : 0;
|
||||
},
|
||||
|
||||
get: function(key) {
|
||||
var entry = cache[key];
|
||||
return entry && entry.value;
|
||||
},
|
||||
|
||||
put: function(key, value, isValid) {
|
||||
if (!cache[key]) {
|
||||
cache[key] = { total: 1, value: value, isValid: isValid };
|
||||
} else {
|
||||
cache[key].total++;
|
||||
cache[key].value = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
};
|
||||
+38
-62
@@ -304,33 +304,6 @@ function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
|
||||
return [style, value];
|
||||
}
|
||||
|
||||
function createLocalCacheLookup() {
|
||||
var cache = Object.create(null);
|
||||
return {
|
||||
flush: function() {
|
||||
cache = Object.create(null);
|
||||
},
|
||||
|
||||
count: function(key) {
|
||||
var entry = cache[key];
|
||||
return entry ? entry.total : 0;
|
||||
},
|
||||
|
||||
get: function(key) {
|
||||
var entry = cache[key];
|
||||
return entry && entry.value;
|
||||
},
|
||||
|
||||
put: function(key, value) {
|
||||
if (!cache[key]) {
|
||||
cache[key] = { total: 1, value: value };
|
||||
} else {
|
||||
cache[key].total++;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// we do not reassign an already present style value since
|
||||
// if we detect the style property value again we may be
|
||||
// detecting styles that were added via the `from` styles.
|
||||
@@ -349,26 +322,16 @@ function registerRestorableStyles(backup, node, properties) {
|
||||
}
|
||||
|
||||
var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animateProvider) {
|
||||
var gcsLookup = createLocalCacheLookup();
|
||||
var gcsStaggerLookup = createLocalCacheLookup();
|
||||
|
||||
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
|
||||
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', '$$animateCache',
|
||||
'$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
|
||||
function($window, $$jqLite, $$AnimateRunner, $timeout,
|
||||
function($window, $$jqLite, $$AnimateRunner, $timeout, $$animateCache,
|
||||
$$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) {
|
||||
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
|
||||
var parentCounter = 0;
|
||||
function gcsHashFn(node, extraClasses) {
|
||||
var KEY = '$$ngAnimateParentKey';
|
||||
var parentNode = node.parentNode;
|
||||
var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
|
||||
return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
|
||||
}
|
||||
|
||||
function computeCachedCssStyles(node, className, cacheKey, properties) {
|
||||
var timings = gcsLookup.get(cacheKey);
|
||||
function computeCachedCssStyles(node, className, cacheKey, allowNoDuration, properties) {
|
||||
var timings = $$animateCache.get(cacheKey);
|
||||
|
||||
if (!timings) {
|
||||
timings = computeCssStyles($window, node, properties);
|
||||
@@ -377,20 +340,26 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
}
|
||||
}
|
||||
|
||||
// if a css animation has no duration we
|
||||
// should mark that so that repeated addClass/removeClass calls are skipped
|
||||
var hasDuration = allowNoDuration || (timings.transitionDuration > 0 || timings.animationDuration > 0);
|
||||
|
||||
// we keep putting this in multiple times even though the value and the cacheKey are the same
|
||||
// because we're keeping an internal tally of how many duplicate animations are detected.
|
||||
gcsLookup.put(cacheKey, timings);
|
||||
$$animateCache.put(cacheKey, timings, hasDuration);
|
||||
|
||||
return timings;
|
||||
}
|
||||
|
||||
function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
|
||||
var stagger;
|
||||
var staggerCacheKey = 'stagger-' + cacheKey;
|
||||
|
||||
// if we have one or more existing matches of matching elements
|
||||
// containing the same parent + CSS styles (which is how cacheKey works)
|
||||
// then staggering is possible
|
||||
if (gcsLookup.count(cacheKey) > 0) {
|
||||
stagger = gcsStaggerLookup.get(cacheKey);
|
||||
if ($$animateCache.count(cacheKey) > 0) {
|
||||
stagger = $$animateCache.get(staggerCacheKey);
|
||||
|
||||
if (!stagger) {
|
||||
var staggerClassName = pendClasses(className, '-stagger');
|
||||
@@ -405,7 +374,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
|
||||
$$jqLite.removeClass(node, staggerClassName);
|
||||
|
||||
gcsStaggerLookup.put(cacheKey, stagger);
|
||||
$$animateCache.put(staggerCacheKey, stagger, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,8 +385,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
function waitUntilQuiet(callback) {
|
||||
rafWaitQueue.push(callback);
|
||||
$$rAFScheduler.waitUntilQuiet(function() {
|
||||
gcsLookup.flush();
|
||||
gcsStaggerLookup.flush();
|
||||
$$animateCache.flush();
|
||||
|
||||
// DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
|
||||
// PLEASE EXAMINE THE `$$forceReflow` service to understand why.
|
||||
@@ -432,8 +400,8 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
});
|
||||
}
|
||||
|
||||
function computeTimings(node, className, cacheKey) {
|
||||
var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
|
||||
function computeTimings(node, className, cacheKey, allowNoDuration) {
|
||||
var timings = computeCachedCssStyles(node, className, cacheKey, allowNoDuration, DETECT_CSS_PROPERTIES);
|
||||
var aD = timings.animationDelay;
|
||||
var tD = timings.transitionDelay;
|
||||
timings.maxDelay = aD && tD
|
||||
@@ -520,7 +488,6 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
|
||||
var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
|
||||
var fullClassName = classes + ' ' + preparationClasses;
|
||||
var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
|
||||
var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
|
||||
var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
|
||||
|
||||
@@ -533,7 +500,12 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
var cacheKey, stagger;
|
||||
var stagger, cacheKey = $$animateCache.cacheKey(node, method, options.addClass, options.removeClass);
|
||||
if ($$animateCache.containsCachedAnimationWithoutDuration(cacheKey)) {
|
||||
preparationClasses = null;
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
if (options.stagger > 0) {
|
||||
var staggerVal = parseFloat(options.stagger);
|
||||
stagger = {
|
||||
@@ -543,7 +515,6 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
animationDuration: 0
|
||||
};
|
||||
} else {
|
||||
cacheKey = gcsHashFn(node, fullClassName);
|
||||
stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
|
||||
}
|
||||
|
||||
@@ -577,7 +548,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
var itemIndex = stagger
|
||||
? options.staggerIndex >= 0
|
||||
? options.staggerIndex
|
||||
: gcsLookup.count(cacheKey)
|
||||
: $$animateCache.count(cacheKey)
|
||||
: 0;
|
||||
|
||||
var isFirst = itemIndex === 0;
|
||||
@@ -589,10 +560,10 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
// that if there is no transition defined then nothing will happen and this will also allow
|
||||
// other transitions to be stacked on top of each other without any chopping them out.
|
||||
if (isFirst && !options.skipBlocking) {
|
||||
blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
|
||||
helpers.blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
|
||||
}
|
||||
|
||||
var timings = computeTimings(node, fullClassName, cacheKey);
|
||||
var timings = computeTimings(node, fullClassName, cacheKey, !isStructural);
|
||||
var relativeDelay = timings.maxDelay;
|
||||
maxDelay = Math.max(relativeDelay, 0);
|
||||
maxDuration = timings.maxDuration;
|
||||
@@ -630,6 +601,8 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
|
||||
|
||||
if (options.delay != null) {
|
||||
var delayStyle;
|
||||
if (typeof options.delay !== 'boolean') {
|
||||
@@ -673,7 +646,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
if (flags.blockTransition || flags.blockKeyframeAnimation) {
|
||||
applyBlocking(maxDuration);
|
||||
} else if (!options.skipBlocking) {
|
||||
blockTransitions(node, false);
|
||||
helpers.blockTransitions(node, false);
|
||||
}
|
||||
|
||||
// TODO(matsko): for 1.5 change this code to have an animator object for better debugging
|
||||
@@ -717,13 +690,16 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
animationClosed = true;
|
||||
animationPaused = false;
|
||||
|
||||
if (!options.$$skipPreparationClasses) {
|
||||
if (preparationClasses && !options.$$skipPreparationClasses) {
|
||||
$$jqLite.removeClass(element, preparationClasses);
|
||||
}
|
||||
$$jqLite.removeClass(element, activeClasses);
|
||||
|
||||
if (activeClasses) {
|
||||
$$jqLite.removeClass(element, activeClasses);
|
||||
}
|
||||
|
||||
blockKeyframeAnimations(node, false);
|
||||
blockTransitions(node, false);
|
||||
helpers.blockTransitions(node, false);
|
||||
|
||||
forEach(temporaryStyles, function(entry) {
|
||||
// There is only one way to remove inline style properties entirely from elements.
|
||||
@@ -774,7 +750,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
|
||||
function applyBlocking(duration) {
|
||||
if (flags.blockTransition) {
|
||||
blockTransitions(node, duration);
|
||||
helpers.blockTransitions(node, duration);
|
||||
}
|
||||
|
||||
if (flags.blockKeyframeAnimation) {
|
||||
@@ -904,9 +880,9 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
|
||||
if (flags.recalculateTimingStyles) {
|
||||
fullClassName = node.getAttribute('class') + ' ' + preparationClasses;
|
||||
cacheKey = gcsHashFn(node, fullClassName);
|
||||
cacheKey = $$animateCache.cacheKey(node, method, options.addClass, options.removeClass);
|
||||
|
||||
timings = computeTimings(node, fullClassName, cacheKey);
|
||||
timings = computeTimings(node, fullClassName, cacheKey, false);
|
||||
relativeDelay = timings.maxDelay;
|
||||
maxDelay = Math.max(relativeDelay, 0);
|
||||
maxDuration = timings.maxDuration;
|
||||
|
||||
@@ -13,6 +13,15 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
join: []
|
||||
};
|
||||
|
||||
function getEventData(options) {
|
||||
return {
|
||||
addClass: options.addClass,
|
||||
removeClass: options.removeClass,
|
||||
from: options.from,
|
||||
to: options.to
|
||||
};
|
||||
}
|
||||
|
||||
function makeTruthyCssClassMap(classString) {
|
||||
if (!classString) {
|
||||
return null;
|
||||
@@ -111,6 +120,10 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
var disabledElementsLookup = new $$Map();
|
||||
var animationsEnabled = null;
|
||||
|
||||
function removeFromDisabledElementsLookup(evt) {
|
||||
disabledElementsLookup.delete(evt.target);
|
||||
}
|
||||
|
||||
function postDigestTaskFactory() {
|
||||
var postDigestCalled = false;
|
||||
return function(fn) {
|
||||
@@ -294,6 +307,11 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
bool = !disabledElementsLookup.get(node);
|
||||
} else {
|
||||
// (element, bool) - Element setter
|
||||
if (!disabledElementsLookup.has(node)) {
|
||||
// The element is added to the map for the first time.
|
||||
// Create a listener to remove it on `$destroy` (to avoid memory leak).
|
||||
jqLite(element).on('$destroy', removeFromDisabledElementsLookup);
|
||||
}
|
||||
disabledElementsLookup.set(node, !bool);
|
||||
}
|
||||
}
|
||||
@@ -379,9 +397,9 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
|
||||
if (skipAnimations) {
|
||||
// Callbacks should fire even if the document is hidden (regression fix for issue #14120)
|
||||
if (documentHidden) notifyProgress(runner, event, 'start');
|
||||
if (documentHidden) notifyProgress(runner, event, 'start', getEventData(options));
|
||||
close();
|
||||
if (documentHidden) notifyProgress(runner, event, 'close');
|
||||
if (documentHidden) notifyProgress(runner, event, 'close', getEventData(options));
|
||||
return runner;
|
||||
}
|
||||
|
||||
@@ -438,7 +456,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
if (existingAnimation.state === RUNNING_STATE) {
|
||||
normalizeAnimationDetails(element, newAnimation);
|
||||
} else {
|
||||
applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
|
||||
applyGeneratedPreparationClasses($$jqLite, element, isStructural ? event : null, options);
|
||||
|
||||
event = newAnimation.event = existingAnimation.event;
|
||||
options = mergeAnimationDetails(element, existingAnimation, newAnimation);
|
||||
@@ -543,7 +561,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
// this will update the runner's flow-control events based on
|
||||
// the `realRunner` object.
|
||||
runner.setHost(realRunner);
|
||||
notifyProgress(runner, event, 'start', {});
|
||||
notifyProgress(runner, event, 'start', getEventData(options));
|
||||
|
||||
realRunner.done(function(status) {
|
||||
close(!status);
|
||||
@@ -551,7 +569,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
if (animationDetails && animationDetails.counter === counter) {
|
||||
clearElementAnimationState(node);
|
||||
}
|
||||
notifyProgress(runner, event, 'close', {});
|
||||
notifyProgress(runner, event, 'close', getEventData(options));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+56
-17
@@ -8,6 +8,7 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
var drivers = this.drivers = [];
|
||||
|
||||
var RUNNER_STORAGE_KEY = '$$animationRunner';
|
||||
var PREPARE_CLASSES_KEY = '$$animatePrepareClasses';
|
||||
|
||||
function setRunner(element, runner) {
|
||||
element.data(RUNNER_STORAGE_KEY, runner);
|
||||
@@ -21,8 +22,8 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
return element.data(RUNNER_STORAGE_KEY);
|
||||
}
|
||||
|
||||
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$Map', '$$rAFScheduler',
|
||||
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$Map, $$rAFScheduler) {
|
||||
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$Map', '$$rAFScheduler', '$$animateCache',
|
||||
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$Map, $$rAFScheduler, $$animateCache) {
|
||||
|
||||
var animationQueue = [];
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
@@ -37,6 +38,7 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
var animation = animations[i];
|
||||
lookup.set(animation.domNode, animations[i] = {
|
||||
domNode: animation.domNode,
|
||||
element: animation.element,
|
||||
fn: animation.fn,
|
||||
children: []
|
||||
});
|
||||
@@ -93,7 +95,7 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
result.push(row);
|
||||
row = [];
|
||||
}
|
||||
row.push(entry.fn);
|
||||
row.push(entry);
|
||||
entry.children.forEach(function(childEntry) {
|
||||
nextLevelEntries++;
|
||||
queue.push(childEntry);
|
||||
@@ -128,8 +130,6 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
return runner;
|
||||
}
|
||||
|
||||
setRunner(element, runner);
|
||||
|
||||
var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));
|
||||
var tempClasses = options.tempClasses;
|
||||
if (tempClasses) {
|
||||
@@ -137,12 +137,12 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
options.tempClasses = null;
|
||||
}
|
||||
|
||||
var prepareClassName;
|
||||
if (isStructural) {
|
||||
prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
|
||||
$$jqLite.addClass(element, prepareClassName);
|
||||
element.data(PREPARE_CLASSES_KEY, 'ng-' + event + PREPARE_CLASS_SUFFIX);
|
||||
}
|
||||
|
||||
setRunner(element, runner);
|
||||
|
||||
animationQueue.push({
|
||||
// this data is used by the postDigest code and passed into
|
||||
// the driver step function
|
||||
@@ -182,16 +182,31 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
var toBeSortedAnimations = [];
|
||||
|
||||
forEach(groupedAnimations, function(animationEntry) {
|
||||
var element = animationEntry.from ? animationEntry.from.element : animationEntry.element;
|
||||
var extraClasses = options.addClass;
|
||||
|
||||
extraClasses = (extraClasses ? (extraClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
|
||||
var cacheKey = $$animateCache.cacheKey(element[0], animationEntry.event, extraClasses, options.removeClass);
|
||||
|
||||
toBeSortedAnimations.push({
|
||||
domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element),
|
||||
element: element,
|
||||
domNode: getDomNode(element),
|
||||
fn: function triggerAnimationStart() {
|
||||
var startAnimationFn, closeFn = animationEntry.close;
|
||||
|
||||
// in the event that we've cached the animation status for this element
|
||||
// and it's in fact an invalid animation (something that has duration = 0)
|
||||
// then we should skip all the heavy work from here on
|
||||
if ($$animateCache.containsCachedAnimationWithoutDuration(cacheKey)) {
|
||||
closeFn();
|
||||
return;
|
||||
}
|
||||
|
||||
// it's important that we apply the `ng-animate` CSS class and the
|
||||
// temporary classes before we do any driver invoking since these
|
||||
// CSS classes may be required for proper CSS detection.
|
||||
animationEntry.beforeStart();
|
||||
|
||||
var startAnimationFn, closeFn = animationEntry.close;
|
||||
|
||||
// in the event that the element was removed before the digest runs or
|
||||
// during the RAF sequencing then we should not trigger the animation.
|
||||
var targetElement = animationEntry.anchors
|
||||
@@ -221,7 +236,32 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
// we need to sort each of the animations in order of parent to child
|
||||
// relationships. This ensures that the child classes are applied at the
|
||||
// right time.
|
||||
$$rAFScheduler(sortAnimations(toBeSortedAnimations));
|
||||
var finalAnimations = sortAnimations(toBeSortedAnimations);
|
||||
for (var i = 0; i < finalAnimations.length; i++) {
|
||||
var innerArray = finalAnimations[i];
|
||||
for (var j = 0; j < innerArray.length; j++) {
|
||||
var entry = innerArray[j];
|
||||
var element = entry.element;
|
||||
|
||||
// the RAFScheduler code only uses functions
|
||||
finalAnimations[i][j] = entry.fn;
|
||||
|
||||
// the first row of elements shouldn't have a prepare-class added to them
|
||||
// since the elements are at the top of the animation hierarchy and they
|
||||
// will be applied without a RAF having to pass...
|
||||
if (i === 0) {
|
||||
element.removeData(PREPARE_CLASSES_KEY);
|
||||
continue;
|
||||
}
|
||||
|
||||
var prepareClassName = element.data(PREPARE_CLASSES_KEY);
|
||||
if (prepareClassName) {
|
||||
$$jqLite.addClass(element, prepareClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$$rAFScheduler(finalAnimations);
|
||||
});
|
||||
|
||||
return runner;
|
||||
@@ -359,10 +399,10 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
}
|
||||
|
||||
function beforeStart() {
|
||||
element.addClass(NG_ANIMATE_CLASSNAME);
|
||||
if (tempClasses) {
|
||||
$$jqLite.addClass(element, tempClasses);
|
||||
}
|
||||
tempClasses = (tempClasses ? (tempClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
|
||||
$$jqLite.addClass(element, tempClasses);
|
||||
|
||||
var prepareClassName = element.data(PREPARE_CLASSES_KEY);
|
||||
if (prepareClassName) {
|
||||
$$jqLite.removeClass(element, prepareClassName);
|
||||
prepareClassName = null;
|
||||
@@ -402,7 +442,6 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
$$jqLite.removeClass(element, tempClasses);
|
||||
}
|
||||
|
||||
element.removeClass(NG_ANIMATE_CLASSNAME);
|
||||
runner.complete(!rejected);
|
||||
}
|
||||
};
|
||||
|
||||
+36
-14
@@ -17,20 +17,28 @@
|
||||
* ## Directive Support
|
||||
* The following directives are "animation aware":
|
||||
*
|
||||
* | Directive | Supported Animations |
|
||||
* |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
|
||||
* | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |
|
||||
* | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
* | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
* | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
* | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
* | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) |
|
||||
* | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) |
|
||||
* | {@link ng.directive:form#animations form} & {@link ng.directive:ngModel#animations ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
|
||||
* | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) |
|
||||
* | {@link module:ngMessages#animations ngMessage} | enter and leave |
|
||||
* | Directive | Supported Animations |
|
||||
* |-------------------------------------------------------------------------------|---------------------------------------------------------------------------|
|
||||
* | {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
|
||||
* | {@link ngAnimate.directive:ngAnimateSwap#animations ngAnimateSwap} | enter and leave |
|
||||
* | {@link ng.directive:ngClass#animations ngClass / {{class}​}} | add and remove |
|
||||
* | {@link ng.directive:ngClassEven#animations ngClassEven} | add and remove |
|
||||
* | {@link ng.directive:ngClassOdd#animations ngClassOdd} | add and remove |
|
||||
* | {@link ng.directive:ngHide#animations ngHide} | add and remove (the `ng-hide` class) |
|
||||
* | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
* | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
* | {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
|
||||
* | {@link module:ngMessages#animations ngMessages} | add and remove (the `ng-active`/`ng-inactive` classes) |
|
||||
* | {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
|
||||
* | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
|
||||
* | {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
|
||||
* | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
* | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
*
|
||||
* (More information can be found by visiting each the documentation associated with each directive.)
|
||||
* (More information can be found by visiting the documentation associated with each directive.)
|
||||
*
|
||||
* For a full breakdown of the steps involved during each animation event, refer to the
|
||||
* {@link ng.$animate `$animate` API docs}.
|
||||
*
|
||||
* ## CSS-based Animations
|
||||
*
|
||||
@@ -267,9 +275,22 @@
|
||||
* .message.ng-enter-prepare {
|
||||
* opacity: 0;
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* ### Animating between value changes
|
||||
*
|
||||
* Sometimes you need to animate between different expression states, whose values
|
||||
* don't necessary need to be known or referenced in CSS styles.
|
||||
* Unless possible with another {@link ngAnimate#directive-support "animation aware" directive},
|
||||
* that specific use case can always be covered with {@link ngAnimate.directive:ngAnimateSwap} as
|
||||
* can be seen in {@link ngAnimate.directive:ngAnimateSwap#examples this example}.
|
||||
*
|
||||
* Note that {@link ngAnimate.directive:ngAnimateSwap} is a *structural directive*, which means it
|
||||
* creates a new instance of the element (including any other/child directives it may have) and
|
||||
* links it to a new scope every time *swap* happens. In some cases this might not be desirable
|
||||
* (e.g. for performance reasons, or when you wish to retain internal state on the original
|
||||
* element instance).
|
||||
*
|
||||
* ## JavaScript-based Animations
|
||||
*
|
||||
* ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
|
||||
@@ -765,6 +786,7 @@ angular.module('ngAnimate', [], function initAngularHelpers() {
|
||||
.factory('$$rAFScheduler', $$rAFSchedulerFactory)
|
||||
|
||||
.provider('$$animateQueue', $$AnimateQueueProvider)
|
||||
.provider('$$animateCache', $$AnimateCacheProvider)
|
||||
.provider('$$animation', $$AnimationProvider)
|
||||
|
||||
.provider('$animateCss', $AnimateCssProvider)
|
||||
|
||||
@@ -92,7 +92,8 @@ var ngAnimateSwapDirective = ['$animate', function($animate) {
|
||||
restrict: 'A',
|
||||
transclude: 'element',
|
||||
terminal: true,
|
||||
priority: 600, // we use 600 here to ensure that the directive is caught before others
|
||||
priority: 550, // We use 550 here to ensure that the directive is caught before others,
|
||||
// but after `ngIf` (at priority 600).
|
||||
link: function(scope, $element, attrs, ctrl, $transclude) {
|
||||
var previousElement, previousScope;
|
||||
scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user