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
-1772
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
-1
@@ -74,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',
|
||||
@@ -110,6 +109,7 @@ var angularFiles = {
|
||||
],
|
||||
'ngCookies': [
|
||||
'src/ngCookies/cookies.js',
|
||||
'src/ngCookies/cookieStore.js',
|
||||
'src/ngCookies/cookieWriter.js'
|
||||
],
|
||||
'ngMessageFormat': [
|
||||
@@ -146,6 +146,7 @@ var angularFiles = {
|
||||
'ngTouch': [
|
||||
'src/ngTouch/touch.js',
|
||||
'src/ngTouch/swipe.js',
|
||||
'src/ngTouch/directive/ngClick.js',
|
||||
'src/ngTouch/directive/ngSwipe.js'
|
||||
],
|
||||
'ngAria': [
|
||||
@@ -153,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',
|
||||
@@ -175,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',
|
||||
@@ -206,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>
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -91,13 +91,16 @@ directivesModule
|
||||
.component('tocTree', {
|
||||
template: '<ul>' +
|
||||
'<li ng-repeat="item in $ctrl.items">' +
|
||||
'<a ng-href="#{{item.fragment}}">{{item.title}}</a>' +
|
||||
'<a ng-href="{{ $ctrl.path }}#{{item.fragment}}">{{item.title}}</a>' +
|
||||
'<toc-tree ng-if="::item.children.length > 0" items="item.children"></toc-tree>' +
|
||||
'</li>' +
|
||||
'</ul>',
|
||||
bindings: {
|
||||
items: '<'
|
||||
}
|
||||
},
|
||||
controller: ['$location', function TocTreeController($location) {
|
||||
this.path = $location.path().replace(/^\/?(.+?)(\/index)?\/?$/, '$1');
|
||||
}]
|
||||
})
|
||||
.directive('tocContainer', function() {
|
||||
return {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 +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>
|
||||
|
||||
```
|
||||
@@ -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.)
|
||||
|
||||
@@ -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,764 +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_
|
||||
|
||||
<a name="migrate1.6to1.7-ng-directives-form"></a>
|
||||
|
||||
#### **form**
|
||||
**Due to [223de5](https://github.com/angular/angular.js/commit/223de59e988dc0cc8b4ec3a045b7c0735eba1c77)**,
|
||||
forms will now set $submitted on child forms when they are submitted.
|
||||
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 the 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:
|
||||
|
||||
```
|
||||
{
|
||||
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" do 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:
|
||||
```
|
||||
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:
|
||||
```
|
||||
ng-model-options="{
|
||||
updateOn: 'default blur mouseup',
|
||||
debounce: { '*': 500, 'blur': 0 }
|
||||
}
|
||||
```
|
||||
|
||||
In contrast, when only 'default' is used, 'blur' and 'mouseup' are not debounced:
|
||||
```
|
||||
ng-model-options="{
|
||||
updateOn: 'default blur mouseup',
|
||||
debounce: { 'default': 500 }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### **ngStyle**
|
||||
|
||||
**Due to [15bbd3](https://github.com/angular/angular.js/commit/15bbd3e18cd89b91f7206a06c73d40e54a8a48a0)**,
|
||||
previously the use of deep watch by ng-style would trigger styles to be
|
||||
re-applied when nested state changed. Now only changes to direct
|
||||
properties of the watched object will trigger changes.
|
||||
|
||||
|
||||
<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)**,
|
||||
deepWatch is no longer used in 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 them.
|
||||
|
||||
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)**,
|
||||
give tpload error namespace has changed. Previously the `tpload` error was namespaced to `$compile`.
|
||||
If you have code that matches errors of the form `[$compile:tpload]` it will no
|
||||
longer run. You should change the code to match
|
||||
`[$templateRequest:tpload]`.
|
||||
|
||||
<hr />
|
||||
**Due to ([fb0099](https://github.com/angular/angular.js/commit/fb00991460cf69ae8bc7f1f826363d09c73c0d5e)**,
|
||||
the service now returns the result of `$templateCache.put()` when making a server request to the
|
||||
template. Previously it would return the content of the response directly.
|
||||
This now means if you are decorating `$templateCache.put()` to manipulate the template, you will
|
||||
now get this manipulated result also on the first `$templateRequest` rather than only on subsequent
|
||||
calls (when the template is retrived from the cache).
|
||||
In practice this should not affect any apps, as it is unlikely that they rely on the template being
|
||||
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 bad. To migrate, remove the call to $controllerProvider.allowGlobals() in the config, and
|
||||
register your controller via the Module API or the $controllerProvider, e.g.
|
||||
|
||||
```
|
||||
angular.module('myModule', []).controller('myController', function() {...});
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
<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.
|
||||
|
||||
|
||||
|
||||
#### **Angular**
|
||||
|
||||
**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-ins `toLowerCase` and `toUpperCase`
|
||||
or `toLocaleLowerCase` and `toLocaleUpperCase` for special cases.
|
||||
|
||||
Further, we generally discourage using the angular.x helpers in application code.
|
||||
|
||||
<hr />
|
||||
**Due to [e3ece2](https://github.com/angular/angular.js/commit/e3ece2fad9e1e6d47b5f06815ff186d7e6f44948)**,
|
||||
`angular.isArray()` now supports Array subclasses.
|
||||
|
||||
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)**,
|
||||
the ngAria directive no longer sets aria-* attributes on input[type="hidden"] with ngModel.
|
||||
This can affect apps that test for the presence of aria attributes on hidden inputs.
|
||||
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 was deprecated and removed from the documentation in 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 from 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.
|
||||
@@ -2030,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`.
|
||||
|
||||
@@ -2189,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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -111,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
|
||||
|
||||
@@ -30,21 +30,13 @@ Any version branch not shown in the following table (e.g. 1.5.x) is no longer be
|
||||
Any version branch not shown in the following table (e.g. 1.6.x) is no longer being developed.
|
||||
|
||||
<table class="dev-status table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Version</th><th>Status</th><th>Comments</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="security">
|
||||
<td><span>1.2.x</span></td>
|
||||
<td>Long Term Support</td>
|
||||
<td>Last version to provide IE 8 support</td>
|
||||
</tr>
|
||||
<tr class="stable">
|
||||
<td><span>1.7.x</span></td>
|
||||
<td>Long Term Support</td>
|
||||
<td>See {@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
|
||||
@@ -59,4 +51,4 @@ At this time we will focus exclusively on providing fixes to bugs that satisfy a
|
||||
|
||||
### 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',
|
||||
|
||||
+8
-7
@@ -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.2",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.16.4",
|
||||
"eslint-plugin-promise": "^3.6.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"glob": "^6.0.1",
|
||||
@@ -71,6 +71,7 @@
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"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",
|
||||
@@ -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
-1
@@ -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 */
|
||||
|
||||
+68
-24
@@ -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
|
||||
@@ -1898,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.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
htmlAnchorDirective,
|
||||
inputDirective,
|
||||
inputDirective,
|
||||
hiddenInputBrowserCacheDirective,
|
||||
formDirective,
|
||||
scriptDirective,
|
||||
selectDirective,
|
||||
@@ -28,7 +28,6 @@
|
||||
ngInitDirective,
|
||||
ngNonBindableDirective,
|
||||
ngPluralizeDirective,
|
||||
ngRefDirective,
|
||||
ngRepeatDirective,
|
||||
ngShowDirective,
|
||||
ngStyleDirective,
|
||||
@@ -151,6 +150,8 @@ function publishExternalAPI(angular) {
|
||||
'isArray': isArray,
|
||||
'version': version,
|
||||
'isDate': isDate,
|
||||
'lowercase': lowercase,
|
||||
'uppercase': uppercase,
|
||||
'callbacks': {$$counter: 0},
|
||||
'getTestability': getTestability,
|
||||
'reloadWithDebugInfo': reloadWithDebugInfo,
|
||||
@@ -158,9 +159,7 @@ function publishExternalAPI(angular) {
|
||||
'$$csp': csp,
|
||||
'$$encodeUriSegment': encodeUriSegment,
|
||||
'$$encodeUriQuery': encodeUriQuery,
|
||||
'$$lowercase': lowercase,
|
||||
'$$stringify': stringify,
|
||||
'$$uppercase': uppercase
|
||||
'$$stringify': stringify
|
||||
});
|
||||
|
||||
angularModule = setupModuleLoader(window);
|
||||
@@ -195,7 +194,6 @@ function publishExternalAPI(angular) {
|
||||
ngInit: ngInitDirective,
|
||||
ngNonBindable: ngNonBindableDirective,
|
||||
ngPluralize: ngPluralizeDirective,
|
||||
ngRef: ngRefDirective,
|
||||
ngRepeat: ngRepeatDirective,
|
||||
ngShow: ngShowDirective,
|
||||
ngStyle: ngStyleDirective,
|
||||
@@ -219,7 +217,8 @@ function publishExternalAPI(angular) {
|
||||
ngModelOptions: ngModelOptionsDirective
|
||||
}).
|
||||
directive({
|
||||
ngInclude: ngIncludeFillContentDirective
|
||||
ngInclude: ngIncludeFillContentDirective,
|
||||
input: hiddenInputBrowserCacheDirective
|
||||
}).
|
||||
directive(ngAttributeAliasDirectives).
|
||||
directive(ngEventDirectives);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+11
-75
@@ -464,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();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -560,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);
|
||||
@@ -592,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);
|
||||
@@ -619,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() {
|
||||
@@ -649,7 +585,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} animationRunner the animation runner
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
addClass: function(element, className, options) {
|
||||
options = prepareAnimateOptions(options);
|
||||
@@ -679,7 +615,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
|
||||
*/
|
||||
removeClass: function(element, className, options) {
|
||||
options = prepareAnimateOptions(options);
|
||||
@@ -710,7 +646,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
|
||||
*/
|
||||
setClass: function(element, add, remove, options) {
|
||||
options = prepareAnimateOptions(options);
|
||||
@@ -757,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);
|
||||
|
||||
+2
-1
@@ -172,7 +172,8 @@ function Browser(window, document, $log, $sniffer) {
|
||||
// - pendingLocation is needed as browsers don't allow to read out
|
||||
// the new location.href if a reload happened or if there is a bug like in iOS 9 (see
|
||||
// https://openradar.appspot.com/22186109).
|
||||
return pendingLocation || location.href;
|
||||
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
|
||||
return pendingLocation || location.href.replace(/%27/g,'\'');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+119
-105
@@ -303,22 +303,21 @@
|
||||
* name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: {
|
||||
* localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the
|
||||
* value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in
|
||||
* `localModel` and vice versa. If the binding expression is non-assignable, or if the attribute
|
||||
* isn't optional and doesn't exist, an exception
|
||||
* ({@link error/$compile/nonassign `$compile:nonassign`}) will be thrown upon discovering changes
|
||||
* to the local value, since it will be impossible to sync them back to the parent scope.
|
||||
*
|
||||
* By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
|
||||
* `localModel` and vice versa. Optional attributes should be marked as such with a question mark:
|
||||
* `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't
|
||||
* optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`})
|
||||
* will be thrown upon discovering changes to the local value, since it will be impossible to sync
|
||||
* them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
|
||||
* method is used for tracking changes, and the equality check is based on object identity.
|
||||
* However, if an object literal or an array literal is passed as the binding expression, the
|
||||
* equality check is done by value (using the {@link angular.equals} function). It's also possible
|
||||
* to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection
|
||||
* `$watchCollection`}: use `=*` or `=*attr`
|
||||
* `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional).
|
||||
*
|
||||
* * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an
|
||||
* expression passed via the attribute `attr`. The expression is evaluated in the context of the
|
||||
* parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the
|
||||
* local name.
|
||||
* local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`.
|
||||
*
|
||||
* For example, given `<my-component my-attr="parentModel">` and directive definition of
|
||||
* `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the
|
||||
@@ -339,11 +338,6 @@
|
||||
* One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
|
||||
* back to the parent. However, it does not make this completely impossible.
|
||||
*
|
||||
* By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
|
||||
* method is used for tracking changes, and the equality check is based on object identity.
|
||||
* It's also possible to watch the evaluated value shallowly with
|
||||
* {@link ng.$rootScope.Scope#$watchCollection `$watchCollection`}: use `<*` or `<*attr`
|
||||
*
|
||||
* * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If
|
||||
* no `attr` name is specified then the attribute name is assumed to be the same as the local name.
|
||||
* Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: {
|
||||
@@ -353,36 +347,6 @@
|
||||
* and values into the expression wrapper fn. For example, if the expression is `increment(amount)`
|
||||
* then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`.
|
||||
*
|
||||
* All 4 kinds of bindings (`@`, `=`, `<`, and `&`) can be made optional by adding `?` to the expression.
|
||||
* The marker must come after the mode and before the attribute name.
|
||||
* See the {@link error/$compile/iscp Invalid Isolate Scope Definition error} for definition examples.
|
||||
* This is useful to refine the interface directives provide.
|
||||
* One subtle difference between optional and non-optional happens **when the binding attribute is not
|
||||
* set**:
|
||||
* - the binding is optional: the property will not be defined
|
||||
* - the binding is not optional: the property is defined
|
||||
*
|
||||
* ```js
|
||||
*app.directive('testDir', function() {
|
||||
return {
|
||||
scope: {
|
||||
notoptional: '=',
|
||||
optional: '=?',
|
||||
},
|
||||
bindToController: true,
|
||||
controller: function() {
|
||||
this.$onInit = function() {
|
||||
console.log(this.hasOwnProperty('notoptional')) // true
|
||||
console.log(this.hasOwnProperty('optional')) // false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
*```
|
||||
*
|
||||
*
|
||||
* ##### Combining directives with different scope defintions
|
||||
*
|
||||
* In general it's possible to apply more than one directive to one element, but there might be limitations
|
||||
* depending on the type of scope required by the directives. The following points will help explain these limitations.
|
||||
* For simplicity only two directives are taken into account, but it is also applicable for several directives:
|
||||
@@ -410,6 +374,12 @@
|
||||
* `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings
|
||||
* initialized.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Deprecation warning:** if `$compileProcvider.preAssignBindingsEnabled(true)` was called, bindings for non-ES6 class
|
||||
* controllers are bound to `this` before the controller constructor is called but this use is now deprecated. Please
|
||||
* place initialization code that relies upon bindings inside a `$onInit` method on the controller, instead.
|
||||
* </div>
|
||||
*
|
||||
* It is also possible to set `bindToController` to an object hash with the same format as the `scope` property.
|
||||
* This will set up the scope bindings to the controller directly. Note that `scope` can still be used
|
||||
* to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate
|
||||
@@ -1073,7 +1043,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var bindingCache = createMap();
|
||||
|
||||
function parseIsolateBindings(scope, directiveName, isController) {
|
||||
var LOCAL_REGEXP = /^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/;
|
||||
var LOCAL_REGEXP = /^([@&<]|=(\*?))(\??)\s*([\w$]*)$/;
|
||||
|
||||
var bindings = createMap();
|
||||
|
||||
@@ -1474,21 +1444,54 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compileProvider#strictComponentBindingsEnabled
|
||||
* @name $compileProvider#preAssignBindingsEnabled
|
||||
*
|
||||
* @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided,
|
||||
* otherwise return the current strictComponentBindingsEnabled state.
|
||||
* @param {boolean=} enabled update the preAssignBindingsEnabled state if provided, otherwise just return the
|
||||
* current preAssignBindingsEnabled state
|
||||
* @returns {*} current value if used as getter or itself (chaining) if used as setter
|
||||
*
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Call this method to enable / disable the strict component bindings check. If enabled, the
|
||||
* compiler will enforce that all scope / controller bindings of a
|
||||
* {@link $compileProvider#directive directive} / {@link $compileProvider#component component}
|
||||
* that are not set as optional with `?`, must be provided when the directive is instantiated.
|
||||
* If not provided, the compiler will throw the
|
||||
* {@link error/$compile/missingattr $compile:missingattr error}.
|
||||
* Call this method to enable/disable whether directive controllers are assigned bindings before
|
||||
* calling the controller's constructor.
|
||||
* If enabled (true), the compiler assigns the value of each of the bindings to the
|
||||
* properties of the controller object before the constructor of this object is called.
|
||||
*
|
||||
* If disabled (false), the compiler calls the constructor first before assigning bindings.
|
||||
*
|
||||
* The default value is false.
|
||||
*
|
||||
* @deprecated
|
||||
* sinceVersion="1.6.0"
|
||||
* removeVersion="1.7.0"
|
||||
*
|
||||
* This method and the option to assign the bindings before calling the controller's constructor
|
||||
* will be removed in v1.7.0.
|
||||
*/
|
||||
var preAssignBindingsEnabled = false;
|
||||
this.preAssignBindingsEnabled = function(enabled) {
|
||||
if (isDefined(enabled)) {
|
||||
preAssignBindingsEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
return preAssignBindingsEnabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compileProvider#strictComponentBindingsEnabled
|
||||
*
|
||||
* @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided, otherwise just return the
|
||||
* current strictComponentBindingsEnabled state
|
||||
* @returns {*} current value if used as getter or itself (chaining) if used as setter
|
||||
*
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Call this method to enable/disable strict component bindings check. If enabled, the compiler will enforce that
|
||||
* for all bindings of a component that are not set as optional with `?`, an attribute needs to be provided
|
||||
* on the component's HTML tag.
|
||||
*
|
||||
* The default value is false.
|
||||
*/
|
||||
@@ -1589,9 +1592,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
this.$get = [
|
||||
'$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
|
||||
'$controller', '$rootScope', '$sce', '$animate',
|
||||
'$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri',
|
||||
function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
|
||||
$controller, $rootScope, $sce, $animate) {
|
||||
$controller, $rootScope, $sce, $animate, $$sanitizeUri) {
|
||||
|
||||
var SIMPLE_ATTR_NAME = /^\w/;
|
||||
var specialAttrHolder = window.document.createElement('div');
|
||||
@@ -1736,8 +1739,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
*/
|
||||
$set: function(key, value, writeAttr, attrName) {
|
||||
// TODO: decide whether or not to throw an error if "class"
|
||||
// is set through this function since it may cause $updateClass to
|
||||
// become unstable.
|
||||
//is set through this function since it may cause $updateClass to
|
||||
//become unstable.
|
||||
|
||||
var node = this.$$element[0],
|
||||
booleanKey = getBooleanAttrName(node, key),
|
||||
@@ -1767,20 +1770,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
nodeName = nodeName_(this.$$element);
|
||||
|
||||
// Sanitize img[srcset] values.
|
||||
if (nodeName === 'img' && key === 'srcset' && value) {
|
||||
if (!isString(value)) {
|
||||
throw $compileMinErr('srcset', 'Can\'t pass trusted values to `$set(\'srcset\', value)`: "{0}"', value.toString());
|
||||
}
|
||||
|
||||
// Such values are a bit too complex to handle automatically inside $sce.
|
||||
// Instead, we sanitize each of the URIs individually, which works, even dynamically.
|
||||
|
||||
// It's not possible to work around this using `$sce.trustAsMediaUrl`.
|
||||
// If you want to programmatically set explicitly trusted unsafe URLs, you should use
|
||||
// `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the
|
||||
// `ng-bind-html` directive.
|
||||
|
||||
if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) ||
|
||||
(nodeName === 'img' && key === 'src')) {
|
||||
// sanitize a[href] and img[src] values
|
||||
this[key] = value = (value == null) ? value : $$sanitizeUri(value, key === 'src');
|
||||
} else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) {
|
||||
// sanitize img[srcset] values
|
||||
var result = '';
|
||||
|
||||
// first check if there are spaces because it's not the same pattern
|
||||
@@ -1797,16 +1792,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
for (var i = 0; i < nbrUrisWith2parts; i++) {
|
||||
var innerIdx = i * 2;
|
||||
// sanitize the uri
|
||||
result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx]));
|
||||
result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
|
||||
// add the descriptor
|
||||
result += ' ' + trim(rawUris[innerIdx + 1]);
|
||||
result += (' ' + trim(rawUris[innerIdx + 1]));
|
||||
}
|
||||
|
||||
// split the last item into uri and descriptor
|
||||
var lastTuple = trim(rawUris[i * 2]).split(/\s/);
|
||||
|
||||
// sanitize the last uri
|
||||
result += $sce.getTrustedMediaUrl(trim(lastTuple[0]));
|
||||
result += $$sanitizeUri(trim(lastTuple[0]), true);
|
||||
|
||||
// and add the last descriptor if any
|
||||
if (lastTuple.length === 2) {
|
||||
@@ -1816,7 +1811,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
if (writeAttr !== false) {
|
||||
if (value === null || isUndefined(value)) {
|
||||
if (value == null) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
} else {
|
||||
if (SIMPLE_ATTR_NAME.test(attrName)) {
|
||||
@@ -2554,6 +2549,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
compileNode = $compileNode[0];
|
||||
replaceWith(jqCollection, sliceArgs($template), compileNode);
|
||||
|
||||
// Support: Chrome < 50
|
||||
// https://github.com/angular/angular.js/issues/14041
|
||||
|
||||
// In the versions of V8 prior to Chrome 50, the document fragment that is created
|
||||
// in the `replaceWith` function is improperly garbage collected despite still
|
||||
// being referenced by the `parentNode` property of all of the child nodes. By adding
|
||||
// a reference to the fragment via a different property, we can avoid that incorrect
|
||||
// behavior.
|
||||
// TODO: remove this line after Chrome 50 has been released
|
||||
$template[0].$$parentNode = $template[0].parentNode;
|
||||
|
||||
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
|
||||
replaceDirective && replaceDirective.name, {
|
||||
// Don't pass in:
|
||||
@@ -2575,7 +2581,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// We have transclusion slots,
|
||||
// collect them up, compile them and store their transclusion functions
|
||||
$template = window.document.createDocumentFragment();
|
||||
$template = [];
|
||||
|
||||
var slotMap = createMap();
|
||||
var filledSlots = createMap();
|
||||
@@ -2603,10 +2609,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var slotName = slotMap[directiveNormalize(nodeName_(node))];
|
||||
if (slotName) {
|
||||
filledSlots[slotName] = true;
|
||||
slots[slotName] = slots[slotName] || window.document.createDocumentFragment();
|
||||
slots[slotName].appendChild(node);
|
||||
slots[slotName] = slots[slotName] || [];
|
||||
slots[slotName].push(node);
|
||||
} else {
|
||||
$template.appendChild(node);
|
||||
$template.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2620,11 +2626,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
for (var slotName in slots) {
|
||||
if (slots[slotName]) {
|
||||
// Only define a transclusion function if the slot was filled
|
||||
slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName].childNodes, transcludeFn);
|
||||
slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn);
|
||||
}
|
||||
}
|
||||
|
||||
$template = $template.childNodes;
|
||||
}
|
||||
|
||||
$compileNode.empty(); // clear contents
|
||||
@@ -2815,11 +2819,33 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var controller = elementControllers[name];
|
||||
var bindings = controllerDirective.$$bindings.bindToController;
|
||||
|
||||
controller.instance = controller();
|
||||
$element.data('$' + controllerDirective.name + 'Controller', controller.instance);
|
||||
controller.bindingInfo =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
if (preAssignBindingsEnabled) {
|
||||
if (bindings) {
|
||||
controller.bindingInfo =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
} else {
|
||||
controller.bindingInfo = {};
|
||||
}
|
||||
|
||||
var controllerResult = controller();
|
||||
if (controllerResult !== controller.instance) {
|
||||
// If the controller constructor has a return value, overwrite the instance
|
||||
// from setupControllers
|
||||
controller.instance = controllerResult;
|
||||
$element.data('$' + controllerDirective.name + 'Controller', controllerResult);
|
||||
if (controller.bindingInfo.removeWatches) {
|
||||
controller.bindingInfo.removeWatches();
|
||||
}
|
||||
controller.bindingInfo =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
}
|
||||
} else {
|
||||
controller.instance = controller();
|
||||
$element.data('$' + controllerDirective.name + 'Controller', controller.instance);
|
||||
controller.bindingInfo =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
|
||||
forEach(controllerDirectives, function(controllerDirective, name) {
|
||||
@@ -3323,30 +3349,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
var tag = nodeName_(node);
|
||||
// All tags with src attributes require a RESOURCE_URL value, except for
|
||||
// img and various html5 media tags, which require the MEDIA_URL context.
|
||||
// img and various html5 media tags.
|
||||
if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') {
|
||||
if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) {
|
||||
return $sce.RESOURCE_URL;
|
||||
}
|
||||
return $sce.MEDIA_URL;
|
||||
} else if (attrNormalizedName === 'xlinkHref') {
|
||||
// Some xlink:href are okay, most aren't
|
||||
if (tag === 'image') return $sce.MEDIA_URL;
|
||||
if (tag === 'a') return $sce.URL;
|
||||
return $sce.RESOURCE_URL;
|
||||
} else if (
|
||||
// Formaction
|
||||
// maction[xlink:href] can source SVG. It's not limited to <maction>.
|
||||
} else if (attrNormalizedName === 'xlinkHref' ||
|
||||
(tag === 'form' && attrNormalizedName === 'action') ||
|
||||
// If relative URLs can go where they are not expected to, then
|
||||
// all sorts of trust issues can arise.
|
||||
(tag === 'base' && attrNormalizedName === 'href') ||
|
||||
// links can be stylesheets or imports, which can run script in the current origin
|
||||
(tag === 'link' && attrNormalizedName === 'href')
|
||||
) {
|
||||
return $sce.RESOURCE_URL;
|
||||
} else if (tag === 'a' && (attrNormalizedName === 'href' ||
|
||||
attrNormalizedName === 'ngHref')) {
|
||||
return $sce.URL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3617,21 +3631,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (optional && !attrs[attrName]) break;
|
||||
|
||||
parentGet = $parse(attrs[attrName]);
|
||||
var isLiteral = parentGet.literal;
|
||||
var deepWatch = parentGet.literal;
|
||||
|
||||
var initialValue = destination[scopeName] = parentGet(scope);
|
||||
initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
|
||||
|
||||
removeWatch = scope[definition.collection ? '$watchCollection' : '$watch'](parentGet, function parentValueWatchAction(newValue, oldValue) {
|
||||
removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) {
|
||||
if (oldValue === newValue) {
|
||||
if (oldValue === initialValue || (isLiteral && equals(oldValue, initialValue))) {
|
||||
if (oldValue === initialValue || (deepWatch && equals(oldValue, initialValue))) {
|
||||
return;
|
||||
}
|
||||
oldValue = initialValue;
|
||||
}
|
||||
recordChanges(scopeName, newValue, oldValue);
|
||||
destination[scopeName] = newValue;
|
||||
});
|
||||
}, deepWatch);
|
||||
|
||||
removeWatchCollection.push(removeWatch);
|
||||
break;
|
||||
|
||||
+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
-30
@@ -9,8 +9,7 @@ var nullFormCtrl = {
|
||||
$setValidity: noop,
|
||||
$setDirty: noop,
|
||||
$setPristine: noop,
|
||||
$setSubmitted: noop,
|
||||
$$setSubmitted: noop
|
||||
$setSubmitted: noop
|
||||
},
|
||||
PENDING_CLASS = 'ng-pending',
|
||||
SUBMITTED_CLASS = 'ng-submitted';
|
||||
@@ -275,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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -352,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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
+60
-41
@@ -457,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.
|
||||
*
|
||||
@@ -670,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),
|
||||
@@ -683,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.
|
||||
@@ -1142,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.
|
||||
@@ -1489,11 +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 previousDate;
|
||||
var previousTimezone;
|
||||
|
||||
ctrl.$$parserName = type;
|
||||
ctrl.$parsers.push(function(value) {
|
||||
if (ctrl.$isEmpty(value)) return null;
|
||||
|
||||
@@ -1503,7 +1485,6 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
// contains some different data format!
|
||||
return parseDateAndConvertTimeZoneToLocal(value, previousDate);
|
||||
}
|
||||
ctrl.$$parserName = type;
|
||||
return undefined;
|
||||
});
|
||||
|
||||
@@ -1576,28 +1557,22 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -1679,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);
|
||||
|
||||
@@ -1687,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) {
|
||||
@@ -1699,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) {
|
||||
@@ -1726,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);
|
||||
|
||||
@@ -1865,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);
|
||||
@@ -1877,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);
|
||||
@@ -1901,7 +1878,7 @@ function radioInputType(scope, element, attr, ctrl) {
|
||||
}
|
||||
};
|
||||
|
||||
element.on('change', listener);
|
||||
element.on('click', listener);
|
||||
|
||||
ctrl.$render = function() {
|
||||
var value = attr.value;
|
||||
@@ -1935,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;
|
||||
@@ -2162,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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*
|
||||
@@ -418,9 +319,6 @@ defaultModelOptions = new ModelOptions({
|
||||
* 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}.
|
||||
*
|
||||
*
|
||||
* @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and
|
||||
@@ -441,18 +339,14 @@ 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
+2
-5
@@ -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)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+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));
|
||||
|
||||
+8
-20
@@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var $intervalMinErr = minErr('$interval');
|
||||
|
||||
/** @this */
|
||||
function $IntervalProvider() {
|
||||
this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser',
|
||||
@@ -190,25 +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');
|
||||
$window.clearInterval(id);
|
||||
delete intervals[id];
|
||||
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
return interval;
|
||||
|
||||
@@ -938,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+45
-21
@@ -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;
|
||||
|
||||
|
||||
+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, 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. |
|
||||
* | `$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,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) {
|
||||
|
||||
+7
-19
@@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var $timeoutMinErr = minErr('$timeout');
|
||||
|
||||
/** @this */
|
||||
function $TimeoutProvider() {
|
||||
this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+14
-35
@@ -17,28 +17,20 @@
|
||||
* ## Directive Support
|
||||
* The following directives are "animation aware":
|
||||
*
|
||||
* | Directive | Supported Animations |
|
||||
* |-------------------------------------------------------------------------------|---------------------------------------------------------------------------|
|
||||
* | {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
|
||||
* | {@link ngAnimate.directive:ngAnimateSwap#animations ngAnimateSwap} | enter and leave |
|
||||
* | {@link ng.directive:ngClass#animations ngClass / {{class}​}} | add and remove |
|
||||
* | {@link ng.directive:ngClassEven#animations ngClassEven} | add and remove |
|
||||
* | {@link ng.directive:ngClassOdd#animations ngClassOdd} | add and remove |
|
||||
* | {@link ng.directive:ngHide#animations ngHide} | add and remove (the `ng-hide` class) |
|
||||
* | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
* | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
* | {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
|
||||
* | {@link module:ngMessages#animations ngMessages} | add and remove (the `ng-active`/`ng-inactive` classes) |
|
||||
* | {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
|
||||
* | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
|
||||
* | {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
|
||||
* | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
* | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
* | Directive | Supported Animations |
|
||||
* |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
|
||||
* | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |
|
||||
* | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
* | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
* | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
* | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
* | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) |
|
||||
* | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) |
|
||||
* | {@link ng.directive:form#animations form} & {@link ng.directive:ngModel#animations ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
|
||||
* | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) |
|
||||
* | {@link module:ngMessages#animations ngMessage} | enter and leave |
|
||||
*
|
||||
* (More information can be found by visiting the documentation associated with each directive.)
|
||||
*
|
||||
* For a full breakdown of the steps involved during each animation event, refer to the
|
||||
* {@link ng.$animate `$animate` API docs}.
|
||||
* (More information can be found by visiting each the documentation associated with each directive.)
|
||||
*
|
||||
* ## CSS-based Animations
|
||||
*
|
||||
@@ -275,22 +267,9 @@
|
||||
* .message.ng-enter-prepare {
|
||||
* opacity: 0;
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* ### Animating between value changes
|
||||
*
|
||||
* Sometimes you need to animate between different expression states, whose values
|
||||
* don't necessary need to be known or referenced in CSS styles.
|
||||
* Unless possible with another {@link ngAnimate#directive-support "animation aware" directive},
|
||||
* that specific use case can always be covered with {@link ngAnimate.directive:ngAnimateSwap} as
|
||||
* can be seen in {@link ngAnimate.directive:ngAnimateSwap#examples this example}.
|
||||
*
|
||||
* Note that {@link ngAnimate.directive:ngAnimateSwap} is a *structural directive*, which means it
|
||||
* creates a new instance of the element (including any other/child directives it may have) and
|
||||
* links it to a new scope every time *swap* happens. In some cases this might not be desirable
|
||||
* (e.g. for performance reasons, or when you wish to retain internal state on the original
|
||||
* element instance).
|
||||
*
|
||||
* ## JavaScript-based Animations
|
||||
*
|
||||
* ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var ngAnimateSwapDirective = ['$animate', function($animate) {
|
||||
var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
transclude: 'element',
|
||||
@@ -104,10 +104,10 @@ var ngAnimateSwapDirective = ['$animate', function($animate) {
|
||||
previousScope = null;
|
||||
}
|
||||
if (value || value === 0) {
|
||||
$transclude(function(clone, childScope) {
|
||||
previousElement = clone;
|
||||
previousScope = childScope;
|
||||
$animate.enter(clone, null, $element);
|
||||
previousScope = scope.$new();
|
||||
$transclude(previousScope, function(element) {
|
||||
previousElement = element;
|
||||
$animate.enter(element, null, $element);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
+5
-24
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
|
||||
* directives are supported:
|
||||
* `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`,
|
||||
* `ngClick`, `ngDblClick`, and `ngMessages`.
|
||||
* `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
|
||||
* `ngDblClick`, and `ngMessages`.
|
||||
*
|
||||
* Below is a more detailed breakdown of the attributes handled by ngAria:
|
||||
*
|
||||
@@ -46,17 +46,11 @@
|
||||
* <md-checkbox ng-disabled="disabled" aria-disabled="true">
|
||||
* ```
|
||||
*
|
||||
* ## Disabling Specific Attributes
|
||||
* It is possible to disable individual attributes added by ngAria with the
|
||||
* ## Disabling Attributes
|
||||
* It's possible to disable individual attributes added by ngAria with the
|
||||
* {@link ngAria.$ariaProvider#config config} method. For more details, see the
|
||||
* {@link guide/accessibility Developer Guide}.
|
||||
*
|
||||
* ## Disabling `ngAria` on Specific Elements
|
||||
* It is possible to make `ngAria` ignore a specific element, by adding the `ng-aria-disable`
|
||||
* attribute on it. Note that only the element itself (and not its child elements) will be ignored.
|
||||
*/
|
||||
var ARIA_DISABLE_ATTR = 'ngAriaDisable';
|
||||
|
||||
var ngAriaModule = angular.module('ngAria', ['ng']).
|
||||
info({ angularVersion: '"NG_VERSION_FULL"' }).
|
||||
provider('$aria', $AriaProvider);
|
||||
@@ -138,8 +132,6 @@ function $AriaProvider() {
|
||||
|
||||
function watchExpr(attrName, ariaAttr, nodeBlackList, negate) {
|
||||
return function(scope, elem, attr) {
|
||||
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
|
||||
|
||||
var ariaCamelName = attr.$normalize(ariaAttr);
|
||||
if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) {
|
||||
scope.$watch(attr[attrName], function(boolVal) {
|
||||
@@ -232,10 +224,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
.directive('ngModel', ['$aria', function($aria) {
|
||||
|
||||
function shouldAttachAttr(attr, normalizedAttr, elem, allowBlacklistEls) {
|
||||
return $aria.config(normalizedAttr) &&
|
||||
!elem.attr(attr) &&
|
||||
(allowBlacklistEls || !isNodeOneOf(elem, nodeBlackList)) &&
|
||||
(elem.attr('type') !== 'hidden' || elem[0].nodeName !== 'INPUT');
|
||||
return $aria.config(normalizedAttr) && !elem.attr(attr) && (allowBlacklistEls || !isNodeOneOf(elem, nodeBlackList));
|
||||
}
|
||||
|
||||
function shouldAttachRole(role, elem) {
|
||||
@@ -259,8 +248,6 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
require: 'ngModel',
|
||||
priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value
|
||||
compile: function(elem, attr) {
|
||||
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
|
||||
|
||||
var shape = getShape(attr, elem);
|
||||
|
||||
return {
|
||||
@@ -357,8 +344,6 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
restrict: 'A',
|
||||
require: '?ngMessages',
|
||||
link: function(scope, elem, attr, ngMessages) {
|
||||
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
|
||||
|
||||
if (!elem.attr('aria-live')) {
|
||||
elem.attr('aria-live', 'assertive');
|
||||
}
|
||||
@@ -369,8 +354,6 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function(elem, attr) {
|
||||
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
|
||||
|
||||
var fn = $parse(attr.ngClick);
|
||||
return function(scope, elem, attr) {
|
||||
|
||||
@@ -403,8 +386,6 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}])
|
||||
.directive('ngDblclick', ['$aria', function($aria) {
|
||||
return function(scope, elem, attr) {
|
||||
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
|
||||
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('ngCookies').
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $cookieStore
|
||||
* @deprecated
|
||||
* sinceVersion="v1.4.0"
|
||||
* Please use the {@link ngCookies.$cookies `$cookies`} service instead.
|
||||
*
|
||||
* @requires $cookies
|
||||
*
|
||||
* @description
|
||||
* Provides a key-value (string-object) storage, that is backed by session cookies.
|
||||
* Objects put or retrieved from this storage are automatically serialized or
|
||||
* deserialized by AngularJS's `toJson`/`fromJson`.
|
||||
*
|
||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* angular.module('cookieStoreExample', ['ngCookies'])
|
||||
* .controller('ExampleController', ['$cookieStore', function($cookieStore) {
|
||||
* // Put cookie
|
||||
* $cookieStore.put('myFavorite','oatmeal');
|
||||
* // Get cookie
|
||||
* var favoriteCookie = $cookieStore.get('myFavorite');
|
||||
* // Removing a cookie
|
||||
* $cookieStore.remove('myFavorite');
|
||||
* }]);
|
||||
* ```
|
||||
*/
|
||||
factory('$cookieStore', ['$cookies', function($cookies) {
|
||||
|
||||
return {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookieStore#get
|
||||
*
|
||||
* @description
|
||||
* Returns the value of given cookie key
|
||||
*
|
||||
* @param {string} key Id to use for lookup.
|
||||
* @returns {Object} Deserialized cookie value, undefined if the cookie does not exist.
|
||||
*/
|
||||
get: function(key) {
|
||||
return $cookies.getObject(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookieStore#put
|
||||
*
|
||||
* @description
|
||||
* Sets a value for given cookie key
|
||||
*
|
||||
* @param {string} key Id for the `value`.
|
||||
* @param {Object} value Value to be stored.
|
||||
*/
|
||||
put: function(key, value) {
|
||||
$cookies.putObject(key, value);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookieStore#remove
|
||||
*
|
||||
* @description
|
||||
* Remove given cookie
|
||||
*
|
||||
* @param {string} key Id of the key-value pair to delete.
|
||||
*/
|
||||
remove: function(key) {
|
||||
$cookies.remove(key);
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
@@ -33,7 +33,6 @@ function $$CookieWriter($document, $log, $browser) {
|
||||
str += options.domain ? ';domain=' + options.domain : '';
|
||||
str += expires ? ';expires=' + expires.toUTCString() : '';
|
||||
str += options.secure ? ';secure' : '';
|
||||
str += options.samesite ? ';samesite=' + options.samesite : '';
|
||||
|
||||
// per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
|
||||
// - 300 cookies
|
||||
|
||||
@@ -38,10 +38,6 @@ angular.module('ngCookies', ['ng']).
|
||||
* or a Date object indicating the exact date/time this cookie will expire.
|
||||
* - **secure** - `{boolean}` - If `true`, then the cookie will only be available through a
|
||||
* secured connection.
|
||||
* - **samesite** - `{string}` - prevents the browser from sending the cookie along with cross-site requests.
|
||||
* Accepts the values `lax` and `strict`. See the [OWASP Wiki](https://www.owasp.org/index.php/SameSite)
|
||||
* for more info. Note that as of May 2018, not all browsers support `SameSite`,
|
||||
* so it cannot be used as a single measure against Cross-Site-Request-Forgery (CSRF) attacks.
|
||||
*
|
||||
* Note: By default, the address that appears in your `<base>` tag will be used as the path.
|
||||
* This is important so that cookies will be visible for all routes when html5mode is enabled.
|
||||
|
||||
+46
-128
@@ -18,7 +18,7 @@ var jqLite;
|
||||
* sequencing based on the order of how the messages are defined in the template.
|
||||
*
|
||||
* Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude`
|
||||
* `ngMessage`, `ngMessageExp` and `ngMessageDefault` directives.
|
||||
* `ngMessage` and `ngMessageExp` directives.
|
||||
*
|
||||
* ## Usage
|
||||
* The `ngMessages` directive allows keys in a key/value collection to be associated with a child element
|
||||
@@ -257,26 +257,7 @@ var jqLite;
|
||||
* .some-message.ng-leave.ng-leave-active {}
|
||||
* ```
|
||||
*
|
||||
* {@link ngAnimate See the ngAnimate docs} to learn how to use JavaScript animations or to learn
|
||||
* more about ngAnimate.
|
||||
*
|
||||
* ## Displaying a default message
|
||||
* If the ngMessages renders no inner ngMessage directive (i.e. when none of the truthy
|
||||
* keys are matched by a defined message), then it will render a default message
|
||||
* using the {@link ngMessageDefault} directive.
|
||||
* Note that matched messages will always take precedence over unmatched messages. That means
|
||||
* the default message will not be displayed when another message is matched. This is also
|
||||
* true for `ng-messages-multiple`.
|
||||
*
|
||||
* ```html
|
||||
* <div ng-messages="myForm.myField.$error" role="alert">
|
||||
* <div ng-message="required">This field is required</div>
|
||||
* <div ng-message="minlength">This field is too short</div>
|
||||
* <div ng-message-default>This field has an input error</div>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
|
||||
* {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate.
|
||||
*/
|
||||
angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
// Access helpers from AngularJS core.
|
||||
@@ -305,11 +286,8 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
* at a time and this depends on the prioritization of the messages within the template. (This can
|
||||
* be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
|
||||
*
|
||||
* A remote template can also be used (With {@link ngMessagesInclude}) to promote message
|
||||
* reusability and messages can also be overridden.
|
||||
*
|
||||
* A default message can also be displayed when no `ngMessage` directive is inserted, using the
|
||||
* {@link ngMessageDefault} directive.
|
||||
* A remote template can also be used to promote message reusability and messages can also be
|
||||
* overridden.
|
||||
*
|
||||
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
|
||||
*
|
||||
@@ -320,7 +298,6 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
* <ANY ng-message="stringValue">...</ANY>
|
||||
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
|
||||
* <ANY ng-message-exp="expressionValue">...</ANY>
|
||||
* <ANY ng-message-default>...</ANY>
|
||||
* </ANY>
|
||||
*
|
||||
* <!-- or by using element directives -->
|
||||
@@ -328,7 +305,6 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
* <ng-message when="stringValue">...</ng-message>
|
||||
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
|
||||
* <ng-message when-exp="expressionValue">...</ng-message>
|
||||
* <ng-message-default>...</ng-message-default>
|
||||
* </ng-messages>
|
||||
* ```
|
||||
*
|
||||
@@ -357,7 +333,6 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
* <div ng-message="required">You did not enter a field</div>
|
||||
* <div ng-message="minlength">Your field is too short</div>
|
||||
* <div ng-message="maxlength">Your field is too long</div>
|
||||
* <div ng-message-default>This field has an input error</div>
|
||||
* </div>
|
||||
* </form>
|
||||
* </file>
|
||||
@@ -395,7 +370,6 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
|
||||
var unmatchedMessages = [];
|
||||
var matchedKeys = {};
|
||||
var truthyKeys = 0;
|
||||
var messageItem = ctrl.head;
|
||||
var messageFound = false;
|
||||
var totalMessages = 0;
|
||||
@@ -408,17 +382,13 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
var messageUsed = false;
|
||||
if (!messageFound) {
|
||||
forEach(collection, function(value, key) {
|
||||
if (truthy(value) && !messageUsed) {
|
||||
truthyKeys++;
|
||||
if (!messageUsed && truthy(value) && messageCtrl.test(key)) {
|
||||
// this is to prevent the same error name from showing up twice
|
||||
if (matchedKeys[key]) return;
|
||||
matchedKeys[key] = true;
|
||||
|
||||
if (messageCtrl.test(key)) {
|
||||
// this is to prevent the same error name from showing up twice
|
||||
if (matchedKeys[key]) return;
|
||||
matchedKeys[key] = true;
|
||||
|
||||
messageUsed = true;
|
||||
messageCtrl.attach();
|
||||
}
|
||||
messageUsed = true;
|
||||
messageCtrl.attach();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -438,16 +408,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
messageCtrl.detach();
|
||||
});
|
||||
|
||||
var messageMatched = unmatchedMessages.length !== totalMessages;
|
||||
var attachDefault = ctrl.default && !messageMatched && truthyKeys > 0;
|
||||
|
||||
if (attachDefault) {
|
||||
ctrl.default.attach();
|
||||
} else if (ctrl.default) {
|
||||
ctrl.default.detach();
|
||||
}
|
||||
|
||||
if (messageMatched || attachDefault) {
|
||||
if (unmatchedMessages.length !== totalMessages) {
|
||||
$animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS);
|
||||
} else {
|
||||
$animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
|
||||
@@ -467,31 +428,23 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
}
|
||||
};
|
||||
|
||||
this.register = function(comment, messageCtrl, isDefault) {
|
||||
if (isDefault) {
|
||||
ctrl.default = messageCtrl;
|
||||
} else {
|
||||
var nextKey = latestKey.toString();
|
||||
messages[nextKey] = {
|
||||
message: messageCtrl
|
||||
};
|
||||
insertMessageNode($element[0], comment, nextKey);
|
||||
comment.$$ngMessageNode = nextKey;
|
||||
latestKey++;
|
||||
}
|
||||
this.register = function(comment, messageCtrl) {
|
||||
var nextKey = latestKey.toString();
|
||||
messages[nextKey] = {
|
||||
message: messageCtrl
|
||||
};
|
||||
insertMessageNode($element[0], comment, nextKey);
|
||||
comment.$$ngMessageNode = nextKey;
|
||||
latestKey++;
|
||||
|
||||
ctrl.reRender();
|
||||
};
|
||||
|
||||
this.deregister = function(comment, isDefault) {
|
||||
if (isDefault) {
|
||||
delete ctrl.default;
|
||||
} else {
|
||||
var key = comment.$$ngMessageNode;
|
||||
delete comment.$$ngMessageNode;
|
||||
removeMessageNode($element[0], comment, key);
|
||||
delete messages[key];
|
||||
}
|
||||
this.deregister = function(comment) {
|
||||
var key = comment.$$ngMessageNode;
|
||||
delete comment.$$ngMessageNode;
|
||||
removeMessageNode($element[0], comment, key);
|
||||
delete messages[key];
|
||||
ctrl.reRender();
|
||||
};
|
||||
|
||||
@@ -694,41 +647,9 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
*
|
||||
* @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
|
||||
*/
|
||||
.directive('ngMessageExp', ngMessageDirectiveFactory())
|
||||
.directive('ngMessageExp', ngMessageDirectiveFactory());
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngMessageDefault
|
||||
* @restrict AE
|
||||
* @scope
|
||||
*
|
||||
* @description
|
||||
* `ngMessageDefault` is a directive with the purpose to show and hide a default message for
|
||||
* {@link directive:ngMessages}, when none of provided messages matches.
|
||||
*
|
||||
* More information about using `ngMessageDefault` can be found in the
|
||||
* {@link module:ngMessages `ngMessages` module documentation}.
|
||||
*
|
||||
* @usage
|
||||
* ```html
|
||||
* <!-- using attribute directives -->
|
||||
* <ANY ng-messages="expression" role="alert">
|
||||
* <ANY ng-message="stringValue">...</ANY>
|
||||
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
|
||||
* <ANY ng-message-default>...</ANY>
|
||||
* </ANY>
|
||||
*
|
||||
* <!-- or by using element directives -->
|
||||
* <ng-messages for="expression" role="alert">
|
||||
* <ng-message when="stringValue">...</ng-message>
|
||||
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
|
||||
* <ng-message-default>...</ng-message-default>
|
||||
* </ng-messages>
|
||||
*
|
||||
*/
|
||||
.directive('ngMessageDefault', ngMessageDirectiveFactory(true));
|
||||
|
||||
function ngMessageDirectiveFactory(isDefault) {
|
||||
function ngMessageDirectiveFactory() {
|
||||
return ['$animate', function($animate) {
|
||||
return {
|
||||
restrict: 'AE',
|
||||
@@ -737,28 +658,25 @@ function ngMessageDirectiveFactory(isDefault) {
|
||||
terminal: true,
|
||||
require: '^^ngMessages',
|
||||
link: function(scope, element, attrs, ngMessagesCtrl, $transclude) {
|
||||
var commentNode, records, staticExp, dynamicExp;
|
||||
var commentNode = element[0];
|
||||
|
||||
if (!isDefault) {
|
||||
commentNode = element[0];
|
||||
staticExp = attrs.ngMessage || attrs.when;
|
||||
dynamicExp = attrs.ngMessageExp || attrs.whenExp;
|
||||
var records;
|
||||
var staticExp = attrs.ngMessage || attrs.when;
|
||||
var dynamicExp = attrs.ngMessageExp || attrs.whenExp;
|
||||
var assignRecords = function(items) {
|
||||
records = items
|
||||
? (isArray(items)
|
||||
? items
|
||||
: items.split(/[\s,]+/))
|
||||
: null;
|
||||
ngMessagesCtrl.reRender();
|
||||
};
|
||||
|
||||
var assignRecords = function(items) {
|
||||
records = items
|
||||
? (isArray(items)
|
||||
? items
|
||||
: items.split(/[\s,]+/))
|
||||
: null;
|
||||
ngMessagesCtrl.reRender();
|
||||
};
|
||||
|
||||
if (dynamicExp) {
|
||||
assignRecords(scope.$eval(dynamicExp));
|
||||
scope.$watchCollection(dynamicExp, assignRecords);
|
||||
} else {
|
||||
assignRecords(staticExp);
|
||||
}
|
||||
if (dynamicExp) {
|
||||
assignRecords(scope.$eval(dynamicExp));
|
||||
scope.$watchCollection(dynamicExp, assignRecords);
|
||||
} else {
|
||||
assignRecords(staticExp);
|
||||
}
|
||||
|
||||
var currentElement, messageCtrl;
|
||||
@@ -783,7 +701,7 @@ function ngMessageDirectiveFactory(isDefault) {
|
||||
// If the message element was removed via a call to `detach` then `currentElement` will be null
|
||||
// So this handler only handles cases where something else removed the message element.
|
||||
if (currentElement && currentElement.$$attachId === $$attachId) {
|
||||
ngMessagesCtrl.deregister(commentNode, isDefault);
|
||||
ngMessagesCtrl.deregister(commentNode);
|
||||
messageCtrl.detach();
|
||||
}
|
||||
newScope.$destroy();
|
||||
@@ -798,14 +716,14 @@ function ngMessageDirectiveFactory(isDefault) {
|
||||
$animate.leave(elm);
|
||||
}
|
||||
}
|
||||
}, isDefault);
|
||||
});
|
||||
|
||||
// We need to ensure that this directive deregisters itself when it no longer exists
|
||||
// Normally this is done when the attached element is destroyed; but if this directive
|
||||
// gets removed before we attach the message to the DOM there is nothing to watch
|
||||
// in which case we must deregister when the containing scope is destroyed.
|
||||
scope.$on('$destroy', function() {
|
||||
ngMessagesCtrl.deregister(commentNode, isDefault);
|
||||
ngMessagesCtrl.deregister(commentNode);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Vendored
+42
-115
@@ -1342,7 +1342,6 @@ angular.mock.$httpBackendDecorator =
|
||||
function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
var definitions = [],
|
||||
expectations = [],
|
||||
matchLatestDefinition = false,
|
||||
responses = [],
|
||||
responsesPush = angular.bind(responses, responses.push),
|
||||
copy = angular.copy,
|
||||
@@ -1431,9 +1430,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
wasExpected = true;
|
||||
}
|
||||
|
||||
var i = matchLatestDefinition ? definitions.length : -1, definition;
|
||||
|
||||
while ((definition = definitions[matchLatestDefinition ? --i : ++i])) {
|
||||
var i = -1, definition;
|
||||
while ((definition = definitions[++i])) {
|
||||
if (definition.match(method, url, data, headers || {})) {
|
||||
if (definition.response) {
|
||||
// if $browser specified, we do auto flush all requests
|
||||
@@ -1509,47 +1507,6 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
return chain;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $httpBackend#matchLatestDefinition
|
||||
* @description
|
||||
* This method can be used to change which mocked responses `$httpBackend` returns, when defining
|
||||
* them with {@link ngMock.$httpBackend#when $httpBackend.when()} (and shortcut methods).
|
||||
* By default, `$httpBackend` returns the first definition that matches. When setting
|
||||
* `$http.matchLatestDefinition(true)`, it will use the last response that matches, i.e. the
|
||||
* one that was added last.
|
||||
*
|
||||
* ```js
|
||||
* hb.when('GET', '/url1').respond(200, 'content', {});
|
||||
* hb.when('GET', '/url1').respond(201, 'another', {});
|
||||
* hb('GET', '/url1'); // receives "content"
|
||||
*
|
||||
* $http.matchLatestDefinition(true)
|
||||
* hb('GET', '/url1'); // receives "another"
|
||||
*
|
||||
* hb.when('GET', '/url1').respond(201, 'onemore', {});
|
||||
* hb('GET', '/url1'); // receives "onemore"
|
||||
* ```
|
||||
*
|
||||
* This is useful if a you have a default response that is overriden inside specific tests.
|
||||
*
|
||||
* Note that different from config methods on providers, `matchLatestDefinition()` can be changed
|
||||
* even when the application is already running.
|
||||
*
|
||||
* @param {Boolean=} value value to set, either `true` or `false`. Default is `false`.
|
||||
* If omitted, it will return the current value.
|
||||
* @return {$httpBackend|Boolean} self when used as a setter, and the current value when used
|
||||
* as a getter
|
||||
*/
|
||||
$httpBackend.matchLatestDefinitionEnabled = function(value) {
|
||||
if (isDefined(value)) {
|
||||
matchLatestDefinition = value;
|
||||
return this;
|
||||
} else {
|
||||
return matchLatestDefinition;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $httpBackend#whenGET
|
||||
@@ -1558,8 +1515,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1574,8 +1530,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1590,8 +1545,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1608,8 +1562,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1626,8 +1579,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1659,8 +1611,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {string} url HTTP url string that supports colon param matching.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
* See {@link ngMock.$httpBackend#when `when`} for more info.
|
||||
* order to change how a matched request is handled. See #when for more info.
|
||||
*/
|
||||
$httpBackend.whenRoute = function(method, url) {
|
||||
var pathObj = parseRoute(url);
|
||||
@@ -1749,9 +1700,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for GET requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current expectation.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1765,9 +1715,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for HEAD requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current expectation.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1781,9 +1730,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for DELETE requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current expectation.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1797,12 +1745,11 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for POST requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current expectation.
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1816,12 +1763,11 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for PUT requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current expectation.
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1835,12 +1781,11 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for PATCH requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current expectation.
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1854,7 +1799,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives an url
|
||||
* and returns true if the url matches the current expectation.
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1872,8 +1817,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {string} url HTTP url string that supports colon param matching.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
* See {@link ngMock.$httpBackend#expect `expect`} for more info.
|
||||
* order to change how a matched request is handled. See #expect for more info.
|
||||
*/
|
||||
$httpBackend.expectRoute = function(method, url) {
|
||||
var pathObj = parseRoute(url);
|
||||
@@ -2150,13 +2094,13 @@ function MockXhr() {
|
||||
var header = this.$$respHeaders[name];
|
||||
if (header) return header;
|
||||
|
||||
name = angular.$$lowercase(name);
|
||||
name = angular.lowercase(name);
|
||||
header = this.$$respHeaders[name];
|
||||
if (header) return header;
|
||||
|
||||
header = undefined;
|
||||
angular.forEach(this.$$respHeaders, function(headerVal, headerName) {
|
||||
if (!header && angular.$$lowercase(headerName) === name) header = headerVal;
|
||||
if (!header && angular.lowercase(headerName) === name) header = headerVal;
|
||||
});
|
||||
return header;
|
||||
};
|
||||
@@ -2288,6 +2232,11 @@ angular.mock.$RootElementProvider = function() {
|
||||
* A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing
|
||||
* controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}.
|
||||
*
|
||||
* Depending on the value of
|
||||
* {@link ng.$compileProvider#preAssignBindingsEnabled `preAssignBindingsEnabled()`}, the properties
|
||||
* will be bound before or after invoking the constructor.
|
||||
*
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```js
|
||||
@@ -2333,6 +2282,8 @@ angular.mock.$RootElementProvider = function() {
|
||||
*
|
||||
* * 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
|
||||
@@ -2343,13 +2294,22 @@ angular.mock.$RootElementProvider = function() {
|
||||
* the `bindToController` feature and simplify certain kinds of tests.
|
||||
* @return {Object} Instance of given controller.
|
||||
*/
|
||||
function createControllerDecorator() {
|
||||
function createControllerDecorator(compileProvider) {
|
||||
angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
|
||||
return function(expression, locals, later, ident) {
|
||||
if (later && typeof later === 'object') {
|
||||
var preAssignBindingsEnabled = compileProvider.preAssignBindingsEnabled();
|
||||
|
||||
var instantiate = $delegate(expression, locals, true, ident);
|
||||
if (preAssignBindingsEnabled) {
|
||||
angular.extend(instantiate.instance, later);
|
||||
}
|
||||
|
||||
var instance = instantiate();
|
||||
angular.extend(instance, later);
|
||||
if (!preAssignBindingsEnabled || instance !== instantiate.instance) {
|
||||
angular.extend(instance, later);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
return $delegate(expression, locals, later, ident);
|
||||
@@ -2769,39 +2729,6 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
*/
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $httpBackend#matchLatestDefinition
|
||||
* @module ngMockE2E
|
||||
* @description
|
||||
* This method can be used to change which mocked responses `$httpBackend` returns, when defining
|
||||
* them with {@link ngMock.$httpBackend#when $httpBackend.when()} (and shortcut methods).
|
||||
* By default, `$httpBackend` returns the first definition that matches. When setting
|
||||
* `$http.matchLatestDefinition(true)`, it will use the last response that matches, i.e. the
|
||||
* one that was added last.
|
||||
*
|
||||
* ```js
|
||||
* hb.when('GET', '/url1').respond(200, 'content', {});
|
||||
* hb.when('GET', '/url1').respond(201, 'another', {});
|
||||
* hb('GET', '/url1'); // receives "content"
|
||||
*
|
||||
* $http.matchLatestDefinition(true)
|
||||
* hb('GET', '/url1'); // receives "another"
|
||||
*
|
||||
* hb.when('GET', '/url1').respond(201, 'onemore', {});
|
||||
* hb('GET', '/url1'); // receives "onemore"
|
||||
* ```
|
||||
*
|
||||
* This is useful if a you have a default response that is overriden inside specific tests.
|
||||
*
|
||||
* Note that different from config methods on providers, `matchLatestDefinition()` can be changed
|
||||
* even when the application is already running.
|
||||
*
|
||||
* @param {Boolean=} value value to set, either `true` or `false`. Default is `false`.
|
||||
* If omitted, it will return the current value.
|
||||
* @return {$httpBackend|Boolean} self when used as a setter, and the current value when used
|
||||
* as a getter
|
||||
*/
|
||||
angular.mock.e2e = {};
|
||||
angular.mock.e2e.$httpBackendDecorator =
|
||||
['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock];
|
||||
|
||||
@@ -165,11 +165,30 @@
|
||||
|
||||
if (!evnt) return;
|
||||
|
||||
var originalPreventDefault = evnt.preventDefault,
|
||||
appWindow = element.ownerDocument.defaultView,
|
||||
fakeProcessDefault = true,
|
||||
finalProcessDefault,
|
||||
angular = appWindow.angular || {};
|
||||
|
||||
// igor: temporary fix for https://bugzilla.mozilla.org/show_bug.cgi?id=684208
|
||||
angular['ff-684208-preventDefault'] = false;
|
||||
evnt.preventDefault = function() {
|
||||
fakeProcessDefault = false;
|
||||
return originalPreventDefault.apply(evnt, arguments);
|
||||
};
|
||||
|
||||
if (!eventData.bubbles || supportsEventBubblingInDetachedTree() || isAttachedToDocument(element)) {
|
||||
return element.dispatchEvent(evnt);
|
||||
element.dispatchEvent(evnt);
|
||||
} else {
|
||||
triggerForPath(element, evnt);
|
||||
}
|
||||
|
||||
finalProcessDefault = !(angular['ff-684208-preventDefault'] || !fakeProcessDefault);
|
||||
|
||||
delete angular['ff-684208-preventDefault'];
|
||||
|
||||
return finalProcessDefault;
|
||||
};
|
||||
|
||||
function supportsTouchEvents() {
|
||||
|
||||
+175
-229
@@ -110,13 +110,13 @@ function shallowClearAndCopy(src, dst) {
|
||||
*
|
||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
||||
* `actions` methods. If a parameter value is a function, it will be called every time
|
||||
* a param value needs to be obtained for a request (unless the param was overridden). The
|
||||
* function will be passed the current data value as an argument.
|
||||
* a param value needs to be obtained for a request (unless the param was overridden). The function
|
||||
* will be passed the current data value as an argument.
|
||||
*
|
||||
* Each key value in the parameter object is first bound to url template if present and then any
|
||||
* excess keys are appended to the url search query after the `?`.
|
||||
*
|
||||
* Given a template `/path/:verb` and parameter `{verb: 'greet', salutation: 'Hello'}` results in
|
||||
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
|
||||
* URL `/path/greet?salutation=Hello`.
|
||||
*
|
||||
* If the parameter value is prefixed with `@`, then the value for that parameter will be
|
||||
@@ -125,7 +125,7 @@ function shallowClearAndCopy(src, dst) {
|
||||
* For example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of
|
||||
* `someParam` will be `data.someProp`.
|
||||
* Note that the parameter will be ignored, when calling a "GET" action method (i.e. an action
|
||||
* method that does not accept a request body).
|
||||
* method that does not accept a request body)
|
||||
*
|
||||
* @param {Object.<Object>=} actions Hash with declaration of custom actions that will be available
|
||||
* in addition to the default set of resource actions (see below). If a custom action has the same
|
||||
@@ -134,11 +134,9 @@ function shallowClearAndCopy(src, dst) {
|
||||
*
|
||||
* The declaration should be created in the format of {@link ng.$http#usage $http.config}:
|
||||
*
|
||||
* {
|
||||
* action1: {method:?, params:?, isArray:?, headers:?, ...},
|
||||
* action2: {method:?, params:?, isArray:?, headers:?, ...},
|
||||
* ...
|
||||
* }
|
||||
* {action1: {method:?, params:?, isArray:?, headers:?, ...},
|
||||
* action2: {method:?, params:?, isArray:?, headers:?, ...},
|
||||
* ...}
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
@@ -150,58 +148,54 @@ function shallowClearAndCopy(src, dst) {
|
||||
* the parameter value is a function, it will be called every time when a param value needs to
|
||||
* be obtained for a request (unless the param was overridden). The function will be passed the
|
||||
* current data value as an argument.
|
||||
* - **`url`** – {string} – Action specific `url` override. The url templating is supported just
|
||||
* - **`url`** – {string} – action specific `url` override. The url templating is supported just
|
||||
* like for the resource-level urls.
|
||||
* - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
|
||||
* see `returns` section.
|
||||
* - **`transformRequest`** –
|
||||
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||||
* Transform function or an array of such functions. The transform function takes the http
|
||||
* transform function or an array of such functions. The transform function takes the http
|
||||
* request body and headers and returns its transformed (typically serialized) version.
|
||||
* By default, transformRequest will contain one function that checks if the request data is
|
||||
* an object and serializes it using `angular.toJson`. To prevent this behavior, set
|
||||
* `transformRequest` to an empty array: `transformRequest: []`
|
||||
* - **`transformResponse`** –
|
||||
* `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
|
||||
* Transform function or an array of such functions. The transform function takes the HTTP
|
||||
* transform function or an array of such functions. The transform function takes the http
|
||||
* response body, headers and status and returns its transformed (typically deserialized)
|
||||
* version.
|
||||
* By default, transformResponse will contain one function that checks if the response looks
|
||||
* like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior,
|
||||
* set `transformResponse` to an empty array: `transformResponse: []`
|
||||
* - **`cache`** – `{boolean|Cache}` – A boolean value or object created with
|
||||
* {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response.
|
||||
* See {@link $http#caching $http Caching} for more information.
|
||||
* - **`timeout`** – `{number}` – Timeout in milliseconds.<br />
|
||||
* - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
|
||||
* GET request, otherwise if a cache instance built with
|
||||
* {@link ng.$cacheFactory $cacheFactory} is supplied, this cache will be used for
|
||||
* caching.
|
||||
* - **`timeout`** – `{number}` – timeout in milliseconds.<br />
|
||||
* **Note:** In contrast to {@link ng.$http#usage $http.config}, {@link ng.$q promises} are
|
||||
* **not** supported in `$resource`, because the same value would be used for multiple requests.
|
||||
* **not** supported in $resource, because the same value would be used for multiple requests.
|
||||
* If you are looking for a way to cancel requests, you should use the `cancellable` option.
|
||||
* - **`cancellable`** – `{boolean}` – If true, the request made by a "non-instance" call will be
|
||||
* cancelled (if not already completed) by calling `$cancelRequest()` on the call's return
|
||||
* value. Calling `$cancelRequest()` for a non-cancellable or an already completed/cancelled
|
||||
* request will have no effect.
|
||||
* - **`withCredentials`** – `{boolean}` – Whether to set the `withCredentials` flag on the
|
||||
* - **`cancellable`** – `{boolean}` – if set to true, the request made by a "non-instance" call
|
||||
* will be cancelled (if not already completed) by calling `$cancelRequest()` on the call's
|
||||
* return value. Calling `$cancelRequest()` for a non-cancellable or an already
|
||||
* completed/cancelled request will have no effect.<br />
|
||||
* - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
|
||||
* XHR object. See
|
||||
* [XMLHttpRequest.withCredentials](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials)
|
||||
* [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
|
||||
* for more information.
|
||||
* - **`responseType`** – `{string}` – See
|
||||
* [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType).
|
||||
* - **`interceptor`** – `{Object=}` – The interceptor object has four optional methods -
|
||||
* `request`, `requestError`, `response`, and `responseError`. See
|
||||
* {@link ng.$http#interceptors $http interceptors} for details. Note that
|
||||
* `request`/`requestError` interceptors are applied before calling `$http`, thus before any
|
||||
* global `$http` interceptors. Also, rejecting or throwing an error inside the `request`
|
||||
* interceptor will result in calling the `responseError` interceptor.
|
||||
* The resource instance or collection is available on the `resource` property of the
|
||||
* `http response` object passed to `response`/`responseError` interceptors.
|
||||
* - **`responseType`** - `{string}` - see
|
||||
* [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
|
||||
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
|
||||
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
|
||||
* with `http response` object. See {@link ng.$http $http interceptors}. In addition, the
|
||||
* resource instance or array object is accessible by the `resource` property of the
|
||||
* `http response` object.
|
||||
* Keep in mind that the associated promise will be resolved with the value returned by the
|
||||
* response interceptors. Make sure you return an appropriate value and not the `response`
|
||||
* object passed as input. For reference, the default `response` interceptor (which gets applied
|
||||
* if you don't specify a custom one) returns `response.resource`.<br />
|
||||
* See {@link ngResource.$resource#using-interceptors below} for an example of using
|
||||
* interceptors in `$resource`.
|
||||
* - **`hasBody`** – `{boolean}` – If true, then the request will have a body.
|
||||
* If not specified, then only POST, PUT and PATCH requests will have a body. *
|
||||
* response interceptor, if one is specified. The default response interceptor returns
|
||||
* `response.resource` (i.e. the resource instance or array).
|
||||
* - **`hasBody`** - `{boolean}` - allows to specify if a request body should be included or not.
|
||||
* If not specified only POST, PUT and PATCH requests will have a body.
|
||||
*
|
||||
* @param {Object} options Hash with custom settings that should extend the
|
||||
* default `$resourceProvider` behavior. The supported options are:
|
||||
*
|
||||
@@ -214,29 +208,27 @@ function shallowClearAndCopy(src, dst) {
|
||||
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
||||
* optionally extended with custom `actions`. The default set contains these actions:
|
||||
* ```js
|
||||
* {
|
||||
* 'get': {method: 'GET'},
|
||||
* 'save': {method: 'POST'},
|
||||
* 'query': {method: 'GET', isArray: true},
|
||||
* 'remove': {method: 'DELETE'},
|
||||
* 'delete': {method: 'DELETE'}
|
||||
* }
|
||||
* { 'get': {method:'GET'},
|
||||
* 'save': {method:'POST'},
|
||||
* 'query': {method:'GET', isArray:true},
|
||||
* 'remove': {method:'DELETE'},
|
||||
* 'delete': {method:'DELETE'} };
|
||||
* ```
|
||||
*
|
||||
* Calling these methods invoke {@link ng.$http} with the specified http method, destination and
|
||||
* parameters. When the data is returned from the server then the object is an instance of the
|
||||
* resource class. The actions `save`, `remove` and `delete` are available on it as methods with
|
||||
* the `$` prefix. This allows you to easily perform CRUD operations (create, read, update,
|
||||
* delete) on server-side data like this:
|
||||
* Calling these methods invoke an {@link ng.$http} with the specified http method,
|
||||
* destination and parameters. When the data is returned from the server then the object is an
|
||||
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
|
||||
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
|
||||
* read, update, delete) on server-side data like this:
|
||||
* ```js
|
||||
* var User = $resource('/user/:userId', {userId: '@id'});
|
||||
* User.get({userId: 123}).$promise.then(function(user) {
|
||||
* var User = $resource('/user/:userId', {userId:'@id'});
|
||||
* var user = User.get({userId:123}, function() {
|
||||
* user.abc = true;
|
||||
* user.$save();
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* It is important to realize that invoking a `$resource` object method immediately returns an
|
||||
* It is important to realize that invoking a $resource object method immediately returns an
|
||||
* empty reference (object or array depending on `isArray`). Once the data is returned from the
|
||||
* server the existing reference is populated with the actual data. This is a useful trick since
|
||||
* usually the resource is assigned to a model which is then rendered by the view. Having an empty
|
||||
@@ -259,31 +251,30 @@ function shallowClearAndCopy(src, dst) {
|
||||
*
|
||||
*
|
||||
* Success callback is called with (value (Object|Array), responseHeaders (Function),
|
||||
* status (number), statusText (string)) arguments, where `value` is the populated resource
|
||||
* status (number), statusText (string)) arguments, where the value is the populated resource
|
||||
* instance or collection object. The error callback is called with (httpResponse) argument.
|
||||
*
|
||||
* Class actions return an empty instance (with the additional properties listed below).
|
||||
* Instance actions return a promise for the operation.
|
||||
* Class actions return empty instance (with additional properties below).
|
||||
* Instance actions return promise of the action.
|
||||
*
|
||||
* The Resource instances and collections have these additional properties:
|
||||
*
|
||||
* - `$promise`: The {@link ng.$q promise} of the original server interaction that created this
|
||||
* - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
|
||||
* instance or collection.
|
||||
*
|
||||
* On success, the promise is resolved with the same resource instance or collection object,
|
||||
* updated with data from server. This makes it easy to use in the
|
||||
* {@link ngRoute.$routeProvider `resolve` section of `$routeProvider.when()`} to defer view
|
||||
* updated with data from server. This makes it easy to use in
|
||||
* {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
|
||||
* rendering until the resource(s) are loaded.
|
||||
*
|
||||
* On failure, the promise is rejected with the {@link ng.$http http response} object.
|
||||
*
|
||||
* If an interceptor object was provided, the promise will instead be resolved with the value
|
||||
* returned by the response interceptor (on success) or responceError interceptor (on failure).
|
||||
* returned by the interceptor.
|
||||
*
|
||||
* - `$resolved`: `true` after first server interaction is completed (either with success or
|
||||
* rejection), `false` before that. Knowing if the Resource has been resolved is useful in
|
||||
* data-binding. If there is a response/responseError interceptor and it returns a promise,
|
||||
* `$resolved` will wait for that too.
|
||||
* data-binding.
|
||||
*
|
||||
* The Resource instances and collections have these additional methods:
|
||||
*
|
||||
@@ -300,128 +291,121 @@ function shallowClearAndCopy(src, dst) {
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ### Basic usage
|
||||
* ### Credit card resource
|
||||
*
|
||||
```js
|
||||
// Define a CreditCard class
|
||||
var CreditCard = $resource('/users/:userId/cards/:cardId',
|
||||
{userId: 123, cardId: '@id'}, {
|
||||
charge: {method: 'POST', params: {charge: true}}
|
||||
});
|
||||
* ```js
|
||||
// Define CreditCard class
|
||||
var CreditCard = $resource('/user/:userId/card/:cardId',
|
||||
{userId:123, cardId:'@id'}, {
|
||||
charge: {method:'POST', params:{charge:true}}
|
||||
});
|
||||
|
||||
// We can retrieve a collection from the server
|
||||
var cards = CreditCard.query();
|
||||
// GET: /users/123/cards
|
||||
// server returns: [{id: 456, number: '1234', name: 'Smith'}]
|
||||
var cards = CreditCard.query(function() {
|
||||
// GET: /user/123/card
|
||||
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
||||
|
||||
// Wait for the request to complete
|
||||
cards.$promise.then(function() {
|
||||
var card = cards[0];
|
||||
|
||||
// Each item is an instance of CreditCard
|
||||
// each item is an instance of CreditCard
|
||||
expect(card instanceof CreditCard).toEqual(true);
|
||||
|
||||
// Non-GET methods are mapped onto the instances
|
||||
card.name = 'J. Smith';
|
||||
card.name = "J. Smith";
|
||||
// non GET methods are mapped onto the instances
|
||||
card.$save();
|
||||
// POST: /users/123/cards/456 {id: 456, number: '1234', name: 'J. Smith'}
|
||||
// server returns: {id: 456, number: '1234', name: 'J. Smith'}
|
||||
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
||||
|
||||
// Our custom method is mapped as well (since it uses POST)
|
||||
card.$charge({amount: 9.99});
|
||||
// POST: /users/123/cards/456?amount=9.99&charge=true {id: 456, number: '1234', name: 'J. Smith'}
|
||||
// our custom method is mapped as well.
|
||||
card.$charge({amount:9.99});
|
||||
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
||||
});
|
||||
|
||||
// We can create an instance as well
|
||||
var newCard = new CreditCard({number: '0123'});
|
||||
newCard.name = 'Mike Smith';
|
||||
|
||||
var savePromise = newCard.$save();
|
||||
// POST: /users/123/cards {number: '0123', name: 'Mike Smith'}
|
||||
// server returns: {id: 789, number: '0123', name: 'Mike Smith'}
|
||||
|
||||
savePromise.then(function() {
|
||||
// Once the promise is resolved, the created instance
|
||||
// is populated with the data returned by the server
|
||||
expect(newCard.id).toEqual(789);
|
||||
});
|
||||
```
|
||||
// we can create an instance as well
|
||||
var newCard = new CreditCard({number:'0123'});
|
||||
newCard.name = "Mike Smith";
|
||||
newCard.$save();
|
||||
// POST: /user/123/card {number:'0123', name:'Mike Smith'}
|
||||
// server returns: {id:789, number:'0123', name: 'Mike Smith'};
|
||||
expect(newCard.id).toEqual(789);
|
||||
* ```
|
||||
*
|
||||
* The object returned from a call to `$resource` is a resource "class" which has one "static"
|
||||
* method for each action in the definition.
|
||||
* The object returned from this function execution is a resource "class" which has "static" method
|
||||
* for each action in the definition.
|
||||
*
|
||||
* Calling these methods invokes `$http` on the `url` template with the given HTTP `method`,
|
||||
* `params` and `headers`.
|
||||
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
|
||||
* `headers`.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ### Accessing the response
|
||||
* ### User resource
|
||||
*
|
||||
* When the data is returned from the server then the object is an instance of the resource type and
|
||||
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
|
||||
* operations (create, read, update, delete) on server-side data.
|
||||
*
|
||||
|
||||
```js
|
||||
var User = $resource('/users/:userId', {userId: '@id'});
|
||||
User.get({userId: 123}).$promise.then(function(user) {
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
User.get({userId:123}, function(user) {
|
||||
user.abc = true;
|
||||
user.$save();
|
||||
});
|
||||
```
|
||||
*
|
||||
* It's worth noting that the success callback for `get`, `query` and other methods gets called with
|
||||
* the resource instance (populated with the data that came from the server) as well as an `$http`
|
||||
* header getter function, the HTTP status code and the response status text. So one could rewrite
|
||||
* the above example and get access to HTTP headers as follows:
|
||||
* It's worth noting that the success callback for `get`, `query` and other methods gets passed
|
||||
* in the response that came from the server as well as $http header getter function, so one
|
||||
* could rewrite the above example and get access to http headers as:
|
||||
*
|
||||
```js
|
||||
var User = $resource('/users/:userId', {userId: '@id'});
|
||||
User.get({userId: 123}, function(user, getResponseHeaders) {
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
User.get({userId:123}, function(user, getResponseHeaders){
|
||||
user.abc = true;
|
||||
user.$save(function(user, putResponseHeaders) {
|
||||
// `user` => saved `User` object
|
||||
// `putResponseHeaders` => `$http` header getter
|
||||
//user => saved user object
|
||||
//putResponseHeaders => $http header getter
|
||||
});
|
||||
});
|
||||
```
|
||||
*
|
||||
* You can also access the raw `$http` promise via the `$promise` property on the object returned
|
||||
*
|
||||
```
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
User.get({userId:123})
|
||||
.$promise.then(function(user) {
|
||||
$scope.user = user;
|
||||
});
|
||||
```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ### Creating custom actions
|
||||
* ### Creating a custom 'PUT' request
|
||||
*
|
||||
* In this example we create a custom method on our resource to make a PUT request:
|
||||
* In this example we create a custom method on our resource to make a PUT request
|
||||
* ```js
|
||||
* var app = angular.module('app', ['ngResource', 'ngRoute']);
|
||||
*
|
||||
```js
|
||||
var app = angular.module('app', ['ngResource']);
|
||||
|
||||
// Some APIs expect a PUT request in the format URL/object/ID
|
||||
// Here we are creating an 'update' method
|
||||
app.factory('Notes', ['$resource', function($resource) {
|
||||
return $resource('/notes/:id', {id: '@id'}, {
|
||||
update: {method: 'PUT'}
|
||||
});
|
||||
}]);
|
||||
|
||||
// In our controller we get the ID from the URL using `$location`
|
||||
app.controller('NotesCtrl', ['$location', 'Notes', function($location, Notes) {
|
||||
// First, retrieve the corresponding `Note` object from the server
|
||||
// (Assuming a URL of the form `.../notes?id=XYZ`)
|
||||
var noteId = $location.search().id;
|
||||
var note = Notes.get({id: noteId});
|
||||
|
||||
note.$promise.then(function() {
|
||||
note.content = 'Hello, world!';
|
||||
|
||||
// Now call `update` to save the changes on the server
|
||||
Notes.update(note);
|
||||
// This will PUT /notes/ID with the note object as the request payload
|
||||
|
||||
// Since `update` is a non-GET method, it will also be available on the instance
|
||||
// (prefixed with `$`), so we could replace the `Note.update()` call with:
|
||||
//note.$update();
|
||||
});
|
||||
}]);
|
||||
```
|
||||
* // Some APIs expect a PUT request in the format URL/object/ID
|
||||
* // Here we are creating an 'update' method
|
||||
* app.factory('Notes', ['$resource', function($resource) {
|
||||
* return $resource('/notes/:id', null,
|
||||
* {
|
||||
* 'update': { method:'PUT' }
|
||||
* });
|
||||
* }]);
|
||||
*
|
||||
* // In our controller we get the ID from the URL using ngRoute and $routeParams
|
||||
* // We pass in $routeParams and our Notes factory along with $scope
|
||||
* app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
|
||||
function($scope, $routeParams, Notes) {
|
||||
* // First get a note object from the factory
|
||||
* var note = Notes.get({ id:$routeParams.id });
|
||||
* $id = note.id;
|
||||
*
|
||||
* // Now call update passing in the ID first then the object you are updating
|
||||
* Notes.update({ id:$id }, note);
|
||||
*
|
||||
* // This will PUT /notes/ID with the note object in the request payload
|
||||
* }]);
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
@@ -432,7 +416,7 @@ function shallowClearAndCopy(src, dst) {
|
||||
*
|
||||
```js
|
||||
// ...defining the `Hotel` resource...
|
||||
var Hotel = $resource('/api/hotels/:id', {id: '@id'}, {
|
||||
var Hotel = $resource('/api/hotel/:id', {id: '@id'}, {
|
||||
// Let's make the `query()` method cancellable
|
||||
query: {method: 'get', isArray: true, cancellable: true}
|
||||
});
|
||||
@@ -442,54 +426,14 @@ function shallowClearAndCopy(src, dst) {
|
||||
this.onDestinationChanged = function onDestinationChanged(destination) {
|
||||
// We don't care about any pending request for hotels
|
||||
// in a different destination any more
|
||||
if (this.availableHotels) {
|
||||
this.availableHotels.$cancelRequest();
|
||||
}
|
||||
this.availableHotels.$cancelRequest();
|
||||
|
||||
// Let's query for hotels in `destination`
|
||||
// (calls: /api/hotels?location=<destination>)
|
||||
// Let's query for hotels in '<destination>'
|
||||
// (calls: /api/hotel?location=<destination>)
|
||||
this.availableHotels = Hotel.query({location: destination});
|
||||
};
|
||||
```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ### Using interceptors
|
||||
*
|
||||
* You can use interceptors to transform the request or response, perform additional operations, and
|
||||
* modify the returned instance/collection. The following example, uses `request` and `response`
|
||||
* interceptors to augment the returned instance with additional info:
|
||||
*
|
||||
```js
|
||||
var Thing = $resource('/api/things/:id', {id: '@id'}, {
|
||||
save: {
|
||||
method: 'POST',
|
||||
interceptor: {
|
||||
request: function(config) {
|
||||
// Before the request is sent out, store a timestamp on the request config
|
||||
config.requestTimestamp = Date.now();
|
||||
return config;
|
||||
},
|
||||
response: function(response) {
|
||||
// Get the instance from the response object
|
||||
var instance = response.resource;
|
||||
|
||||
// Augment the instance with a custom `saveLatency` property, computed as the time
|
||||
// between sending the request and receiving the response.
|
||||
instance.saveLatency = Date.now() - response.config.requestTimestamp;
|
||||
|
||||
// Return the instance
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Thing.save({foo: 'bar'}).$promise.then(function(thing) {
|
||||
console.log('That thing was saved in ' + thing.saveLatency + 'ms.');
|
||||
});
|
||||
```
|
||||
*
|
||||
*/
|
||||
angular.module('ngResource', ['ng']).
|
||||
info({ angularVersion: '"NG_VERSION_FULL"' }).
|
||||
@@ -722,34 +666,34 @@ angular.module('ngResource', ['ng']).
|
||||
}
|
||||
|
||||
Resource[name] = function(a1, a2, a3, a4) {
|
||||
var params = {}, data, onSuccess, onError;
|
||||
var params = {}, data, success, error;
|
||||
|
||||
switch (arguments.length) {
|
||||
case 4:
|
||||
onError = a4;
|
||||
onSuccess = a3;
|
||||
error = a4;
|
||||
success = a3;
|
||||
// falls through
|
||||
case 3:
|
||||
case 2:
|
||||
if (isFunction(a2)) {
|
||||
if (isFunction(a1)) {
|
||||
onSuccess = a1;
|
||||
onError = a2;
|
||||
success = a1;
|
||||
error = a2;
|
||||
break;
|
||||
}
|
||||
|
||||
onSuccess = a2;
|
||||
onError = a3;
|
||||
success = a2;
|
||||
error = a3;
|
||||
// falls through
|
||||
} else {
|
||||
params = a1;
|
||||
data = a2;
|
||||
onSuccess = a3;
|
||||
success = a3;
|
||||
break;
|
||||
}
|
||||
// falls through
|
||||
case 1:
|
||||
if (isFunction(a1)) onSuccess = a1;
|
||||
if (isFunction(a1)) success = a1;
|
||||
else if (hasBody) data = a1;
|
||||
else params = a1;
|
||||
break;
|
||||
@@ -763,20 +707,14 @@ angular.module('ngResource', ['ng']).
|
||||
var isInstanceCall = this instanceof Resource;
|
||||
var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
|
||||
var httpConfig = {};
|
||||
var requestInterceptor = action.interceptor && action.interceptor.request || undefined;
|
||||
var requestErrorInterceptor = action.interceptor && action.interceptor.requestError ||
|
||||
undefined;
|
||||
var responseInterceptor = action.interceptor && action.interceptor.response ||
|
||||
defaultResponseInterceptor;
|
||||
var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
|
||||
$q.reject;
|
||||
var successCallback = onSuccess ? function(val) {
|
||||
onSuccess(val, response.headers, response.status, response.statusText);
|
||||
} : undefined;
|
||||
var errorCallback = onError || undefined;
|
||||
undefined;
|
||||
var hasError = !!error;
|
||||
var hasResponseErrorInterceptor = !!responseErrorInterceptor;
|
||||
var timeoutDeferred;
|
||||
var numericTimeoutPromise;
|
||||
var response;
|
||||
|
||||
forEach(action, function(value, key) {
|
||||
switch (key) {
|
||||
@@ -805,15 +743,8 @@ angular.module('ngResource', ['ng']).
|
||||
extend({}, extractParams(data, action.params || {}), params),
|
||||
action.url);
|
||||
|
||||
// Start the promise chain
|
||||
var promise = $q.
|
||||
resolve(httpConfig).
|
||||
then(requestInterceptor).
|
||||
catch(requestErrorInterceptor).
|
||||
then($http);
|
||||
|
||||
promise = promise.then(function(resp) {
|
||||
var data = resp.data;
|
||||
var promise = $http(httpConfig).then(function(response) {
|
||||
var data = response.data;
|
||||
|
||||
if (data) {
|
||||
// Need to convert action.isArray to boolean in case it is undefined
|
||||
@@ -841,14 +772,12 @@ angular.module('ngResource', ['ng']).
|
||||
value.$promise = promise; // Restore the promise
|
||||
}
|
||||
}
|
||||
response.resource = value;
|
||||
|
||||
resp.resource = value;
|
||||
response = resp;
|
||||
return responseInterceptor(resp);
|
||||
}, function(rejectionOrResponse) {
|
||||
rejectionOrResponse.resource = value;
|
||||
response = rejectionOrResponse;
|
||||
return responseErrorInterceptor(rejectionOrResponse);
|
||||
return response;
|
||||
}, function(response) {
|
||||
response.resource = value;
|
||||
return $q.reject(response);
|
||||
});
|
||||
|
||||
promise = promise['finally'](function() {
|
||||
@@ -860,8 +789,25 @@ angular.module('ngResource', ['ng']).
|
||||
}
|
||||
});
|
||||
|
||||
// Run the `success`/`error` callbacks, but do not let them affect the returned promise.
|
||||
promise.then(successCallback, errorCallback);
|
||||
promise = promise.then(
|
||||
function(response) {
|
||||
var value = responseInterceptor(response);
|
||||
(success || noop)(value, response.headers, response.status, response.statusText);
|
||||
return value;
|
||||
},
|
||||
(hasError || hasResponseErrorInterceptor) ?
|
||||
function(response) {
|
||||
if (hasError && !hasResponseErrorInterceptor) {
|
||||
// Avoid `Possibly Unhandled Rejection` error,
|
||||
// but still fulfill the returned promise with a rejection
|
||||
promise.catch(noop);
|
||||
}
|
||||
if (hasError) error(response);
|
||||
return hasResponseErrorInterceptor ?
|
||||
responseErrorInterceptor(response) :
|
||||
$q.reject(response);
|
||||
} :
|
||||
undefined);
|
||||
|
||||
if (!isInstanceCall) {
|
||||
// we are creating instance / collection
|
||||
|
||||
+7
-43
@@ -183,22 +183,11 @@ function $RouteProvider() {
|
||||
* `redirectTo` takes precedence over `resolveRedirectTo`, so specifying both on the same
|
||||
* route definition, will cause the latter to be ignored.
|
||||
*
|
||||
* - `[reloadOnUrl=true]` - `{boolean=}` - reload route when any part of the URL changes
|
||||
* (inluding the path) even if the new URL maps to the same route.
|
||||
*
|
||||
* If the option is set to `false` and the URL in the browser changes, but the new URL maps
|
||||
* to the same route, then a `$routeUpdate` event is broadcasted on the root scope (without
|
||||
* reloading the route).
|
||||
*
|
||||
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
|
||||
* or `$location.hash()` changes.
|
||||
*
|
||||
* If the option is set to `false` and the URL in the browser changes, then a `$routeUpdate`
|
||||
* event is broadcasted on the root scope (without reloading the route).
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** This option has no effect if `reloadOnUrl` is set to `false`.
|
||||
* </div>
|
||||
* If the option is set to `false` and url in the browser changes, then
|
||||
* `$routeUpdate` event is broadcasted on the root scope.
|
||||
*
|
||||
* - `[caseInsensitiveMatch=false]` - `{boolean=}` - match routes without being case sensitive
|
||||
*
|
||||
@@ -213,9 +202,6 @@ function $RouteProvider() {
|
||||
this.when = function(path, route) {
|
||||
//copy original route object to preserve params inherited from proto chain
|
||||
var routeCopy = shallowCopy(route);
|
||||
if (angular.isUndefined(routeCopy.reloadOnUrl)) {
|
||||
routeCopy.reloadOnUrl = true;
|
||||
}
|
||||
if (angular.isUndefined(routeCopy.reloadOnSearch)) {
|
||||
routeCopy.reloadOnSearch = true;
|
||||
}
|
||||
@@ -558,9 +544,8 @@ function $RouteProvider() {
|
||||
* @name $route#$routeUpdate
|
||||
* @eventType broadcast on root scope
|
||||
* @description
|
||||
* Broadcasted if the same instance of a route (including template, controller instance,
|
||||
* resolved dependencies, etc.) is being reused. This can happen if either `reloadOnSearch` or
|
||||
* `reloadOnUrl` has been set to `false`.
|
||||
* The `reloadOnSearch` property has been set to false, and we are reusing the same
|
||||
* instance of the Controller.
|
||||
*
|
||||
* @param {Object} angularEvent Synthetic event object
|
||||
* @param {Route} current Current/previous route information.
|
||||
@@ -668,7 +653,9 @@ function $RouteProvider() {
|
||||
var lastRoute = $route.current;
|
||||
|
||||
preparedRoute = parseRoute();
|
||||
preparedRouteIsUpdateOnly = isNavigationUpdateOnly(preparedRoute, lastRoute);
|
||||
preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route
|
||||
&& angular.equals(preparedRoute.pathParams, lastRoute.pathParams)
|
||||
&& !preparedRoute.reloadOnSearch && !forceReload;
|
||||
|
||||
if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) {
|
||||
if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) {
|
||||
@@ -848,29 +835,6 @@ function $RouteProvider() {
|
||||
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} newRoute - The new route configuration (as returned by `parseRoute()`).
|
||||
* @param {Object} oldRoute - The previous route configuration (as returned by `parseRoute()`).
|
||||
* @returns {boolean} Whether this is an "update-only" navigation, i.e. the URL maps to the same
|
||||
* route and it can be reused (based on the config and the type of change).
|
||||
*/
|
||||
function isNavigationUpdateOnly(newRoute, oldRoute) {
|
||||
// IF this is not a forced reload
|
||||
return !forceReload
|
||||
// AND both `newRoute`/`oldRoute` are defined
|
||||
&& newRoute && oldRoute
|
||||
// AND they map to the same Route Definition Object
|
||||
&& (newRoute.$$route === oldRoute.$$route)
|
||||
// AND `reloadOnUrl` is disabled
|
||||
&& (!newRoute.reloadOnUrl
|
||||
// OR `reloadOnSearch` is disabled
|
||||
|| (!newRoute.reloadOnSearch
|
||||
// AND both routes have the same path params
|
||||
&& angular.equals(newRoute.pathParams, oldRoute.pathParams)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} interpolation of the redirect path with the parameters
|
||||
*/
|
||||
|
||||
@@ -42,11 +42,12 @@ var htmlSanitizeWriter;
|
||||
* Sanitizes an html string by stripping all potentially dangerous tokens.
|
||||
*
|
||||
* The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
|
||||
* then serialized back to a properly escaped HTML string. This means that no unsafe input can make
|
||||
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
||||
* it into the returned string.
|
||||
*
|
||||
* The whitelist for URL sanitization of attribute values is configured using the functions
|
||||
* `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link $compileProvider}.
|
||||
* `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider
|
||||
* `$compileProvider`}.
|
||||
*
|
||||
* The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
|
||||
*
|
||||
@@ -310,7 +311,7 @@ function $SanitizeProvider() {
|
||||
forEach = angular.forEach;
|
||||
isArray = angular.isArray;
|
||||
isDefined = angular.isDefined;
|
||||
lowercase = angular.$$lowercase;
|
||||
lowercase = angular.lowercase;
|
||||
noop = angular.noop;
|
||||
|
||||
htmlParser = htmlParserImpl;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"globals": {
|
||||
"includes": false,
|
||||
"asyncForEach": false,
|
||||
"msie": false,
|
||||
"browserTrigger": false,
|
||||
"console": false,
|
||||
"alert": false,
|
||||
"_jQuery": false,
|
||||
"angularInit": false,
|
||||
"formatException": false,
|
||||
"$runner": false,
|
||||
"callerFile": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Represents the application currently being tested and abstracts usage
|
||||
* of iframes or separate windows.
|
||||
*
|
||||
* @param {Object} context jQuery wrapper around HTML context.
|
||||
*/
|
||||
angular.scenario.Application = function(context) {
|
||||
this.context = context;
|
||||
context.append(
|
||||
'<h2>Current URL: <a href="about:blank">None</a></h2>' +
|
||||
'<div id="test-frames"></div>'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the jQuery collection of frames. Don't use this directly because
|
||||
* frames may go stale.
|
||||
*
|
||||
* @private
|
||||
* @return {Object} jQuery collection
|
||||
*/
|
||||
angular.scenario.Application.prototype.getFrame_ = function() {
|
||||
return this.context.find('#test-frames iframe:last');
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the window of the test runner frame. Always favor executeAction()
|
||||
* instead of this method since it prevents you from getting a stale window.
|
||||
*
|
||||
* @private
|
||||
* @return {Object} the window of the frame
|
||||
*/
|
||||
angular.scenario.Application.prototype.getWindow_ = function() {
|
||||
var contentWindow = this.getFrame_().prop('contentWindow');
|
||||
if (!contentWindow) {
|
||||
throw new Error('Frame window is not accessible.');
|
||||
}
|
||||
return contentWindow;
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the location of the frame.
|
||||
*
|
||||
* @param {string} url The URL. If it begins with a # then only the
|
||||
* hash of the page is changed.
|
||||
* @param {function()} loadFn function($window, $document) Called when frame loads.
|
||||
* @param {function()} errorFn function(error) Called if any error when loading.
|
||||
*/
|
||||
angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorFn) {
|
||||
var self = this;
|
||||
var frame = self.getFrame_();
|
||||
//TODO(esprehn): Refactor to use rethrow()
|
||||
errorFn = errorFn || function(e) { throw e; };
|
||||
if (url === 'about:blank') {
|
||||
errorFn('Sandbox Error: Navigating to about:blank is not allowed.');
|
||||
} else if (url.charAt(0) === '#') {
|
||||
url = frame.attr('src').split('#')[0] + url;
|
||||
frame.attr('src', url);
|
||||
self.executeAction(loadFn);
|
||||
} else {
|
||||
frame.remove();
|
||||
self.context.find('#test-frames').append('<iframe>');
|
||||
frame = self.getFrame_();
|
||||
|
||||
frame.on('load', function() {
|
||||
frame.off();
|
||||
try {
|
||||
var $window = self.getWindow_();
|
||||
|
||||
if (!$window.angular) {
|
||||
self.executeAction(loadFn);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$window.angular.resumeBootstrap) {
|
||||
$window.angular.resumeDeferredBootstrap = resumeDeferredBootstrap;
|
||||
} else {
|
||||
resumeDeferredBootstrap();
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
errorFn(e);
|
||||
}
|
||||
|
||||
function resumeDeferredBootstrap() {
|
||||
// Disable animations
|
||||
var $injector = $window.angular.resumeBootstrap([['$provide', function($provide) {
|
||||
return ['$animate', function($animate) {
|
||||
$animate.enabled(false);
|
||||
}];
|
||||
}]]);
|
||||
self.rootElement = $injector.get('$rootElement')[0];
|
||||
self.executeAction(loadFn);
|
||||
}
|
||||
}).attr('src', url);
|
||||
|
||||
// for IE compatibility set the name *after* setting the frame url
|
||||
frame[0].contentWindow.name = 'NG_DEFER_BOOTSTRAP!';
|
||||
}
|
||||
self.context.find('> h2 a').attr('href', url).text(url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes a function in the context of the tested application. Will wait
|
||||
* for all pending AngularJS xhr requests before executing.
|
||||
*
|
||||
* @param {function()} action The callback to execute. function($window, $document)
|
||||
* $document is a jQuery wrapped document.
|
||||
*/
|
||||
angular.scenario.Application.prototype.executeAction = function(action) {
|
||||
var self = this;
|
||||
var $window = this.getWindow_();
|
||||
if (!$window.document) {
|
||||
throw new Error('Sandbox Error: Application document not accessible.');
|
||||
}
|
||||
if (!$window.angular) {
|
||||
return action.call(this, $window, _jQuery($window.document));
|
||||
}
|
||||
|
||||
if (this.rootElement) {
|
||||
executeWithElement(this.rootElement);
|
||||
} else {
|
||||
angularInit($window.document, angular.bind(this, executeWithElement));
|
||||
}
|
||||
|
||||
function executeWithElement(element) {
|
||||
var $injector = $window.angular.element(element).injector();
|
||||
var $element = _jQuery(element);
|
||||
|
||||
$element.injector = function() {
|
||||
return $injector;
|
||||
};
|
||||
|
||||
$injector.invoke(function($browser) {
|
||||
$browser.notifyWhenNoOutstandingRequests(function() {
|
||||
action.call(self, $window, $element);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* The representation of define blocks. Don't used directly, instead use
|
||||
* define() in your tests.
|
||||
*
|
||||
* @param {string} descName Name of the block
|
||||
* @param {Object} parent describe or undefined if the root.
|
||||
*/
|
||||
angular.scenario.Describe = function(descName, parent) {
|
||||
this.only = parent && parent.only;
|
||||
this.beforeEachFns = [];
|
||||
this.afterEachFns = [];
|
||||
this.its = [];
|
||||
this.children = [];
|
||||
this.name = descName;
|
||||
this.parent = parent;
|
||||
this.id = angular.scenario.Describe.id++;
|
||||
|
||||
/**
|
||||
* Calls all before functions.
|
||||
*/
|
||||
var beforeEachFns = this.beforeEachFns;
|
||||
this.setupBefore = function() {
|
||||
if (parent) parent.setupBefore.call(this);
|
||||
angular.forEach(beforeEachFns, /** @this */ function(fn) { fn.call(this); }, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls all after functions.
|
||||
*/
|
||||
var afterEachFns = this.afterEachFns;
|
||||
this.setupAfter = function() {
|
||||
angular.forEach(afterEachFns, /** @this */ function(fn) { fn.call(this); }, this);
|
||||
if (parent) parent.setupAfter.call(this);
|
||||
};
|
||||
};
|
||||
|
||||
// Shared Unique ID generator for every describe block
|
||||
angular.scenario.Describe.id = 0;
|
||||
|
||||
// Shared Unique ID generator for every it (spec)
|
||||
angular.scenario.Describe.specId = 0;
|
||||
|
||||
/**
|
||||
* Defines a block to execute before each it or nested describe.
|
||||
*
|
||||
* @param {function()} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.beforeEach = function(body) {
|
||||
this.beforeEachFns.push(body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a block to execute after each it or nested describe.
|
||||
*
|
||||
* @param {function()} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.afterEach = function(body) {
|
||||
this.afterEachFns.push(body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new describe block that's a child of this one.
|
||||
*
|
||||
* @param {string} name Name of the block. Appended to the parent block's name.
|
||||
* @param {function()} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.describe = function(name, body) {
|
||||
var child = new angular.scenario.Describe(name, this);
|
||||
this.children.push(child);
|
||||
body.call(child);
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as describe() but makes ddescribe blocks the only to run.
|
||||
*
|
||||
* @param {string} name Name of the test.
|
||||
* @param {function()} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.ddescribe = function(name, body) {
|
||||
var child = new angular.scenario.Describe(name, this);
|
||||
child.only = true;
|
||||
this.children.push(child);
|
||||
body.call(child);
|
||||
};
|
||||
|
||||
/**
|
||||
* Use to disable a describe block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.xdescribe = angular.noop;
|
||||
|
||||
/**
|
||||
* Defines a test.
|
||||
*
|
||||
* @param {string} name Name of the test.
|
||||
* @param {function()} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.it = function(name, body) {
|
||||
this.its.push({
|
||||
id: angular.scenario.Describe.specId++,
|
||||
definition: this,
|
||||
only: this.only,
|
||||
name: name,
|
||||
before: this.setupBefore,
|
||||
body: body,
|
||||
after: this.setupAfter
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as it() but makes iit tests the only test to run.
|
||||
*
|
||||
* @param {string} name Name of the test.
|
||||
* @param {function()} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.iit = function(name, body) {
|
||||
this.it.apply(this, arguments);
|
||||
this.its[this.its.length - 1].only = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Use to disable a test block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.xit = angular.noop;
|
||||
|
||||
/**
|
||||
* Gets an array of functions representing all the tests (recursively).
|
||||
* that can be executed with SpecRunner's.
|
||||
*
|
||||
* @return {Array<Object>} Array of it blocks {
|
||||
* definition : Object // parent Describe
|
||||
* only: boolean
|
||||
* name: string
|
||||
* before: Function
|
||||
* body: Function
|
||||
* after: Function
|
||||
* }
|
||||
*/
|
||||
angular.scenario.Describe.prototype.getSpecs = function() {
|
||||
var specs = arguments[0] || [];
|
||||
angular.forEach(this.children, function(child) {
|
||||
child.getSpecs(specs);
|
||||
});
|
||||
angular.forEach(this.its, function(it) {
|
||||
specs.push(it);
|
||||
});
|
||||
var only = [];
|
||||
angular.forEach(specs, function(it) {
|
||||
if (it.only) {
|
||||
only.push(it);
|
||||
}
|
||||
});
|
||||
return (only.length && only) || specs;
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A future action in a spec.
|
||||
*
|
||||
* @param {string} name name of the future action
|
||||
* @param {function()} behavior future callback(error, result)
|
||||
* @param {function()} line Optional. function that returns the file/line number.
|
||||
*/
|
||||
angular.scenario.Future = function(name, behavior, line) {
|
||||
this.name = name;
|
||||
this.behavior = behavior;
|
||||
this.fulfilled = false;
|
||||
this.value = undefined;
|
||||
this.parser = angular.identity;
|
||||
this.line = line || function() { return ''; };
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the behavior of the closure.
|
||||
*
|
||||
* @param {function()} doneFn Callback function(error, result)
|
||||
*/
|
||||
angular.scenario.Future.prototype.execute = function(doneFn) {
|
||||
var self = this;
|
||||
this.behavior(function(error, result) {
|
||||
self.fulfilled = true;
|
||||
if (result) {
|
||||
try {
|
||||
result = self.parser(result);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
self.value = error || result;
|
||||
doneFn(error, result);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Configures the future to convert its final with a function fn(value)
|
||||
*
|
||||
* @param {function()} fn function(value) that returns the parsed value
|
||||
*/
|
||||
angular.scenario.Future.prototype.parsedWith = function(fn) {
|
||||
this.parser = fn;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configures the future to parse its final value from JSON
|
||||
* into objects.
|
||||
*/
|
||||
angular.scenario.Future.prototype.fromJson = function() {
|
||||
return this.parsedWith(angular.fromJson);
|
||||
};
|
||||
|
||||
/**
|
||||
* Configures the future to convert its final value from objects
|
||||
* into JSON.
|
||||
*/
|
||||
angular.scenario.Future.prototype.toJson = function() {
|
||||
return this.parsedWith(angular.toJson);
|
||||
};
|
||||
@@ -0,0 +1,252 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Maintains an object tree from the runner events.
|
||||
*
|
||||
* @param {Object} runner The scenario Runner instance to connect to.
|
||||
*
|
||||
* TODO(esprehn): Every output type creates one of these, but we probably
|
||||
* want one global shared instance. Need to handle events better too
|
||||
* so the HTML output doesn't need to do spec model.getSpec(spec.id)
|
||||
* silliness.
|
||||
*
|
||||
* TODO(vojta) refactor on, emit methods (from all objects) - use inheritance
|
||||
*/
|
||||
angular.scenario.ObjectModel = function(runner) {
|
||||
var self = this;
|
||||
|
||||
this.specMap = {};
|
||||
this.listeners = [];
|
||||
this.value = {
|
||||
name: '',
|
||||
children: {}
|
||||
};
|
||||
|
||||
runner.on('SpecBegin', function(spec) {
|
||||
var block = self.value,
|
||||
definitions = [];
|
||||
|
||||
angular.forEach(self.getDefinitionPath(spec), function(def) {
|
||||
if (!block.children[def.name]) {
|
||||
block.children[def.name] = {
|
||||
id: def.id,
|
||||
name: def.name,
|
||||
children: {},
|
||||
specs: {}
|
||||
};
|
||||
}
|
||||
block = block.children[def.name];
|
||||
definitions.push(def.name);
|
||||
});
|
||||
|
||||
var it = self.specMap[spec.id] =
|
||||
block.specs[spec.name] =
|
||||
new angular.scenario.ObjectModel.Spec(spec.id, spec.name, definitions);
|
||||
|
||||
// forward the event
|
||||
self.emit('SpecBegin', it);
|
||||
});
|
||||
|
||||
runner.on('SpecError', function(spec, error) {
|
||||
var it = self.getSpec(spec.id);
|
||||
it.status = 'error';
|
||||
it.error = error;
|
||||
|
||||
// forward the event
|
||||
self.emit('SpecError', it, error);
|
||||
});
|
||||
|
||||
runner.on('SpecEnd', function(spec) {
|
||||
var it = self.getSpec(spec.id);
|
||||
complete(it);
|
||||
|
||||
// forward the event
|
||||
self.emit('SpecEnd', it);
|
||||
});
|
||||
|
||||
runner.on('StepBegin', function(spec, step) {
|
||||
var it = self.getSpec(spec.id);
|
||||
step = new angular.scenario.ObjectModel.Step(step.name);
|
||||
it.steps.push(step);
|
||||
|
||||
// forward the event
|
||||
self.emit('StepBegin', it, step);
|
||||
});
|
||||
|
||||
runner.on('StepEnd', function(spec) {
|
||||
var it = self.getSpec(spec.id);
|
||||
var step = it.getLastStep();
|
||||
if (step.name !== step.name) {
|
||||
throw new Error('Events fired in the wrong order. Step names don\'t match.');
|
||||
}
|
||||
complete(step);
|
||||
|
||||
// forward the event
|
||||
self.emit('StepEnd', it, step);
|
||||
});
|
||||
|
||||
runner.on('StepFailure', function(spec, step, error) {
|
||||
var it = self.getSpec(spec.id),
|
||||
modelStep = it.getLastStep();
|
||||
|
||||
modelStep.setErrorStatus('failure', error, step.line());
|
||||
it.setStatusFromStep(modelStep);
|
||||
|
||||
// forward the event
|
||||
self.emit('StepFailure', it, modelStep, error);
|
||||
});
|
||||
|
||||
runner.on('StepError', function(spec, step, error) {
|
||||
var it = self.getSpec(spec.id),
|
||||
modelStep = it.getLastStep();
|
||||
|
||||
modelStep.setErrorStatus('error', error, step.line());
|
||||
it.setStatusFromStep(modelStep);
|
||||
|
||||
// forward the event
|
||||
self.emit('StepError', it, modelStep, error);
|
||||
});
|
||||
|
||||
runner.on('RunnerBegin', function() {
|
||||
self.emit('RunnerBegin');
|
||||
});
|
||||
runner.on('RunnerEnd', function() {
|
||||
self.emit('RunnerEnd');
|
||||
});
|
||||
|
||||
function complete(item) {
|
||||
item.endTime = Date.now();
|
||||
item.duration = item.endTime - item.startTime;
|
||||
item.status = item.status || 'success';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a listener for an event.
|
||||
*
|
||||
* @param {string} eventName Name of the event to add a handler for
|
||||
* @param {function()} listener Function that will be called when event is fired
|
||||
*/
|
||||
angular.scenario.ObjectModel.prototype.on = function(eventName, listener) {
|
||||
eventName = eventName.toLowerCase();
|
||||
this.listeners[eventName] = this.listeners[eventName] || [];
|
||||
this.listeners[eventName].push(listener);
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits an event which notifies listeners and passes extra
|
||||
* arguments.
|
||||
*
|
||||
* @param {string} eventName Name of the event to fire.
|
||||
*/
|
||||
angular.scenario.ObjectModel.prototype.emit = function(eventName) {
|
||||
var self = this,
|
||||
args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
eventName = eventName.toLowerCase();
|
||||
|
||||
if (this.listeners[eventName]) {
|
||||
angular.forEach(this.listeners[eventName], function(listener) {
|
||||
listener.apply(self, args);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the path of definition describe blocks that wrap around
|
||||
* this spec.
|
||||
*
|
||||
* @param spec Spec to compute the path for.
|
||||
* @return {Array<Describe>} The describe block path
|
||||
*/
|
||||
angular.scenario.ObjectModel.prototype.getDefinitionPath = function(spec) {
|
||||
var path = [];
|
||||
var currentDefinition = spec.definition;
|
||||
while (currentDefinition && currentDefinition.name) {
|
||||
path.unshift(currentDefinition);
|
||||
currentDefinition = currentDefinition.parent;
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a spec by id.
|
||||
*
|
||||
* @param {string} id The id of the spec to get the object for.
|
||||
* @return {Object} the Spec instance
|
||||
*/
|
||||
angular.scenario.ObjectModel.prototype.getSpec = function(id) {
|
||||
return this.specMap[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* A single it block.
|
||||
*
|
||||
* @param {string} id Id of the spec
|
||||
* @param {string} name Name of the spec
|
||||
* @param {Array<string>=} definitionNames List of all describe block names that wrap this spec
|
||||
*/
|
||||
angular.scenario.ObjectModel.Spec = function(id, name, definitionNames) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.startTime = Date.now();
|
||||
this.steps = [];
|
||||
this.fullDefinitionName = (definitionNames || []).join(' ');
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new step to the Spec.
|
||||
*
|
||||
* @param {string} name Name of the step (really name of the future)
|
||||
* @return {Object} the added step
|
||||
*/
|
||||
angular.scenario.ObjectModel.Spec.prototype.addStep = function(name) {
|
||||
var step = new angular.scenario.ObjectModel.Step(name);
|
||||
this.steps.push(step);
|
||||
return step;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the most recent step.
|
||||
*
|
||||
* @return {Object} the step
|
||||
*/
|
||||
angular.scenario.ObjectModel.Spec.prototype.getLastStep = function() {
|
||||
return this.steps[this.steps.length - 1];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set status of the Spec from given Step
|
||||
*
|
||||
* @param {angular.scenario.ObjectModel.Step} step
|
||||
*/
|
||||
angular.scenario.ObjectModel.Spec.prototype.setStatusFromStep = function(step) {
|
||||
if (!this.status || step.status === 'error') {
|
||||
this.status = step.status;
|
||||
this.error = step.error;
|
||||
this.line = step.line;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A single step inside a Spec.
|
||||
*
|
||||
* @param {string} name Name of the step
|
||||
*/
|
||||
angular.scenario.ObjectModel.Step = function(name) {
|
||||
this.name = name;
|
||||
this.startTime = Date.now();
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper method for setting all error status related properties
|
||||
*
|
||||
* @param {string} status
|
||||
* @param {string} error
|
||||
* @param {string} line
|
||||
*/
|
||||
angular.scenario.ObjectModel.Step.prototype.setErrorStatus = function(status, error, line) {
|
||||
this.status = status;
|
||||
this.error = error;
|
||||
this.line = line;
|
||||
};
|
||||
@@ -0,0 +1,229 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Runner for scenarios
|
||||
*
|
||||
* Has to be initialized before any test is loaded,
|
||||
* because it publishes the API into window (global space).
|
||||
*/
|
||||
angular.scenario.Runner = function($window) {
|
||||
this.listeners = [];
|
||||
this.$window = $window;
|
||||
this.rootDescribe = new angular.scenario.Describe();
|
||||
this.currentDescribe = this.rootDescribe;
|
||||
this.api = {
|
||||
it: this.it,
|
||||
iit: this.iit,
|
||||
xit: angular.noop,
|
||||
describe: this.describe,
|
||||
ddescribe: this.ddescribe,
|
||||
xdescribe: angular.noop,
|
||||
beforeEach: this.beforeEach,
|
||||
afterEach: this.afterEach
|
||||
};
|
||||
angular.forEach(this.api, angular.bind(this, /** @this */ function(fn, key) {
|
||||
this.$window[key] = angular.bind(this, fn);
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits an event which notifies listeners and passes extra
|
||||
* arguments.
|
||||
*
|
||||
* @param {string} eventName Name of the event to fire.
|
||||
*/
|
||||
angular.scenario.Runner.prototype.emit = function(eventName) {
|
||||
var self = this;
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
eventName = eventName.toLowerCase();
|
||||
if (!this.listeners[eventName]) {
|
||||
return;
|
||||
}
|
||||
angular.forEach(this.listeners[eventName], function(listener) {
|
||||
listener.apply(self, args);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a listener for an event.
|
||||
*
|
||||
* @param {string} eventName The name of the event to add a handler for
|
||||
* @param {string} listener The fn(...) that takes the extra arguments from emit()
|
||||
*/
|
||||
angular.scenario.Runner.prototype.on = function(eventName, listener) {
|
||||
eventName = eventName.toLowerCase();
|
||||
this.listeners[eventName] = this.listeners[eventName] || [];
|
||||
this.listeners[eventName].push(listener);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a describe block of a spec.
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {function()} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.describe = function(name, body) {
|
||||
var self = this;
|
||||
this.currentDescribe.describe(name, /** @this */ function() {
|
||||
var parentDescribe = self.currentDescribe;
|
||||
self.currentDescribe = this;
|
||||
try {
|
||||
body.call(this);
|
||||
} finally {
|
||||
self.currentDescribe = parentDescribe;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as describe, but makes ddescribe the only blocks to run.
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {function()} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.ddescribe = function(name, body) {
|
||||
var self = this;
|
||||
this.currentDescribe.ddescribe(name, /** @this */ function() {
|
||||
var parentDescribe = self.currentDescribe;
|
||||
self.currentDescribe = this;
|
||||
try {
|
||||
body.call(this);
|
||||
} finally {
|
||||
self.currentDescribe = parentDescribe;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a test in a describe block of a spec.
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {function()} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.it = function(name, body) {
|
||||
this.currentDescribe.it(name, body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as it, but makes iit tests the only tests to run.
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {function()} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.iit = function(name, body) {
|
||||
this.currentDescribe.iit(name, body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a function to be called before each it block in the describe
|
||||
* (and before all nested describes).
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {function()} Callback to execute
|
||||
*/
|
||||
angular.scenario.Runner.prototype.beforeEach = function(body) {
|
||||
this.currentDescribe.beforeEach(body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a function to be called after each it block in the describe
|
||||
* (and before all nested describes).
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {function()} Callback to execute
|
||||
*/
|
||||
angular.scenario.Runner.prototype.afterEach = function(body) {
|
||||
this.currentDescribe.afterEach(body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new spec runner.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} scope parent scope
|
||||
*/
|
||||
angular.scenario.Runner.prototype.createSpecRunner_ = function(scope) {
|
||||
var child = scope.$new();
|
||||
var Cls = angular.scenario.SpecRunner;
|
||||
|
||||
// Export all the methods to child scope manually as now we don't mess controllers with scopes
|
||||
// TODO(vojta): refactor scenario runner so that these objects are not tightly coupled as current
|
||||
for (var name in Cls.prototype) {
|
||||
child[name] = angular.bind(child, Cls.prototype[name]);
|
||||
}
|
||||
|
||||
Cls.call(child);
|
||||
return child;
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs all the loaded tests with the specified runner class on the
|
||||
* provided application.
|
||||
*
|
||||
* @param {angular.scenario.Application} application App to remote control.
|
||||
*/
|
||||
angular.scenario.Runner.prototype.run = function(application) {
|
||||
var self = this;
|
||||
var $root = angular.injector(['ng']).get('$rootScope');
|
||||
angular.extend($root, this);
|
||||
angular.forEach(angular.scenario.Runner.prototype, function(fn, name) {
|
||||
$root[name] = angular.bind(self, fn);
|
||||
});
|
||||
$root.application = application;
|
||||
$root.emit('RunnerBegin');
|
||||
asyncForEach(this.rootDescribe.getSpecs(), function(spec, specDone) {
|
||||
var dslCache = {};
|
||||
var runner = self.createSpecRunner_($root);
|
||||
angular.forEach(angular.scenario.dsl, function(fn, key) {
|
||||
dslCache[key] = fn.call($root);
|
||||
});
|
||||
angular.forEach(angular.scenario.dsl, function(fn, key) {
|
||||
self.$window[key] = function() {
|
||||
var line = callerFile(3);
|
||||
var scope = runner.$new();
|
||||
|
||||
// Make the dsl accessible on the current chain
|
||||
scope.dsl = {};
|
||||
angular.forEach(dslCache, function(fn, key) {
|
||||
scope.dsl[key] = function() {
|
||||
return dslCache[key].apply(scope, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
// Make these methods work on the current chain
|
||||
scope.addFuture = function() {
|
||||
Array.prototype.push.call(arguments, line);
|
||||
return angular.scenario.SpecRunner.
|
||||
prototype.addFuture.apply(scope, arguments);
|
||||
};
|
||||
scope.addFutureAction = function() {
|
||||
Array.prototype.push.call(arguments, line);
|
||||
return angular.scenario.SpecRunner.
|
||||
prototype.addFutureAction.apply(scope, arguments);
|
||||
};
|
||||
|
||||
return scope.dsl[key].apply(scope, arguments);
|
||||
};
|
||||
});
|
||||
runner.run(spec, /** @this */ function() {
|
||||
runner.$destroy();
|
||||
specDone.apply(this, arguments);
|
||||
});
|
||||
},
|
||||
function(error) {
|
||||
if (error) {
|
||||
self.emit('RunnerError', error);
|
||||
}
|
||||
self.emit('RunnerEnd');
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,329 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* Setup file for the Scenario.
|
||||
* Must be first in the compilation/bootstrap list.
|
||||
*/
|
||||
|
||||
// Public namespace
|
||||
angular.scenario = angular.scenario || {};
|
||||
|
||||
/**
|
||||
* Expose jQuery (e.g. for custom dsl extensions).
|
||||
*/
|
||||
angular.scenario.jQuery = _jQuery;
|
||||
|
||||
/**
|
||||
* Defines a new output format.
|
||||
*
|
||||
* @param {string} name the name of the new output format
|
||||
* @param {function()} fn function(context, runner) that generates the output
|
||||
*/
|
||||
angular.scenario.output = angular.scenario.output || function(name, fn) {
|
||||
angular.scenario.output[name] = fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a new DSL statement. If your factory function returns a Future
|
||||
* it's returned, otherwise the result is assumed to be a map of functions
|
||||
* for chaining. Chained functions are subject to the same rules.
|
||||
*
|
||||
* Note: All functions on the chain are bound to the chain scope so values
|
||||
* set on "this" in your statement function are available in the chained
|
||||
* functions.
|
||||
*
|
||||
* @param {string} name The name of the statement
|
||||
* @param {function()} fn Factory function(), return a function for
|
||||
* the statement.
|
||||
*/
|
||||
angular.scenario.dsl = angular.scenario.dsl || function(name, fn) {
|
||||
angular.scenario.dsl[name] = function() {
|
||||
// The dsl binds `this` for us when calling chained functions
|
||||
/** @this */
|
||||
function executeStatement(statement, args) {
|
||||
var result = statement.apply(this, args);
|
||||
if (angular.isFunction(result) || result instanceof angular.scenario.Future) {
|
||||
return result;
|
||||
}
|
||||
var self = this;
|
||||
var chain = angular.extend({}, result);
|
||||
angular.forEach(chain, function(value, name) {
|
||||
if (angular.isFunction(value)) {
|
||||
chain[name] = function() {
|
||||
return executeStatement.call(self, value, arguments);
|
||||
};
|
||||
} else {
|
||||
chain[name] = value;
|
||||
}
|
||||
});
|
||||
return chain;
|
||||
}
|
||||
var statement = fn.apply(this, arguments);
|
||||
return /** @this */ function() {
|
||||
return executeStatement.call(this, statement, arguments);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a new matcher for use with the expects() statement. The value
|
||||
* this.actual (like in Jasmine) is available in your matcher to compare
|
||||
* against. Your function should return a boolean. The future is automatically
|
||||
* created for you.
|
||||
*
|
||||
* @param {string} name The name of the matcher
|
||||
* @param {function()} fn The matching function(expected).
|
||||
*/
|
||||
angular.scenario.matcher = angular.scenario.matcher || function(name, fn) {
|
||||
angular.scenario.matcher[name] = function(expected) {
|
||||
var description = this.future.name +
|
||||
(this.inverse ? ' not ' : ' ') + name +
|
||||
' ' + angular.toJson(expected);
|
||||
var self = this;
|
||||
this.addFuture('expect ' + description,
|
||||
function(done) {
|
||||
var error;
|
||||
self.actual = self.future.value;
|
||||
if ((self.inverse && fn.call(self, expected)) ||
|
||||
(!self.inverse && !fn.call(self, expected))) {
|
||||
error = 'expected ' + description +
|
||||
' but was ' + angular.toJson(self.actual);
|
||||
}
|
||||
done(error);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the scenario runner and run !
|
||||
*
|
||||
* Access global window and document object
|
||||
* Access $runner through closure
|
||||
*
|
||||
* @param {Object=} config Config options
|
||||
*/
|
||||
angular.scenario.setUpAndRun = function(config) {
|
||||
var href = window.location.href;
|
||||
var body = _jQuery(window.document.body);
|
||||
var output = [];
|
||||
var objModel = new angular.scenario.ObjectModel($runner);
|
||||
|
||||
if (config && config.scenario_output) {
|
||||
output = config.scenario_output.split(',');
|
||||
}
|
||||
|
||||
angular.forEach(angular.scenario.output, function(fn, name) {
|
||||
if (!output.length || output.indexOf(name) !== -1) {
|
||||
var context = body.append('<div></div>').find('div:last');
|
||||
context.attr('id', name);
|
||||
fn.call({}, context, $runner, objModel);
|
||||
}
|
||||
});
|
||||
|
||||
if (!/^http/.test(href) && !/^https/.test(href)) {
|
||||
body.append('<p id="system-error"></p>');
|
||||
body.find('#system-error').text(
|
||||
'Scenario runner must be run using http or https. The protocol ' +
|
||||
href.split(':')[0] + ':// is not supported.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var appFrame = body.append('<div id="application"></div>').find('#application');
|
||||
var application = new angular.scenario.Application(appFrame);
|
||||
|
||||
$runner.on('RunnerEnd', function() {
|
||||
appFrame.css('display', 'none');
|
||||
appFrame.find('iframe').attr('src', 'about:blank');
|
||||
});
|
||||
|
||||
$runner.on('RunnerError', function(error) {
|
||||
if (window.console) {
|
||||
console.log(formatException(error));
|
||||
} else {
|
||||
// Do something for IE
|
||||
// eslint-disable-next-line no-alert
|
||||
alert(error);
|
||||
}
|
||||
});
|
||||
|
||||
$runner.run(application);
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterates through list with iterator function that must call the
|
||||
* continueFunction to continue iterating.
|
||||
*
|
||||
* @param {Array} list list to iterate over
|
||||
* @param {function()} iterator Callback function(value, continueFunction)
|
||||
* @param {function()} done Callback function(error, result) called when
|
||||
* iteration finishes or an error occurs.
|
||||
*/
|
||||
function asyncForEach(list, iterator, done) {
|
||||
var i = 0;
|
||||
function loop(error, index) {
|
||||
if (index && index > i) {
|
||||
i = index;
|
||||
}
|
||||
if (error || i >= list.length) {
|
||||
done(error);
|
||||
} else {
|
||||
try {
|
||||
iterator(list[i++], loop);
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
loop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an exception into a string with the stack trace, but limits
|
||||
* to a specific line length.
|
||||
*
|
||||
* @param {Object} error The exception to format, can be anything throwable
|
||||
* @param {Number=} [maxStackLines=5] max lines of the stack trace to include
|
||||
* default is 5.
|
||||
*/
|
||||
function formatException(error, maxStackLines) {
|
||||
maxStackLines = maxStackLines || 5;
|
||||
var message = error.toString();
|
||||
if (error.stack) {
|
||||
var stack = error.stack.split('\n');
|
||||
if (stack[0].indexOf(message) === -1) {
|
||||
maxStackLines++;
|
||||
stack.unshift(error.message);
|
||||
}
|
||||
message = stack.slice(0, maxStackLines).join('\n');
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that gets the file name and line number from a
|
||||
* location in the stack if available based on the call site.
|
||||
*
|
||||
* Note: this returns another function because accessing .stack is very
|
||||
* expensive in Chrome.
|
||||
*
|
||||
* @param {Number} offset Number of stack lines to skip
|
||||
*/
|
||||
function callerFile(offset) {
|
||||
var error = new Error();
|
||||
|
||||
return function() {
|
||||
var line = (error.stack || '').split('\n')[offset];
|
||||
|
||||
// Clean up the stack trace line
|
||||
if (line) {
|
||||
if (line.indexOf('@') !== -1) {
|
||||
// Firefox
|
||||
line = line.substring(line.indexOf('@') + 1);
|
||||
} else {
|
||||
// Chrome
|
||||
line = line.substring(line.indexOf('(') + 1).replace(')', '');
|
||||
}
|
||||
}
|
||||
|
||||
return line || '';
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Don't use the jQuery trigger method since it works incorrectly.
|
||||
*
|
||||
* jQuery notifies listeners and then changes the state of a checkbox and
|
||||
* does not create a real browser event. A real click changes the state of
|
||||
* the checkbox and then notifies listeners.
|
||||
*
|
||||
* To work around this we instead use our own handler that fires a real event.
|
||||
*/
|
||||
(function(fn) {
|
||||
// We need a handle to the original trigger function for input tests.
|
||||
var parentTrigger = fn._originalTrigger = fn.trigger;
|
||||
fn.trigger = function(type) {
|
||||
if (/(click|change|keydown|blur|input|mousedown|mouseup)/.test(type)) {
|
||||
var processDefaults = [];
|
||||
this.each(function(index, node) {
|
||||
processDefaults.push(browserTrigger(node, type));
|
||||
});
|
||||
|
||||
// this is not compatible with jQuery - we return an array of returned values,
|
||||
// so that scenario runner know whether JS code has preventDefault() of the event or not...
|
||||
return processDefaults;
|
||||
}
|
||||
return parentTrigger.apply(this, arguments);
|
||||
};
|
||||
})(_jQuery.fn);
|
||||
|
||||
/**
|
||||
* Finds all bindings with the substring match of name and returns an
|
||||
* array of their values.
|
||||
*
|
||||
* @param {string} bindExp The name to match
|
||||
* @return {Array.<string>} String of binding values
|
||||
*/
|
||||
_jQuery.fn.bindings = function(windowJquery, bindExp) {
|
||||
var result = [], match,
|
||||
bindSelector = '.ng-binding:visible';
|
||||
if (angular.isString(bindExp)) {
|
||||
bindExp = bindExp.replace(/\s/g, '');
|
||||
match = function(actualExp) {
|
||||
if (actualExp) {
|
||||
actualExp = actualExp.replace(/\s/g, '');
|
||||
if (actualExp === bindExp) return true;
|
||||
if (actualExp.indexOf(bindExp) === 0) {
|
||||
return actualExp.charAt(bindExp.length) === '|';
|
||||
}
|
||||
}
|
||||
};
|
||||
} else if (bindExp) {
|
||||
match = function(actualExp) {
|
||||
return actualExp && bindExp.exec(actualExp);
|
||||
};
|
||||
} else {
|
||||
match = function(actualExp) {
|
||||
return !!actualExp;
|
||||
};
|
||||
}
|
||||
var selection = this.find(bindSelector);
|
||||
if (this.is(bindSelector)) {
|
||||
selection = selection.add(this);
|
||||
}
|
||||
|
||||
function push(value) {
|
||||
if (angular.isUndefined(value)) {
|
||||
value = '';
|
||||
} else if (typeof value !== 'string') {
|
||||
value = angular.toJson(value);
|
||||
}
|
||||
result.push('' + value);
|
||||
}
|
||||
|
||||
selection.each(/* @this Node */ function() {
|
||||
var element = windowJquery(this),
|
||||
bindings;
|
||||
if ((bindings = element.data('$binding'))) {
|
||||
for (var expressions = [], binding, j = 0, jj = bindings.length; j < jj; j++) {
|
||||
binding = bindings[j];
|
||||
|
||||
if (binding.expressions) {
|
||||
expressions = binding.expressions;
|
||||
} else {
|
||||
expressions = [binding];
|
||||
}
|
||||
for (var scope, expression, i = 0, ii = expressions.length; i < ii; i++) {
|
||||
expression = expressions[i];
|
||||
if (match(expression)) {
|
||||
scope = scope || element.scope();
|
||||
push(scope.$eval(expression));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
@@ -0,0 +1,149 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This class is the "this" of the it/beforeEach/afterEach method.
|
||||
* Responsibilities:
|
||||
* - "this" for it/beforeEach/afterEach
|
||||
* - keep state for single it/beforeEach/afterEach execution
|
||||
* - keep track of all of the futures to execute
|
||||
* - run single spec (execute each future)
|
||||
*/
|
||||
angular.scenario.SpecRunner = function() {
|
||||
this.futures = [];
|
||||
this.afterIndex = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes a spec which is an it block with associated before/after functions
|
||||
* based on the describe nesting.
|
||||
*
|
||||
* @param {Object} spec A spec object
|
||||
* @param {function()} specDone function that is called when the spec finishes,
|
||||
* of the form `Function(error, index)`
|
||||
*/
|
||||
angular.scenario.SpecRunner.prototype.run = function(spec, specDone) {
|
||||
var self = this;
|
||||
this.spec = spec;
|
||||
|
||||
this.emit('SpecBegin', spec);
|
||||
|
||||
try {
|
||||
spec.before.call(this);
|
||||
spec.body.call(this);
|
||||
this.afterIndex = this.futures.length;
|
||||
spec.after.call(this);
|
||||
} catch (e) {
|
||||
this.emit('SpecError', spec, e);
|
||||
this.emit('SpecEnd', spec);
|
||||
specDone();
|
||||
return;
|
||||
}
|
||||
|
||||
var handleError = function(error, done) {
|
||||
if (self.error) {
|
||||
return done();
|
||||
}
|
||||
self.error = true;
|
||||
done(null, self.afterIndex);
|
||||
};
|
||||
|
||||
asyncForEach(
|
||||
this.futures,
|
||||
function(future, futureDone) {
|
||||
self.step = future;
|
||||
self.emit('StepBegin', spec, future);
|
||||
try {
|
||||
future.execute(function(error) {
|
||||
if (error) {
|
||||
self.emit('StepFailure', spec, future, error);
|
||||
self.emit('StepEnd', spec, future);
|
||||
return handleError(error, futureDone);
|
||||
}
|
||||
self.emit('StepEnd', spec, future);
|
||||
self.$window.setTimeout(function() { futureDone(); }, 0);
|
||||
});
|
||||
} catch (e) {
|
||||
self.emit('StepError', spec, future, e);
|
||||
self.emit('StepEnd', spec, future);
|
||||
handleError(e, futureDone);
|
||||
}
|
||||
},
|
||||
function(e) {
|
||||
if (e) {
|
||||
self.emit('SpecError', spec, e);
|
||||
}
|
||||
self.emit('SpecEnd', spec);
|
||||
// Call done in a timeout so exceptions don't recursively
|
||||
// call this function
|
||||
self.$window.setTimeout(function() { specDone(); }, 0);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new future action.
|
||||
*
|
||||
* Note: Do not pass line manually. It happens automatically.
|
||||
*
|
||||
* @param {string} name Name of the future
|
||||
* @param {function()} behavior Behavior of the future
|
||||
* @param {function()} line fn() that returns file/line number
|
||||
*/
|
||||
angular.scenario.SpecRunner.prototype.addFuture = function(name, behavior, line) {
|
||||
var future = new angular.scenario.Future(name, angular.bind(this, behavior), line);
|
||||
this.futures.push(future);
|
||||
return future;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new future action to be executed on the application window.
|
||||
*
|
||||
* Note: Do not pass line manually. It happens automatically.
|
||||
*
|
||||
* @param {string} name Name of the future
|
||||
* @param {function()} behavior Behavior of the future
|
||||
* @param {function()} line fn() that returns file/line number
|
||||
*/
|
||||
angular.scenario.SpecRunner.prototype.addFutureAction = function(name, behavior, line) {
|
||||
var self = this;
|
||||
var NG = /\[ng\\:/;
|
||||
return this.addFuture(name, /** @this */ function(done) {
|
||||
this.application.executeAction(function($window, $document) {
|
||||
|
||||
//TODO(esprehn): Refactor this so it doesn't need to be in here.
|
||||
$document.elements = function(selector) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
selector = (self.selector || '') + ' ' + (selector || '');
|
||||
selector = _jQuery.trim(selector) || '*';
|
||||
angular.forEach(args, function(value, index) {
|
||||
selector = selector.replace('$' + (index + 1), value);
|
||||
});
|
||||
var result = $document.find(selector);
|
||||
if (selector.match(NG)) {
|
||||
angular.forEach(['[ng-','[data-ng-','[x-ng-'], function(value, index) {
|
||||
result = result.add(selector.replace(NG, value), $document);
|
||||
});
|
||||
}
|
||||
if (!result.length) {
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
throw {
|
||||
type: 'selector',
|
||||
message: 'Selector ' + selector + ' did not match any elements.'
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
try {
|
||||
behavior.call(self, $window, $document, done);
|
||||
} catch (e) {
|
||||
if (e.type && e.type === 'selector') {
|
||||
done(e.message);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
}, line);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user