Compare commits
428 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e242e9b595 | |||
| 33b5c5067e | |||
| 7a88a34228 | |||
| dc1c180783 | |||
| 6592693aad | |||
| dc3955ca63 | |||
| 9a3737932a | |||
| 0ae0887b2f | |||
| 2549313eab | |||
| 26fccb9943 | |||
| efb822c58d | |||
| b7d1e0fbdb | |||
| 31ea071f2e | |||
| abe6acf598 | |||
| f04e04e0e6 | |||
| 4355dee21d | |||
| 2f0ac696cb | |||
| da3d96c0ea | |||
| bc775759c8 | |||
| fa4451d57f | |||
| a0c55af985 | |||
| d1dd6a3624 | |||
| e5eaecc32b | |||
| 24ddbafbdf | |||
| 980b69dcae | |||
| 52e46683bf | |||
| 0b0a122888 | |||
| b166f2b34a | |||
| ac06fb6592 | |||
| 20dbc1161f | |||
| f78268fbf7 | |||
| 85286bd863 | |||
| 1faf7ec30d | |||
| 20724da544 | |||
| 3964598bcf | |||
| cd700f65f4 | |||
| f36f25f17a | |||
| fcc26063c5 | |||
| 0b66753488 | |||
| b30a925d54 | |||
| fe599dbb72 | |||
| a7dfa7cd83 | |||
| a19a17e288 | |||
| b3224671fb | |||
| 2953e47da4 | |||
| 96bee0c659 | |||
| 5b87647243 | |||
| 6f10c2d9d1 | |||
| 76882169ba | |||
| f93c770cc2 | |||
| 06f5069813 | |||
| 8681fce27b | |||
| 5a022524a3 | |||
| 5acfac894c | |||
| a879e82a9b | |||
| c03fda012a | |||
| 976e34bab6 | |||
| 9a1b7c9fa1 | |||
| c33fd13254 | |||
| 336bad182f | |||
| 553ca78406 | |||
| 0722ab0ba1 | |||
| ee8e05cfaf | |||
| 9509feba99 | |||
| b0b3a2b6cb | |||
| 3abe5b77b3 | |||
| 858807ad5d | |||
| 7436063a7d | |||
| d1bc780304 | |||
| 28f273e473 | |||
| 1cdc72f3a5 | |||
| c3c5c5eea2 | |||
| 4cc7701700 | |||
| 5dc07667de | |||
| 212e5132ab | |||
| 00162655e7 | |||
| 09f1325065 | |||
| c01dad694d | |||
| c4586513d1 | |||
| 7a1270cf4b | |||
| 559e93c7ce | |||
| 9d058de04b | |||
| 1d826e2f1e | |||
| 5789094546 | |||
| cf92c33a32 | |||
| 5d8f5a8be7 | |||
| 74a2808487 | |||
| 9ba181b80f | |||
| 44c16b5b51 | |||
| 70a42fcaee | |||
| 3bd62d2d59 | |||
| b9ef6585e1 | |||
| e06b4fb62c | |||
| 9204a1a153 | |||
| f4d49f3769 | |||
| d5c35b44d7 | |||
| 551375c736 | |||
| 17691790e9 | |||
| e47dead052 | |||
| 1a2532601b | |||
| cb862aaaa0 | |||
| fa2034c167 | |||
| 750b43b6f6 | |||
| 048f5e3ab8 | |||
| 13c566cc03 | |||
| 45834eac09 | |||
| 3673909896 | |||
| 8302981a08 | |||
| d2a7b5162f | |||
| 2ecd85b989 | |||
| 2bdf712687 | |||
| 0de02973c1 | |||
| 55516da2df | |||
| e8adbf8d5a | |||
| c169c60535 | |||
| 0b6ec6b3ab | |||
| d2511cfac0 | |||
| 155efa421b | |||
| f33d95cfcf | |||
| 57b626a673 | |||
| b168aef861 | |||
| 6c4d2707b7 | |||
| 9c95f6d5e0 | |||
| cab9ebfd5a | |||
| 8a739fb4fa | |||
| c4003fd034 | |||
| ab856d8ae3 | |||
| 83e6eef68e | |||
| 87270cb79f | |||
| e06ebfdbb5 | |||
| ddeb1df15a | |||
| 358a69fa8b | |||
| 417aefd45d | |||
| 6ad4c8d1b4 | |||
| 3ef612afa0 | |||
| cc8486f994 | |||
| b990fa91cd | |||
| 17f7d8c5c8 | |||
| 1e8dd0e65c | |||
| b1a18ef381 | |||
| 3470ab5696 | |||
| 5d39a118df | |||
| 421a5856f8 | |||
| 85f40a15fb | |||
| b919a2737e | |||
| a675ea0343 | |||
| 8fb8d52664 | |||
| d60d904747 | |||
| 62afb6204c | |||
| 1eaf4ab414 | |||
| 1de5d181da | |||
| 73088bb7ee | |||
| 898140edb7 | |||
| dab60352e5 | |||
| df4955fe78 | |||
| 14519488ce | |||
| 569e906a58 | |||
| 18b8a63a3c | |||
| 6e78fee732 | |||
| 6a99eaf1c8 | |||
| c94e44b9f8 | |||
| bbca6b0893 | |||
| 1555a4911a | |||
| dbba98b9ae | |||
| 3e2cb6c15c | |||
| 1391e99c7f | |||
| 7a5f06d55d | |||
| bf78beeb6d | |||
| 2e892e4072 | |||
| 8f88ea57dc | |||
| 24b0b516b6 | |||
| 1fcf5949d2 | |||
| 46ccaee9be | |||
| 86c9990813 | |||
| 4a34c8e1b8 | |||
| fec2ea8c1b | |||
| 7566c7d26f | |||
| 02a19c0f39 | |||
| 6e6e4eec9d | |||
| 46f14fa4b8 | |||
| bad2249bcd | |||
| c2148ec15c | |||
| 474e40498e | |||
| e9a6792d7f | |||
| adc1501caa | |||
| 7a53707c8f | |||
| 57fe5b320d | |||
| 671bebde0a | |||
| b51ded6736 | |||
| 3ec1819b91 | |||
| 009ebec64c | |||
| 9256dbc420 | |||
| 7504656a26 | |||
| e74cdf4b59 | |||
| 9d6c3f3ec2 | |||
| ac0e260765 | |||
| 28c0213ee5 | |||
| 8b4d85c015 | |||
| 2aeda67909 | |||
| ad68ee192e | |||
| 970ba117eb | |||
| 30e097b389 | |||
| e8e02b8bce | |||
| e36a3e89f5 | |||
| 1102c41196 | |||
| e970c8fce9 | |||
| 04271d6b2c | |||
| 2e1163ef5c | |||
| 75befe723a | |||
| 5e2bc5bbf3 | |||
| 079c485b92 | |||
| 19a0c9324c | |||
| 9ef02e72ab | |||
| 2101126ce7 | |||
| a222d0b452 | |||
| 1b8eb231c9 | |||
| 06baf1869b | |||
| 82597fc12b | |||
| ff52b188a7 | |||
| dc41f465ba | |||
| 6e3b5a57cd | |||
| f003d93a3d | |||
| aac5623247 | |||
| aa03812fd0 | |||
| bcb6a494de | |||
| a1e3f8728e | |||
| 9e8e3e187d | |||
| c6554433cf | |||
| 43d2a75f4e | |||
| 37d2b50812 | |||
| 8f31f1ff43 | |||
| cf84fcf544 | |||
| f63bc3cfde | |||
| a77943110e | |||
| ec97686f2f | |||
| 4ff7b7aa48 | |||
| 57b837bd5c | |||
| 6daca023e4 | |||
| 91b4eb0f69 | |||
| e50ed4da9e | |||
| fe5dd1da8f | |||
| df4c03fa33 | |||
| 8d4e626326 | |||
| 92aef5d456 | |||
| eed13cf732 | |||
| e90200b4de | |||
| 7f36ba77a0 | |||
| 457c58827b | |||
| aae768611f | |||
| ce5ffbf667 | |||
| ab114af850 | |||
| 2759788737 | |||
| c643323c17 | |||
| 9b97a033b0 | |||
| a3226d01fa | |||
| 510d0f946f | |||
| 0b962d4881 | |||
| 71b4daa4e1 | |||
| e0b02a5040 | |||
| a0dd9b0fdd | |||
| 498bef199a | |||
| 17d34b7a98 | |||
| 72359fd097 | |||
| 72882190f2 | |||
| fd2d8a5755 | |||
| fbe84f95a1 | |||
| 3671a43be4 | |||
| f6a1ad528d | |||
| d9128e7b23 | |||
| 0b54c1d4a9 | |||
| 608d623b55 | |||
| 9f30bb5475 | |||
| 182fb18f00 | |||
| 3a9fdceeee | |||
| ee4ac72170 | |||
| eb9fc571a0 | |||
| b4d1e5e492 | |||
| 60394a9d91 | |||
| f5ddb10b56 | |||
| 6a448d3459 | |||
| 6a8c0f5f4a | |||
| 584308fc06 | |||
| 48ad2c44bf | |||
| 100998330a | |||
| b9e85c62be | |||
| 77fad099d2 | |||
| e1f8a6e82b | |||
| 132d767647 | |||
| 9cde98cbc7 | |||
| 1ddbb3ec3e | |||
| 34f40266b2 | |||
| b8c06e3f1b | |||
| a6cf648b3c | |||
| aebde27f1b | |||
| c9be327d53 | |||
| 3dc7d22d90 | |||
| c10c6cac74 | |||
| cee2c4c569 | |||
| a5160c82dc | |||
| 8853312197 | |||
| 049b24de21 | |||
| a9f987a0c9 | |||
| ad4a20d3d2 | |||
| b3972d1b65 | |||
| c3e0d58b3c | |||
| f08156ea9b | |||
| 4bacf5a5da | |||
| df88873bb7 | |||
| c4b1c5e8f1 | |||
| 9822711ad2 | |||
| 30cd764b6d | |||
| 38b75cdb2d | |||
| 6cb8b39af8 | |||
| ebaa336614 | |||
| ef5f567f91 | |||
| 64e5afc478 | |||
| 1e582e4fa4 | |||
| 7421235f24 | |||
| 09ba69078d | |||
| 3536e83d8a | |||
| 3bb1dd5d7f | |||
| 95f964b827 | |||
| c8f78a8ca9 | |||
| f34d48087b | |||
| 2c9ecd01b1 | |||
| a584fb6e15 | |||
| 1f13313f40 | |||
| 5b60303781 | |||
| 10e2552a7d | |||
| ef48b0aa55 | |||
| f57872bca0 | |||
| 2deaf2877e | |||
| 7a146c9cd5 | |||
| 2796ec172b | |||
| 6997c1bf0c | |||
| 4a030f3834 | |||
| f78d8b8ff3 | |||
| f27d19ed60 | |||
| 4a5eaf7bec | |||
| 8513674911 | |||
| 97b74ad6fb | |||
| a47ea79023 | |||
| 5ca0de6487 | |||
| 50a449f053 | |||
| d7422da7d7 | |||
| c7cbc978c6 | |||
| 27146e8a7f | |||
| 5e418b1145 | |||
| f4bb973eb7 | |||
| 848857aa5b | |||
| ee8a05d3f1 | |||
| 275ebbf0ec | |||
| 0f23df4c06 | |||
| 11f700f7bd | |||
| 5785f2a991 | |||
| 2546c29f81 | |||
| 19ea708c9d | |||
| 5cf05d67f2 | |||
| 0377c6f0e8 | |||
| 9c13866824 | |||
| 419a4813e3 | |||
| 131af8272d | |||
| c219a46f59 | |||
| 25f008f541 | |||
| 4a593db79b | |||
| ad4fef0431 | |||
| 8a15fcc1f5 | |||
| f01212ab52 | |||
| 28693a1a67 | |||
| 29fd499552 | |||
| 2f97d9d647 | |||
| 4146b38459 | |||
| 05aab660ce | |||
| 5ecb64849e | |||
| 59dfe1b5a0 | |||
| 50ebfb735c | |||
| 0bdbfe5069 | |||
| 3fc4d6028c | |||
| 2eb12a052b | |||
| bd63b2235c | |||
| f418ffd083 | |||
| 6ab5f8ce4b | |||
| becfeb5aa3 | |||
| eb968c4a68 | |||
| 7f2af3f923 | |||
| cce98ff53a | |||
| b607618342 | |||
| fa50fbaf57 | |||
| f135e2dc05 | |||
| 780351db5e | |||
| a50bb0bfec | |||
| 4d86df6f48 | |||
| bb464d16b4 | |||
| 85b2eb1472 | |||
| 7608f92c6a | |||
| c0bf8db63c | |||
| c95a6737fb | |||
| cd43d24402 | |||
| 086c5d0354 | |||
| 2a2ac5f53a | |||
| 21deaf637a | |||
| 72e15a3a83 | |||
| 174cb4a8c8 | |||
| 33f769b0a1 | |||
| c8abf20558 | |||
| 6dbb183e75 | |||
| bc4844d3b2 | |||
| 708f8b47de | |||
| 5518126d42 | |||
| 5fe73fdc3a | |||
| 394c496bf2 | |||
| 8e1aeba715 | |||
| 0e6e7eb477 | |||
| 183f636816 | |||
| 5f8ed63f2a | |||
| e4f3c94e31 | |||
| 1e5cbcbd93 | |||
| dcf3ec160f | |||
| a7beb5b6d3 | |||
| 1d3b65adc2 | |||
| d528644fe3 | |||
| 6f1bcfc14e | |||
| 996914c6b0 | |||
| fff048d099 | |||
| dcb0da8225 | |||
| b664e20d12 | |||
| 3d68b95028 | |||
| 163aca336d |
+1
-1
@@ -1,4 +1,4 @@
|
||||
# https://editorconfig.org
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
build/**
|
||||
docs/bower_components/**
|
||||
docs/app/assets/js/angular-bootstrap/**
|
||||
docs/config/templates/**
|
||||
node_modules/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- General PR submission guidelines https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#submit-pr -->
|
||||
<!-- General PR submission guidelines https://github.com/angular/angular.js/CONTRIBUTING.md#submit-pr -->
|
||||
**What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)**
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
|
||||
**Please check if the PR fulfills these requirements**
|
||||
- [ ] The commit message follows our [guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits)
|
||||
- [ ] Fix/Feature: [Docs](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#documentation) have been added/updated
|
||||
- [ ] The commit message follows our [guidelines](../DEVELOPERS.md#commits)
|
||||
- [ ] Fix/Feature: [Docs](../DEVELOPERS.md#documentation) have been added/updated
|
||||
- [ ] Fix/Feature: Tests have been added; existing tests pass
|
||||
|
||||
**Other information**:
|
||||
|
||||
@@ -32,6 +32,7 @@ before_install:
|
||||
before_script:
|
||||
- du -sh ./node_modules || true
|
||||
- "./scripts/travis/before_build.sh"
|
||||
|
||||
script:
|
||||
- "./scripts/travis/build.sh"
|
||||
|
||||
|
||||
+2
-1914
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -49,6 +49,7 @@ If you find a bug in the source code, you can help us by submitting an issue to
|
||||
**Special Note for Localization Issues:** AngularJS uses the [Google Closure I18N library] to
|
||||
generate its own I18N files (the ngLocale module). This means that any changes to these files
|
||||
would be lost the next time that we import the library.
|
||||
|
||||
Since the Closure library i18n data is itself auto-generated from the data of the
|
||||
[Common Locale Data Repository (CLDR)] project, errors in the data should
|
||||
be reported there. See also the [Closure guide to i18n changes].
|
||||
@@ -164,7 +165,6 @@ restarted.
|
||||
* Re-run the AngularJS test suite to ensure tests are still passing.
|
||||
* Commit your changes to your branch (e.g. `my-fix-branch`).
|
||||
* Push the changes to your GitHub repository (this will update your Pull Request).
|
||||
|
||||
You can also amend the initial commits and force push them to the branch.
|
||||
|
||||
```shell
|
||||
|
||||
@@ -185,6 +185,7 @@ module.exports = function(grunt) {
|
||||
'test/**/*.js',
|
||||
'i18n/**/*.js',
|
||||
'!docs/app/assets/js/angular-bootstrap/**',
|
||||
'!docs/bower_components/**',
|
||||
'!docs/config/templates/**',
|
||||
'!src/angular.bind.js',
|
||||
'!i18n/closure/**',
|
||||
@@ -194,6 +195,16 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
build: {
|
||||
scenario: {
|
||||
dest: 'build/angular-scenario.js',
|
||||
src: [
|
||||
'bower_components/jquery/dist/jquery.js',
|
||||
util.wrap([files['angularSrc'], files['angularScenario']], 'ngScenario/angular')
|
||||
],
|
||||
styles: {
|
||||
css: ['css/angular.css', 'css/angular-scenario.css']
|
||||
}
|
||||
},
|
||||
angular: {
|
||||
dest: 'build/angular.js',
|
||||
src: util.wrap([files['angularSrc']], 'angular'),
|
||||
@@ -279,7 +290,9 @@ module.exports = function(grunt) {
|
||||
files: [
|
||||
'src/**/*.js',
|
||||
'test/**/*.js',
|
||||
'!test/ngScenario/DescribeSpec.js',
|
||||
'!src/ng/directive/attrs.js', // legitimate xit here
|
||||
'!src/ngScenario/**/*.js',
|
||||
'!test/helpers/privateMocks*.js'
|
||||
],
|
||||
options: {
|
||||
|
||||
Vendored
+29
-6
@@ -28,7 +28,6 @@ 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',
|
||||
@@ -41,7 +40,6 @@ 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',
|
||||
@@ -76,7 +74,6 @@ 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',
|
||||
@@ -106,13 +103,13 @@ 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'
|
||||
],
|
||||
'ngCookies': [
|
||||
'src/ngCookies/cookies.js',
|
||||
'src/ngCookies/cookieStore.js',
|
||||
'src/ngCookies/cookieWriter.js'
|
||||
],
|
||||
'ngMessageFormat': [
|
||||
@@ -134,7 +131,6 @@ var angularFiles = {
|
||||
],
|
||||
'ngRoute': [
|
||||
'src/shallowCopy.js',
|
||||
'src/routeToRegExp.js',
|
||||
'src/ngRoute/route.js',
|
||||
'src/ngRoute/routeParams.js',
|
||||
'src/ngRoute/directive/ngView.js'
|
||||
@@ -144,13 +140,13 @@ var angularFiles = {
|
||||
'src/ngSanitize/filter/linky.js'
|
||||
],
|
||||
'ngMock': [
|
||||
'src/routeToRegExp.js',
|
||||
'src/ngMock/angular-mocks.js',
|
||||
'src/ngMock/browserTrigger.js'
|
||||
],
|
||||
'ngTouch': [
|
||||
'src/ngTouch/touch.js',
|
||||
'src/ngTouch/swipe.js',
|
||||
'src/ngTouch/directive/ngClick.js',
|
||||
'src/ngTouch/directive/ngSwipe.js'
|
||||
],
|
||||
'ngAria': [
|
||||
@@ -158,8 +154,26 @@ var angularFiles = {
|
||||
]
|
||||
},
|
||||
|
||||
'angularScenario': [
|
||||
'src/ngScenario/Scenario.js',
|
||||
'src/ngScenario/Application.js',
|
||||
'src/ngScenario/Describe.js',
|
||||
'src/ngScenario/Future.js',
|
||||
'src/ngScenario/ObjectModel.js',
|
||||
'src/ngScenario/Runner.js',
|
||||
'src/ngScenario/SpecRunner.js',
|
||||
'src/ngScenario/dsl.js',
|
||||
'src/ngScenario/matchers.js',
|
||||
'src/ngScenario/output/Html.js',
|
||||
'src/ngScenario/output/Json.js',
|
||||
'src/ngScenario/output/Xml.js',
|
||||
'src/ngScenario/output/Object.js'
|
||||
],
|
||||
|
||||
'angularTest': [
|
||||
'test/helpers/*.js',
|
||||
'test/ngScenario/*.js',
|
||||
'test/ngScenario/output/*.js',
|
||||
'test/*.js',
|
||||
'test/auto/*.js',
|
||||
'test/ng/**/*.js',
|
||||
@@ -180,15 +194,22 @@ var angularFiles = {
|
||||
'test/jquery_remove.js',
|
||||
'@angularSrc',
|
||||
'@angularSrcModules',
|
||||
'@angularScenario',
|
||||
'@angularTest'
|
||||
],
|
||||
|
||||
'karmaExclude': [
|
||||
'test/jquery_alias.js',
|
||||
'src/angular-bootstrap.js',
|
||||
'src/ngScenario/angular-bootstrap.js',
|
||||
'src/angular.bind.js'
|
||||
],
|
||||
|
||||
'karmaScenario': [
|
||||
'build/angular-scenario.js',
|
||||
'build/docs/docs-scenario.js'
|
||||
],
|
||||
|
||||
'karmaModules': [
|
||||
'build/angular.js',
|
||||
'@angularSrcModules',
|
||||
@@ -211,11 +232,13 @@ var angularFiles = {
|
||||
'test/jquery_alias.js',
|
||||
'@angularSrc',
|
||||
'@angularSrcModules',
|
||||
'@angularScenario',
|
||||
'@angularTest'
|
||||
],
|
||||
|
||||
'karmaJqueryExclude': [
|
||||
'src/angular-bootstrap.js',
|
||||
'src/ngScenario/angular-bootstrap.js',
|
||||
'test/jquery_remove.js',
|
||||
'src/angular.bind.js'
|
||||
]
|
||||
|
||||
@@ -29,16 +29,19 @@ app.directive('bmPeWatch', function() {
|
||||
};
|
||||
});
|
||||
|
||||
//Executes the specified expression as a collection watcher
|
||||
app.directive('bmPeWatchCollection', function() {
|
||||
//Executes the specified expression as a watcher
|
||||
//Adds a simple wrapper method to allow use of $watch instead of $watchCollection
|
||||
app.directive('bmPeWatchLiteral', function($parse) {
|
||||
function retZero() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function($element, $attrs) {
|
||||
$element.text($attrs.bmPeWatchCollection);
|
||||
$element.text($attrs.bmPeWatchLiteral);
|
||||
return function($scope, $element, $attrs) {
|
||||
$scope.$watchCollection($attrs.bmPeWatchCollection, function(val) {
|
||||
$element.text(val);
|
||||
});
|
||||
$scope.$watch($parse($attrs.bmPeWatchLiteral, retZero));
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -69,7 +72,8 @@ app.controller('DataController', function($scope, $rootScope) {
|
||||
date2: new Date(Math.random() * Date.now()),
|
||||
func: function() { return star; },
|
||||
obj: data[i - 1],
|
||||
keys: data[i - 1] && (data[i - 1].keys || Object.keys(data[i - 1]))
|
||||
keys: data[i - 1] && (data[i - 1].keys || Object.keys(data[i - 1])),
|
||||
constructor: data[i - 1]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
<label for="complexPath">Complex Paths</label>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="radio" ng-model="expressionType" value="constructorPath" id="constructorPath">
|
||||
<label for="constructorPath">Constructor Paths</label>
|
||||
($parse special cases "constructor" for security)
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="radio" ng-model="expressionType" value="fieldAccess" id="fieldAccess">
|
||||
<label for="fieldAccess">Field Accessors</label>
|
||||
@@ -60,16 +66,6 @@
|
||||
<input type="radio" ng-model="expressionType" value="arrayLiterals" id="arrayLiterals">
|
||||
<label for="arrayLiterals">Array Literals</label>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="radio" ng-model="expressionType" value="watchCollection" id="watchCollection">
|
||||
<label for="watchCollection">$watchCollection</label>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="radio" ng-model="expressionType" value="watchCollectionLiterals" id="watchCollectionLiterals">
|
||||
<label for="watchCollectionLiterals">$watchCollection Literals</label>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!--
|
||||
@@ -92,6 +88,17 @@
|
||||
<span bm-pe-watch="row.keys"></span>
|
||||
</li>
|
||||
|
||||
<li ng-switch-when="constructorPath" ng-repeat="(rowIdx, row) in ::data">
|
||||
<span bm-pe-watch="row.index"></span>
|
||||
<span bm-pe-watch="row.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.constructor.index"></span>
|
||||
</li>
|
||||
|
||||
<li ng-switch-when="complexPath" ng-repeat="(rowIdx, row) in ::data">
|
||||
<span bm-pe-watch="row.index"></span>
|
||||
<span bm-pe-watch="row.num0"></span>
|
||||
@@ -208,44 +215,27 @@
|
||||
</li>
|
||||
|
||||
<li ng-switch-when="objectLiterals" ng-repeat="(rowIdx, row) in ::data">
|
||||
<span bm-pe-watch="{foo: rowIdx}"></span>
|
||||
<span bm-pe-watch="{foo: row, bar: rowIdx}"></span>
|
||||
<span bm-pe-watch="{0: row, 1: rowIdx, 2: 3}"></span>
|
||||
<span bm-pe-watch="{str: 'foo', num: rowIdx, b: true}"></span>
|
||||
<span bm-pe-watch="{a: {b: {c: {d: {e: {f: rowIdx}}}}}}"></span>
|
||||
<span bm-pe-watch="{a: rowIdx, b: 1, c: 2, d: 3, e: 4, f: 5, g: rowIdx, h: 6, i: 7, j: 8, k: rowIdx}"></span>
|
||||
<span bm-pe-watch-literal="{foo: rowIdx}"></span>
|
||||
<span bm-pe-watch-literal="{foo: row, bar: rowIdx}"></span>
|
||||
<span bm-pe-watch-literal="{0: row, 1: rowIdx, 2: 3}"></span>
|
||||
<span bm-pe-watch-literal="{str: 'foo', num: rowIdx, b: true}"></span>
|
||||
<span bm-pe-watch-literal="{a: {b: {c: {d: {e: {f: rowIdx}}}}}}"></span>
|
||||
<span bm-pe-watch-literal="{a: rowIdx, b: 1, c: 2, d: 3, e: 4, f: 5, g: rowIdx, h: 6, i: 7, j: 8, k: rowIdx}"></span>
|
||||
</li>
|
||||
|
||||
<li ng-switch-when="arrayLiterals" ng-repeat="(rowIdx, row) in ::data">
|
||||
<span bm-pe-watch="[rowIdx]"></span>
|
||||
<span bm-pe-watch="[rowIdx, 0]"></span>
|
||||
<span bm-pe-watch="[rowIdx, 0, 1]"></span>
|
||||
<span bm-pe-watch="[rowIdx, 0, 1, 2]"></span>
|
||||
<span bm-pe-watch="[rowIdx, 0, 1, 2, 3]"></span>
|
||||
<span bm-pe-watch="[[], [rowIdx], [], [], [3], [[[]]]]"></span>
|
||||
<span bm-pe-watch="[rowIdx, undefined, null, true, false]"></span>
|
||||
<span bm-pe-watch="[[][0], [0][0], [][rowIdx]]"></span>
|
||||
<span bm-pe-watch="[0, rowIdx]"></span>
|
||||
<span bm-pe-watch="[0, 1, rowIdx]"></span>
|
||||
<span bm-pe-watch="[0, 1, 2, rowIdx]"></span>
|
||||
<span bm-pe-watch="[0, 1, 2, 3, rowIdx]"></span>
|
||||
</li>
|
||||
|
||||
<li ng-switch-when="watchCollection" ng-repeat="(rowIdx, row) in data">
|
||||
<span bm-pe-watch-collection="data"></span>
|
||||
<span bm-pe-watch-collection="row.keys"></span>
|
||||
<span bm-pe-watch-collection="thisProbablyDoesntHaveAValue"></span>
|
||||
</li>
|
||||
|
||||
<li ng-switch-when="watchCollectionLiterals" ng-repeat="(rowIdx, row) in ::data">
|
||||
<span bm-pe-watch-collection="[rowIdx, row]"></span>
|
||||
<span bm-pe-watch-collection="[rowIdx, row, num0, str0, date0, obj, g, h, i, j, k, l, m, n, o, p]"></span>
|
||||
<span bm-pe-watch-collection="{a: rowIdx, b: row, c: num0, d: str0, e: date0, f: obj, g: g, h: h, i: i, j: j, k: k, l: l, m: m, n: n, o: o, p: p}"></span>
|
||||
|
||||
<!-- primitive/valueOf-compatible -->
|
||||
<span bm-pe-watch-collection="[rowIdx, row]"></span>
|
||||
<span bm-pe-watch-collection="[rowIdx, num0, str0, date0, date1, h, i, j, k, l, m, n, o, p]"></span>
|
||||
<span bm-pe-watch-collection="{a: rowIdx, c: num0, d: str0, e: date0, g: date1, h: h, i: i, j: j, k: k, l: l, m: m, n: n, o: o, p: p}"></span>
|
||||
<span bm-pe-watch-literal="[rowIdx]"></span>
|
||||
<span bm-pe-watch-literal="[rowIdx, 0]"></span>
|
||||
<span bm-pe-watch-literal="[rowIdx, 0, 1]"></span>
|
||||
<span bm-pe-watch-literal="[rowIdx, 0, 1, 2]"></span>
|
||||
<span bm-pe-watch-literal="[rowIdx, 0, 1, 2, 3]"></span>
|
||||
<span bm-pe-watch-literal="[[], [rowIdx], [], [], [3], [[[]]]]"></span>
|
||||
<span bm-pe-watch-literal="[rowIdx, undefined, null, true, false]"></span>
|
||||
<span bm-pe-watch-literal="[[][0], [0][0], [][rowIdx]]"></span>
|
||||
<span bm-pe-watch-literal="[0, rowIdx]"></span>
|
||||
<span bm-pe-watch-literal="[0, 1, rowIdx]"></span>
|
||||
<span bm-pe-watch-literal="[0, 1, 2, rowIdx]"></span>
|
||||
<span bm-pe-watch-literal="[0, 1, 2, 3, rowIdx]"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', ['ngAnimate'])
|
||||
.config(function($animateProvider) {
|
||||
$animateProvider.classNameFilter(/animate-/);
|
||||
})
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'classfilter';
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', [])
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'noanimate';
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', ['ngAnimate'])
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'default';
|
||||
});
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/* 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'
|
||||
}]
|
||||
});
|
||||
};
|
||||
@@ -1,120 +0,0 @@
|
||||
'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();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
})();
|
||||
@@ -1,70 +0,0 @@
|
||||
<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>
|
||||
@@ -13,8 +13,7 @@ body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#json,
|
||||
#xml {
|
||||
#json, #xml {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
+2
-6
@@ -1,11 +1,7 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
[ng\:cloak],
|
||||
[ng-cloak],
|
||||
[data-ng-cloak],
|
||||
[x-ng-cloak],
|
||||
.ng-cloak,
|
||||
.x-ng-cloak,
|
||||
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],
|
||||
.ng-cloak, .x-ng-cloak,
|
||||
.ng-hide:not(.ng-hide-animate) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
describe('table of contents', function() {
|
||||
|
||||
it('on provider pages', function() {
|
||||
browser.get('build/docs/index.html#!/api/ng/provider/$controllerProvider');
|
||||
browser.get('build/docs/index.html#!/api/ng/provider/$interpolateProvider');
|
||||
|
||||
var toc = element.all(by.css('toc-container > div > toc-tree'));
|
||||
toc.getText().then(function(text) {
|
||||
@@ -19,7 +19,7 @@ describe('table of contents', function() {
|
||||
var tocFirstLevel = element.all(by.css('toc-container > div > toc-tree > ul > li'));
|
||||
|
||||
tocFirstLevel.then(function(match) {
|
||||
expect(match.length).toBe(2);
|
||||
expect(match.length).toBe(3);
|
||||
|
||||
expect(match[1].all(by.css('li')).count()).toBe(2);
|
||||
});
|
||||
|
||||
@@ -98,7 +98,7 @@ directivesModule
|
||||
bindings: {
|
||||
items: '<'
|
||||
},
|
||||
controller: ['$location', /** @this */ function($location) {
|
||||
controller: ['$location', function TocTreeController($location) {
|
||||
this.path = $location.path().replace(/^\/?(.+?)(\/index)?\/?$/, '$1');
|
||||
}]
|
||||
})
|
||||
|
||||
@@ -148,7 +148,6 @@ 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; };
|
||||
})
|
||||
|
||||
@@ -5,36 +5,18 @@
|
||||
* @description
|
||||
* Process "error" docType docs and generate errorNamespace docs
|
||||
*/
|
||||
module.exports = function errorDocsProcessor(log, errorNamespaceMap, getMinerrInfo) {
|
||||
module.exports = function errorDocsProcessor(errorNamespaceMap, getMinerrInfo) {
|
||||
return {
|
||||
$runAfter: ['tags-extracted'],
|
||||
$runBefore: ['extra-docs-added'],
|
||||
$process: function(docs) {
|
||||
|
||||
// Get the extracted min errors to compare with the error docs, and report any mismatch
|
||||
var collectedErrors = require('../../../build/errors.json').errors;
|
||||
var flatErrors = [];
|
||||
|
||||
for (var namespace in collectedErrors) {
|
||||
for (var error in collectedErrors[namespace]) {
|
||||
flatErrors.push(namespace + ':' + error);
|
||||
}
|
||||
}
|
||||
|
||||
// Create error namespace docs and attach error docs to each
|
||||
docs.forEach(function(doc) {
|
||||
var parts, namespaceDoc;
|
||||
|
||||
if (doc.docType === 'error') {
|
||||
|
||||
var matchingMinErr = flatErrors.indexOf(doc.name);
|
||||
|
||||
if (matchingMinErr === -1) {
|
||||
log.warn('Error doc: ' + doc.name + ' has no matching min error');
|
||||
} else {
|
||||
flatErrors.splice(matchingMinErr, 1);
|
||||
}
|
||||
|
||||
// Parse out the error info from the id
|
||||
parts = doc.name.split(':');
|
||||
doc.namespace = parts[0];
|
||||
@@ -59,10 +41,6 @@ module.exports = function errorDocsProcessor(log, errorNamespaceMap, getMinerrIn
|
||||
}
|
||||
});
|
||||
|
||||
flatErrors.forEach(function(value) {
|
||||
log.warn('No error doc exists for min error: ' + value);
|
||||
});
|
||||
|
||||
errorNamespaceMap.forEach(function(errorNamespace) {
|
||||
docs.push(errorNamespace);
|
||||
});
|
||||
|
||||
@@ -222,7 +222,11 @@ Use the ngCookies module to handle cookie management within your application.
|
||||
{@link ngCookies#service Services / Factories}
|
||||
</td>
|
||||
<td>
|
||||
The {@link ngCookies.$cookies $cookies} service is a convenient wrapper to store simple data within browser cookies.
|
||||
The following services are used for cookie management:
|
||||
<ul>
|
||||
<li>The {@link ngCookies.$cookies $cookie} service is a convenient wrapper to store simple data within browser cookies.</li>
|
||||
<li>{@link ngCookies.$cookieStore $cookieStore} is used to store more complex data using serialization.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
@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
|
||||
```
|
||||
@@ -10,15 +10,13 @@ myModule.directive('directiveName', function factory() {
|
||||
return {
|
||||
...
|
||||
scope: {
|
||||
'localName': '@', // OK
|
||||
'localName2': '&attr', // OK
|
||||
'localName3': '<?attr', // OK
|
||||
'localName4': ' = attr', // OK
|
||||
'localName5': ' =*attr', // OK
|
||||
'localName6': 'attr', // ERROR: missing mode @&=<
|
||||
'localName7': 'attr=', // ERROR: must be prefixed with @&=<
|
||||
'localName8': '=attr?', // ERROR: ? must come directly after the mode
|
||||
'localName9': '<*' // ERROR: * is only valid with =
|
||||
'attrName': '@', // OK
|
||||
'attrName2': '=localName', // OK
|
||||
'attrName3': '<?localName', // OK
|
||||
'attrName4': ' = name', // OK
|
||||
'attrName5': 'name', // ERROR: missing mode @&=
|
||||
'attrName6': 'name=', // ERROR: must be prefixed with @&=
|
||||
'attrName7': '=name?', // ERROR: ? must come directly after the mode
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
@@ -3,34 +3,6 @@
|
||||
@fullName Missing required attribute
|
||||
@description
|
||||
|
||||
This error may occur only when {@link $compileProvider#strictComponentBindingsEnabled `$compileProvider.strictComponentBindingsEnabled`} is set to `true`.
|
||||
|
||||
If that is the case, then all {@link $compileProvider#component component} controller bindings and
|
||||
{@link $compileProvider#directive directive} scope / controller bindings that are non-optional,
|
||||
must be provided when the directive is instantiated.
|
||||
|
||||
To make a binding optional, add '?' to the definition.
|
||||
|
||||
## Example:
|
||||
|
||||
```js
|
||||
|
||||
app.component('myTest', {
|
||||
bindings: {
|
||||
first: '=?', // optional
|
||||
second: '='
|
||||
},
|
||||
controller: function() {
|
||||
...
|
||||
},
|
||||
template: '...'
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
This component will throw `missingattr` for the `second` binding when used as follows:
|
||||
|
||||
```html
|
||||
<my-test></my-test>
|
||||
```
|
||||
|
||||
This error may occur only when `$compileProvider.strictComponentBindingsEnabled` is set to `true`.
|
||||
Then all attributes mentioned in `bindings` without `?` must be set. If one or more aren't set,
|
||||
the first one will throw an error.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
@ngdoc error
|
||||
@name $compile:nodomevents
|
||||
@fullName Event Attribute/Property Binding
|
||||
@fullName Interpolated Event Attributes
|
||||
@description
|
||||
|
||||
This error occurs when one tries to create a binding for event handler attributes or properties like `onclick`, `onload`, `onsubmit`, etc.
|
||||
This error occurs when one tries to create a binding for event handler attributes like `onclick`, `onload`, `onsubmit`, etc.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
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`).
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $compile:srcset
|
||||
@fullName Invalid value passed to `attr.$set('srcset', value)`
|
||||
@description
|
||||
|
||||
This error occurs if you try to programmatically set the `srcset` attribute with a non-string value.
|
||||
|
||||
This can be the case if you tried to avoid the automatic sanitization of the `srcset` value by
|
||||
passing a "trusted" value provided by calls to `$sce.trustAsMediaUrl(value)`.
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,11 @@
|
||||
@ngdoc error
|
||||
@name $compile:tpload
|
||||
@fullName Error Loading Template
|
||||
@description
|
||||
|
||||
This error occurs when {@link ng.$compile `$compile`} attempts to fetch a template from some URL, and the request fails.
|
||||
|
||||
To resolve this error, ensure that the URL of the template is spelled correctly and resolves to correct absolute URL.
|
||||
The [Chrome Developer Tools](https://developers.google.com/chrome-developer-tools/docs/network#network_panel_overview) might also be helpful in determining why the request failed.
|
||||
|
||||
If you are using {@link ng.$templateCache} to pre-load templates, ensure that the cache was populated with the template.
|
||||
@@ -9,7 +9,7 @@ value is `JSON_CALLBACK`.
|
||||
|
||||
`$http` JSONP requests need to attach a callback query parameter to the URL. The name of this
|
||||
parameter is specified in the configuration object (or in the defaults) via the `jsonpCallbackParam`
|
||||
property. You must not provide your own parameter with this name in the configuration of the request.
|
||||
property. You must not provide your own parameter with this name in the configuratio of the request.
|
||||
|
||||
In previous versions of AngularJS, you specified where to add the callback parameter value via the
|
||||
`JSON_CALLBACK` placeholder. This is no longer allowed.
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $interval:badprom
|
||||
@fullName Non-$interval promise
|
||||
@description
|
||||
|
||||
This error occurs when calling {@link ng.$interval#cancel $interval.cancel()} with a promise that
|
||||
was not generated by the {@link ng.$interval $interval} service. This can, for example, happen when
|
||||
calling {@link ng.$q#the-promise-api then()/catch()} on the returned promise, which creates a new
|
||||
promise, and pass that new promise to {@link ng.$interval#cancel $interval.cancel()}.
|
||||
|
||||
Example of incorrect usage that leads to this error:
|
||||
|
||||
```js
|
||||
var promise = $interval(doSomething, 1000, 5).then(doSomethingElse);
|
||||
$interval.cancel(promise);
|
||||
```
|
||||
|
||||
To fix the example above, keep a reference to the promise returned by
|
||||
{@link ng.$interval $interval()} and pass that to {@link ng.$interval#cancel $interval.cancel()}:
|
||||
|
||||
```js
|
||||
var promise = $interval(doSomething, 1000, 5);
|
||||
var newPromise = promise.then(doSomethingElse);
|
||||
$interval.cancel(promise);
|
||||
```
|
||||
@@ -1,18 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $templateRequest:tpload
|
||||
@fullName Error Loading Template
|
||||
@description
|
||||
|
||||
This error occurs when {@link $templateRequest} attempts to fetch a template from some URL, and
|
||||
the request fails.
|
||||
|
||||
The template URL might be defined in a directive/component definition, an instance of `ngInclude`,
|
||||
an instance of `ngMessagesInclude` or a templated route in a `$route` route definition.
|
||||
|
||||
To resolve this error, ensure that the URL of the template is spelled correctly and resolves to
|
||||
correct absolute URL.
|
||||
The [Chrome Developer Tools](https://developers.google.com/chrome-developer-tools/docs/network#network_panel_overview)
|
||||
might also be helpful in determining why the request failed.
|
||||
|
||||
If you are using {@link ng.$templateCache} to pre-load templates, ensure that the cache was
|
||||
populated with the template.
|
||||
@@ -1,25 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $timeout:badprom
|
||||
@fullName Non-$timeout promise
|
||||
@description
|
||||
|
||||
This error occurs when calling {@link ng.$timeout#cancel $timeout.cancel()} with a promise that
|
||||
was not generated by the {@link ng.$timeout $timeout} service. This can, for example, happen when
|
||||
calling {@link ng.$q#the-promise-api then()/catch()} on the returned promise, which creates a new
|
||||
promise, and pass that new promise to {@link ng.$timeout#cancel $timeout.cancel()}.
|
||||
|
||||
Example of incorrect usage that leads to this error:
|
||||
|
||||
```js
|
||||
var promise = $timeout(doSomething, 1000).then(doSomethingElse);
|
||||
$timeout.cancel(promise);
|
||||
```
|
||||
|
||||
To fix the example above, keep a reference to the promise returned by
|
||||
{@link ng.$timeout $timeout()} and pass that to {@link ng.$timeout#cancel $timeout.cancel()}:
|
||||
|
||||
```js
|
||||
var promise = $timeout(doSomething, 1000);
|
||||
var newPromise = promise.then(doSomethingElse);
|
||||
$timeout.cancel(promise);
|
||||
```
|
||||
@@ -3,9 +3,12 @@
|
||||
@fullName Unsupported Selector Lookup
|
||||
@description
|
||||
|
||||
In order to keep AngularJS small, AngularJS implements only a subset of the selectors in {@link angular.element#angularjs-s-jqlite jqLite}.
|
||||
In order to keep AngularJS small, AngularJS implements only a subset of the selectors in
|
||||
{@link angular.element#angularjs-s-jqlite jqLite}.
|
||||
This error occurs when a jqLite instance is invoked with a selector other than this subset.
|
||||
|
||||
In order to resolve this error, rewrite your code to only use tag name selectors and manually traverse the DOM using the APIs provided by jqLite.
|
||||
In order to resolve this error, rewrite your code to only use tag name selectors and manually
|
||||
traverse the DOM using the APIs provided by jqLite.
|
||||
|
||||
Alternatively, you can include a full version of jQuery, which AngularJS will automatically use and that will make all selectors available.
|
||||
Alternatively, you can include a full version of jQuery, which AngularJS will automatically use
|
||||
and that will make all selectors available.
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
@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">`.
|
||||
@@ -1,27 +0,0 @@
|
||||
@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
|
||||
|
||||
# Using the `$location` service
|
||||
# What does it do?
|
||||
|
||||
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,17 +746,22 @@ 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
|
||||
|
||||
Most modern search engines are able to crawl AJAX applications with dynamic content, provided all
|
||||
included resources are available to the crawler bots.
|
||||
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
|
||||
your document:
|
||||
|
||||
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.
|
||||
```html
|
||||
<meta name="fragment" content="!" />
|
||||
```
|
||||
|
||||
## Testing with the $location service
|
||||
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
|
||||
|
||||
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()`.
|
||||
@@ -779,6 +784,85 @@ 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 }"`
|
||||
@@ -800,6 +884,6 @@ angular.module('locationExample', [])
|
||||
</file>
|
||||
</example>
|
||||
|
||||
## Related API
|
||||
# Related API
|
||||
|
||||
* {@link ng.$location `$location` API}
|
||||
|
||||
@@ -222,26 +222,23 @@ triggered:
|
||||
|
||||
| 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: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: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 the documentation associated with each directive.)
|
||||
| {@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 module:ngMessages#animations ngMessages} | add and remove (the `ng-active`/`ng-inactive` classes) |
|
||||
|
||||
For a full breakdown of the steps involved during each animation event, refer to the
|
||||
{@link ng.$animate `$animate` API docs}.
|
||||
{@link ng.$animate API docs}.
|
||||
|
||||
## How do I use animations in my own directives?
|
||||
|
||||
@@ -471,7 +468,7 @@ You can also use one of the other
|
||||
strategies to disable animations}.
|
||||
|
||||
|
||||
## Enable animations outside of the application DOM tree: {@link ng.$animate#pin $animate.pin()}
|
||||
### Enable animations for elements outside of the AngularJS application DOM tree: {@link ng.$animate#pin $animate.pin()}
|
||||
|
||||
Before animating, `ngAnimate` checks if the animated element is inside the application DOM tree. If
|
||||
not, no animation is run. Usually, this is not a problem since most apps use the `html` or `body`
|
||||
|
||||
@@ -14,7 +14,6 @@ This guide describes the Component Router for AngularJS.
|
||||
|
||||
<div class="alert alert-info">
|
||||
If you are looking for information about the default router for AngularJS have a look at the {@link ngRoute} module.
|
||||
|
||||
If you are looking for information about the Component Router for the new Angular then
|
||||
check out the [Angular Router Guide](https://angular.io/docs/ts/latest/guide/router.html).
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@ When not to use Components:
|
||||
|
||||
## Creating and configuring a Component
|
||||
|
||||
Components can be registered using the {@link ng.$compileProvider#component `.component()`} method of an AngularJS module (returned by {@link module `angular.module()`}). The method takes two arguments:
|
||||
Components can be registered using the `.component()` method of an AngularJS module (returned by {@link module `angular.module()`}). The method takes two arguments:
|
||||
|
||||
* The name of the Component (as string).
|
||||
* The Component config object. (Note that, unlike the `.directive()` method, this method does **not** take a factory function.)
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ objects (or primitives) assigned to the scope become model properties. Any metho
|
||||
the scope are available in the template/view, and can be invoked via AngularJS expressions
|
||||
and `ng` event handler directives (e.g. {@link ng.directive:ngClick ngClick}).
|
||||
|
||||
|
||||
## Simple Spicy Controller Example
|
||||
|
||||
To illustrate further how Controller components work in AngularJS, let's create a little app with the
|
||||
@@ -156,7 +157,7 @@ string "very". Depending on which button is clicked, the `spice` model is set to
|
||||
|
||||
Things to notice in the example above:
|
||||
|
||||
- The `ng-controller` directive is used to (implicitly) create a scope for our template, and the
|
||||
- The `ngController` directive is used to (implicitly) create a scope for our template, and the
|
||||
scope is augmented (managed) by the `SpicyController` Controller.
|
||||
- `SpicyController` is just a plain JavaScript function. As an (optional) naming convention the name
|
||||
starts with capital letter and ends with "Controller".
|
||||
|
||||
@@ -125,8 +125,8 @@ and comments (M).
|
||||
|
||||
The built-in AngularJS directives show in their documentation page which type of matching they support.
|
||||
|
||||
The following demonstrates the various ways a directive (`myDir` in this case) that matches all
|
||||
4 types can be referenced from within a template.
|
||||
The following demonstrates the various ways a directive that matches all 4 types
|
||||
(`myDir` in this case) can be referenced from within a template.
|
||||
|
||||
```html
|
||||
<my-dir></my-dir>
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
# E2E Testing
|
||||
|
||||
<div class="alert alert-danger">
|
||||
**Note:** In the past, end-to-end testing could be done with a deprecated tool called
|
||||
[AngularJS Scenario Runner](http://code.angularjs.org/1.2.16/docs/guide/e2e-testing). That tool
|
||||
is now in maintenance mode, and will be removed in version 1.7.0.
|
||||
</div>
|
||||
|
||||
As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
|
||||
verify the correctness of new features, catch bugs and notice regressions. Unit tests
|
||||
are the first line of defense for catching bugs, but sometimes issues come up with integration
|
||||
|
||||
@@ -37,8 +37,8 @@ AngularJS expressions are like JavaScript expressions with the following differe
|
||||
even inside `ng-init` directive.
|
||||
|
||||
* **No RegExp Creation With Literal Notation:** You cannot create regular expressions
|
||||
in an AngularJS expression. An exception to this rule is {@link ngPattern `ng-pattern`} which accepts valid
|
||||
RegExp.
|
||||
in an AngularJS expression. An exception to this rule is {@link ngPattern `ng-pattern`} which
|
||||
accepts valid RegExp.
|
||||
|
||||
* **No Object Creation With New Operator:** You cannot use `new` operator in an AngularJS expression.
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ Official announcements, news and releases are posted to our blog, G+ and Twitter
|
||||
|
||||
* [AngularJS Blog](http://blog.angularjs.org/)
|
||||
* [Google+](https://plus.google.com/u/0/+AngularJS)
|
||||
* [Twitter](https://twitter.com/angular)
|
||||
* [Twitter](https://twitter.com/angularjs)
|
||||
* [AngularJS on YouTube](http://youtube.com/angularjs)
|
||||
|
||||
## Contributing to AngularJS
|
||||
|
||||
@@ -15,810 +15,6 @@ which drives many of these changes.
|
||||
* Several new features, especially animations, would not be possible without a few changes.
|
||||
* Finally, some outstanding bugs were best fixed by changing an existing API.
|
||||
|
||||
|
||||
## Migrating from 1.6 to 1.7
|
||||
|
||||
AngularJS 1.7 contains bug fixes and features to AngularJS core and its external modules, some of
|
||||
which contain breaking changes. However, most of these address internal behavior and not APIs, and
|
||||
should not affect many applications.
|
||||
Additionally, we have removed some long-deprecated modules and APIs.
|
||||
|
||||
The most notable changes are:
|
||||
|
||||
- `$resource` has now support for request and requestError interceptors
|
||||
|
||||
- Several deprecated features have been removed:
|
||||
- the `$controllerProvider.allowGlobals()` flag
|
||||
- the `$compileProvider.preAssignBindingsEnabled()` flag
|
||||
- the `angular.lowercase` and `angular.uppercase` methods
|
||||
- the `$cookieStore` service from the `ngCookies` module
|
||||
- the `ngClick` override directive and corresponding services from the `ngTouch` module
|
||||
- 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.
|
||||
|
||||
|
||||
<br />
|
||||
Below is the full list of breaking changes:
|
||||
|
||||
|
||||
<br />
|
||||
<a name="migrate1.6to1.7-ng-directives"></a>
|
||||
### Core: _Directives_
|
||||
|
||||
|
||||
#### **form**
|
||||
|
||||
**Due to [223de5](https://github.com/angular/angular.js/commit/223de59e988dc0cc8b4ec3a045b7c0735eba1c77)**,
|
||||
forms will now set `$submitted` on child forms when they are submitted.
|
||||
For example:
|
||||
```
|
||||
<form name="parentform" ng-submit="$ctrl.submit()">
|
||||
<ng-form name="childform">
|
||||
<input type="text" name="input" ng-model="my.model" />
|
||||
</ng-form>
|
||||
<input type="submit" />
|
||||
</form>
|
||||
```
|
||||
|
||||
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
|
||||
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"
|
||||
happens.
|
||||
|
||||
Two scenarios might need migration:
|
||||
|
||||
- Custom click events:
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
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.
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
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);
|
||||
});
|
||||
```
|
||||
|
||||
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);
|
||||
|
||||
$rootElement.append(inputElm);
|
||||
$document.append($rootElement);
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
{
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
var maxValidator = ctrl.$validators.max;
|
||||
|
||||
ctrk.$validators.max = function(modelValue, viewValue) {
|
||||
return maxValidator(modelValue, modelValue);
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### **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" 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
|
||||
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.
|
||||
|
||||
Previously, it also applied to other update triggers defined in 'updateOn' that
|
||||
did not have a corresponding key in the 'debounce'.
|
||||
|
||||
This behavior is now supported via a special wildcard / catch-all key: '*'.
|
||||
|
||||
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 }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### **ngStyle**
|
||||
|
||||
**Due to [15bbd3](https://github.com/angular/angular.js/commit/15bbd3e18cd89b91f7206a06c73d40e54a8a48a0)**,
|
||||
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)**,
|
||||
directive bindings are no longer available in the constructor.
|
||||
|
||||
Previously, the `$compileProvider.preAssignBindingsEnabled` flag was supported.
|
||||
The flag controlled whether bindings were available inside the controller
|
||||
constructor or only in the `$onInit` hook. The bindings are now no longer
|
||||
available in the constructor.
|
||||
|
||||
To migrate your code:
|
||||
|
||||
1. If you haven't invoked `$compileProvider.preAssignBindingsEnabled()` you
|
||||
don't have to do anything to migrate.
|
||||
|
||||
2. If you specified `$compileProvider.preAssignBindingsEnabled(false)`, you
|
||||
can remove that statement - since AngularJS 1.6.0 this is the default so your
|
||||
app should still work even in AngularJS 1.6 after such removal. Afterwards,
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
To fix this you need to ensure that the values used for binding to the affected
|
||||
`xlink:href` contexts are considered safe URLs, e.g. by whitelisting them in
|
||||
`$compileProvider`'s `aHrefSanitizationWhitelist` (for `<a>` elements) or
|
||||
`imgSrcSanitizationWhitelist` (for `<image>` elements).
|
||||
|
||||
<hr />
|
||||
|
||||
**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
|
||||
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
|
||||
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.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [1cf728](https://github.com/angular/angular.js/commit/1cf728e209a9e0016068fac2769827e8f747760e)**,
|
||||
`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
|
||||
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.
|
||||
|
||||
Also, concatenation in trusted contexts is not allowed, which means that the
|
||||
following won't work: `<base href="/something/{{ $ctrl.partialPath }}" />`.
|
||||
|
||||
Either construct complex values in a controller (recommended):
|
||||
|
||||
```js
|
||||
this.baseUrl = '/something/' + this.partialPath;
|
||||
```
|
||||
```html
|
||||
<base href="{{ $ctrl.baseUrl }}" />
|
||||
```
|
||||
|
||||
Or use string concatenation in the interpolation expression (not recommended
|
||||
except for the simplest of cases):
|
||||
|
||||
```html
|
||||
<base href="{{ '/something/' + $ctrl.partialPath }}" />
|
||||
```
|
||||
|
||||
|
||||
#### **$rootScope**
|
||||
|
||||
**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
|
||||
`oldValues` represented the *most recent change of each entry*.
|
||||
|
||||
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.
|
||||
|
||||
For example `$scope.$watchGroup(['a', 'b'], fn)` would previously:
|
||||
|
||||
| Action | newValue | oldValue |
|
||||
|----------|------------|------------|
|
||||
| (init) | [undefined, undefined] | [undefined, undefined] |
|
||||
| `a=1` | [1, undefined] | [undefined, undefined] |
|
||||
| `a=2` | [2, undefined] | [1, undefined] |
|
||||
| `b=3` | [2, 3] | [1, undefined] |
|
||||
|
||||
|
||||
Now the `oldValue` will always equal the previous `newValue`:
|
||||
|
||||
| Action | newValue | oldValue |
|
||||
|----------|------------|------------|
|
||||
| (init) | [undefined, undefined] | [undefined, undefined] |
|
||||
| `a=1` | [1, undefined] | [undefined, undefined] |
|
||||
| `a=2` | [2, undefined] | [1, undefined] |
|
||||
| `b=3` | [2, 3] | [2, undefined] |
|
||||
|
||||
Note the last call now shows `a === 2` in the `oldValues` array.
|
||||
|
||||
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:
|
||||
|
||||
| Action | newValue | oldValue |
|
||||
|----------|------------|------------|
|
||||
| (init) | [undefined, undefined] | [undefined, undefined] |
|
||||
| `a=1` | [1, undefined] | [undefined, undefined] |
|
||||
| `b=2` | [1, 2] | [undefined, undefined] |
|
||||
| `a=b=3` | [3, 2] | [1, undefined] |
|
||||
|
||||
Where now the `oldValue` will always equal the previous `newValue`:
|
||||
|
||||
| Action | newValue | oldValue |
|
||||
|----------|------------|------------|
|
||||
| (init) | [undefined, undefined] | [undefined, undefined] |
|
||||
| `a=1` | [1, undefined] | [undefined, undefined] |
|
||||
| `b=2` | [1, 2] | [1, undefined] |
|
||||
| `a=b=3` | [3, 2] | [1, 2] |
|
||||
|
||||
|
||||
#### **$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()`. Previously, it would silently do nothing.
|
||||
|
||||
Before:
|
||||
```js
|
||||
var promise = $interval(doSomething, 1000, 5).then(doSomethingElse);
|
||||
$interval.cancel(promise); // No error; interval NOT canceled.
|
||||
```
|
||||
|
||||
After:
|
||||
```js
|
||||
var promise = $interval(doSomething, 1000, 5).then(doSomethingElse);
|
||||
$interval.cancel(promise); // Throws error.
|
||||
```
|
||||
|
||||
Correct usage:
|
||||
```js
|
||||
var promise = $interval(doSomething, 1000, 5);
|
||||
var newPromise = promise.then(doSomethingElse);
|
||||
$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()`. Previously, it would silently do nothing.
|
||||
|
||||
Before:
|
||||
```js
|
||||
var promise = $timeout(doSomething, 1000).then(doSomethingElse);
|
||||
$timeout.cancel(promise); // No error; timeout NOT canceled.
|
||||
```
|
||||
|
||||
After:
|
||||
```js
|
||||
var promise = $timeout(doSomething, 1000).then(doSomethingElse);
|
||||
$timeout.cancel(promise); // Throws error.
|
||||
```
|
||||
|
||||
Correct usage:
|
||||
```js
|
||||
var promise = $timeout(doSomething, 1000);
|
||||
var newPromise = promise.then(doSomethingElse);
|
||||
$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 the object values.
|
||||
|
||||
Before:
|
||||
```js
|
||||
$cookieStore.put('name', {key: 'value'});
|
||||
$cookieStore.get('name'); // {key: 'value'}
|
||||
$cookieStore.remove('name');
|
||||
```
|
||||
|
||||
|
||||
#### **$templateRequest**
|
||||
|
||||
**Due to [c617d6](https://github.com/angular/angular.js/commit/c617d6dceee5b000bfceda44ced22fc16b48b18b)**,
|
||||
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)**,
|
||||
`$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.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
var runner = $animate.addClass('red');
|
||||
runner.then(function() { console.log('success')});
|
||||
runner.catch(function() { console.log('cancelled')});
|
||||
|
||||
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.
|
||||
|
||||
|
||||
#### **$controller**
|
||||
|
||||
**Due to [e269c1](https://github.com/angular/angular.js/commit/e269c14425a3209040f65c022658770e00a36f16)**,
|
||||
the option to instantiate controllers from constructors on the global `window` object
|
||||
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 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
|
||||
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.
|
||||
|
||||
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
|
||||
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`.
|
||||
Previously, they were sorted as strings. This will result in different (but more
|
||||
intuitive) sorting order.
|
||||
|
||||
Before:
|
||||
```js
|
||||
orderByFilter(['a', undefined, 'o', null, 'z']);
|
||||
//--> 'a', null, 'o', 'z', undefined
|
||||
```
|
||||
|
||||
After:
|
||||
```js
|
||||
orderByFilter(['a', undefined, 'o', null, 'z']);
|
||||
//--> 'a', 'o', 'z', null, undefined
|
||||
```
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ng-misc"></a>
|
||||
### Core: _Misceallenous_
|
||||
|
||||
|
||||
#### **jqLite**
|
||||
|
||||
**Due to [b7d396](https://github.com/angular/angular.js/commit/b7d396b8b6e8f27a1f4556d58fc903321e8d532a)**,
|
||||
`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:
|
||||
|
||||
```js
|
||||
elem.removeData();
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```js
|
||||
angular.element.cleanData(elem);
|
||||
```
|
||||
|
||||
In most cases, though, cleaning up after an element is supposed to be done
|
||||
only when it's removed from the DOM as well; in such cases the following:
|
||||
|
||||
```js
|
||||
elem.remove();
|
||||
```
|
||||
|
||||
will remove event handlers as well.
|
||||
|
||||
|
||||
#### **Helpers**
|
||||
|
||||
**Due to [1daa4f](https://github.com/angular/angular.js/commit/1daa4f2231a89ee88345689f001805ffffa9e7de)**,
|
||||
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-in methods `toLowerCase` and `toUpperCase`
|
||||
or `toLocaleLowerCase` and `toLocaleUpperCase` for special cases.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [e3ece2](https://github.com/angular/angular.js/commit/e3ece2fad9e1e6d47b5f06815ff186d7e6f44948)**,
|
||||
`angular.isArray()` now supports Array subclasses.
|
||||
|
||||
Previously, `angular.isArray()` was an alias for `Array.isArray()`.
|
||||
Therefore, objects that prototypally inherit from `Array` where not
|
||||
considered arrays. Now such objects are considered arrays too.
|
||||
|
||||
This change affects several other methods that use `angular.isArray()`
|
||||
under the hood, such as `angular.copy()`, `angular.equals()`,
|
||||
`angular.forEach()`, and `angular.merge()`.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ngAria"></a>
|
||||
### ngAria
|
||||
|
||||
**Due to [6d5ef3](https://github.com/angular/angular.js/commit/6d5ef34fc6a974cde73157ba94f9706723dd8f5b)**,
|
||||
`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.
|
||||
|
||||
If you are not using `success` or `error` callbacks with `$resource`,
|
||||
your app should not be affected by this change.
|
||||
|
||||
If you are using `success` or `error` callbacks (with or without
|
||||
response interceptors), one (subtle) difference is that throwing an
|
||||
error inside the callbacks will not propagate to the returned
|
||||
`$promise`. Therefore, you should try to use the promises whenever
|
||||
possible. E.g.:
|
||||
|
||||
```js
|
||||
// Avoid
|
||||
User.query(function onSuccess(users) { throw new Error(); }).
|
||||
$promise.
|
||||
catch(function onError() { /* Will not be called. */ });
|
||||
|
||||
// Prefer
|
||||
User.query().
|
||||
$promise.
|
||||
then(function onSuccess(users) { throw new Error(); }).
|
||||
catch(function onError() { /* Will be called. */ });
|
||||
```
|
||||
|
||||
Finally, if you are using `success` or `error` callbacks with response
|
||||
interceptors, the callbacks will now always run _after_ the interceptors
|
||||
(and wait for them to resolve in case they return a promise).
|
||||
Previously, the `error` callback was called before the `responseError`
|
||||
interceptor and the `success` callback was synchronously called after
|
||||
the `response` interceptor. E.g.:
|
||||
|
||||
```js
|
||||
var User = $resource('/api/users/:id', {id: '@id'}, {
|
||||
get: {
|
||||
method: 'get',
|
||||
interceptor: {
|
||||
response: function(response) {
|
||||
console.log('responseInterceptor-1');
|
||||
return $timeout(1000).then(function() {
|
||||
console.log('responseInterceptor-2');
|
||||
return response.resource;
|
||||
});
|
||||
},
|
||||
responseError: function(response) {
|
||||
console.log('responseErrorInterceptor-1');
|
||||
return $timeout(1000).then(function() {
|
||||
console.log('responseErrorInterceptor-2');
|
||||
return $q.reject('Ooops!');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
var onSuccess = function(value) { console.log('successCallback', value); };
|
||||
var onError = function(error) { console.log('errorCallback', error); };
|
||||
|
||||
// Assuming the following call is successful...
|
||||
User.get({id: 1}, onSuccess, onError);
|
||||
// Old behavior:
|
||||
// responseInterceptor-1
|
||||
// successCallback, {/* Promise object */}
|
||||
// responseInterceptor-2
|
||||
// New behavior:
|
||||
// responseInterceptor-1
|
||||
// responseInterceptor-2
|
||||
// successCallback, {/* User object */}
|
||||
|
||||
// Assuming the following call returns an error...
|
||||
User.get({id: 2}, onSuccess, onError);
|
||||
// Old behavior:
|
||||
// errorCallback, {/* Response object */}
|
||||
// responseErrorInterceptor-1
|
||||
// responseErrorInterceptor-2
|
||||
// New behavior:
|
||||
// responseErrorInterceptor-1
|
||||
// responseErrorInterceptor-2
|
||||
// errorCallback, Ooops!
|
||||
```
|
||||
|
||||
<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).
|
||||
|
||||
Previously, calling a `$resource` method would synchronously call
|
||||
`$http`.
|
||||
|
||||
This is not expected to affect applications at runtime, since the
|
||||
overall operation is asynchronous already, but may affect assertions in
|
||||
tests. For example, if you want to assert that `$http` has been called
|
||||
with specific arguments as a result of a `$resource` call, you now need
|
||||
to run a `$digest` first, to ensure the (possibly empty) request
|
||||
interceptor promise has been resolved.
|
||||
|
||||
Before:
|
||||
```js
|
||||
it('...', function() {
|
||||
$httpBackend.expectGET('/api/things').respond(...);
|
||||
var Things = $resource('/api/things');
|
||||
Things.query();
|
||||
|
||||
expect($http).toHaveBeenCalledWith(...);
|
||||
});
|
||||
```
|
||||
|
||||
After:
|
||||
```js
|
||||
it('...', function() {
|
||||
$httpBackend.expectGET('/api/things').respond(...);
|
||||
var Things = $resource('/api/things');
|
||||
Things.query();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($http).toHaveBeenCalledWith(...);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ngScenario"></a>
|
||||
### ngScenario
|
||||
|
||||
**Due to[0cd392](https://github.com/angular/angular.js/commit/0cd39217828b0ad53eaf731576af17d66c18ff60)**,
|
||||
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 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
|
||||
older version of the scenario runner, as the underlying APIs have
|
||||
not changed. However, we do not guarantee future compatibility.
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ngTouch"></a>
|
||||
### ngTouch
|
||||
|
||||
**Due to [11d9ad](https://github.com/angular/angular.js/commit/11d9ad1eb25eaf5967195e424108207427835d50)**,
|
||||
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
|
||||
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.
|
||||
|
||||
The `ngClick` override directive had been deprecated and by default disabled since v1.5.0,
|
||||
because of buggy behavior in edge cases, and a general trend to avoid special touch based
|
||||
overrides of click events. In modern browsers, it should not be necessary to use a touch override
|
||||
library:
|
||||
|
||||
- Chrome, Firefox, Edge, and Safari remove the 300ms delay when
|
||||
`<meta name="viewport" content="width=device-width">` is set.
|
||||
- Internet Explorer 10+, Edge, Safari, and Chrome remove the delay on elements that have the
|
||||
`touch-action` css property is set to `manipulation`.
|
||||
|
||||
You can find out more in these articles:
|
||||
https://developers.google.com/web/updates/2013/12/300ms-tap-delay-gone-away
|
||||
https://developer.apple.com/library/content/releasenotes/General/WhatsNewInSafari/Articles/Safari_9_1.html#//apple_ref/doc/uid/TP40014305-CH10-SW8
|
||||
https://blogs.msdn.microsoft.com/ie/2015/02/24/pointer-events-w3c-recommendation-interoperable-touch-and-removing-the-dreaded-300ms-tap-delay/
|
||||
|
||||
|
||||
|
||||
## Migrating from 1.5 to 1.6
|
||||
|
||||
AngularJS 1.6 fixes numerous bugs and adds new features, both in core and in external modules.
|
||||
@@ -2076,7 +1272,7 @@ Due to [b71d7c3f](https://github.com/angular/angular.js/commit/b71d7c3f3c04e65b0
|
||||
falsy values (`''`, `0`, `false` and `null`) are properly recognized as option group identifiers for
|
||||
options passed to `ngOptions`. Previously, all of these values were ignored and the option was not
|
||||
assigned to any group. `undefined` is still interpreted as "no group".
|
||||
If you have options with falsy group identifiers that should still not be assigned to any group,
|
||||
If you have options with falsy group indentifiers that should still not be assigned to any group,
|
||||
then you must filter the values before passing them to `ngOptions`, converting falsy values to
|
||||
`undefined`.
|
||||
|
||||
@@ -2235,8 +1431,8 @@ For more info on the topic, you can take a look at this
|
||||
## Migrating from 1.3 to 1.4
|
||||
|
||||
AngularJS 1.4 fixes major animation issues and introduces a new API for `ngCookies`. Further, there
|
||||
are changes to `ngMessages`, `$compile`, `ngRepeat`, `ngOptions`, `ngPattern`, `pattern` and some fixes to core filters:
|
||||
`limitTo` and `filter`.
|
||||
are changes to `ngMessages`, `$compile`, `ngRepeat`, `ngOptions`, `ngPattern`, `pattern`, and
|
||||
some fixes to core filters: `limitTo` and `filter`.
|
||||
|
||||
The reason for the ngAnimate refactor was to fix timing issues and to expose new APIs to allow
|
||||
for developers to construct more versatile animations. We now have access to `$animateCss`
|
||||
|
||||
@@ -188,6 +188,11 @@ myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);
|
||||
|
||||
Much simpler!
|
||||
|
||||
Note: Yes, we have called one of our service recipes 'Service'. We regret this and know that we'll
|
||||
be somehow punished for our misdeed. It's like we named one of our offspring 'Child'. Boy,
|
||||
that would mess with the teachers.
|
||||
|
||||
|
||||
## Provider Recipe
|
||||
|
||||
As already mentioned in the intro, the Provider recipe is the core recipe type and
|
||||
|
||||
@@ -109,14 +109,12 @@ to test the behavior without being distracted by the rendering details.
|
||||
## Scope Hierarchies
|
||||
|
||||
Each AngularJS application has exactly one {@link ng.$rootScope root scope}, but
|
||||
may have any number of child scopes.
|
||||
may have several child scopes.
|
||||
|
||||
The application can have multiple scopes, because {@link guide/directive directives} can create new
|
||||
child scopes. When new scopes are created, they are added as children of their parent scope. This
|
||||
creates a tree structure which parallels the DOM where they're attached.
|
||||
|
||||
The section {@link guide/scope#directives-that-create-scopes Directives that Create Scopes} has more
|
||||
info about which directives create scopes.
|
||||
The application can have multiple scopes, because some {@link guide/directive directives} create
|
||||
new child scopes (refer to directive documentation to see which directives create new scopes).
|
||||
When new scopes are created, they are added as children of their parent scope. This creates a tree
|
||||
structure which parallels the DOM where they're attached.
|
||||
|
||||
When AngularJS evaluates `{{name}}`, it first looks at the scope associated with the given
|
||||
element for the `name` property. If no such property is found, it searches the parent scope
|
||||
@@ -185,11 +183,8 @@ To examine the scope in the debugger:
|
||||
2. The debugger allows you to access the currently selected element in the console as `$0`
|
||||
variable.
|
||||
|
||||
3. To retrieve the associated scope in console execute: `angular.element($0).scope()`
|
||||
3. To retrieve the associated scope in console execute: `angular.element($0).scope()` or just type $scope
|
||||
|
||||
<div class="alert alert-warning">
|
||||
The `scope()` function is only available when {@link ng.$compileProvider#debugInfoEnabled `$compileProvider.debugInfoEnabled()`} is true (which is the default).
|
||||
</div>
|
||||
|
||||
## Scope Events Propagation
|
||||
|
||||
@@ -314,16 +309,12 @@ correctly.
|
||||
|
||||
In most cases, {@link ng.$compileProvider#directive directives} and scopes interact
|
||||
but do not create new instances of scope. However, some directives, such as {@link
|
||||
ng.directive:ngController ng-controller} and {@link ng.directive:ngRepeat ng-repeat},
|
||||
create new child scopes and attach the child scope to the corresponding DOM element.
|
||||
|
||||
A special type of scope is the `isolate` scope, which does not inherit prototypically from the parent scope.
|
||||
This type of scope is useful for component directives that should be isolated from their parent scope.
|
||||
See the {@link guide/directive#isolating-the-scope-of-a-directive directives guide} for
|
||||
more information about isolate scopes in custom directives.
|
||||
|
||||
Note also that component directives, which are created with the
|
||||
{@link api/ng/type/angular.Module#component .component()} helper always create an isolate scope.
|
||||
ng.directive:ngController ng-controller} and {@link
|
||||
ng.directive:ngRepeat ng-repeat}, create new child scopes
|
||||
and attach the child scope to the corresponding DOM element. You can retrieve a scope for any DOM
|
||||
element by using an `angular.element(aDomElement).scope()` method call.
|
||||
See the {@link guide/directive#isolating-the-scope-of-a-directive
|
||||
directives guide} for more information about isolate scopes.
|
||||
|
||||
### Controllers and Scopes
|
||||
|
||||
|
||||
@@ -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 a regular `<script>` tag.
|
||||
to the browser as regular `<script>` tag.
|
||||
|
||||
## Strict Contextual Escaping
|
||||
|
||||
|
||||
@@ -149,17 +149,16 @@ for instantiating controllers.
|
||||
describe('PasswordController', function() {
|
||||
beforeEach(module('app'));
|
||||
|
||||
var $controller, $rootScope;
|
||||
var $controller;
|
||||
|
||||
beforeEach(inject(function(_$controller_, _$rootScope_){
|
||||
beforeEach(inject(function(_$controller_){
|
||||
// The injector unwraps the underscores (_) from around the parameter names when matching
|
||||
$controller = _$controller_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
describe('$scope.grade', function() {
|
||||
it('sets the strength to "strong" if the password length is >8 chars', function() {
|
||||
var $scope = $rootScope.$new();
|
||||
var $scope = {};
|
||||
var controller = $controller('PasswordController', { $scope: $scope });
|
||||
$scope.password = 'longerthaneightchars';
|
||||
$scope.grade();
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
@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 URL.
|
||||
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.
|
||||
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
|
||||
@@ -110,7 +111,7 @@ The set of files included in each version directory are:
|
||||
* __`angular-route.js`__ — Routing and deep-linking services and directives for AngularJS apps.
|
||||
({@link module:ngRoute API docs})
|
||||
* __`angular-sanitize.js`__ — Functionality to sanitize HTML. ({@link module:ngSanitize API docs})
|
||||
* __`angular-touch.js`__ — Touch events for touch-enabled devices.
|
||||
* __`angular-touch.js`__ — Touch events and other helpers for touch-enabled devices.
|
||||
({@link module:ngTouch API docs})
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
### Why is this project called "AngularJS"? Why is the namespace called "ng"?
|
||||
|
||||
Because HTML has angular brackets and "ng" sounds like "AngularJS".
|
||||
Because HTML has AngularJS brackets and "ng" sounds like "AngularJS".
|
||||
|
||||
|
||||
### Is AngularJS a library, framework, plugin or a browser extension?
|
||||
@@ -39,7 +39,7 @@ When we are making a release we generate updates to the changelog directly from
|
||||
generated update contains a highlighted section that contains all the breaking changes that have been
|
||||
extracted from the commits. We can quickly see in the new changelog exactly what commits contain breaking
|
||||
changes and so can application developers when they are deciding whether to update to a new version of
|
||||
AngularJS.
|
||||
Angular.
|
||||
|
||||
Features with non-breaking changes can also appear in the "patch" version, e.g. in version 1.6.3 there might
|
||||
be a feature that is not available in 1.6.2.
|
||||
@@ -94,7 +94,8 @@ When adding new code to AngularJS, we have a very stringent commit policy:
|
||||
- Commit messages must be written in a specific manner that allows us to parse them and extract the changes
|
||||
for release notes ([see the contributing guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md))
|
||||
|
||||
The AngularJS code base has a very large set of unit tests and end-to-end tests. This means that a breaking change will require one or more tests to be changed to allow the
|
||||
The AngularJS code base has a very large set of unit tests and end-to-end tests. This means that
|
||||
a breaking change will require one or more tests to be changed to allow the
|
||||
tests to pass. So when a commit includes tests that are being removed or modified, this is a flag that the
|
||||
code might include a breaking change. When reviewing the commit we can then decide whether there really is
|
||||
a breaking change and if it is appropriate for the branch to which it is being merged. If so, then we
|
||||
|
||||
@@ -7,41 +7,48 @@
|
||||
This page describes the support status of the significant versions of AngularJS.
|
||||
|
||||
<div class="alert alert-info">
|
||||
On July 1, 2018 AngularJS entered a 3 year Long Term Support period.
|
||||
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.
|
||||
</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>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>
|
||||
<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>
|
||||
</table>
|
||||
|
||||
### Long Term Support
|
||||
|
||||
On July 1st 2018, AngularJS entered a Long Term Support period for AngularJS.
|
||||
On July 1st 2018, we will enter a Long Term Support period for AngularJS.
|
||||
|
||||
We now focus exclusively on providing fixes to bugs that satisfy at least one of the following criteria:
|
||||
At this time we will 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).
|
||||
@@ -151,20 +151,38 @@ To learn more about AngularJS scopes, see the {@link ng.$rootScope.Scope Angular
|
||||
|
||||
### Testing Controllers
|
||||
|
||||
The "AngularJS way" of separating the controller from the view makes it easy to test code as it is being
|
||||
developed. In the section "Model and Controller" we have registered our controller via a constructor
|
||||
function on the `phonecatApp` module.
|
||||
The "AngularJS way" of separating controller from the view, makes it easy to test code as it is being
|
||||
developed. If our controller were available on the global namespace, we could simply instantiate it
|
||||
with a mock scope object:
|
||||
|
||||
In tests, we use an AngularJS service, `$controller`, which will retrieve a controller by name. It
|
||||
also takes a second argument - a map of dependencies that should be injected.
|
||||
<br />
|
||||
```js
|
||||
describe('PhoneListController', function() {
|
||||
|
||||
The following test instantiates `PhoneListController` with a mock scope object,
|
||||
and verifies that the phones array property on the scope contains three records.
|
||||
it('should create a `phones` model with 3 phones', function() {
|
||||
var scope = {};
|
||||
var ctrl = new PhoneListController(scope);
|
||||
|
||||
This example demonstrates how easy it is to create a unit test for
|
||||
expect(scope.phones.length).toBe(3);
|
||||
});
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
The test instantiates `PhoneListController` and verifies that the phones array property on the
|
||||
scope contains three records. This example demonstrates how easy it is to create a unit test for
|
||||
code in AngularJS. Since testing is such a critical part of software development, we make it easy to
|
||||
create tests in AngularJS so that developers are encouraged to write them.
|
||||
|
||||
|
||||
### Testing non-global Controllers
|
||||
|
||||
In practice, you will not want to have your controller functions in the global namespace. Instead,
|
||||
you can see that we have registered it via a constructor function on the `phonecatApp` module.
|
||||
|
||||
In this case AngularJS provides a service, `$controller`, which will retrieve your controller by name.
|
||||
Here is the same test using `$controller`:
|
||||
|
||||
<br />
|
||||
**`app/app.spec.js`:**
|
||||
|
||||
|
||||
@@ -107,33 +107,31 @@ module.exports = function(config, specificOptions) {
|
||||
'SL_iOS_10': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
platform: 'OS X 10.12',
|
||||
version: '10.3'
|
||||
},
|
||||
'SL_iOS_11': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
platform: 'OS X 10.12',
|
||||
version: '11.2'
|
||||
version: '11'
|
||||
},
|
||||
|
||||
'BS_Chrome': {
|
||||
base: 'BrowserStack',
|
||||
browser: 'chrome',
|
||||
os: 'OS X',
|
||||
os_version: 'Sierra'
|
||||
os_version: 'Yosemite'
|
||||
},
|
||||
'BS_Safari': {
|
||||
base: 'BrowserStack',
|
||||
browser: 'safari',
|
||||
os: 'OS X',
|
||||
os_version: 'Sierra'
|
||||
os_version: 'Yosemite'
|
||||
},
|
||||
'BS_Firefox': {
|
||||
base: 'BrowserStack',
|
||||
browser: 'firefox',
|
||||
os: 'Windows',
|
||||
os_version: '10'
|
||||
os_version: '8'
|
||||
},
|
||||
'BS_IE_9': {
|
||||
base: 'BrowserStack',
|
||||
|
||||
+1
-1
@@ -221,7 +221,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 -d32 -version 2>&1', {silent: true}).code !== 0) return '';
|
||||
if (shell.exec('java -version -d32 2>&1', {silent: true}).code !== 0) return '';
|
||||
return ' -d32 -client';
|
||||
},
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ 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.12"
|
||||
SC_VERSION="4.4.1"
|
||||
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"
|
||||
|
||||
+14
-13
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"license": "MIT",
|
||||
"branchVersion": "^1.7.0",
|
||||
"branchPattern": "1.7.*",
|
||||
"distTag": "latest",
|
||||
"branchVersion": "^1.6.0",
|
||||
"branchPattern": "1.6.*",
|
||||
"distTag": "previous_1_6",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
@@ -32,8 +32,8 @@
|
||||
"commitplease": "^2.7.10",
|
||||
"cross-spawn": "^4.0.0",
|
||||
"cz-conventional-changelog": "1.1.4",
|
||||
"dgeni": "^0.4.9",
|
||||
"dgeni-packages": "^0.26.5",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.16.4",
|
||||
"eslint-plugin-promise": "^3.6.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"glob": "^6.0.1",
|
||||
@@ -63,18 +63,19 @@
|
||||
"jquery": "3.2.1",
|
||||
"jquery-2.1": "npm:jquery@2.1.4",
|
||||
"jquery-2.2": "npm:jquery@2.2.4",
|
||||
"karma": "^2.0.4",
|
||||
"karma-browserstack-launcher": "^1.3.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma": "^2.0.0",
|
||||
"karma-browserstack-launcher": "^1.2.0",
|
||||
"karma-chrome-launcher": "^2.1.1",
|
||||
"karma-edge-launcher": "^0.4.2",
|
||||
"karma-firefox-launcher": "^1.1.0",
|
||||
"karma-firefox-launcher": "^1.0.1",
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"karma-jasmine": "^1.1.2",
|
||||
"karma-jasmine": "^1.1.0",
|
||||
"karma-junit-reporter": "^1.2.0",
|
||||
"karma-ng-scenario": "^1.0.0",
|
||||
"karma-safari-launcher": "^1.0.0",
|
||||
"karma-sauce-launcher": "^1.2.0",
|
||||
"karma-script-launcher": "^1.0.0",
|
||||
"karma-spec-reporter": "^0.0.32",
|
||||
"karma-spec-reporter": "^0.0.31",
|
||||
"load-grunt-tasks": "^3.5.0",
|
||||
"lodash": "~2.4.1",
|
||||
"log4js": "^0.6.27",
|
||||
@@ -84,13 +85,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",
|
||||
|
||||
@@ -12,7 +12,7 @@ set -xe
|
||||
# This is the default set of browsers to use on the CI server unless overridden via env variable
|
||||
if [[ -z "$BROWSERS" ]]
|
||||
then
|
||||
BROWSERS="Chrome"
|
||||
BROWSERS="Chrome,Firefox,/Users/jenkins/bin/safari.sh"
|
||||
fi
|
||||
|
||||
# CLEAN #
|
||||
|
||||
+2
-7
@@ -28,6 +28,8 @@
|
||||
"REGEX_STRING_REGEXP" : false,
|
||||
"lowercase": false,
|
||||
"uppercase": false,
|
||||
"manualLowercase": false,
|
||||
"manualUppercase": false,
|
||||
"isArrayLike": false,
|
||||
"forEach": false,
|
||||
"forEachSorted": false,
|
||||
@@ -159,7 +161,6 @@
|
||||
/* urlUtils.js */
|
||||
"urlResolve": false,
|
||||
"urlIsSameOrigin": false,
|
||||
"urlIsSameOriginAsBaseUrl": false,
|
||||
"urlIsAllowedOriginFactory": false,
|
||||
|
||||
/* ng/controller.js */
|
||||
@@ -171,15 +172,9 @@
|
||||
/* 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,
|
||||
|
||||
+73
-35
@@ -21,6 +21,8 @@
|
||||
|
||||
lowercase,
|
||||
uppercase,
|
||||
manualLowercase,
|
||||
manualUppercase,
|
||||
nodeName_,
|
||||
isArrayLike,
|
||||
forEach,
|
||||
@@ -128,7 +130,15 @@ var VALIDITY_STATE_PROPERTY = 'validity';
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @ngdoc function
|
||||
* @name angular.lowercase
|
||||
* @module ng
|
||||
* @kind function
|
||||
*
|
||||
* @deprecated
|
||||
* sinceVersion="1.5.0"
|
||||
* removeVersion="1.7.0"
|
||||
* Use [String.prototype.toLowerCase](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase) instead.
|
||||
*
|
||||
* @description Converts the specified string to lowercase.
|
||||
* @param {string} string String to be converted to lowercase.
|
||||
@@ -137,7 +147,15 @@ var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @ngdoc function
|
||||
* @name angular.uppercase
|
||||
* @module ng
|
||||
* @kind function
|
||||
*
|
||||
* @deprecated
|
||||
* sinceVersion="1.5.0"
|
||||
* removeVersion="1.7.0"
|
||||
* Use [String.prototype.toUpperCase](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) instead.
|
||||
*
|
||||
* @description Converts the specified string to uppercase.
|
||||
* @param {string} string String to be converted to uppercase.
|
||||
@@ -146,6 +164,31 @@ var lowercase = function(string) {return isString(string) ? string.toLowerCase()
|
||||
var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
|
||||
|
||||
|
||||
var manualLowercase = function(s) {
|
||||
/* eslint-disable no-bitwise */
|
||||
return isString(s)
|
||||
? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
|
||||
: s;
|
||||
/* eslint-enable */
|
||||
};
|
||||
var manualUppercase = function(s) {
|
||||
/* eslint-disable no-bitwise */
|
||||
return isString(s)
|
||||
? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
|
||||
: s;
|
||||
/* eslint-enable */
|
||||
};
|
||||
|
||||
|
||||
// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
|
||||
// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
|
||||
// with correct but slower alternatives. See https://github.com/angular/angular.js/issues/11387
|
||||
if ('i' !== 'I'.toLowerCase()) {
|
||||
lowercase = manualLowercase;
|
||||
uppercase = manualUppercase;
|
||||
}
|
||||
|
||||
|
||||
var
|
||||
msie, // holds major version number for IE, or NaN if UA is not IE.
|
||||
jqLite, // delay binding since jQuery could be loaded after us.
|
||||
@@ -193,7 +236,8 @@ function isArrayLike(obj) {
|
||||
|
||||
// NodeList objects (with `item` method) and
|
||||
// other objects with suitable length characteristics are array-like
|
||||
return isNumber(length) && (length >= 0 && (length - 1) in obj || typeof obj.item === 'function');
|
||||
return isNumber(length) &&
|
||||
(length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item === 'function');
|
||||
|
||||
}
|
||||
|
||||
@@ -342,8 +386,10 @@ function baseExtend(dst, objs, deep) {
|
||||
} else if (isElement(src)) {
|
||||
dst[key] = src.clone();
|
||||
} else {
|
||||
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
|
||||
baseExtend(dst[key], [src], true);
|
||||
if (key !== '__proto__') {
|
||||
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
|
||||
baseExtend(dst[key], [src], true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dst[key] = src;
|
||||
@@ -608,14 +654,12 @@ function isDate(value) {
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Determines if a reference is an `Array`.
|
||||
* Determines if a reference is an `Array`. Alias of Array.isArray.
|
||||
*
|
||||
* @param {*} value Reference to check.
|
||||
* @returns {boolean} True if `value` is an `Array`.
|
||||
*/
|
||||
function isArray(arr) {
|
||||
return Array.isArray(arr) || arr instanceof Array;
|
||||
}
|
||||
var isArray = Array.isArray;
|
||||
|
||||
/**
|
||||
* @description
|
||||
@@ -792,16 +836,15 @@ 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
|
||||
@@ -1696,13 +1739,8 @@ function angularInit(element, bootstrap) {
|
||||
});
|
||||
if (appElement) {
|
||||
if (!isAutoBootstrapAllowed) {
|
||||
try {
|
||||
window.console.error('AngularJS: disabling automatic bootstrap. <script> protocol indicates ' +
|
||||
window.console.error('AngularJS: disabling automatic bootstrap. <script> protocol indicates ' +
|
||||
'an extension, document.location.href does not match.');
|
||||
} catch (e) {
|
||||
// Support: Safari 11 w/ Webdriver
|
||||
// The console.error will throw and make the test fail
|
||||
}
|
||||
return;
|
||||
}
|
||||
config.strictDi = getNgAttribute(appElement, 'strict-di') !== null;
|
||||
@@ -1904,25 +1942,25 @@ function bindJQuery() {
|
||||
injector: JQLitePrototype.injector,
|
||||
inheritedData: JQLitePrototype.inheritedData
|
||||
});
|
||||
|
||||
// All nodes removed from the DOM via various jQuery APIs like .remove()
|
||||
// are passed through jQuery.cleanData. Monkey-patch this method to fire
|
||||
// the $destroy event on all removed nodes.
|
||||
originalCleanData = jQuery.cleanData;
|
||||
jQuery.cleanData = function(elems) {
|
||||
var events;
|
||||
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
|
||||
events = jQuery._data(elem, 'events');
|
||||
if (events && events.$destroy) {
|
||||
jQuery(elem).triggerHandler('$destroy');
|
||||
}
|
||||
}
|
||||
originalCleanData(elems);
|
||||
};
|
||||
} else {
|
||||
jqLite = JQLite;
|
||||
}
|
||||
|
||||
// All nodes removed from the DOM via various jqLite/jQuery APIs like .remove()
|
||||
// are passed through jqLite/jQuery.cleanData. Monkey-patch this method to fire
|
||||
// the $destroy event on all removed nodes.
|
||||
originalCleanData = jqLite.cleanData;
|
||||
jqLite.cleanData = function(elems) {
|
||||
var events;
|
||||
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
|
||||
events = (jqLite._data(elem) || {}).events;
|
||||
if (events && events.$destroy) {
|
||||
jqLite(elem).triggerHandler('$destroy');
|
||||
}
|
||||
}
|
||||
originalCleanData(elems);
|
||||
};
|
||||
|
||||
angular.element = jqLite;
|
||||
|
||||
// Prevent double-proxying.
|
||||
|
||||
+6
-11
@@ -7,7 +7,7 @@
|
||||
|
||||
htmlAnchorDirective,
|
||||
inputDirective,
|
||||
inputDirective,
|
||||
hiddenInputBrowserCacheDirective,
|
||||
formDirective,
|
||||
scriptDirective,
|
||||
selectDirective,
|
||||
@@ -28,7 +28,6 @@
|
||||
ngInitDirective,
|
||||
ngNonBindableDirective,
|
||||
ngPluralizeDirective,
|
||||
ngRefDirective,
|
||||
ngRepeatDirective,
|
||||
ngShowDirective,
|
||||
ngStyleDirective,
|
||||
@@ -70,7 +69,6 @@
|
||||
$FilterProvider,
|
||||
$$ForceReflowProvider,
|
||||
$InterpolateProvider,
|
||||
$$IntervalFactoryProvider,
|
||||
$IntervalProvider,
|
||||
$HttpProvider,
|
||||
$HttpParamSerializerProvider,
|
||||
@@ -89,7 +87,6 @@
|
||||
$SceProvider,
|
||||
$SceDelegateProvider,
|
||||
$SnifferProvider,
|
||||
$$TaskTrackerFactoryProvider,
|
||||
$TemplateCacheProvider,
|
||||
$TemplateRequestProvider,
|
||||
$$TestabilityProvider,
|
||||
@@ -153,6 +150,8 @@ function publishExternalAPI(angular) {
|
||||
'isArray': isArray,
|
||||
'version': version,
|
||||
'isDate': isDate,
|
||||
'lowercase': lowercase,
|
||||
'uppercase': uppercase,
|
||||
'callbacks': {$$counter: 0},
|
||||
'getTestability': getTestability,
|
||||
'reloadWithDebugInfo': reloadWithDebugInfo,
|
||||
@@ -160,9 +159,7 @@ function publishExternalAPI(angular) {
|
||||
'$$csp': csp,
|
||||
'$$encodeUriSegment': encodeUriSegment,
|
||||
'$$encodeUriQuery': encodeUriQuery,
|
||||
'$$lowercase': lowercase,
|
||||
'$$stringify': stringify,
|
||||
'$$uppercase': uppercase
|
||||
'$$stringify': stringify
|
||||
});
|
||||
|
||||
angularModule = setupModuleLoader(window);
|
||||
@@ -197,7 +194,6 @@ function publishExternalAPI(angular) {
|
||||
ngInit: ngInitDirective,
|
||||
ngNonBindable: ngNonBindableDirective,
|
||||
ngPluralize: ngPluralizeDirective,
|
||||
ngRef: ngRefDirective,
|
||||
ngRepeat: ngRepeatDirective,
|
||||
ngShow: ngShowDirective,
|
||||
ngStyle: ngStyleDirective,
|
||||
@@ -221,7 +217,8 @@ function publishExternalAPI(angular) {
|
||||
ngModelOptions: ngModelOptionsDirective
|
||||
}).
|
||||
directive({
|
||||
ngInclude: ngIncludeFillContentDirective
|
||||
ngInclude: ngIncludeFillContentDirective,
|
||||
input: hiddenInputBrowserCacheDirective
|
||||
}).
|
||||
directive(ngAttributeAliasDirectives).
|
||||
directive(ngEventDirectives);
|
||||
@@ -243,7 +240,6 @@ function publishExternalAPI(angular) {
|
||||
$$forceReflow: $$ForceReflowProvider,
|
||||
$interpolate: $InterpolateProvider,
|
||||
$interval: $IntervalProvider,
|
||||
$$intervalFactory: $$IntervalFactoryProvider,
|
||||
$http: $HttpProvider,
|
||||
$httpParamSerializer: $HttpParamSerializerProvider,
|
||||
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
|
||||
@@ -259,7 +255,6 @@ function publishExternalAPI(angular) {
|
||||
$sce: $SceProvider,
|
||||
$sceDelegate: $SceDelegateProvider,
|
||||
$sniffer: $SnifferProvider,
|
||||
$$taskTrackerFactory: $$TaskTrackerFactoryProvider,
|
||||
$templateCache: $TemplateCacheProvider,
|
||||
$templateRequest: $TemplateRequestProvider,
|
||||
$$testability: $$TestabilityProvider,
|
||||
|
||||
+4
-8
@@ -45,10 +45,11 @@ function NgMapShim() {
|
||||
}
|
||||
NgMapShim.prototype = {
|
||||
_idx: function(key) {
|
||||
if (key !== this._lastKey) {
|
||||
this._lastKey = key;
|
||||
this._lastIndex = this._keys.indexOf(key);
|
||||
if (key === this._lastKey) {
|
||||
return this._lastIndex;
|
||||
}
|
||||
this._lastKey = key;
|
||||
this._lastIndex = this._keys.indexOf(key);
|
||||
return this._lastIndex;
|
||||
},
|
||||
_transformKey: function(key) {
|
||||
@@ -61,11 +62,6 @@ 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);
|
||||
|
||||
@@ -922,7 +922,9 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
}
|
||||
var result = func.$$ngIsClass;
|
||||
if (!isBoolean(result)) {
|
||||
result = func.$$ngIsClass = /^class\b/.test(stringifyFn(func));
|
||||
// Support: Edge 12-13 only
|
||||
// See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6156135/
|
||||
result = func.$$ngIsClass = /^(?:class\b|constructor\()/.test(stringifyFn(func));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+9
-28
@@ -311,28 +311,6 @@ function jqLiteDealoc(element, onlyDescendants) {
|
||||
}
|
||||
}
|
||||
|
||||
function isEmptyObject(obj) {
|
||||
var name;
|
||||
|
||||
for (name in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function removeIfEmptyData(element) {
|
||||
var expandoId = element.ng339;
|
||||
var expandoStore = expandoId && jqCache[expandoId];
|
||||
|
||||
var events = expandoStore && expandoStore.events;
|
||||
var data = expandoStore && expandoStore.data;
|
||||
|
||||
if ((!data || isEmptyObject(data)) && (!events || isEmptyObject(events))) {
|
||||
delete jqCache[expandoId];
|
||||
element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
|
||||
}
|
||||
}
|
||||
|
||||
function jqLiteOff(element, type, fn, unsupported) {
|
||||
if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
|
||||
|
||||
@@ -369,8 +347,6 @@ function jqLiteOff(element, type, fn, unsupported) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeIfEmptyData(element);
|
||||
}
|
||||
|
||||
function jqLiteRemoveData(element, name) {
|
||||
@@ -380,11 +356,17 @@ function jqLiteRemoveData(element, name) {
|
||||
if (expandoStore) {
|
||||
if (name) {
|
||||
delete expandoStore.data[name];
|
||||
} else {
|
||||
expandoStore.data = {};
|
||||
return;
|
||||
}
|
||||
|
||||
removeIfEmptyData(element);
|
||||
if (expandoStore.handle) {
|
||||
if (expandoStore.events.$destroy) {
|
||||
expandoStore.handle({}, '$destroy');
|
||||
}
|
||||
jqLiteOff(element);
|
||||
}
|
||||
delete jqCache[expandoId];
|
||||
element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,7 +616,6 @@ forEach({
|
||||
cleanData: function jqLiteCleanData(nodes) {
|
||||
for (var i = 0, ii = nodes.length; i < ii; i++) {
|
||||
jqLiteRemoveData(nodes[i]);
|
||||
jqLiteOff(nodes[i]);
|
||||
}
|
||||
}
|
||||
}, function(fn, name) {
|
||||
|
||||
+1
-2
@@ -328,8 +328,7 @@ function setupModuleLoader(window) {
|
||||
* @ngdoc method
|
||||
* @name angular.Module#component
|
||||
* @module ng
|
||||
* @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`),
|
||||
* or an object map of components where the keys are the names and the values are the component definition objects.
|
||||
* @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp)
|
||||
* @param {Object} options Component definition object (a simplified
|
||||
* {@link ng.$compile#directive-definition-object directive definition object})
|
||||
*
|
||||
|
||||
+3
-16
@@ -7,8 +7,7 @@
|
||||
*/
|
||||
|
||||
var minErrConfig = {
|
||||
objectMaxDepth: 5,
|
||||
urlErrorParamsEnabled: true
|
||||
objectMaxDepth: 5
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -31,21 +30,12 @@ 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;
|
||||
}
|
||||
@@ -60,7 +50,6 @@ function isValidObjectMaxDepth(maxDepth) {
|
||||
return isNumber(maxDepth) && maxDepth > 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description
|
||||
*
|
||||
@@ -124,10 +113,8 @@ function minErr(module, ErrorConstructor) {
|
||||
|
||||
message += '\n' + url + (module ? module + '/' : '') + code;
|
||||
|
||||
if (minErrConfig.urlErrorParamsEnabled) {
|
||||
for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
|
||||
message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
|
||||
}
|
||||
for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
|
||||
message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
|
||||
}
|
||||
|
||||
return new ErrorConstructor(message);
|
||||
|
||||
+16
-103
@@ -369,39 +369,14 @@ 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,
|
||||
|
||||
@@ -489,77 +464,13 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* @ngdoc method
|
||||
* @name $animate#cancel
|
||||
* @kind function
|
||||
* @description Cancels the provided animation and applies the end state of the animation.
|
||||
* Note that this does not cancel the underlying operation, e.g. the setting of classes or
|
||||
* adding the element to the DOM.
|
||||
* @description Cancels the provided animation.
|
||||
*
|
||||
* @param {animationRunner} animationRunner An animation runner returned by an $animate function.
|
||||
*
|
||||
* @example
|
||||
<example module="animationExample" deps="angular-animate.js" animations="true" name="animate-cancel">
|
||||
<file name="app.js">
|
||||
angular.module('animationExample', ['ngAnimate']).component('cancelExample', {
|
||||
templateUrl: 'template.html',
|
||||
controller: function($element, $animate) {
|
||||
this.runner = null;
|
||||
|
||||
this.addClass = function() {
|
||||
this.runner = $animate.addClass($element.find('div'), 'red');
|
||||
var ctrl = this;
|
||||
this.runner.finally(function() {
|
||||
ctrl.runner = null;
|
||||
});
|
||||
};
|
||||
|
||||
this.removeClass = function() {
|
||||
this.runner = $animate.removeClass($element.find('div'), 'red');
|
||||
var ctrl = this;
|
||||
this.runner.finally(function() {
|
||||
ctrl.runner = null;
|
||||
});
|
||||
};
|
||||
|
||||
this.cancel = function() {
|
||||
$animate.cancel(this.runner);
|
||||
};
|
||||
}
|
||||
});
|
||||
</file>
|
||||
<file name="template.html">
|
||||
<p>
|
||||
<button id="add" ng-click="$ctrl.addClass()">Add</button>
|
||||
<button ng-click="$ctrl.removeClass()">Remove</button>
|
||||
<br>
|
||||
<button id="cancel" ng-click="$ctrl.cancel()" ng-disabled="!$ctrl.runner">Cancel</button>
|
||||
<br>
|
||||
<div id="target">CSS-Animated Text</div>
|
||||
</p>
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<cancel-example></cancel-example>
|
||||
</file>
|
||||
<file name="style.css">
|
||||
.red-add, .red-remove {
|
||||
transition: all 4s cubic-bezier(0.250, 0.460, 0.450, 0.940);
|
||||
}
|
||||
|
||||
.red,
|
||||
.red-add.red-add-active {
|
||||
color: #FF0000;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.red-remove.red-remove-active {
|
||||
font-size: 10px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
</file>
|
||||
</example>
|
||||
* @param {Promise} animationPromise The animation promise that is returned when an animation is started.
|
||||
*/
|
||||
cancel: function(runner) {
|
||||
if (runner.cancel) {
|
||||
runner.cancel();
|
||||
if (runner.end) {
|
||||
runner.end();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -585,7 +496,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* - **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
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
enter: function(element, parent, after, options) {
|
||||
parent = parent && jqLite(parent);
|
||||
@@ -617,7 +528,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* - **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
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
move: function(element, parent, after, options) {
|
||||
parent = parent && jqLite(parent);
|
||||
@@ -644,7 +555,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* - **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
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
leave: function(element, options) {
|
||||
return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
|
||||
@@ -669,11 +580,12 @@ 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:
|
||||
*
|
||||
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
|
||||
* - **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} animationRunner the animation runner
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
addClass: function(element, className, options) {
|
||||
options = prepareAnimateOptions(options);
|
||||
@@ -700,9 +612,10 @@ 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
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
removeClass: function(element, className, options) {
|
||||
options = prepareAnimateOptions(options);
|
||||
@@ -729,11 +642,11 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* The object can have the following properties:
|
||||
*
|
||||
* - **addClass** - `{string}` - space-separated CSS classes to add to element
|
||||
* - **removeClass** - `{string}` - space-separated CSS classes to remove from 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
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
setClass: function(element, add, remove, options) {
|
||||
options = prepareAnimateOptions(options);
|
||||
@@ -780,7 +693,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* - **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
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
animate: function(element, from, to, className, options) {
|
||||
options = prepareAnimateOptions(options);
|
||||
|
||||
+65
-47
@@ -1,14 +1,5 @@
|
||||
'use strict';
|
||||
/* global getHash: true, stripHash: false */
|
||||
|
||||
function getHash(url) {
|
||||
var index = url.indexOf('#');
|
||||
return index === -1 ? '' : url.substr(index);
|
||||
}
|
||||
|
||||
function trimEmptyHash(url) {
|
||||
return url.replace(/#$/, '');
|
||||
}
|
||||
/* global stripHash: true */
|
||||
|
||||
/**
|
||||
* ! This is a private undocumented service !
|
||||
@@ -31,27 +22,61 @@ function trimEmptyHash(url) {
|
||||
* @param {object} $log window.console or an object with the same interface.
|
||||
* @param {object} $sniffer $sniffer service
|
||||
*/
|
||||
function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
|
||||
function Browser(window, document, $log, $sniffer) {
|
||||
var self = this,
|
||||
location = window.location,
|
||||
history = window.history,
|
||||
setTimeout = window.setTimeout,
|
||||
clearTimeout = window.clearTimeout,
|
||||
pendingDeferIds = {},
|
||||
taskTracker = $$taskTrackerFactory($log);
|
||||
pendingDeferIds = {};
|
||||
|
||||
self.isMock = false;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Task-tracking API
|
||||
//////////////////////////////////////////////////////////////
|
||||
var outstandingRequestCount = 0;
|
||||
var outstandingRequestCallbacks = [];
|
||||
|
||||
// TODO(vojta): remove this temporary api
|
||||
self.$$completeOutstandingRequest = taskTracker.completeTask;
|
||||
self.$$incOutstandingRequestCount = taskTracker.incTaskCount;
|
||||
self.$$completeOutstandingRequest = completeOutstandingRequest;
|
||||
self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
|
||||
|
||||
// TODO(vojta): prefix this method with $$ ?
|
||||
self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks;
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// URL API
|
||||
@@ -76,21 +101,20 @@ function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
|
||||
*
|
||||
* @description
|
||||
* GETTER:
|
||||
* Without any argument, this method just returns current value of `location.href` (with a
|
||||
* trailing `#` stripped of if the hash is empty).
|
||||
* Without any argument, this method just returns current value of location.href.
|
||||
*
|
||||
* 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 State object to use with `pushState`/`replaceState`
|
||||
* @param {object=} 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
|
||||
@@ -148,7 +172,8 @@ function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
|
||||
// - 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 trimEmptyHash(pendingLocation || location.href);
|
||||
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
|
||||
return pendingLocation || location.href.replace(/%27/g,'\'');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -283,8 +308,7 @@ function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
|
||||
/**
|
||||
* @name $browser#defer
|
||||
* @param {function()} fn A function, who's execution should be deferred.
|
||||
* @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.
|
||||
* @param {number=} [delay=0] of milliseconds to defer the function execution.
|
||||
* @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
|
||||
*
|
||||
* @description
|
||||
@@ -295,19 +319,14 @@ function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
|
||||
* via `$browser.defer.flush()`.
|
||||
*
|
||||
*/
|
||||
self.defer = function(fn, delay, taskType) {
|
||||
self.defer = function(fn, delay) {
|
||||
var timeoutId;
|
||||
|
||||
delay = delay || 0;
|
||||
taskType = taskType || taskTracker.DEFAULT_TASK_TYPE;
|
||||
|
||||
taskTracker.incTaskCount(taskType);
|
||||
outstandingRequestCount++;
|
||||
timeoutId = setTimeout(function() {
|
||||
delete pendingDeferIds[timeoutId];
|
||||
taskTracker.completeTask(fn, taskType);
|
||||
}, delay);
|
||||
pendingDeferIds[timeoutId] = taskType;
|
||||
|
||||
completeOutstandingRequest(fn);
|
||||
}, delay || 0);
|
||||
pendingDeferIds[timeoutId] = true;
|
||||
return timeoutId;
|
||||
};
|
||||
|
||||
@@ -323,11 +342,10 @@ function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
|
||||
* canceled.
|
||||
*/
|
||||
self.defer.cancel = function(deferId) {
|
||||
if (pendingDeferIds.hasOwnProperty(deferId)) {
|
||||
var taskType = pendingDeferIds[deferId];
|
||||
if (pendingDeferIds[deferId]) {
|
||||
delete pendingDeferIds[deferId];
|
||||
clearTimeout(deferId);
|
||||
taskTracker.completeTask(noop, taskType);
|
||||
completeOutstandingRequest(noop);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -337,8 +355,8 @@ function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
|
||||
|
||||
/** @this */
|
||||
function $BrowserProvider() {
|
||||
this.$get = ['$window', '$log', '$sniffer', '$document', '$$taskTrackerFactory',
|
||||
function($window, $log, $sniffer, $document, $$taskTrackerFactory) {
|
||||
return new Browser($window, $document, $log, $sniffer, $$taskTrackerFactory);
|
||||
}];
|
||||
this.$get = ['$window', '$log', '$sniffer', '$document',
|
||||
function($window, $log, $sniffer, $document) {
|
||||
return new Browser($window, $document, $log, $sniffer);
|
||||
}];
|
||||
}
|
||||
|
||||
+178
-657
File diff suppressed because it is too large
Load Diff
+22
-3
@@ -26,7 +26,8 @@ function identifierForController(controller, ident) {
|
||||
* {@link ng.$controllerProvider#register register} method.
|
||||
*/
|
||||
function $ControllerProvider() {
|
||||
var controllers = {};
|
||||
var controllers = {},
|
||||
globals = false;
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -54,7 +55,22 @@ function $ControllerProvider() {
|
||||
}
|
||||
};
|
||||
|
||||
this.$get = ['$injector', function($injector) {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $controllerProvider#allowGlobals
|
||||
* @description If called, allows `$controller` to find controller constructors on `window`
|
||||
*
|
||||
* @deprecated
|
||||
* sinceVersion="v1.3.0"
|
||||
* removeVersion="v1.7.0"
|
||||
* This method of finding controllers has been deprecated.
|
||||
*/
|
||||
this.allowGlobals = function() {
|
||||
globals = true;
|
||||
};
|
||||
|
||||
|
||||
this.$get = ['$injector', '$window', function($injector, $window) {
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
@@ -67,6 +83,8 @@ function $ControllerProvider() {
|
||||
*
|
||||
* * check if a controller with given name is registered via `$controllerProvider`
|
||||
* * check if evaluating the string on the current scope returns a constructor
|
||||
* * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
|
||||
* `window` object (deprecated, not recommended)
|
||||
*
|
||||
* The string can use the `controller as property` syntax, where the controller instance is published
|
||||
* as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
|
||||
@@ -106,7 +124,8 @@ function $ControllerProvider() {
|
||||
identifier = identifier || match[3];
|
||||
expression = controllers.hasOwnProperty(constructor)
|
||||
? controllers[constructor]
|
||||
: getter(locals.$scope, constructor, true);
|
||||
: getter(locals.$scope, constructor, true) ||
|
||||
(globals ? getter($window, constructor, true) : undefined);
|
||||
|
||||
if (!expression) {
|
||||
throw $controllerMinErr('ctrlreg',
|
||||
|
||||
@@ -436,7 +436,7 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
|
||||
// On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
|
||||
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
|
||||
// to set the property as well to achieve the desired effect.
|
||||
// We use attr[attrName] value since $set might have sanitized the url.
|
||||
// We use attr[attrName] value since $set can sanitize the url.
|
||||
if (msie && propName) element.prop(propName, attr[name]);
|
||||
});
|
||||
}
|
||||
|
||||
+11
-55
@@ -4,14 +4,12 @@
|
||||
*/
|
||||
var nullFormCtrl = {
|
||||
$addControl: noop,
|
||||
$getControls: valueFn([]),
|
||||
$$renameControl: nullFormRenameControl,
|
||||
$removeControl: noop,
|
||||
$setValidity: noop,
|
||||
$setDirty: noop,
|
||||
$setPristine: noop,
|
||||
$setSubmitted: noop,
|
||||
$$setSubmitted: noop
|
||||
$setSubmitted: noop
|
||||
},
|
||||
PENDING_CLASS = 'ng-pending',
|
||||
SUBMITTED_CLASS = 'ng-submitted';
|
||||
@@ -160,30 +158,6 @@ 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;
|
||||
@@ -300,25 +274,12 @@ FormController.prototype = {
|
||||
* @name form.FormController#$setSubmitted
|
||||
*
|
||||
* @description
|
||||
* Sets the form to its `$submitted` state. This will also set `$submitted` on all child and
|
||||
* parent forms of the form.
|
||||
* Sets the form to its submitted state.
|
||||
*/
|
||||
$setSubmitted: function() {
|
||||
var rootForm = this;
|
||||
while (rootForm.$$parentForm && (rootForm.$$parentForm !== nullFormCtrl)) {
|
||||
rootForm = rootForm.$$parentForm;
|
||||
}
|
||||
rootForm.$$setSubmitted();
|
||||
},
|
||||
|
||||
$$setSubmitted: function() {
|
||||
this.$$animate.addClass(this.$$element, SUBMITTED_CLASS);
|
||||
this.$submitted = true;
|
||||
forEach(this.$$controls, function(control) {
|
||||
if (control.$$setSubmitted) {
|
||||
control.$$setSubmitted();
|
||||
}
|
||||
});
|
||||
this.$$parentForm.$setSubmitted();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -377,21 +338,16 @@ addSetValidityMethod({
|
||||
* @restrict EAC
|
||||
*
|
||||
* @description
|
||||
* Helper directive that makes it possible to create control groups inside a
|
||||
* {@link ng.directive:form `form`} directive.
|
||||
* These "child forms" can be used, for example, to determine the validity of a sub-group of
|
||||
* controls.
|
||||
* Nestable alias of {@link ng.directive:form `form`} directive. HTML
|
||||
* does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
|
||||
* sub-group of controls needs to be determined.
|
||||
*
|
||||
* <div class="alert alert-danger">
|
||||
* **Note**: `ngForm` cannot be used as a replacement for `<form>`, because it lacks its
|
||||
* [built-in HTML functionality](https://html.spec.whatwg.org/#the-form-element).
|
||||
* Specifically, you cannot submit `ngForm` like a `<form>` tag. That means,
|
||||
* you cannot send data to the server with `ngForm`, or integrate it with
|
||||
* {@link ng.directive:ngSubmit `ngSubmit`}.
|
||||
* </div>
|
||||
* Note: the purpose of `ngForm` is to group controls,
|
||||
* but not to be a replacement for the `<form>` tag with all of its capabilities
|
||||
* (e.g. posting to the server, ...).
|
||||
*
|
||||
* @param {string=} ngForm|name Name of the form. If specified, the form controller will
|
||||
* be published into the related scope, under this name.
|
||||
* @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
|
||||
* related scope, under this name.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
+62
-74
@@ -255,10 +255,6 @@ 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`.
|
||||
@@ -360,12 +356,7 @@ 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-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`.
|
||||
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
|
||||
*
|
||||
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
@@ -466,10 +457,6 @@ var inputType = {
|
||||
* The model must always be a Date object, otherwise AngularJS will throw an error.
|
||||
* Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
|
||||
*
|
||||
* The value of the resulting Date object will be set to Thursday at 00:00:00 of the requested week,
|
||||
* due to ISO-8601 week numbering standards. Information on ISO's system for numbering the weeks of the
|
||||
* year can be found at: https://en.wikipedia.org/wiki/ISO_8601#Week_dates
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@@ -679,11 +666,7 @@ var inputType = {
|
||||
* error docs for more information and an example of how to convert your model if necessary.
|
||||
* </div>
|
||||
*
|
||||
*
|
||||
*
|
||||
* @knownIssue
|
||||
*
|
||||
* ### HTML5 constraint validation and `allowInvalid`
|
||||
* ## Issues with HTML5 constraint validation
|
||||
*
|
||||
* In browsers that follow the
|
||||
* [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
|
||||
@@ -692,17 +675,6 @@ var inputType = {
|
||||
* which means the view / model values in `ngModel` and subsequently the scope value
|
||||
* will also be an empty string.
|
||||
*
|
||||
* @knownIssue
|
||||
*
|
||||
* ### Large numbers and `step` validation
|
||||
*
|
||||
* The `step` validation will not work correctly for very large numbers (e.g. 9999999999) due to
|
||||
* Javascript's arithmetic limitations. If you need to handle large numbers, purpose-built
|
||||
* libraries (e.g. https://github.com/MikeMcl/big.js/), can be included into AngularJS by
|
||||
* {@link guide/forms#modifying-built-in-validators overwriting the validators}
|
||||
* for `number` and / or `step`, or by {@link guide/forms#custom-validation applying custom validators}
|
||||
* to an `input[text]` element. The source for `input[number]` type can be used as a starting
|
||||
* point for both implementations.
|
||||
*
|
||||
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
@@ -1151,8 +1123,8 @@ var inputType = {
|
||||
* Can be interpolated.
|
||||
* @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step`
|
||||
* Can be interpolated.
|
||||
* @param {expression=} ngChange AngularJS expression to be executed when the ngModel value changes due
|
||||
* to user interaction with the input element.
|
||||
* @param {string=} ngChange AngularJS expression to be executed when the ngModel value changes due
|
||||
* to user interaction with the input element.
|
||||
* @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the
|
||||
* element. **Note** : `ngChecked` should not be used alongside `ngModel`.
|
||||
* Checkout {@link ng.directive:ngChecked ngChecked} for usage.
|
||||
@@ -1498,13 +1470,12 @@ function createDateParser(regexp, mapping) {
|
||||
|
||||
function createDateInputType(type, regexp, parseDate, format) {
|
||||
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
|
||||
badInputChecker(scope, element, attr, ctrl, type);
|
||||
badInputChecker(scope, element, attr, ctrl);
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
|
||||
var isTimeType = type === 'time' || type === 'datetimelocal';
|
||||
var previousDate;
|
||||
var previousTimezone;
|
||||
|
||||
ctrl.$$parserName = type;
|
||||
ctrl.$parsers.push(function(value) {
|
||||
if (ctrl.$isEmpty(value)) return null;
|
||||
|
||||
@@ -1514,7 +1485,6 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
// contains some different data format!
|
||||
return parseDateAndConvertTimeZoneToLocal(value, previousDate);
|
||||
}
|
||||
ctrl.$$parserName = type;
|
||||
return undefined;
|
||||
});
|
||||
|
||||
@@ -1525,13 +1495,11 @@ 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 formatter(value, timezone);
|
||||
return $filter('date')(value, format, timezone);
|
||||
} else {
|
||||
previousDate = null;
|
||||
previousTimezone = null;
|
||||
@@ -1586,49 +1554,25 @@ 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function badInputChecker(scope, element, attr, ctrl, parserName) {
|
||||
function badInputChecker(scope, element, attr, ctrl) {
|
||||
var node = element[0];
|
||||
var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
|
||||
if (nativeValidation) {
|
||||
ctrl.$parsers.push(function(value) {
|
||||
var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
|
||||
if (validity.badInput || validity.typeMismatch) {
|
||||
ctrl.$$parserName = parserName;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return value;
|
||||
return validity.badInput || validity.typeMismatch ? undefined : value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function numberFormatterParser(ctrl) {
|
||||
ctrl.$$parserName = 'number';
|
||||
ctrl.$parsers.push(function(value) {
|
||||
if (ctrl.$isEmpty(value)) return null;
|
||||
if (NUMBER_REGEXP.test(value)) return parseFloat(value);
|
||||
|
||||
ctrl.$$parserName = 'number';
|
||||
return undefined;
|
||||
});
|
||||
|
||||
@@ -1710,7 +1654,7 @@ function isValidForStep(viewValue, stepBase, step) {
|
||||
}
|
||||
|
||||
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
badInputChecker(scope, element, attr, ctrl, 'number');
|
||||
badInputChecker(scope, element, attr, ctrl);
|
||||
numberFormatterParser(ctrl);
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
|
||||
@@ -1718,8 +1662,8 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
var maxVal;
|
||||
|
||||
if (isDefined(attr.min) || attr.ngMin) {
|
||||
ctrl.$validators.min = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
|
||||
ctrl.$validators.min = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
|
||||
};
|
||||
|
||||
attr.$observe('min', function(val) {
|
||||
@@ -1730,8 +1674,8 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
if (isDefined(attr.max) || attr.ngMax) {
|
||||
ctrl.$validators.max = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
|
||||
ctrl.$validators.max = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
|
||||
};
|
||||
|
||||
attr.$observe('max', function(val) {
|
||||
@@ -1757,7 +1701,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
badInputChecker(scope, element, attr, ctrl, 'range');
|
||||
badInputChecker(scope, element, attr, ctrl);
|
||||
numberFormatterParser(ctrl);
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
|
||||
@@ -1896,6 +1840,7 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
stringBasedInputType(ctrl);
|
||||
|
||||
ctrl.$$parserName = 'url';
|
||||
ctrl.$validators.url = function(modelValue, viewValue) {
|
||||
var value = modelValue || viewValue;
|
||||
return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
|
||||
@@ -1908,6 +1853,7 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
stringBasedInputType(ctrl);
|
||||
|
||||
ctrl.$$parserName = 'email';
|
||||
ctrl.$validators.email = function(modelValue, viewValue) {
|
||||
var value = modelValue || viewValue;
|
||||
return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
|
||||
@@ -1932,7 +1878,7 @@ function radioInputType(scope, element, attr, ctrl) {
|
||||
}
|
||||
};
|
||||
|
||||
element.on('change', listener);
|
||||
element.on('click', listener);
|
||||
|
||||
ctrl.$render = function() {
|
||||
var value = attr.value;
|
||||
@@ -1966,7 +1912,7 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
|
||||
ctrl.$setViewValue(element[0].checked, ev && ev.type);
|
||||
};
|
||||
|
||||
element.on('change', listener);
|
||||
element.on('click', listener);
|
||||
|
||||
ctrl.$render = function() {
|
||||
element[0].checked = ctrl.$viewValue;
|
||||
@@ -2193,6 +2139,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+)$/;
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,13 @@ function classDirective(name, selector) {
|
||||
return {
|
||||
restrict: 'AC',
|
||||
link: function(scope, element, attr) {
|
||||
var expression = attr[name].trim();
|
||||
var isOneTime = (expression.charAt(0) === ':') && (expression.charAt(1) === ':');
|
||||
|
||||
var watchInterceptor = isOneTime ? toFlatValue : toClassString;
|
||||
var watchExpression = $parse(expression, watchInterceptor);
|
||||
var watchAction = isOneTime ? ngClassOneTimeWatchAction : ngClassWatchAction;
|
||||
|
||||
var classCounts = element.data('$classCounts');
|
||||
var oldModulo = true;
|
||||
var oldClassString;
|
||||
@@ -36,7 +43,7 @@ function classDirective(name, selector) {
|
||||
scope.$watch(indexWatchExpression, ngClassIndexWatchAction);
|
||||
}
|
||||
|
||||
scope.$watch($parse(attr[name], toClassString), ngClassWatchAction);
|
||||
scope.$watch(watchExpression, watchAction, isOneTime);
|
||||
|
||||
function addClasses(classString) {
|
||||
classString = digestClassCounts(split(classString), 1);
|
||||
@@ -78,9 +85,9 @@ function classDirective(name, selector) {
|
||||
}
|
||||
|
||||
function ngClassIndexWatchAction(newModulo) {
|
||||
// This watch-action should run before the `ngClassWatchAction()`, thus it
|
||||
// This watch-action should run before the `ngClass[OneTime]WatchAction()`, thus it
|
||||
// adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the
|
||||
// `ngClassWatchAction()` will update the classes.
|
||||
// `ngClass[OneTime]WatchAction()` will update the classes.
|
||||
if (newModulo === selector) {
|
||||
addClasses(oldClassString);
|
||||
} else {
|
||||
@@ -90,6 +97,14 @@ function classDirective(name, selector) {
|
||||
oldModulo = newModulo;
|
||||
}
|
||||
|
||||
function ngClassOneTimeWatchAction(newClassValue) {
|
||||
var newClassString = toClassString(newClassValue);
|
||||
|
||||
if (newClassString !== oldClassString) {
|
||||
ngClassWatchAction(newClassString);
|
||||
}
|
||||
}
|
||||
|
||||
function ngClassWatchAction(newClassString) {
|
||||
if (oldModulo === selector) {
|
||||
updateClasses(oldClassString, newClassString);
|
||||
@@ -137,6 +152,34 @@ function classDirective(name, selector) {
|
||||
|
||||
return classString;
|
||||
}
|
||||
|
||||
function toFlatValue(classValue) {
|
||||
var flatValue = classValue;
|
||||
|
||||
if (isArray(classValue)) {
|
||||
flatValue = classValue.map(toFlatValue);
|
||||
} else if (isObject(classValue)) {
|
||||
var hasUndefined = false;
|
||||
|
||||
flatValue = Object.keys(classValue).filter(function(key) {
|
||||
var value = classValue[key];
|
||||
|
||||
if (!hasUndefined && isUndefined(value)) {
|
||||
hasUndefined = true;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
if (hasUndefined) {
|
||||
// Prevent the `oneTimeLiteralWatchInterceptor` from unregistering
|
||||
// the watcher, by including at least one `undefined` value.
|
||||
flatValue.push(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
return flatValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +221,6 @@ 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.
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
* The controller instance can be published into a scope property by specifying
|
||||
* `ng-controller="as propertyName"`.
|
||||
*
|
||||
* If the current `$controllerProvider` is configured to use globals (via
|
||||
* {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may
|
||||
* also be the name of a globally accessible constructor function (deprecated, not recommended).
|
||||
*
|
||||
* @example
|
||||
* Here is a simple form for editing user contact information. Adding, removing, clearing, and
|
||||
* greeting are methods declared on the controller (see source tab). These methods can
|
||||
|
||||
@@ -50,44 +50,33 @@ 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', '$exceptionHandler', function($parse, $rootScope, $exceptionHandler) {
|
||||
return createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsyncEvents[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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
}
|
||||
);
|
||||
|
||||
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
|
||||
|
||||
@@ -282,12 +282,12 @@ function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $
|
||||
this.$$ngModelSet = this.$$parsedNgModelAssign;
|
||||
this.$$pendingDebounce = null;
|
||||
this.$$parserValid = undefined;
|
||||
this.$$parserName = 'parse';
|
||||
|
||||
this.$$currentValidationRunId = 0;
|
||||
|
||||
this.$$scope = $scope;
|
||||
this.$$rootScope = $scope.$root;
|
||||
// https://github.com/angular/angular.js/issues/15833
|
||||
// Prevent `$$scope` from being iterated over by `copy` when NgModelController is deep watched
|
||||
Object.defineProperty(this, '$$scope', {value: $scope});
|
||||
this.$$attr = $attr;
|
||||
this.$$element = $element;
|
||||
this.$$animate = $animate;
|
||||
@@ -614,8 +614,7 @@ NgModelController.prototype = {
|
||||
processAsyncValidators();
|
||||
|
||||
function processParseErrors() {
|
||||
var errorKey = that.$$parserName;
|
||||
|
||||
var errorKey = that.$$parserName || 'parse';
|
||||
if (isUndefined(that.$$parserValid)) {
|
||||
setValidity(errorKey, null);
|
||||
} else {
|
||||
@@ -627,7 +626,6 @@ NgModelController.prototype = {
|
||||
setValidity(name, null);
|
||||
});
|
||||
}
|
||||
|
||||
// Set the parse error last, to prevent unsetting it, should a $validators key == parserName
|
||||
setValidity(errorKey, that.$$parserValid);
|
||||
return that.$$parserValid;
|
||||
@@ -730,10 +728,6 @@ NgModelController.prototype = {
|
||||
|
||||
this.$$parserValid = isUndefined(modelValue) ? undefined : true;
|
||||
|
||||
// Reset any previous parse error
|
||||
this.$setValidity(this.$$parserName, null);
|
||||
this.$$parserName = 'parse';
|
||||
|
||||
if (this.$$parserValid) {
|
||||
for (var i = 0; i < this.$parsers.length; i++) {
|
||||
modelValue = this.$parsers[i](modelValue);
|
||||
@@ -851,12 +845,8 @@ NgModelController.prototype = {
|
||||
|
||||
if (isNumber(debounceDelay[trigger])) {
|
||||
debounceDelay = debounceDelay[trigger];
|
||||
} else if (isNumber(debounceDelay['default']) &&
|
||||
this.$options.getOption('updateOn').indexOf(trigger) === -1
|
||||
) {
|
||||
} else if (isNumber(debounceDelay['default'])) {
|
||||
debounceDelay = debounceDelay['default'];
|
||||
} else if (isNumber(debounceDelay['*'])) {
|
||||
debounceDelay = debounceDelay['*'];
|
||||
}
|
||||
|
||||
this.$$timeout.cancel(this.$$pendingDebounce);
|
||||
@@ -865,7 +855,7 @@ NgModelController.prototype = {
|
||||
this.$$pendingDebounce = this.$$timeout(function() {
|
||||
that.$commitViewValue();
|
||||
}, debounceDelay);
|
||||
} else if (this.$$rootScope.$$phase) {
|
||||
} else if (this.$$scope.$root.$$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;
|
||||
@@ -264,105 +264,6 @@ defaultModelOptions = new ModelOptions({
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* ### Default events, extra triggers, and catch-all debounce values
|
||||
*
|
||||
* This example shows the relationship between "default" update events and
|
||||
* additional `updateOn` triggers.
|
||||
*
|
||||
* `default` events are those that are bound to the control, and when fired, update the `$viewValue`
|
||||
* via {@link ngModel.NgModelController#$setViewValue $setViewValue}. Every event that is not listed
|
||||
* in `updateOn` is considered a "default" event, since different control types have different
|
||||
* default events.
|
||||
*
|
||||
* The control in this example updates by "default", "click", and "blur", with different `debounce`
|
||||
* values. You can see that "click" doesn't have an individual `debounce` value -
|
||||
* therefore it uses the `*` debounce value.
|
||||
*
|
||||
* There is also a button that calls {@link ngModel.NgModelController#$setViewValue $setViewValue}
|
||||
* directly with a "custom" event. Since "custom" is not defined in the `updateOn` list,
|
||||
* it is considered a "default" event and will update the
|
||||
* control if "default" is defined in `updateOn`, and will receive the "default" debounce value.
|
||||
* Note that this is just to illustrate how custom controls would possibly call `$setViewValue`.
|
||||
*
|
||||
* You can change the `updateOn` and `debounce` configuration to test different scenarios. This
|
||||
* is done with {@link ngModel.NgModelController#$overrideModelOptions $overrideModelOptions}.
|
||||
*
|
||||
<example name="ngModelOptions-advanced" module="optionsExample">
|
||||
<file name="index.html">
|
||||
<model-update-demo></model-update-demo>
|
||||
</file>
|
||||
<file name="app.js">
|
||||
angular.module('optionsExample', [])
|
||||
.component('modelUpdateDemo', {
|
||||
templateUrl: 'template.html',
|
||||
controller: function() {
|
||||
this.name = 'Chinua';
|
||||
|
||||
this.options = {
|
||||
updateOn: 'default blur click',
|
||||
debounce: {
|
||||
default: 2000,
|
||||
blur: 0,
|
||||
'*': 1000
|
||||
}
|
||||
};
|
||||
|
||||
this.updateEvents = function() {
|
||||
var eventList = this.options.updateOn.split(' ');
|
||||
eventList.push('*');
|
||||
var events = {};
|
||||
|
||||
for (var i = 0; i < eventList.length; i++) {
|
||||
events[eventList[i]] = this.options.debounce[eventList[i]];
|
||||
}
|
||||
|
||||
this.events = events;
|
||||
};
|
||||
|
||||
this.updateOptions = function() {
|
||||
var options = angular.extend(this.options, {
|
||||
updateOn: Object.keys(this.events).join(' ').replace('*', ''),
|
||||
debounce: this.events
|
||||
});
|
||||
|
||||
this.form.input.$overrideModelOptions(options);
|
||||
};
|
||||
|
||||
// Initialize the event form
|
||||
this.updateEvents();
|
||||
}
|
||||
});
|
||||
</file>
|
||||
<file name="template.html">
|
||||
<form name="$ctrl.form">
|
||||
Input: <input type="text" name="input" ng-model="$ctrl.name" ng-model-options="$ctrl.options" />
|
||||
</form>
|
||||
Model: <tt>{{$ctrl.name}}</tt>
|
||||
<hr>
|
||||
<button ng-click="$ctrl.form.input.$setViewValue('some value', 'custom')">Trigger setViewValue with 'some value' and 'custom' event</button>
|
||||
|
||||
<hr>
|
||||
<form ng-submit="$ctrl.updateOptions()">
|
||||
<b>updateOn</b><br>
|
||||
<input type="text" ng-model="$ctrl.options.updateOn" ng-change="$ctrl.updateEvents()" ng-model-options="{debounce: 500}">
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>Debounce value</th>
|
||||
</tr>
|
||||
<tr ng-repeat="(key, value) in $ctrl.events">
|
||||
<td>{{key}}</td>
|
||||
<td><input type="number" ng-model="$ctrl.events[key]" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
<input type="submit" value="Update options">
|
||||
</form>
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
*
|
||||
* ## Model updates and validation
|
||||
*
|
||||
@@ -406,81 +307,22 @@ defaultModelOptions = new ModelOptions({
|
||||
* </example>
|
||||
*
|
||||
*
|
||||
* ## Programmatically changing options
|
||||
*
|
||||
* The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not
|
||||
* watched for changes. However, it is possible to override the options on a single
|
||||
* {@link ngModel.NgModelController} instance with
|
||||
* {@link ngModel.NgModelController#$overrideModelOptions `NgModelController#$overrideModelOptions()`}.
|
||||
* See also the example for
|
||||
* {@link ngModelOptions#default-events-extra-triggers-and-catch-all-debounce-values
|
||||
* 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`
|
||||
* ## Programmatically changing options
|
||||
*
|
||||
* 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.
|
||||
* The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not
|
||||
* watched for changes. However, it is possible to override the options on a single
|
||||
* {@link ngModel.NgModelController} instance with
|
||||
* {@link ngModel.NgModelController#$overrideModelOptions `NgModelController#$overrideModelOptions()`}.
|
||||
*
|
||||
<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.
|
||||
*
|
||||
* **General options**:
|
||||
*
|
||||
* and its descendents. Valid keys are:
|
||||
* - `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
|
||||
@@ -497,26 +339,18 @@ defaultModelOptions = new ModelOptions({
|
||||
* custom value for each event. For example:
|
||||
* ```
|
||||
* ng-model-options="{
|
||||
* updateOn: 'default blur',
|
||||
* updateOn: 'default blur click',
|
||||
* debounce: { 'default': 500, 'blur': 0 }
|
||||
* }"
|
||||
* ```
|
||||
* You can use the `*` key to specify a debounce value that applies to all events that are not
|
||||
* specifically listed. In the following example, `mouseup` would have a debounce delay of 1000:
|
||||
* ```
|
||||
* ng-model-options="{
|
||||
* updateOn: 'default blur mouseup',
|
||||
* debounce: { 'default': 500, 'blur': 0, '*': 1000 }
|
||||
* }"
|
||||
* ```
|
||||
*
|
||||
* "default" also applies to all events that are listed in `updateOn` but are not
|
||||
* listed in `debounce`, i.e. "click" would also be debounced by 500 milliseconds.
|
||||
*
|
||||
* - `allowInvalid`: boolean value which indicates that the model can be set with values that did
|
||||
* 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
|
||||
@@ -525,24 +359,6 @@ 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'];
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
'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 their support for duplicate
|
||||
* All different types of tracking functions, their syntax, and and their support for duplicate
|
||||
* items in collections can be found in the
|
||||
* {@link ngRepeat#ngRepeat-arguments ngRepeat expression description}.
|
||||
*
|
||||
|
||||
@@ -52,10 +52,10 @@
|
||||
</example>
|
||||
*/
|
||||
var ngStyleDirective = ngDirective(function(scope, element, attr) {
|
||||
scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
if (oldStyles && (newStyles !== oldStyles)) {
|
||||
forEach(oldStyles, function(val, style) { element.css(style, '');});
|
||||
}
|
||||
if (newStyles) element.css(newStyles);
|
||||
});
|
||||
}, true);
|
||||
});
|
||||
|
||||
@@ -383,7 +383,7 @@ var SelectController =
|
||||
|
||||
if (optionAttrs.$attr.ngValue) {
|
||||
// The value attribute is set by ngValue
|
||||
var oldVal, hashedVal;
|
||||
var oldVal, hashedVal = NaN;
|
||||
optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
|
||||
|
||||
var removal;
|
||||
@@ -556,6 +556,18 @@ 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
|
||||
*
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
});
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
*/ /** CLOSURE HACK COMMENT */
|
||||
var requiredDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
@@ -161,7 +161,7 @@ var requiredDirective = function() {
|
||||
});
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
*/ /** CLOSURE HACK COMMENT */
|
||||
var patternDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
@@ -263,7 +263,7 @@ var patternDirective = function() {
|
||||
});
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
*/ /** CLOSURE HACK COMMENT */
|
||||
var maxlengthDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
@@ -352,7 +352,7 @@ var maxlengthDirective = function() {
|
||||
});
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
*/ /** CLOSURE HACK COMMENT */
|
||||
var minlengthDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
* index: ...
|
||||
* }
|
||||
* ```
|
||||
* **Note:** `null` values use `'null'` as their type.
|
||||
* 2. The comparator function is used to sort the items, based on the derived values, types and
|
||||
* indices.
|
||||
*
|
||||
@@ -75,15 +74,11 @@
|
||||
*
|
||||
* The default, built-in comparator should be sufficient for most usecases. In short, it compares
|
||||
* numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to
|
||||
* using their index in the original collection, sorts values of different types by type and puts
|
||||
* `undefined` and `null` values at the end of the sorted list.
|
||||
* using their index in the original collection, and sorts values of different types by type.
|
||||
*
|
||||
* More specifically, it follows these steps to determine the relative order of items:
|
||||
*
|
||||
* 1. If the compared values are of different types:
|
||||
* - If one of the values is undefined, consider it "greater than" the other.
|
||||
* - Else if one of the values is null, consider it "greater than" the other.
|
||||
* - Else compare the types themselves alphabetically.
|
||||
* 1. If the compared values are of different types, compare the types themselves alphabetically.
|
||||
* 2. If both values are of type `string`, compare them alphabetically in a case- and
|
||||
* locale-insensitive way.
|
||||
* 3. If both values are objects, compare their indices instead.
|
||||
@@ -94,10 +89,9 @@
|
||||
*
|
||||
* **Note:** If you notice numbers not being sorted as expected, make sure they are actually being
|
||||
* saved as numbers and not strings.
|
||||
* **Note:** For the purpose of sorting, `null` and `undefined` are considered "greater than"
|
||||
* any other value (with undefined "greater than" null). This effectively means that `null`
|
||||
* and `undefined` values end up at the end of a list sorted in ascending order.
|
||||
* **Note:** `null` values use `'null'` as their type to be able to distinguish them from objects.
|
||||
* **Note:** For the purpose of sorting, `null` values are treated as the string `'null'` (i.e.
|
||||
* `type: 'string'`, `value: 'null'`). This may cause unexpected sort order relative to
|
||||
* other values.
|
||||
*
|
||||
* @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort.
|
||||
* @param {(Function|string|Array.<Function|string>)=} expression - A predicate (or list of
|
||||
@@ -664,7 +658,8 @@ function orderByFilter($parse) {
|
||||
function getPredicateValue(value, index) {
|
||||
var type = typeof value;
|
||||
if (value === null) {
|
||||
type = 'null';
|
||||
type = 'string';
|
||||
value = 'null';
|
||||
} else if (type === 'object') {
|
||||
value = objectValue(value);
|
||||
}
|
||||
@@ -695,11 +690,7 @@ function orderByFilter($parse) {
|
||||
result = value1 < value2 ? -1 : 1;
|
||||
}
|
||||
} else {
|
||||
result = (type1 === 'undefined') ? 1 :
|
||||
(type2 === 'undefined') ? -1 :
|
||||
(type1 === 'null') ? 1 :
|
||||
(type2 === 'null') ? -1 :
|
||||
(type1 < type2) ? -1 : 1;
|
||||
result = type1 < type2 ? -1 : 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
+4
-7
@@ -110,6 +110,7 @@ function $HttpParamSerializerJQLikeProvider() {
|
||||
return parts.join('&');
|
||||
|
||||
function serialize(toSerialize, prefix, topLevel) {
|
||||
if (toSerialize === null || isUndefined(toSerialize)) return;
|
||||
if (isArray(toSerialize)) {
|
||||
forEach(toSerialize, function(value, index) {
|
||||
serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
|
||||
@@ -122,11 +123,7 @@ function $HttpParamSerializerJQLikeProvider() {
|
||||
(topLevel ? '' : ']'));
|
||||
});
|
||||
} else {
|
||||
if (isFunction(toSerialize)) {
|
||||
toSerialize = toSerialize();
|
||||
}
|
||||
parts.push(encodeUriQuery(prefix) + '=' +
|
||||
(toSerialize == null ? '' : encodeUriQuery(serializeValue(toSerialize))));
|
||||
parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1054,7 +1051,7 @@ function $HttpProvider() {
|
||||
config.paramSerializer = isString(config.paramSerializer) ?
|
||||
$injector.get(config.paramSerializer) : config.paramSerializer;
|
||||
|
||||
$browser.$$incOutstandingRequestCount('$http');
|
||||
$browser.$$incOutstandingRequestCount();
|
||||
|
||||
var requestInterceptors = [];
|
||||
var responseInterceptors = [];
|
||||
@@ -1092,7 +1089,7 @@ function $HttpProvider() {
|
||||
}
|
||||
|
||||
function completeOutstandingRequest() {
|
||||
$browser.$$completeOutstandingRequest(noop, '$http');
|
||||
$browser.$$completeOutstandingRequest(noop);
|
||||
}
|
||||
|
||||
function executeHeaderFns(headers, config) {
|
||||
|
||||
+30
-53
@@ -73,8 +73,9 @@ function $InterpolateProvider() {
|
||||
if (value) {
|
||||
startSymbol = value;
|
||||
return this;
|
||||
} else {
|
||||
return startSymbol;
|
||||
}
|
||||
return startSymbol;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -90,8 +91,9 @@ function $InterpolateProvider() {
|
||||
if (value) {
|
||||
endSymbol = value;
|
||||
return this;
|
||||
} else {
|
||||
return endSymbol;
|
||||
}
|
||||
return endSymbol;
|
||||
};
|
||||
|
||||
|
||||
@@ -138,7 +140,7 @@ function $InterpolateProvider() {
|
||||
* ```js
|
||||
* var $interpolate = ...; // injected
|
||||
* var exp = $interpolate('Hello {{name | uppercase}}!');
|
||||
* expect(exp({name:'AngularJS'})).toEqual('Hello ANGULARJS!');
|
||||
* expect(exp({name:'AngularJS'})).toEqual('Hello ANGULAR!');
|
||||
* ```
|
||||
*
|
||||
* `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
|
||||
@@ -238,21 +240,16 @@ function $InterpolateProvider() {
|
||||
* - `context`: evaluation context for all expressions embedded in the interpolated text
|
||||
*/
|
||||
function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
|
||||
var contextAllowsConcatenation = trustedContext === $sce.URL || trustedContext === $sce.MEDIA_URL;
|
||||
|
||||
// Provide a quick exit and simplified result function for text with no interpolation
|
||||
if (!text.length || text.indexOf(startSymbol) === -1) {
|
||||
if (mustHaveExpression && !contextAllowsConcatenation) return;
|
||||
|
||||
var unescapedText = unescapeText(text);
|
||||
if (contextAllowsConcatenation) {
|
||||
unescapedText = $sce.getTrusted(trustedContext, unescapedText);
|
||||
var constantInterp;
|
||||
if (!mustHaveExpression) {
|
||||
var unescapedText = unescapeText(text);
|
||||
constantInterp = valueFn(unescapedText);
|
||||
constantInterp.exp = text;
|
||||
constantInterp.expressions = [];
|
||||
constantInterp.$$watchDelegate = constantWatchDelegate;
|
||||
}
|
||||
var constantInterp = valueFn(unescapedText);
|
||||
constantInterp.exp = text;
|
||||
constantInterp.expressions = [];
|
||||
constantInterp.$$watchDelegate = constantWatchDelegate;
|
||||
|
||||
return constantInterp;
|
||||
}
|
||||
|
||||
@@ -261,13 +258,11 @@ function $InterpolateProvider() {
|
||||
endIndex,
|
||||
index = 0,
|
||||
expressions = [],
|
||||
parseFns,
|
||||
parseFns = [],
|
||||
textLength = text.length,
|
||||
exp,
|
||||
concat = [],
|
||||
expressionPositions = [],
|
||||
singleExpression;
|
||||
|
||||
expressionPositions = [];
|
||||
|
||||
while (index < textLength) {
|
||||
if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
|
||||
@@ -277,9 +272,10 @@ function $InterpolateProvider() {
|
||||
}
|
||||
exp = text.substring(startIndex + startSymbolLength, endIndex);
|
||||
expressions.push(exp);
|
||||
parseFns.push($parse(exp, parseStringifyInterceptor));
|
||||
index = endIndex + endSymbolLength;
|
||||
expressionPositions.push(concat.length);
|
||||
concat.push(''); // Placeholder that will get replaced with the evaluated expression.
|
||||
concat.push('');
|
||||
} else {
|
||||
// we did not find an interpolation, so we have to add the remainder to the separators array
|
||||
if (index !== textLength) {
|
||||
@@ -289,25 +285,15 @@ function $InterpolateProvider() {
|
||||
}
|
||||
}
|
||||
|
||||
singleExpression = concat.length === 1 && expressionPositions.length === 1;
|
||||
// Intercept expression if we need to stringify concatenated inputs, which may be SCE trusted
|
||||
// objects rather than simple strings
|
||||
// (we don't modify the expression if the input consists of only a single trusted input)
|
||||
var interceptor = contextAllowsConcatenation && singleExpression ? undefined : parseStringifyInterceptor;
|
||||
parseFns = expressions.map(function(exp) { return $parse(exp, interceptor); });
|
||||
|
||||
// Concatenating expressions makes it hard to reason about whether some combination of
|
||||
// concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
|
||||
// single expression be used for some $sce-managed secure contexts (RESOURCE_URLs mostly),
|
||||
// we ensure that the value that's used is assigned or constructed by some JS code somewhere
|
||||
// that is more testable or make it obvious that you bound the value to some user controlled
|
||||
// value. This helps reduce the load when auditing for XSS issues.
|
||||
|
||||
// Note that URL and MEDIA_URL $sce contexts do not need this, since `$sce` can sanitize the values
|
||||
// passed to it. In that case, `$sce.getTrusted` will be called on either the single expression
|
||||
// or on the overall concatenated string (losing trusted types used in the mix, by design).
|
||||
// Both these methods will sanitize plain strings. Also, HTML could be included, but since it's
|
||||
// only used in srcdoc attributes, this would not be very useful.
|
||||
// single expression be used for iframe[src], object[src], etc., we ensure that the value
|
||||
// that's used is assigned or constructed by some JS code somewhere that is more testable or
|
||||
// make it obvious that you bound the value to some user controlled value. This helps reduce
|
||||
// the load when auditing for XSS issues.
|
||||
if (trustedContext && concat.length > 1) {
|
||||
$interpolateMinErr.throwNoconcat(text);
|
||||
}
|
||||
|
||||
if (!mustHaveExpression || expressions.length) {
|
||||
var compute = function(values) {
|
||||
@@ -315,18 +301,15 @@ function $InterpolateProvider() {
|
||||
if (allOrNothing && isUndefined(values[i])) return;
|
||||
concat[expressionPositions[i]] = values[i];
|
||||
}
|
||||
|
||||
if (contextAllowsConcatenation) {
|
||||
// If `singleExpression` then `concat[0]` might be a "trusted" value or `null`, rather than a string
|
||||
return $sce.getTrusted(trustedContext, singleExpression ? concat[0] : concat.join(''));
|
||||
} else if (trustedContext && concat.length > 1) {
|
||||
// This context does not allow more than one part, e.g. expr + string or exp + exp.
|
||||
$interpolateMinErr.throwNoconcat(text);
|
||||
}
|
||||
// In an unprivileged context or only one part: just concatenate and return.
|
||||
return concat.join('');
|
||||
};
|
||||
|
||||
var getValue = function(value) {
|
||||
return trustedContext ?
|
||||
$sce.getTrusted(trustedContext, value) :
|
||||
$sce.valueOf(value);
|
||||
};
|
||||
|
||||
return extend(function interpolationFn(context) {
|
||||
var i = 0;
|
||||
var ii = expressions.length;
|
||||
@@ -359,13 +342,7 @@ function $InterpolateProvider() {
|
||||
|
||||
function parseStringifyInterceptor(value) {
|
||||
try {
|
||||
// In concatenable contexts, getTrusted comes at the end, to avoid sanitizing individual
|
||||
// parts of a full URL. We don't care about losing the trustedness here.
|
||||
// In non-concatenable contexts, where there is only one expression, this interceptor is
|
||||
// not applied to the expression.
|
||||
value = (trustedContext && !contextAllowsConcatenation) ?
|
||||
$sce.getTrusted(trustedContext, value) :
|
||||
$sce.valueOf(value);
|
||||
value = getValue(value);
|
||||
return allOrNothing && !isDefined(value) ? value : stringify(value);
|
||||
} catch (err) {
|
||||
$exceptionHandler($interpolateMinErr.interr(text, err));
|
||||
|
||||
+54
-31
@@ -1,21 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
var $intervalMinErr = minErr('$interval');
|
||||
|
||||
/** @this */
|
||||
function $IntervalProvider() {
|
||||
this.$get = ['$$intervalFactory', '$window',
|
||||
function($$intervalFactory, $window) {
|
||||
this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser',
|
||||
function($rootScope, $window, $q, $$q, $browser) {
|
||||
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
|
||||
@@ -143,7 +133,49 @@ function $IntervalProvider() {
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var interval = $$intervalFactory(setIntervalFn, clearIntervalFn);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -156,24 +188,15 @@ function $IntervalProvider() {
|
||||
* @returns {boolean} Returns `true` if the task was successfully canceled.
|
||||
*/
|
||||
interval.cancel = function(promise) {
|
||||
if (!promise) return false;
|
||||
|
||||
if (!promise.hasOwnProperty('$$intervalId')) {
|
||||
throw $intervalMinErr('badprom',
|
||||
'`$interval.cancel()` called with a promise that was not generated by `$interval()`.');
|
||||
if (promise && promise.$$intervalId in intervals) {
|
||||
// Interval cancels should not report as unhandled promise.
|
||||
markQExceptionHandled(intervals[promise.$$intervalId].promise);
|
||||
intervals[promise.$$intervalId].reject('canceled');
|
||||
$window.clearInterval(promise.$$intervalId);
|
||||
delete intervals[promise.$$intervalId];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!intervals.hasOwnProperty(promise.$$intervalId)) return false;
|
||||
|
||||
var id = promise.$$intervalId;
|
||||
var deferred = intervals[id];
|
||||
|
||||
// Interval cancels should not report an unhandled promise.
|
||||
markQExceptionHandled(deferred.promise);
|
||||
deferred.reject('canceled');
|
||||
clearIntervalFn(id);
|
||||
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
return interval;
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
'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;
|
||||
};
|
||||
};
|
||||
}];
|
||||
}
|
||||
+46
-36
@@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
/* global stripHash: true */
|
||||
|
||||
var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/,
|
||||
DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
|
||||
@@ -39,14 +38,6 @@ 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);
|
||||
|
||||
@@ -95,11 +86,17 @@ 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);
|
||||
}
|
||||
@@ -146,8 +143,18 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
|
||||
this.$$compose();
|
||||
};
|
||||
|
||||
this.$$normalizeUrl = function(url) {
|
||||
return appBaseNoFile + url.substr(1); // first char is always '/'
|
||||
/**
|
||||
* 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.$$parseLinkUrl = function(url, relHref) {
|
||||
@@ -271,8 +278,18 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
|
||||
}
|
||||
};
|
||||
|
||||
this.$$normalizeUrl = function(url) {
|
||||
return appBase + (url ? hashPrefix + url : '');
|
||||
/**
|
||||
* 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.$$parseLinkUrl = function(url, relHref) {
|
||||
@@ -323,10 +340,17 @@ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
|
||||
return !!rewrittenUrl;
|
||||
};
|
||||
|
||||
this.$$normalizeUrl = function(url) {
|
||||
this.$$compose = function() {
|
||||
var search = toKeyValue(this.$$search),
|
||||
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
|
||||
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
// include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
|
||||
return appBase + hashPrefix + url;
|
||||
this.$$absUrl = appBase + hashPrefix + this.$$url;
|
||||
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -350,16 +374,6 @@ 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
|
||||
@@ -865,13 +879,6 @@ 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;
|
||||
@@ -931,6 +938,8 @@ function $LocationProvider() {
|
||||
// update location manually
|
||||
if ($location.absUrl() !== $browser.url()) {
|
||||
$rootScope.$apply();
|
||||
// hack to work around FF6 bug 684208 when scenario runner clicks on links
|
||||
$window.angular['ff-684208-preventDefault'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -938,7 +947,7 @@ function $LocationProvider() {
|
||||
|
||||
|
||||
// rewrite hashbang url <> html5 url
|
||||
if ($location.absUrl() !== initialUrl) {
|
||||
if (trimEmptyHash($location.absUrl()) !== trimEmptyHash(initialUrl)) {
|
||||
$browser.url($location.absUrl(), true);
|
||||
}
|
||||
|
||||
@@ -957,6 +966,7 @@ function $LocationProvider() {
|
||||
var oldUrl = $location.absUrl();
|
||||
var oldState = $location.$$state;
|
||||
var defaultPrevented;
|
||||
newUrl = trimEmptyHash(newUrl);
|
||||
$location.$$parse(newUrl);
|
||||
$location.$$state = newState;
|
||||
|
||||
@@ -984,11 +994,11 @@ function $LocationProvider() {
|
||||
if (initializing || $location.$$urlUpdatedByLocation) {
|
||||
$location.$$urlUpdatedByLocation = false;
|
||||
|
||||
var oldUrl = $browser.url();
|
||||
var newUrl = $location.absUrl();
|
||||
var oldUrl = trimEmptyHash($browser.url());
|
||||
var newUrl = trimEmptyHash($location.absUrl());
|
||||
var oldState = $browser.state();
|
||||
var currentReplace = $location.$$replace;
|
||||
var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) ||
|
||||
var urlOrStateChanged = oldUrl !== newUrl ||
|
||||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
|
||||
|
||||
if (initializing || urlOrStateChanged) {
|
||||
|
||||
+78
-87
@@ -1803,8 +1803,15 @@ function $ParseProvider() {
|
||||
var lexer = new Lexer($parseOptions);
|
||||
var parser = new Parser(lexer, $filter, $parseOptions);
|
||||
parsedExpression = parser.parse(exp);
|
||||
|
||||
cache[cacheKey] = addWatchDelegate(parsedExpression);
|
||||
if (parsedExpression.constant) {
|
||||
parsedExpression.$$watchDelegate = constantWatchDelegate;
|
||||
} else if (parsedExpression.oneTime) {
|
||||
parsedExpression.$$watchDelegate = parsedExpression.literal ?
|
||||
oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
|
||||
} else if (parsedExpression.inputs) {
|
||||
parsedExpression.$$watchDelegate = inputsWatchDelegate;
|
||||
}
|
||||
cache[cacheKey] = parsedExpression;
|
||||
}
|
||||
return addInterceptor(parsedExpression, interceptorFn);
|
||||
|
||||
@@ -1892,48 +1899,57 @@ function $ParseProvider() {
|
||||
}
|
||||
|
||||
function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
|
||||
var isDone = parsedExpression.literal ? isAllDefined : isDefined;
|
||||
var unwatch, lastValue;
|
||||
|
||||
var exp = parsedExpression.$$intercepted || parsedExpression;
|
||||
var post = parsedExpression.$$interceptor || identity;
|
||||
|
||||
var useInputs = parsedExpression.inputs && !exp.inputs;
|
||||
|
||||
// Propogate the literal/inputs/constant attributes
|
||||
// ... but not oneTime since we are handling it
|
||||
oneTimeWatch.literal = parsedExpression.literal;
|
||||
oneTimeWatch.constant = parsedExpression.constant;
|
||||
oneTimeWatch.inputs = parsedExpression.inputs;
|
||||
|
||||
// Allow other delegates to run on this wrapped expression
|
||||
addWatchDelegate(oneTimeWatch);
|
||||
|
||||
unwatch = scope.$watch(oneTimeWatch, listener, objectEquality, prettyPrintExpression);
|
||||
|
||||
if (parsedExpression.inputs) {
|
||||
unwatch = inputsWatchDelegate(scope, oneTimeListener, objectEquality, parsedExpression, prettyPrintExpression);
|
||||
} else {
|
||||
unwatch = scope.$watch(oneTimeWatch, oneTimeListener, objectEquality);
|
||||
}
|
||||
return unwatch;
|
||||
|
||||
function unwatchIfDone() {
|
||||
if (isDone(lastValue)) {
|
||||
unwatch();
|
||||
}
|
||||
function oneTimeWatch(scope) {
|
||||
return parsedExpression(scope);
|
||||
}
|
||||
|
||||
function oneTimeWatch(scope, locals, assign, inputs) {
|
||||
lastValue = useInputs && inputs ? inputs[0] : exp(scope, locals, assign, inputs);
|
||||
if (isDone(lastValue)) {
|
||||
scope.$$postDigest(unwatchIfDone);
|
||||
function oneTimeListener(value, old, scope) {
|
||||
lastValue = value;
|
||||
if (isFunction(listener)) {
|
||||
listener(value, old, scope);
|
||||
}
|
||||
if (isDefined(value)) {
|
||||
scope.$$postDigest(function() {
|
||||
if (isDefined(lastValue)) {
|
||||
unwatch();
|
||||
}
|
||||
});
|
||||
}
|
||||
return post(lastValue);
|
||||
}
|
||||
}
|
||||
|
||||
function isAllDefined(value) {
|
||||
var allDefined = true;
|
||||
forEach(value, function(val) {
|
||||
if (!isDefined(val)) allDefined = false;
|
||||
});
|
||||
return allDefined;
|
||||
function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
|
||||
var unwatch, lastValue;
|
||||
unwatch = scope.$watch(function oneTimeWatch(scope) {
|
||||
return parsedExpression(scope);
|
||||
}, function oneTimeListener(value, old, scope) {
|
||||
lastValue = value;
|
||||
if (isFunction(listener)) {
|
||||
listener(value, old, scope);
|
||||
}
|
||||
if (isAllDefined(value)) {
|
||||
scope.$$postDigest(function() {
|
||||
if (isAllDefined(lastValue)) unwatch();
|
||||
});
|
||||
}
|
||||
}, objectEquality);
|
||||
|
||||
return unwatch;
|
||||
|
||||
function isAllDefined(value) {
|
||||
var allDefined = true;
|
||||
forEach(value, function(val) {
|
||||
if (!isDefined(val)) allDefined = false;
|
||||
});
|
||||
return allDefined;
|
||||
}
|
||||
}
|
||||
|
||||
function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
|
||||
@@ -1944,74 +1960,49 @@ function $ParseProvider() {
|
||||
return unwatch;
|
||||
}
|
||||
|
||||
function addWatchDelegate(parsedExpression) {
|
||||
if (parsedExpression.constant) {
|
||||
parsedExpression.$$watchDelegate = constantWatchDelegate;
|
||||
} else if (parsedExpression.oneTime) {
|
||||
parsedExpression.$$watchDelegate = oneTimeWatchDelegate;
|
||||
} else if (parsedExpression.inputs) {
|
||||
parsedExpression.$$watchDelegate = inputsWatchDelegate;
|
||||
}
|
||||
|
||||
return parsedExpression;
|
||||
}
|
||||
|
||||
function chainInterceptors(first, second) {
|
||||
function chainedInterceptor(value) {
|
||||
return second(first(value));
|
||||
}
|
||||
chainedInterceptor.$stateful = first.$stateful || second.$stateful;
|
||||
chainedInterceptor.$$pure = first.$$pure && second.$$pure;
|
||||
|
||||
return chainedInterceptor;
|
||||
}
|
||||
|
||||
function addInterceptor(parsedExpression, interceptorFn) {
|
||||
if (!interceptorFn) return parsedExpression;
|
||||
|
||||
// Extract any existing interceptors out of the parsedExpression
|
||||
// to ensure the original parsedExpression is always the $$intercepted
|
||||
if (parsedExpression.$$interceptor) {
|
||||
interceptorFn = chainInterceptors(parsedExpression.$$interceptor, interceptorFn);
|
||||
parsedExpression = parsedExpression.$$intercepted;
|
||||
}
|
||||
|
||||
var watchDelegate = parsedExpression.$$watchDelegate;
|
||||
var useInputs = false;
|
||||
|
||||
var fn = function interceptedExpression(scope, locals, assign, inputs) {
|
||||
var regularWatch =
|
||||
watchDelegate !== oneTimeLiteralWatchDelegate &&
|
||||
watchDelegate !== oneTimeWatchDelegate;
|
||||
|
||||
var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
|
||||
var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
|
||||
return interceptorFn(value);
|
||||
return interceptorFn(value, scope, locals);
|
||||
} : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
|
||||
var value = parsedExpression(scope, locals, assign, inputs);
|
||||
var result = interceptorFn(value, scope, locals);
|
||||
// we only return the interceptor's result if the
|
||||
// initial value is defined (for bind-once)
|
||||
return isDefined(value) ? result : value;
|
||||
};
|
||||
|
||||
// Maintain references to the interceptor/intercepted
|
||||
fn.$$intercepted = parsedExpression;
|
||||
fn.$$interceptor = interceptorFn;
|
||||
|
||||
// Propogate the literal/oneTime/constant attributes
|
||||
fn.literal = parsedExpression.literal;
|
||||
fn.oneTime = parsedExpression.oneTime;
|
||||
fn.constant = parsedExpression.constant;
|
||||
|
||||
// Treat the interceptor like filters.
|
||||
// If it is not $stateful then only watch its inputs.
|
||||
// If the expression itself has no inputs then use the full expression as an input.
|
||||
if (!interceptorFn.$stateful) {
|
||||
useInputs = !parsedExpression.inputs;
|
||||
// Propagate $$watchDelegates other then inputsWatchDelegate
|
||||
useInputs = !parsedExpression.inputs;
|
||||
if (watchDelegate && watchDelegate !== inputsWatchDelegate) {
|
||||
fn.$$watchDelegate = watchDelegate;
|
||||
fn.inputs = parsedExpression.inputs;
|
||||
} else if (!interceptorFn.$stateful) {
|
||||
// Treat interceptor like filters - assume non-stateful by default and use the inputsWatchDelegate
|
||||
fn.$$watchDelegate = inputsWatchDelegate;
|
||||
fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
|
||||
}
|
||||
|
||||
if (!interceptorFn.$$pure) {
|
||||
fn.inputs = fn.inputs.map(function(e) {
|
||||
if (fn.inputs) {
|
||||
fn.inputs = fn.inputs.map(function(e) {
|
||||
// Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a
|
||||
// non-pure interceptor function.
|
||||
// potentially non-pure interceptor function.
|
||||
if (e.isPure === PURITY_RELATIVE) {
|
||||
return function depurifier(s) { return e(s); };
|
||||
}
|
||||
return e;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return addWatchDelegate(fn);
|
||||
return fn;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+47
-23
@@ -299,8 +299,8 @@ function $RootScopeProvider() {
|
||||
* according to the {@link angular.equals} function. To save the value of the object for
|
||||
* later comparison, the {@link angular.copy} function is used. This therefore means that
|
||||
* watching complex objects will have adverse memory and performance implications.
|
||||
* - This should not be used to watch for changes in objects that are (or contain)
|
||||
* [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}.
|
||||
* - This should not be used to watch for changes in objects that are
|
||||
* or contain [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}.
|
||||
* - The watch `listener` may change the model, which may trigger other `listener`s to fire.
|
||||
* This is achieved by rerunning the watchers until no changes are detected. The rerun
|
||||
* iteration limit is 10 to prevent an infinite loop deadlock.
|
||||
@@ -448,6 +448,12 @@ function $RootScopeProvider() {
|
||||
* values are examined for changes on every call to `$digest`.
|
||||
* - The `listener` is called whenever any expression in the `watchExpressions` array changes.
|
||||
*
|
||||
* `$watchGroup` is more performant than watching each expression individually, and should be
|
||||
* used when the listener does not need to know which expression has changed.
|
||||
* If the listener needs to know which expression has changed,
|
||||
* {@link ng.$rootScope.Scope#$watch $watch()} or
|
||||
* {@link ng.$rootScope.Scope#$watchCollection $watchCollection()} should be used.
|
||||
*
|
||||
* @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
|
||||
* watched using {@link ng.$rootScope.Scope#$watch $watch()}
|
||||
*
|
||||
@@ -456,7 +462,34 @@ function $RootScopeProvider() {
|
||||
* The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
|
||||
* those of `watchExpression`
|
||||
* and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
|
||||
* those of `watchExpression`
|
||||
* those of `watchExpression`.
|
||||
*
|
||||
* Note that `newValues` and `oldValues` reflect the differences in each **individual**
|
||||
* expression, and not the difference of the values between each call of the listener.
|
||||
* That means the difference between `newValues` and `oldValues` cannot be used to determine
|
||||
* which expression has changed / remained stable:
|
||||
*
|
||||
* ```js
|
||||
*
|
||||
* $scope.$watchGroup(['v1', 'v2'], function(newValues, oldValues) {
|
||||
* console.log(newValues, oldValues);
|
||||
* });
|
||||
*
|
||||
* // newValues, oldValues initially
|
||||
* // [undefined, undefined], [undefined, undefined]
|
||||
*
|
||||
* $scope.v1 = 'a';
|
||||
* $scope.v2 = 'a';
|
||||
*
|
||||
* // ['a', 'a'], [undefined, undefined]
|
||||
*
|
||||
* $scope.v2 = 'b'
|
||||
*
|
||||
* // v1 hasn't changed since it became `'a'`, therefore its oldValue is still `undefined`
|
||||
* // ['a', 'b'], [undefined, 'a']
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* The `scope` refers to the current scope.
|
||||
* @returns {function()} Returns a de-registration function for all listeners.
|
||||
*/
|
||||
@@ -489,8 +522,9 @@ function $RootScopeProvider() {
|
||||
}
|
||||
|
||||
forEach(watchExpressions, function(expr, i) {
|
||||
var unwatchFn = self.$watch(expr, function watchGroupSubAction(value) {
|
||||
var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
|
||||
newValues[i] = value;
|
||||
oldValues[i] = oldValue;
|
||||
if (!changeReactionScheduled) {
|
||||
changeReactionScheduled = true;
|
||||
self.$evalAsync(watchGroupAction);
|
||||
@@ -502,17 +536,11 @@ function $RootScopeProvider() {
|
||||
function watchGroupAction() {
|
||||
changeReactionScheduled = false;
|
||||
|
||||
try {
|
||||
if (firstRun) {
|
||||
firstRun = false;
|
||||
listener(newValues, newValues, self);
|
||||
} else {
|
||||
listener(newValues, oldValues, self);
|
||||
}
|
||||
} finally {
|
||||
for (var i = 0; i < watchExpressions.length; i++) {
|
||||
oldValues[i] = newValues[i];
|
||||
}
|
||||
if (firstRun) {
|
||||
firstRun = false;
|
||||
listener(newValues, newValues, self);
|
||||
} else {
|
||||
listener(newValues, oldValues, self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,11 +608,7 @@ function $RootScopeProvider() {
|
||||
* de-registration function is executed, the internal watch operation is terminated.
|
||||
*/
|
||||
$watchCollection: function(obj, listener) {
|
||||
// Mark the interceptor as
|
||||
// ... $$pure when literal since the instance will change when any input changes
|
||||
$watchCollectionInterceptor.$$pure = $parse(obj).literal;
|
||||
// ... $stateful when non-literal since we must read the state of the collection
|
||||
$watchCollectionInterceptor.$stateful = !$watchCollectionInterceptor.$$pure;
|
||||
$watchCollectionInterceptor.$stateful = true;
|
||||
|
||||
var self = this;
|
||||
// the current value, updated on each dirty-check run
|
||||
@@ -772,7 +796,7 @@ function $RootScopeProvider() {
|
||||
var watch, value, last, fn, get,
|
||||
watchers,
|
||||
dirty, ttl = TTL,
|
||||
next, current, target = asyncQueue.length ? $rootScope : this,
|
||||
next, current, target = this,
|
||||
watchLog = [],
|
||||
logIdx, asyncTask;
|
||||
|
||||
@@ -1122,7 +1146,7 @@ function $RootScopeProvider() {
|
||||
if (asyncQueue.length) {
|
||||
$rootScope.$digest();
|
||||
}
|
||||
}, null, '$evalAsync');
|
||||
});
|
||||
}
|
||||
|
||||
asyncQueue.push({scope: this, fn: $parse(expr), locals: locals});
|
||||
@@ -1493,7 +1517,7 @@ function $RootScopeProvider() {
|
||||
if (applyAsyncId === null) {
|
||||
applyAsyncId = $browser.defer(function() {
|
||||
$rootScope.$apply(flushApplyAsync);
|
||||
}, null, '$applyAsync');
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
+14
-24
@@ -6,7 +6,6 @@
|
||||
* Private service to sanitize uris for links and images. Used by $compile and $sanitize.
|
||||
*/
|
||||
function $$SanitizeUriProvider() {
|
||||
|
||||
var aHrefSanitizationWhitelist = /^\s*(https?|s?ftp|mailto|tel|file):/,
|
||||
imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
|
||||
|
||||
@@ -15,16 +14,12 @@ function $$SanitizeUriProvider() {
|
||||
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
|
||||
* urls during a[href] sanitization.
|
||||
*
|
||||
* The sanitization is a security measure aimed at prevent XSS attacks via HTML anchor links.
|
||||
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
|
||||
*
|
||||
* Any url due to be assigned to an `a[href]` attribute via interpolation is marked as requiring
|
||||
* the $sce.URL security context. When interpolation occurs a call is made to `$sce.trustAsUrl(url)`
|
||||
* which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize the potentially malicious URL.
|
||||
*
|
||||
* If the URL matches the `aHrefSanitizationWhitelist` regular expression, it is returned unchanged.
|
||||
*
|
||||
* If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written
|
||||
* to the DOM it is inactive and potentially malicious code will not be executed.
|
||||
* Any url about to be assigned to a[href] via data-binding is first normalized and turned into
|
||||
* an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
|
||||
* regular expression. If a match is found, the original url is written into the dom. Otherwise,
|
||||
* the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
|
||||
*
|
||||
* @param {RegExp=} regexp New regexp to whitelist urls with.
|
||||
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
|
||||
@@ -44,17 +39,12 @@ function $$SanitizeUriProvider() {
|
||||
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
|
||||
* urls during img[src] sanitization.
|
||||
*
|
||||
* The sanitization is a security measure aimed at prevent XSS attacks via HTML image src links.
|
||||
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
|
||||
*
|
||||
* Any URL due to be assigned to an `img[src]` attribute via interpolation is marked as requiring
|
||||
* the $sce.MEDIA_URL security context. When interpolation occurs a call is made to
|
||||
* `$sce.trustAsMediaUrl(url)` which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize
|
||||
* the potentially malicious URL.
|
||||
*
|
||||
* If the URL matches the `aImgSanitizationWhitelist` regular expression, it is returned unchanged.
|
||||
*
|
||||
* If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written
|
||||
* to the DOM it is inactive and potentially malicious code will not be executed.
|
||||
* Any url about to be assigned to img[src] via data-binding is first normalized and turned into
|
||||
* an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
|
||||
* regular expression. If a match is found, the original url is written into the dom. Otherwise,
|
||||
* the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
|
||||
*
|
||||
* @param {RegExp=} regexp New regexp to whitelist urls with.
|
||||
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
|
||||
@@ -69,10 +59,10 @@ function $$SanitizeUriProvider() {
|
||||
};
|
||||
|
||||
this.$get = function() {
|
||||
return function sanitizeUri(uri, isMediaUrl) {
|
||||
// if (!uri) return uri;
|
||||
var regex = isMediaUrl ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
|
||||
var normalizedVal = urlResolve(uri && uri.trim()).href;
|
||||
return function sanitizeUri(uri, isImage) {
|
||||
var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
|
||||
var normalizedVal;
|
||||
normalizedVal = urlResolve(uri && uri.trim()).href;
|
||||
if (normalizedVal !== '' && !normalizedVal.match(regex)) {
|
||||
return 'unsafe:' + normalizedVal;
|
||||
}
|
||||
|
||||
+32
-67
@@ -22,17 +22,12 @@ var SCE_CONTEXTS = {
|
||||
// Style statements or stylesheets. Currently unused in AngularJS.
|
||||
CSS: 'css',
|
||||
|
||||
// An URL used in a context where it refers to the source of media, which are not expected to be run
|
||||
// as scripts, such as an image, audio, video, etc.
|
||||
MEDIA_URL: 'mediaUrl',
|
||||
|
||||
// An URL used in a context where it does not refer to a resource that loads code.
|
||||
// A value that can be trusted as a URL can also trusted as a MEDIA_URL.
|
||||
// An URL used in a context where it does not refer to a resource that loads code. Currently
|
||||
// unused in AngularJS.
|
||||
URL: 'url',
|
||||
|
||||
// RESOURCE_URL is a subtype of URL used where the referred-to resource could be interpreted as
|
||||
// code. (e.g. ng-include, script src binding, templateUrl)
|
||||
// A value that can be trusted as a RESOURCE_URL, can also trusted as a URL and a MEDIA_URL.
|
||||
RESOURCE_URL: 'resourceUrl',
|
||||
|
||||
// Script. Currently unused in AngularJS.
|
||||
@@ -247,7 +242,7 @@ function $SceDelegateProvider() {
|
||||
return resourceUrlBlacklist;
|
||||
};
|
||||
|
||||
this.$get = ['$injector', '$$sanitizeUri', function($injector, $$sanitizeUri) {
|
||||
this.$get = ['$injector', function($injector) {
|
||||
|
||||
var htmlSanitizer = function htmlSanitizer(html) {
|
||||
throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
|
||||
@@ -260,7 +255,7 @@ function $SceDelegateProvider() {
|
||||
|
||||
function matchUrl(matcher, parsedUrl) {
|
||||
if (matcher === 'self') {
|
||||
return urlIsSameOrigin(parsedUrl) || urlIsSameOriginAsBaseUrl(parsedUrl);
|
||||
return urlIsSameOrigin(parsedUrl);
|
||||
} else {
|
||||
// definitely a regex. See adjustMatchers()
|
||||
return !!matcher.exec(parsedUrl.href);
|
||||
@@ -312,8 +307,7 @@ function $SceDelegateProvider() {
|
||||
|
||||
byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
|
||||
byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
|
||||
byType[SCE_CONTEXTS.MEDIA_URL] = generateHolderType(trustedValueHolderBase);
|
||||
byType[SCE_CONTEXTS.URL] = generateHolderType(byType[SCE_CONTEXTS.MEDIA_URL]);
|
||||
byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
|
||||
byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
|
||||
byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
|
||||
|
||||
@@ -392,27 +386,15 @@ function $SceDelegateProvider() {
|
||||
* @name $sceDelegate#getTrusted
|
||||
*
|
||||
* @description
|
||||
* Given an object and a security context in which to assign it, returns a value that's safe to
|
||||
* use in this context, which was represented by the parameter. To do so, this function either
|
||||
* unwraps the safe type it has been given (for instance, a {@link ng.$sceDelegate#trustAs
|
||||
* `$sceDelegate.trustAs`} result), or it might try to sanitize the value given, depending on
|
||||
* the context and sanitizer availablility.
|
||||
* Takes any input, and either returns a value that's safe to use in the specified context, or
|
||||
* throws an exception.
|
||||
*
|
||||
* The contexts that can be sanitized are $sce.MEDIA_URL, $sce.URL and $sce.HTML. The first two are available
|
||||
* by default, and the third one relies on the `$sanitize` service (which may be loaded through
|
||||
* the `ngSanitize` module). Furthermore, for $sce.RESOURCE_URL context, a plain string may be
|
||||
* accepted if the resource url policy defined by {@link ng.$sceDelegateProvider#resourceUrlWhitelist
|
||||
* `$sceDelegateProvider.resourceUrlWhitelist`} and {@link ng.$sceDelegateProvider#resourceUrlBlacklist
|
||||
* `$sceDelegateProvider.resourceUrlBlacklist`} accepts that resource.
|
||||
*
|
||||
* This function will throw if the safe type isn't appropriate for this context, or if the
|
||||
* value given cannot be accepted in the context (which might be caused by sanitization not
|
||||
* being available, or the value not being recognized as safe).
|
||||
*
|
||||
* <div class="alert alert-danger">
|
||||
* Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting
|
||||
* (XSS) vulnerability in your application.
|
||||
* </div>
|
||||
* In practice, there are several cases. When given a string, this function runs checks
|
||||
* and sanitization to make it safe without prior assumptions. When given the result of a {@link
|
||||
* ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call, it returns the originally supplied
|
||||
* value if that value's context is valid for this call's context. Finally, this function can
|
||||
* also throw when there is no way to turn `maybeTrusted` in a safe value (e.g., no sanitization
|
||||
* is available or possible.)
|
||||
*
|
||||
* @param {string} type The context in which this value is to be used (such as `$sce.HTML`).
|
||||
* @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
|
||||
@@ -430,18 +412,12 @@ function $SceDelegateProvider() {
|
||||
if (constructor && maybeTrusted instanceof constructor) {
|
||||
return maybeTrusted.$$unwrapTrustedValue();
|
||||
}
|
||||
|
||||
// If maybeTrusted is a trusted class instance but not of the correct trusted type
|
||||
// then unwrap it and allow it to pass through to the rest of the checks
|
||||
if (isFunction(maybeTrusted.$$unwrapTrustedValue)) {
|
||||
maybeTrusted = maybeTrusted.$$unwrapTrustedValue();
|
||||
}
|
||||
|
||||
// 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.toString(), type === SCE_CONTEXTS.MEDIA_URL);
|
||||
} else if (type === SCE_CONTEXTS.RESOURCE_URL) {
|
||||
// Otherwise, if we get here, then we may either make it safe, or throw an exception. This
|
||||
// depends on the context: some are sanitizatible (HTML), some use whitelists (RESOURCE_URL),
|
||||
// some are impossible to do (JS). This step isn't implemented for CSS and URL, as AngularJS
|
||||
// has no corresponding sinks.
|
||||
if (type === SCE_CONTEXTS.RESOURCE_URL) {
|
||||
// RESOURCE_URL uses a whitelist.
|
||||
if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
|
||||
return maybeTrusted;
|
||||
} else {
|
||||
@@ -596,10 +572,9 @@ function $SceDelegateProvider() {
|
||||
*
|
||||
* If your expressions are constant literals, they're automatically trusted and you don't need to
|
||||
* call `$sce.trustAs` on them (e.g.
|
||||
* `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works (remember to include the
|
||||
* `ngSanitize` module). The `$sceDelegate` will also use the `$sanitize` service if it is available
|
||||
* when binding untrusted values to `$sce.HTML` context.
|
||||
* AngularJS provides an implementation in `angular-sanitize.js`, and if you
|
||||
* `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. The `$sceDelegate` will
|
||||
* also use the `$sanitize` service if it is available when binding untrusted values to
|
||||
* `$sce.HTML` context. AngularJS provides an implementation in `angular-sanitize.js`, and if you
|
||||
* wish to use it, you will also need to depend on the {@link ngSanitize `ngSanitize`} module in
|
||||
* your application.
|
||||
*
|
||||
@@ -619,27 +594,17 @@ function $SceDelegateProvider() {
|
||||
*
|
||||
* | Context | Notes |
|
||||
* |---------------------|----------------|
|
||||
* | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. |
|
||||
* | `$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. <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. |
|
||||
* | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered, and the {@link ngSanitize.$sanitize $sanitize} service is available (implemented by the {@link ngSanitize ngSanitize} module) this will sanitize the value instead of throwing an error. |
|
||||
* | `$sce.CSS` | For CSS that's safe to source into the application. Currently, no bindings require this context. Feel free to use it in your own directives. |
|
||||
* | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=`, `<img src=`, and some others sanitize their urls and don't constitute an SCE context.) |
|
||||
* | `$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`, `VIDEO`, `AUDIO`, `SOURCE`, and `TRACK` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does (it's not just the URL that matters, but also what is at the end of it), and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. |
|
||||
* | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently, no bindings require this context. Feel free to use it in your own directives. |
|
||||
*
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* Be aware that, before AngularJS 1.7.0, `a[href]` and `img[src]` used to sanitize their
|
||||
* interpolated values directly rather than rely upon {@link ng.$sce#getTrusted `$sce.getTrusted`}.
|
||||
*
|
||||
* **As of 1.7.0, this is no longer the case.**
|
||||
*
|
||||
* Now such interpolations are marked as requiring `$sce.URL` (for `a[href]`) or `$sce.MEDIA_URL`
|
||||
* (for `img[src]`), so that the sanitization happens (via `$sce.getTrusted...`) when the `$interpolate`
|
||||
* service evaluates the expressions.
|
||||
* </div>
|
||||
*
|
||||
* There are no CSS or JS context bindings in AngularJS currently, so their corresponding `$sce.trustAs`
|
||||
* functions aren't useful yet. This might evolve.
|
||||
* Be aware that `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
|
||||
* through {@link ng.$sce#getTrusted $sce.getTrusted}. There's no CSS-, URL-, or JS-context bindings
|
||||
* in AngularJS currently, so their corresponding `$sce.trustAs` functions aren't useful yet. This
|
||||
* might evolve.
|
||||
*
|
||||
* ### Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
|
||||
*
|
||||
@@ -813,7 +778,7 @@ function $SceProvider() {
|
||||
* such a value.
|
||||
*
|
||||
* - getTrusted(contextEnum, value)
|
||||
* This function should return the value that is safe to use in the context specified by
|
||||
* This function should return the a value that is safe to use in the context specified by
|
||||
* contextEnum or throw and exception otherwise.
|
||||
*
|
||||
* NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
'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});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var $templateRequestMinErr = minErr('$templateRequest');
|
||||
var $templateRequestMinErr = minErr('$compile');
|
||||
|
||||
/**
|
||||
* @ngdoc provider
|
||||
@@ -99,7 +99,8 @@ function $TemplateRequestProvider() {
|
||||
handleRequestFn.totalPendingRequests--;
|
||||
})
|
||||
.then(function(response) {
|
||||
return $templateCache.put(tpl, response.data);
|
||||
$templateCache.put(tpl, response.data);
|
||||
return response.data;
|
||||
}, handleError);
|
||||
|
||||
function handleError(resp) {
|
||||
|
||||
@@ -104,15 +104,7 @@ function $$TestabilityProvider() {
|
||||
* @name $$testability#whenStable
|
||||
*
|
||||
* @description
|
||||
* 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).
|
||||
* Calls the callback when $timeout and $http requests are completed.
|
||||
*
|
||||
* @param {function} callback
|
||||
*/
|
||||
|
||||
+8
-20
@@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var $timeoutMinErr = minErr('$timeout');
|
||||
|
||||
/** @this */
|
||||
function $TimeoutProvider() {
|
||||
this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
|
||||
@@ -63,7 +61,7 @@ function $TimeoutProvider() {
|
||||
}
|
||||
|
||||
if (!skipApply) $rootScope.$apply();
|
||||
}, delay, '$timeout');
|
||||
}, delay);
|
||||
|
||||
promise.$$timeoutId = timeoutId;
|
||||
deferreds[timeoutId] = deferred;
|
||||
@@ -85,24 +83,14 @@ function $TimeoutProvider() {
|
||||
* canceled.
|
||||
*/
|
||||
timeout.cancel = function(promise) {
|
||||
if (!promise) return false;
|
||||
|
||||
if (!promise.hasOwnProperty('$$timeoutId')) {
|
||||
throw $timeoutMinErr('badprom',
|
||||
'`$timeout.cancel()` called with a promise that was not generated by `$timeout()`.');
|
||||
if (promise && promise.$$timeoutId in deferreds) {
|
||||
// Timeout cancels should not report an unhandled promise.
|
||||
markQExceptionHandled(deferreds[promise.$$timeoutId].promise);
|
||||
deferreds[promise.$$timeoutId].reject('canceled');
|
||||
delete deferreds[promise.$$timeoutId];
|
||||
return $browser.defer.cancel(promise.$$timeoutId);
|
||||
}
|
||||
|
||||
if (!deferreds.hasOwnProperty(promise.$$timeoutId)) return false;
|
||||
|
||||
var id = promise.$$timeoutId;
|
||||
var deferred = deferreds[id];
|
||||
|
||||
// Timeout cancels should not report an unhandled promise.
|
||||
markQExceptionHandled(deferred.promise);
|
||||
deferred.reject('canceled');
|
||||
delete deferreds[id];
|
||||
|
||||
return $browser.defer.cancel(id);
|
||||
return false;
|
||||
};
|
||||
|
||||
return timeout;
|
||||
|
||||
+8
-43
@@ -8,7 +8,6 @@
|
||||
// service.
|
||||
var urlParsingNode = window.document.createElement('a');
|
||||
var originUrl = urlResolve(window.location.href);
|
||||
var baseUrlParsingNode;
|
||||
|
||||
|
||||
/**
|
||||
@@ -45,16 +44,16 @@ var baseUrlParsingNode;
|
||||
* @description Normalizes and parses a URL.
|
||||
* @returns {object} Returns the normalized URL as a dictionary.
|
||||
*
|
||||
* | member name | Description |
|
||||
* |---------------|------------------------------------------------------------------------|
|
||||
* | member name | Description |
|
||||
* |---------------|----------------|
|
||||
* | href | A normalized version of the provided URL if it was not an absolute URL |
|
||||
* | protocol | The protocol without the trailing colon |
|
||||
* | protocol | The protocol including the trailing colon |
|
||||
* | host | The host and port (if the port is non-default) of the normalizedUrl |
|
||||
* | search | The search params, minus the question mark |
|
||||
* | hash | The hash string, minus the hash symbol |
|
||||
* | hostname | The hostname |
|
||||
* | port | The port, without ":" |
|
||||
* | pathname | The pathname, beginning with "/" |
|
||||
* | hash | The hash string, minus the hash symbol
|
||||
* | hostname | The hostname
|
||||
* | port | The port, without ":"
|
||||
* | pathname | The pathname, beginning with "/"
|
||||
*
|
||||
*/
|
||||
function urlResolve(url) {
|
||||
@@ -72,6 +71,7 @@ function urlResolve(url) {
|
||||
|
||||
urlParsingNode.setAttribute('href', href);
|
||||
|
||||
// urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
|
||||
return {
|
||||
href: urlParsingNode.href,
|
||||
protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
|
||||
@@ -98,20 +98,6 @@ function urlIsSameOrigin(requestUrl) {
|
||||
return urlsAreSameOrigin(requestUrl, originUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a request URL and determine whether it is same-origin as the current document base URL.
|
||||
*
|
||||
* Note: The base URL is usually the same as the document location (`location.href`) but can
|
||||
* be overriden by using the `<base>` tag.
|
||||
*
|
||||
* @param {string|object} requestUrl The url of the request as a string that will be resolved
|
||||
* or a parsed URL object.
|
||||
* @returns {boolean} Whether the URL is same-origin as the document base URL.
|
||||
*/
|
||||
function urlIsSameOriginAsBaseUrl(requestUrl) {
|
||||
return urlsAreSameOrigin(requestUrl, getBaseUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a function that can check a URL's origin against a list of allowed/whitelisted origins.
|
||||
* The current location's origin is implicitly trusted.
|
||||
@@ -157,24 +143,3 @@ function urlsAreSameOrigin(url1, url2) {
|
||||
return (url1.protocol === url2.protocol &&
|
||||
url1.host === url2.host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current document base URL.
|
||||
* @returns {string}
|
||||
*/
|
||||
function getBaseUrl() {
|
||||
if (window.document.baseURI) {
|
||||
return window.document.baseURI;
|
||||
}
|
||||
|
||||
// `document.baseURI` is available everywhere except IE
|
||||
if (!baseUrlParsingNode) {
|
||||
baseUrlParsingNode = window.document.createElement('a');
|
||||
baseUrlParsingNode.href = '.';
|
||||
|
||||
// Work-around for IE bug described in Implementation Notes. The fix in `urlResolve()` is not
|
||||
// suitable here because we need to track changes to the base URL.
|
||||
baseUrlParsingNode = baseUrlParsingNode.cloneNode(false);
|
||||
}
|
||||
return baseUrlParsingNode.href;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
/* ngAnimate directives/services */
|
||||
"ngAnimateSwapDirective": true,
|
||||
"$$rAFSchedulerFactory": true,
|
||||
"$$AnimateCacheProvider": true,
|
||||
"$$AnimateChildrenDirective": true,
|
||||
"$$AnimateQueueProvider": true,
|
||||
"$$AnimationProvider": true,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user