Compare commits
251 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47a55ca767 | |||
| 3d78bf3fa8 | |||
| 6e80d0ad54 | |||
| ae637acdfb | |||
| 661f6d9ecf | |||
| 56a7abd38f | |||
| 7d70dcdab1 | |||
| 9e6161e579 | |||
| bd28c74c1d | |||
| cd77c089ba | |||
| 83f88c1818 | |||
| fb2c585897 | |||
| bdbe4fd34a | |||
| 0b4e150aa5 | |||
| e091bb7dbb | |||
| b62c858499 | |||
| 4a18274670 | |||
| e3bf1ed217 | |||
| a5f037d033 | |||
| 25152bb218 | |||
| 24eb528b05 | |||
| d161cc6b25 | |||
| d604f941e8 | |||
| 85758ce3af | |||
| 924e68c7d5 | |||
| f297aa5d0a | |||
| 337ce67612 | |||
| 8b56c08327 | |||
| 32eec67023 | |||
| fd1528a6c8 | |||
| 6cb5fbf5ef | |||
| f6644c720e | |||
| 0d9aafba3b | |||
| 0524e92d2e | |||
| 9b96cea462 | |||
| c90ad96808 | |||
| 69f69db1e0 | |||
| 8e28bb4c2f | |||
| ab41e48493 | |||
| ef640cbc2a | |||
| 081fef60b2 | |||
| 99d1a438b6 | |||
| 6bd4292c24 | |||
| e0663312fb | |||
| 9ae0c01c2b | |||
| 79b3b8b686 | |||
| 1b740974f5 | |||
| b9bdbe615c | |||
| 6617b42bc7 | |||
| 8a907eb3ff | |||
| aac3c4aa63 | |||
| d162f152b8 | |||
| 4025883803 | |||
| c437d0a470 | |||
| 5d28d19623 | |||
| 7fd2dc11ca | |||
| 63db09753e | |||
| a097aa95b7 | |||
| b3dfb38359 | |||
| 3b5ba87797 | |||
| e50002baed | |||
| a8089166f5 | |||
| d8e3707860 | |||
| ca4df475e1 | |||
| 02c9dc6e16 | |||
| 6ad109e745 | |||
| c5cba6e9c1 | |||
| 924d3c6bfe | |||
| ee29819dba | |||
| 41f03e4b02 | |||
| facfec9841 | |||
| e2d1969d68 | |||
| f380cd220c | |||
| 2db0aabee3 | |||
| ee42cfea04 | |||
| fbbf6ac161 | |||
| a1c5f2b4f0 | |||
| 2d6a0a1dc1 | |||
| 0c4f9fa574 | |||
| 7ad66527ba | |||
| f2e7f875e2 | |||
| 40a537c25f | |||
| cb192293f4 | |||
| 8c3a42cd68 | |||
| 96e7897fef | |||
| 2dc34a9699 | |||
| 10ac594809 | |||
| d21dff21ed | |||
| 55d9db56a6 | |||
| 579aa59324 | |||
| b9e5eaf669 | |||
| 228281eecc | |||
| acb066e84a | |||
| ed1243ffc7 | |||
| 3aa5752894 | |||
| 8bfeddb5d6 | |||
| 015111fd79 | |||
| cc0fbe37d4 | |||
| 1b640f9665 | |||
| 32806caf13 | |||
| 9c113aa4af | |||
| 9a616eade4 | |||
| 7daf4e0125 | |||
| 1191edba4e | |||
| c8c9bbc412 | |||
| 429938da1f | |||
| a75537d461 | |||
| 5ced914cc8 | |||
| a631a759d2 | |||
| f7cf846045 | |||
| 96c61fe756 | |||
| 915a891ad4 | |||
| 8df47db72f | |||
| 013b522c9e | |||
| 7c6be43e83 | |||
| e93710fe0e | |||
| 5481e2cfcd | |||
| c6b57f1ec6 | |||
| 240e0d5c8e | |||
| 9fa73cb4e7 | |||
| f6458826ac | |||
| ab2531143e | |||
| 9a83f9d2fa | |||
| 3f07eb227d | |||
| 446e5669a1 | |||
| b264be40bc | |||
| 814c9847e8 | |||
| dde613f18e | |||
| e6a2527cdf | |||
| d0351c4803 | |||
| b2b6d74ae5 | |||
| 30694c8027 | |||
| 655ac6474b | |||
| e5a9b265ba | |||
| e2b9eccde0 | |||
| 9b3d9656a6 | |||
| 1e6a5b29a6 | |||
| 8f05ca5552 | |||
| 2ec8d1ffc0 | |||
| 08cd5c19c7 | |||
| 41dc7d5ebd | |||
| 0caa5ad83f | |||
| 5d36353bc9 | |||
| 719d5c5fa5 | |||
| 266da34098 | |||
| 9474ec120a | |||
| 09a9832358 | |||
| bf6a79c348 | |||
| 8ee8ffeba0 | |||
| 42d09f1772 | |||
| 8692f87a46 | |||
| 40406e2f22 | |||
| 4644c5840f | |||
| addb4bdd57 | |||
| 7496e8e5b7 | |||
| e9b9421cdb | |||
| 7a374691b9 | |||
| 3be6835e0f | |||
| a3d79775e1 | |||
| 920b595080 | |||
| 3109342679 | |||
| aa01be8b2c | |||
| bb4d3b73a1 | |||
| 6dbd606ad7 | |||
| 764fa869dd | |||
| 7812dfcee8 | |||
| 00b623e86b | |||
| 92f87b1142 | |||
| 5c1fdff691 | |||
| 891acf4c20 | |||
| 93552fed1c | |||
| b5fbd6a2f6 | |||
| 5c43b94fc4 | |||
| 7dd94b9595 | |||
| 95f8a8bab0 | |||
| dc5bba8615 | |||
| 16c8f29ef6 | |||
| d3fb8dd776 | |||
| 95e03bce7e | |||
| 5388ca5710 | |||
| 1b275fb00e | |||
| 1c68d00fbf | |||
| 158241e212 | |||
| eab271876c | |||
| 637d3b47d1 | |||
| 8ac369e829 | |||
| 5c611e898a | |||
| dc9775da96 | |||
| e8941c0fe5 | |||
| bb16759f0b | |||
| 2b41a5868a | |||
| 637c020f82 | |||
| f7fde935d5 | |||
| 5f552896ab | |||
| d5968c7853 | |||
| 4f4ff5f31b | |||
| e3764e30a3 | |||
| c9899c53ac | |||
| 4d4e6036a9 | |||
| 4d885cbd62 | |||
| eec2020518 | |||
| 2901c53f42 | |||
| e80053d91f | |||
| fa12c3c86a | |||
| cf43ccdf9b | |||
| a9352c19ce | |||
| 6f19a6fd33 | |||
| f30163e63a | |||
| 5b23bc9b07 | |||
| 9d1e87a3f1 | |||
| 6604c23614 | |||
| 195deca6e2 | |||
| 85eb9660ef | |||
| d81ff8885b | |||
| eca14d98a4 | |||
| 22ecbc50f4 | |||
| 7f857e44a2 | |||
| be6920b356 | |||
| 8eabc5463c | |||
| 14ff529fbb | |||
| fbad280570 | |||
| 8b775a0d58 | |||
| 0bbc6ee481 | |||
| 381b185117 | |||
| fa0d8c47c3 | |||
| 7e233eb58a | |||
| b7afd11d26 | |||
| 8582088b36 | |||
| 0db573b749 | |||
| 5e78af769e | |||
| 804e75045c | |||
| ebc3b7b1c3 | |||
| 830846f664 | |||
| 0462ee6659 | |||
| 9cc6835819 | |||
| ee1fc1dc13 | |||
| 41b36e689c | |||
| 52545e5a74 | |||
| 2abea7514a | |||
| f157d02793 | |||
| 77d8ae1d45 | |||
| e21b6ff3ff | |||
| 06016bb12c | |||
| 50e72fcae1 | |||
| b84e62bd28 | |||
| b6fd184a93 | |||
| 1db9e617ce | |||
| 0918f146e4 | |||
| 6dfd938bbc | |||
| 4f5a60bcca | |||
| e593939411 |
@@ -23,8 +23,16 @@
|
||||
"disallowTrailingWhitespace": true,
|
||||
"requireCommaBeforeLineBreak": true,
|
||||
"requireLineFeedAtFileEnd": true,
|
||||
"requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
|
||||
"requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
|
||||
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
|
||||
"requireSpaceBeforeBlockStatements": true,
|
||||
"requireSpacesInConditionalExpression": {
|
||||
"afterTest": true,
|
||||
"beforeConsequent": true,
|
||||
"afterConsequent": true,
|
||||
"beforeAlternate": true
|
||||
},
|
||||
"requireSpacesInFunction": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
{
|
||||
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
|
||||
"requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
|
||||
"requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
|
||||
"disallowImplicitTypeConversion": ["string"],
|
||||
"disallowMultipleLineBreaks": true,
|
||||
"disallowKeywordsOnNewLine": ["else"],
|
||||
|
||||
+10
-5
@@ -8,14 +8,19 @@ branches:
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- JOB=unit
|
||||
- JOB=e2e TEST_TARGET=jqlite
|
||||
- JOB=e2e TEST_TARGET=jquery
|
||||
- JOB=unit BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
|
||||
- JOB=unit BROWSER_PROVIDER=browserstack
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack
|
||||
global:
|
||||
- SAUCE_USERNAME=angular-ci
|
||||
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
|
||||
- BROWSER_STACK_USERNAME=VojtaJina
|
||||
- BROWSER_STACK_ACCESS_KEY=QCQJ1ZpWXpBkSwEdD8ev
|
||||
- LOGS_DIR=/tmp/angular-build/logs
|
||||
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
|
||||
- BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready
|
||||
|
||||
install:
|
||||
# - npm config set registry http://23.251.144.68
|
||||
@@ -28,7 +33,7 @@ install:
|
||||
|
||||
before_script:
|
||||
- mkdir -p $LOGS_DIR
|
||||
- ./lib/sauce/sauce_connect_setup.sh
|
||||
- ./scripts/travis/start_browser_provider.sh
|
||||
- npm install -g grunt-cli
|
||||
- grunt package
|
||||
- ./scripts/travis/wait_for_browser_provider.sh
|
||||
|
||||
+501
@@ -1,3 +1,494 @@
|
||||
<a name="1.3.8"></a>
|
||||
# 1.3.8 prophetic-narwhal (2014-12-19)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
- **filterFilter:**
|
||||
- make `$` match properties on deeper levels as well
|
||||
([bd28c74c](https://github.com/angular/angular.js/commit/bd28c74c1d91c477a86f10fe36576cba0249e6ef),
|
||||
[#10401](https://github.com/angular/angular.js/issues/10401))
|
||||
- let expression object `{$: '...'}` also match primitive items
|
||||
([fb2c5858](https://github.com/angular/angular.js/commit/fb2c58589758744c0eef8c2ead3dbcf27a5cf200),
|
||||
[#10428](https://github.com/angular/angular.js/issues/10428))
|
||||
- **ngAria:** trigger digest on `ng-click` via keypress, pass `$event` to expression
|
||||
([924e68c7](https://github.com/angular/angular.js/commit/924e68c7d522a1086969f3583d0ce87e59110bc5),
|
||||
[#10442](https://github.com/angular/angular.js/issues/10442), [#10443](https://github.com/angular/angular.js/issues/10443), [#10447](https://github.com/angular/angular.js/issues/10447))
|
||||
- **orderBy:** compare timestamps when sorting date objects
|
||||
([661f6d9e](https://github.com/angular/angular.js/commit/661f6d9ecf1459ce3b2794c3cde373e17ae83972),
|
||||
[#10512](https://github.com/angular/angular.js/issues/10512), [#10516](https://github.com/angular/angular.js/issues/10516))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **limitTo:** replace for loop with slice
|
||||
([cd77c089](https://github.com/angular/angular.js/commit/cd77c089ba2f4b94ccc74f32f0ffa9fb70851c02))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.3.7"></a>
|
||||
# 1.3.7 leaky-obstruction (2014-12-15)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** use `createMap()` for `$$observe` listeners when initialized from attr interpolation
|
||||
([8e28bb4c](https://github.com/angular/angular.js/commit/8e28bb4c2f6d015dfe1cec7755f1ca9b0ecef1f8))
|
||||
- **$http:**
|
||||
- only parse as JSON when opening/closing brackets match
|
||||
([b9bdbe61](https://github.com/angular/angular.js/commit/b9bdbe615cc4070d2233ff06830a4c6fb1217cda),
|
||||
[#10349](https://github.com/angular/angular.js/issues/10349), [#10357](https://github.com/angular/angular.js/issues/10357))
|
||||
- don't convert FormData objects to JSON
|
||||
([40258838](https://github.com/angular/angular.js/commit/40258838031604feecb862afdc6f1f503d80ce4a),
|
||||
[#10373](https://github.com/angular/angular.js/issues/10373))
|
||||
- **$parse:** a chain of field accessors should use a single `getterFn`
|
||||
([c90ad968](https://github.com/angular/angular.js/commit/c90ad96808be350526516626205c3a7d1da79024))
|
||||
- **ngRepeat:** allow extra whitespaces in `(key,value)` part of micro-syntax
|
||||
([ef640cbc](https://github.com/angular/angular.js/commit/ef640cbc2af5794c987e75472c12e63a59590044),
|
||||
[#6827](https://github.com/angular/angular.js/issues/6827), [#6833](https://github.com/angular/angular.js/issues/6833))
|
||||
- **orderBy:** do not try to call `valueOf`/`toString` on `null`
|
||||
([a097aa95](https://github.com/angular/angular.js/commit/a097aa95b7c78beab6d1b7d521c25f7d9d7843d9),
|
||||
[#10385](https://github.com/angular/angular.js/issues/10385), [#10386](https://github.com/angular/angular.js/issues/10386))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** add support for `ng-attr` with camelCased attributes
|
||||
([d8e37078](https://github.com/angular/angular.js/commit/d8e37078600089839f82f0e84022f1087e1fd3f2),
|
||||
[#9845](https://github.com/angular/angular.js/issues/9845), [#10194](https://github.com/angular/angular.js/issues/10194))
|
||||
- **$http:** pass response status code to data transform functions
|
||||
([1b740974](https://github.com/angular/angular.js/commit/1b740974f5eb373bed04071d51f908ced7c5a8e5),
|
||||
[#10324](https://github.com/angular/angular.js/issues/10324), [#6734](https://github.com/angular/angular.js/issues/6734), [#10440](https://github.com/angular/angular.js/issues/10440))
|
||||
- **$rootScope:** allow passing `locals` argument to `$evalAsync`
|
||||
([9b96cea4](https://github.com/angular/angular.js/commit/9b96cea462676d123e1b2dd852aedbe3da8fa4a0),
|
||||
[#10390](https://github.com/angular/angular.js/issues/10390))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** only re-`$interpolate` attribute values at link time if changed since compile
|
||||
([9ae0c01c](https://github.com/angular/angular.js/commit/9ae0c01c2bcaff2f3906eec574f9c6ed8abde14a))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **orderBy:** due to [a097aa95](https://github.com/angular/angular.js/commit/a097aa95b7c78beab6d1b7d521c25f7d9d7843d9),
|
||||
|
||||
Previously, if either value being compared in the orderBy comparator was null or undefined, the
|
||||
order would, incorrectly, not change. Now, this order behaves more like Array.prototype.sort, which
|
||||
by default pushes `null` behind objects, due to `n` occurring after `[` (the first characters of their
|
||||
stringified forms) in ASCII / Unicode. If `toString` is customized, or does not exist, the
|
||||
behaviour is undefined.
|
||||
|
||||
|
||||
|
||||
<a name="1.2.28"></a>
|
||||
# 1.2.28 finnish-disembarkation (2014-12-15)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$route:** fix redirection with optional/eager params
|
||||
([1b9e408d](https://github.com/angular/angular.js/commit/1b9e408ddbe48a6d3db27f501515d6efad01f42d),
|
||||
[#9742](https://github.com/angular/angular.js/issues/9742), [#10202](https://github.com/angular/angular.js/issues/10202))
|
||||
- **linky:** encode double quotes when serializing email addresses
|
||||
([929dd15b](https://github.com/angular/angular.js/commit/929dd15b9b65034350f18abe6c56a8d956f4b978),
|
||||
[#8945](https://github.com/angular/angular.js/issues/8945), [#8964](https://github.com/angular/angular.js/issues/8964), [#5946](https://github.com/angular/angular.js/issues/5946), [#10090](https://github.com/angular/angular.js/issues/10090), [#9256](https://github.com/angular/angular.js/issues/9256))
|
||||
|
||||
|
||||
|
||||
<a name="1.3.6"></a>
|
||||
# 1.3.6 robofunky-danceblaster (2014-12-08)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$browser:** prevent infinite digests when clearing the hash of a url
|
||||
([10ac5948](https://github.com/angular/angular.js/commit/10ac5948097e2c8eaead238603d29ee580dc8273),
|
||||
[#9629](https://github.com/angular/angular.js/issues/9629), [#9635](https://github.com/angular/angular.js/issues/9635), [#10228](https://github.com/angular/angular.js/issues/10228), [#10308](https://github.com/angular/angular.js/issues/10308))
|
||||
- **$http:** preserve config object when resolving from cache
|
||||
([facfec98](https://github.com/angular/angular.js/commit/facfec98412c0bb8678d578bade05ffef06a9e84),
|
||||
[#9004](https://github.com/angular/angular.js/issues/9004), [#9030](https://github.com/angular/angular.js/issues/9030))
|
||||
- **$location:**
|
||||
- allow hash fragments with hashPrefix in hash-bang location urls
|
||||
([2dc34a96](https://github.com/angular/angular.js/commit/2dc34a969956eea680be4c8d9f800556d110996a),
|
||||
[#9629](https://github.com/angular/angular.js/issues/9629), [#9635](https://github.com/angular/angular.js/issues/9635), [#10228](https://github.com/angular/angular.js/issues/10228), [#10308](https://github.com/angular/angular.js/issues/10308))
|
||||
- strip off empty hash segments when comparing
|
||||
([e93710fe](https://github.com/angular/angular.js/commit/e93710fe0e4fb05ceee59a04f290692a5bec5d20),
|
||||
[#9635](https://github.com/angular/angular.js/issues/9635))
|
||||
- **$parse:**
|
||||
- fix operators associativity
|
||||
([ed1243ff](https://github.com/angular/angular.js/commit/ed1243ffc7c2cb4bd5b4dece597597db8eb08e34))
|
||||
- follow JavaScript context for unbound functions
|
||||
([429938da](https://github.com/angular/angular.js/commit/429938da1f45b8a649b8c77762fb0ae59b6d0cea))
|
||||
- **filterFilter:**
|
||||
- don't match primitive sub-expressions against any prop
|
||||
([a75537d4](https://github.com/angular/angular.js/commit/a75537d461c92e3455e372ff5005bf0cad2d2e95))
|
||||
- ignore function properties and account for inherited properties
|
||||
([5ced914c](https://github.com/angular/angular.js/commit/5ced914cc8625008e6249d5ac5942d5822287cc0),
|
||||
[#9984](https://github.com/angular/angular.js/issues/9984))
|
||||
- correctly handle deep expression objects
|
||||
([f7cf8460](https://github.com/angular/angular.js/commit/f7cf846045b1e2fb39c62e304c61b44d5c805e31),
|
||||
[#7323](https://github.com/angular/angular.js/issues/7323), [#9698](https://github.com/angular/angular.js/issues/9698), [#9757](https://github.com/angular/angular.js/issues/9757))
|
||||
- **inputs:** ignoring input events in IE caused by placeholder changes or focus/blur on inputs with placeholders
|
||||
([55d9db56](https://github.com/angular/angular.js/commit/55d9db56a6f7d29b16f8393612648080c6d535d6),
|
||||
[#9265](https://github.com/angular/angular.js/issues/9265))
|
||||
- **linky:** make urls starting with www. links, like markdown
|
||||
([915a891a](https://github.com/angular/angular.js/commit/915a891ad4cdcaa5e47e976db8f4d402d230be77),
|
||||
[#10290](https://github.com/angular/angular.js/issues/10290))
|
||||
- **ngAnimate:** do not use jQuery class API
|
||||
([40a537c2](https://github.com/angular/angular.js/commit/40a537c25f70ad556a41bb2d00ea3e257410e9af),
|
||||
[#10024](https://github.com/angular/angular.js/issues/10024), [#10329](https://github.com/angular/angular.js/issues/10329))
|
||||
- **ngMock:** allow numeric timeouts in $httpBackend mock
|
||||
([acb066e8](https://github.com/angular/angular.js/commit/acb066e84a10483e1025eed295352b66747dbb8a),
|
||||
[#4891](https://github.com/angular/angular.js/issues/4891))
|
||||
- **ngModel:**
|
||||
- always use the most recent viewValue for validation
|
||||
([2d6a0a1d](https://github.com/angular/angular.js/commit/2d6a0a1dc1e7125cab2e30244e35e97e11802843),
|
||||
[#10126](https://github.com/angular/angular.js/issues/10126), [#10299](https://github.com/angular/angular.js/issues/10299))
|
||||
- fixing many keys incorrectly marking inputs as dirty
|
||||
([d21dff21](https://github.com/angular/angular.js/commit/d21dff21ed8beb015ad911f11d57cceb56fc439f))
|
||||
- **ngSanitize:** exclude smart quotes at the end of the link
|
||||
([7c6be43e](https://github.com/angular/angular.js/commit/7c6be43e83590798cffef63d076fb79d5296fba2),
|
||||
[#7307](https://github.com/angular/angular.js/issues/7307))
|
||||
- **numberFilter:** numbers rounding to zero shouldn't be negative
|
||||
([96c61fe7](https://github.com/angular/angular.js/commit/96c61fe756d7d3db011818bf0925e3d86ffff8ce),
|
||||
[#10278](https://github.com/angular/angular.js/issues/10278))
|
||||
- **orderBy:**
|
||||
- make object-to-primtiive behaviour work for objects with null prototype
|
||||
([3aa57528](https://github.com/angular/angular.js/commit/3aa5752894419b4638d5c934879258fa6a1c0d07))
|
||||
- maintain order in array of objects when predicate is not provided
|
||||
([8bfeddb5](https://github.com/angular/angular.js/commit/8bfeddb5d671017f4a21b8b46334ac816710b143),
|
||||
[#9566](https://github.com/angular/angular.js/issues/9566), [#9747](https://github.com/angular/angular.js/issues/9747), [#10311](https://github.com/angular/angular.js/issues/10311))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$$jqLite:** export jqLite as a private service
|
||||
([f2e7f875](https://github.com/angular/angular.js/commit/f2e7f875e2ad4b271c4e72ebd3860f905132eed9))
|
||||
- **$injector:** print caller name in "unknown provider" errors (when available)
|
||||
([013b522c](https://github.com/angular/angular.js/commit/013b522c9e690665aecb0e0f656e4557a673ec09),
|
||||
[#8135](https://github.com/angular/angular.js/issues/8135), [#9721](https://github.com/angular/angular.js/issues/9721))
|
||||
- **jsonFilter:** add optional arg to define custom indentation
|
||||
([1191edba](https://github.com/angular/angular.js/commit/1191edba4eaa15f675fa4ed047949a150843971b),
|
||||
[#9771](https://github.com/angular/angular.js/issues/9771))
|
||||
- **ngAria:** bind keypress on ng-click w/ option
|
||||
([5481e2cf](https://github.com/angular/angular.js/commit/5481e2cfcd4d136a1c7f45cd4ce0fa1a8a15074d),
|
||||
[#10288](https://github.com/angular/angular.js/issues/10288))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$location:** due to [2dc34a96](https://github.com/angular/angular.js/commit/2dc34a969956eea680be4c8d9f800556d110996a),
|
||||
|
||||
|
||||
We no longer throw an `ihshprfx` error if the URL after the base path
|
||||
contains only a hash fragment. Previously, if the base URL was `http://abc.com/base/`
|
||||
and the hashPrefix is `!` then trying to parse `http://abc.com/base/#some-fragment`
|
||||
would have thrown an error. Now we simply assume it is a normal fragment and
|
||||
that the path is empty, resulting `$location.absUrl() === "http://abc.com/base/#!/#some-fragment"`.
|
||||
|
||||
This should not break any applications, but you can no longer rely on receiving the
|
||||
`ihshprfx` error for paths that have the syntax above. It is actually more similar
|
||||
to what currently happens for invalid extra paths anyway: If the base URL
|
||||
and hashPrfix are set up as above, then `http://abc.com/base/other/path` does not
|
||||
throw an error but just ignores the extra path: `http://abc.com/base`.
|
||||
|
||||
|
||||
- **filterFilter:** due to [a75537d4](https://github.com/angular/angular.js/commit/a75537d461c92e3455e372ff5005bf0cad2d2e95),
|
||||
|
||||
Named properties in the expression object will only match against properties on the **same level**.
|
||||
Previously, named string properties would match against properties on the same level **or deeper**.
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {level1: 'test'}); // arr.length -> 1
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {level1: 'test'}); // arr.length -> 0
|
||||
```
|
||||
|
||||
In order to match deeper nested properties, you have to either match the depth level of the
|
||||
property or use the special `$` key (which still matches properties on the same level
|
||||
**or deeper**). E.g.:
|
||||
|
||||
```js
|
||||
// Both examples below have `arr.length === 1`
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {level1: {level2: 'test'}});
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {$: 'test'});
|
||||
```
|
||||
|
||||
|
||||
<a name="1.3.5"></a>
|
||||
# 1.3.5 cybernetic-mercantilism (2014-12-01)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$templateRequest:** propagate rejection reason when ignoreRequestError flag is set
|
||||
([f6458826](https://github.com/angular/angular.js/commit/f6458826ac974914597a10b0ffdeee3c5d2c62ef),
|
||||
[#10266](https://github.com/angular/angular.js/issues/10266))
|
||||
- **$httpBackend:** allow canceling request with falsy timeoutId
|
||||
([719d5c5f](https://github.com/angular/angular.js/commit/719d5c5fa59ae1617691a0dca02da861fcf5f933),
|
||||
[#10177](https://github.com/angular/angular.js/issues/10177))
|
||||
- **linky:** encode all double quotes when serializing email addresses
|
||||
([2ec8d1ff](https://github.com/angular/angular.js/commit/2ec8d1ffc04e06a39cb1b74a8d675da38e0a1c6b),
|
||||
[#10090](https://github.com/angular/angular.js/issues/10090))
|
||||
- **ngMock:**
|
||||
- annotate $RootScopeDecorator
|
||||
([9a83f9d2](https://github.com/angular/angular.js/commit/9a83f9d2fabe0a259c283b7f7cd935e4b36e2b5d),
|
||||
[#10273](https://github.com/angular/angular.js/issues/10273), [#10275](https://github.com/angular/angular.js/issues/10275), [#10277](https://github.com/angular/angular.js/issues/10277))
|
||||
- respond did not always take a statusText argument
|
||||
([08cd5c19](https://github.com/angular/angular.js/commit/08cd5c19c7a5116e7e74691391fc5e28bfae4521),
|
||||
[#8270](https://github.com/angular/angular.js/issues/8270))
|
||||
- **select:**
|
||||
- use strict compare when removing option from ctrl
|
||||
([9fa73cb4](https://github.com/angular/angular.js/commit/9fa73cb4e7190b4d00b65f2f8f9f7d37607308ba),
|
||||
[#9714](https://github.com/angular/angular.js/issues/9714), [#10115](https://github.com/angular/angular.js/issues/10115), [#10203](https://github.com/angular/angular.js/issues/10203))
|
||||
- fix several issues when moving options between groups
|
||||
([30694c80](https://github.com/angular/angular.js/commit/30694c802763d46d6787f7298f47dfef53ed4229),
|
||||
[#10166](https://github.com/angular/angular.js/issues/10166))
|
||||
|
||||
|
||||
<a name="1.3.4"></a>
|
||||
# 1.3.4 highfalutin-petroglyph (2014-11-24)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$browser:** allow chaining url() calls in setter mode
|
||||
([764fa869](https://github.com/angular/angular.js/commit/764fa869dd8809d494924c23f30ddaa4cac84249),
|
||||
[#10157](https://github.com/angular/angular.js/issues/10157))
|
||||
- **$http:** return empty headers, ignore properties in Object prototype
|
||||
([637c020f](https://github.com/angular/angular.js/commit/637c020f828a7ceeaacf83bb1a54ed3092e6c273),
|
||||
[#7779](https://github.com/angular/angular.js/issues/7779), [#10113](https://github.com/angular/angular.js/issues/10113), [#10091](https://github.com/angular/angular.js/issues/10091))
|
||||
- **$locale:** Allow currency filter to fall back to maxFrac from locale
|
||||
([6dbd606a](https://github.com/angular/angular.js/commit/6dbd606ad7b708d5886c0e7ffee20ae8f8719711),
|
||||
[#10179](https://github.com/angular/angular.js/issues/10179))
|
||||
- **$location:** allow empty string URLs to reset path, search, and hash
|
||||
([7812dfce](https://github.com/angular/angular.js/commit/7812dfcee8ab98cbf38261f9948d9541656bf554),
|
||||
[#10063](https://github.com/angular/angular.js/issues/10063), [#10064](https://github.com/angular/angular.js/issues/10064))
|
||||
- **$route:** fix redirection with optional/eager params
|
||||
([891acf4c](https://github.com/angular/angular.js/commit/891acf4c201823fd2c925ee321c70d06737d5944),
|
||||
[#9819](https://github.com/angular/angular.js/issues/9819), [#9827](https://github.com/angular/angular.js/issues/9827))
|
||||
- **Angular:** properly get node name for svg element wrapper
|
||||
([09a98323](https://github.com/angular/angular.js/commit/09a9832358960c98392c9df1a9fd9592f59bc844),
|
||||
[#10078](https://github.com/angular/angular.js/issues/10078), [#10172](https://github.com/angular/angular.js/issues/10172))
|
||||
- **NgModelController:** typo $rawModelValue -> $$rawModelValue
|
||||
([4f4ff5f3](https://github.com/angular/angular.js/commit/4f4ff5f31b82c6f7be409ea4edbad4c2913ac1f1))
|
||||
- **input:**
|
||||
- set ngTrueValue on required checkbox
|
||||
([8692f87a](https://github.com/angular/angular.js/commit/8692f87a4689fa0dd3640f4dcab5c6b6f960489b),
|
||||
[#5164](https://github.com/angular/angular.js/issues/5164))
|
||||
- call $setTouched in blur asynchronously if necessary
|
||||
([eab27187](https://github.com/angular/angular.js/commit/eab271876cb87c1f5f6c6f29e814fb8fecad87ff),
|
||||
[#8762](https://github.com/angular/angular.js/issues/8762), [#9808](https://github.com/angular/angular.js/issues/9808), [#10014](https://github.com/angular/angular.js/issues/10014))
|
||||
- **input[date]:** do not use `$isEmpty` to check the model validity
|
||||
([40406e2f](https://github.com/angular/angular.js/commit/40406e2f22713efbd37ef3eff408339727cb62d9))
|
||||
- **linky:** encode double quotes when serializing email addresses
|
||||
([8ee8ffeb](https://github.com/angular/angular.js/commit/8ee8ffeba0a5a133fa792745c1019d294ecfcef3),
|
||||
[#8945](https://github.com/angular/angular.js/issues/8945), [#8964](https://github.com/angular/angular.js/issues/8964), [#5946](https://github.com/angular/angular.js/issues/5946), [#10090](https://github.com/angular/angular.js/issues/10090), [#9256](https://github.com/angular/angular.js/issues/9256))
|
||||
- **ngMaxlength:** ignore maxlength when not set to a non-negative integer
|
||||
([92f87b11](https://github.com/angular/angular.js/commit/92f87b114242b01876e1dc5c6fddd061352ecb2c),
|
||||
[#9874](https://github.com/angular/angular.js/issues/9874))
|
||||
- **ngModel:** don't run parsers when executing $validate
|
||||
([e3764e30](https://github.com/angular/angular.js/commit/e3764e30a301ec6136c8e6b5493d39feb3cd1ecc))
|
||||
- **ngModelOptions:** preserve context of getter/setters
|
||||
([bb4d3b73](https://github.com/angular/angular.js/commit/bb4d3b73a1ccf3dee55b0c25baf031bae5cbb676),
|
||||
[#9394](https://github.com/angular/angular.js/issues/9394), [#9865](https://github.com/angular/angular.js/issues/9865))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngMaxlength:** add support for disabling max length limit
|
||||
([5c1fdff6](https://github.com/angular/angular.js/commit/5c1fdff691b9367d73f72f6a0298cb6a6e259f35),
|
||||
[#9995](https://github.com/angular/angular.js/issues/9995))
|
||||
- **ngModelController:** add $setDirty method
|
||||
([e8941c0f](https://github.com/angular/angular.js/commit/e8941c0fe5217d2e705bad8253dc0162aff4c709),
|
||||
[#10038](https://github.com/angular/angular.js/issues/10038), [#10049](https://github.com/angular/angular.js/issues/10049))
|
||||
- **ngPluralize:** add support for `count` to be a one-time expression
|
||||
([2b41a586](https://github.com/angular/angular.js/commit/2b41a5868aee79e3872ad92db66e30959207d98e),
|
||||
[#10004](https://github.com/angular/angular.js/issues/10004))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- use Object.create instead of creating temporary constructors
|
||||
([bf6a79c3](https://github.com/angular/angular.js/commit/bf6a79c3484f474c300b5442ae73483030ef5782),
|
||||
[#10058](https://github.com/angular/angular.js/issues/10058))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **ngModelOptions:** due to [bb4d3b73](https://github.com/angular/angular.js/commit/bb4d3b73a1ccf3dee55b0c25baf031bae5cbb676),
|
||||
previously, ngModel invoked getter/setters in the global context.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
<input ng-model="model.value" ng-model-options="{ getterSetter: true }">
|
||||
```
|
||||
|
||||
would previously invoke `model.value()` in the global context.
|
||||
|
||||
Now, ngModel invokes `value` with `model` as the context.
|
||||
|
||||
It's unlikely that real apps relied on this behavior. If they did they can use `.bind` to explicilty
|
||||
bind a getter/getter to the global context, or just reference globals normally without `this`.
|
||||
|
||||
|
||||
<a name="1.2.27"></a>
|
||||
# 1.2.27 prime-factorization (2014-11-20)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** clear the GCS cache even when no animation is detected
|
||||
([f619d032](https://github.com/angular/angular.js/commit/f619d032c932752313c646b5295bad8a68ef3871),
|
||||
[#8813](https://github.com/angular/angular.js/issues/8813))
|
||||
- **$browser:**
|
||||
- Cache `location.href` only during page reload phase
|
||||
([434d7a09](https://github.com/angular/angular.js/commit/434d7a09039151c1e627ac156213905d06b7df10),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235), [#9470](https://github.com/angular/angular.js/issues/9470))
|
||||
- don’t use history api when only the hash changes
|
||||
([a6e6438d](https://github.com/angular/angular.js/commit/a6e6438dae1ed92b29608d0b8830b0a7fbb624ef),
|
||||
[#9423](https://github.com/angular/angular.js/issues/9423), [#9424](https://github.com/angular/angular.js/issues/9424))
|
||||
- handle async href on url change in <=IE9
|
||||
([fe7d9ded](https://github.com/angular/angular.js/commit/fe7d9dedaa5ec3b3f56d9eb9c513cf99e40121ce),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235))
|
||||
- **$http:** add missing shortcut methods and missing docs
|
||||
([ec4fe1bc](https://github.com/angular/angular.js/commit/ec4fe1bcab6f981103a10f860a3a00122aa78607),
|
||||
[#9180](https://github.com/angular/angular.js/issues/9180), [#9321](https://github.com/angular/angular.js/issues/9321))
|
||||
- **$location:**
|
||||
- revert erroneous logic and backport refactorings from master
|
||||
([1ee9b4ef](https://github.com/angular/angular.js/commit/1ee9b4ef5e4a795061d3aa19adefdeb7e0209eeb),
|
||||
[#8492](https://github.com/angular/angular.js/issues/8492))
|
||||
- allow 0 in path() and hash()
|
||||
([f807d7ab](https://github.com/angular/angular.js/commit/f807d7ab4ebd18899154528ea9ed50d5bc25c57a))
|
||||
- **$parse:** add quick check for Function constructor in fast path
|
||||
([756640f5](https://github.com/angular/angular.js/commit/756640f5aa8f3fd0084bff50534e23976a6fff00))
|
||||
- **$parse, events:** prevent accidental misuse of properties on $event
|
||||
([4d0614fd](https://github.com/angular/angular.js/commit/4d0614fd0da12c5783dfb4956c330edac87e62fe),
|
||||
[#9969](https://github.com/angular/angular.js/issues/9969))
|
||||
- **ngMock:** $httpBackend should match data containing Date objects correctly
|
||||
([1426b029](https://github.com/angular/angular.js/commit/1426b02980badfd322eb960d71bfb1a14d657847),
|
||||
[#5127](https://github.com/angular/angular.js/issues/5127))
|
||||
- **orderBy:** sort by identity if no predicate is given
|
||||
([45b896a1](https://github.com/angular/angular.js/commit/45b896a16abbcbfcdfb9a95c2d10c76a805b57cc),
|
||||
[#5847](https://github.com/angular/angular.js/issues/5847), [#4579](https://github.com/angular/angular.js/issues/4579), [#9403](https://github.com/angular/angular.js/issues/9403))
|
||||
- **select:** ensure the label attribute is updated in Internet Explorer
|
||||
([16833d0f](https://github.com/angular/angular.js/commit/16833d0fb6585117e9978d1accc3ade83e22e797),
|
||||
[#9621](https://github.com/angular/angular.js/issues/9621), [#10042](https://github.com/angular/angular.js/issues/10042))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **orderBy:** copy array with slice instead of for loop
|
||||
([409bcb38](https://github.com/angular/angular.js/commit/409bcb3810a1622178268f7ff7f4130887a1a3dc),
|
||||
[#9942](https://github.com/angular/angular.js/issues/9942))
|
||||
|
||||
|
||||
<a name="1.3.3"></a>
|
||||
# 1.3.3 undersea-arithmetic (2014-11-17)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$http:** don't parse single space responses as JSON
|
||||
([6f19a6fd](https://github.com/angular/angular.js/commit/6f19a6fd33ab72d3908e3418fba47ee8e1598fa6),
|
||||
[#9907](https://github.com/angular/angular.js/issues/9907))
|
||||
- **minErr:** stringify non-JSON compatible objects in error messages
|
||||
([cf43ccdf](https://github.com/angular/angular.js/commit/cf43ccdf9b8665a2fd5d6aa52f80cb2d7c9bb7e2),
|
||||
[#10085](https://github.com/angular/angular.js/issues/10085))
|
||||
- **$rootScope:** handle cyclic references in scopes when creating error messages
|
||||
([e80053d9](https://github.com/angular/angular.js/commit/e80053d91fd7c722e092a23d326384de2e552eb6),
|
||||
[#10085](https://github.com/angular/angular.js/issues/10085))
|
||||
- **ngRepeat:** support cyclic object references in error messages
|
||||
([fa12c3c8](https://github.com/angular/angular.js/commit/fa12c3c86af7965d1b9d9a5dd3434755e9e04635),
|
||||
[#9838](https://github.com/angular/angular.js/issues/9838), [#10065](https://github.com/angular/angular.js/issues/10065), [#10085](https://github.com/angular/angular.js/issues/10085))
|
||||
- **ngMock:** call $interval callbacks even when invokeApply is false
|
||||
([d81ff888](https://github.com/angular/angular.js/commit/d81ff8885b77f70c6417d7be3124d86d07447375),
|
||||
[#10032](https://github.com/angular/angular.js/issues/10032))
|
||||
- **ngPattern:** match behaviour of native HTML pattern attribute
|
||||
([85eb9660](https://github.com/angular/angular.js/commit/85eb9660ef67c24d5104a6a1921bedad0bd1b57e),
|
||||
[#9881](https://github.com/angular/angular.js/issues/9881), [#9888](https://github.com/angular/angular.js/issues/9888))
|
||||
- **select:** ensure the label attribute is updated in Internet Explorer
|
||||
([6604c236](https://github.com/angular/angular.js/commit/6604c2361427fba8c43a39dc2e92197390dfbdbe),
|
||||
[#9621](https://github.com/angular/angular.js/issues/9621), [#10042](https://github.com/angular/angular.js/issues/10042))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$location:** allow to location to be changed during $locationChangeStart
|
||||
([a9352c19](https://github.com/angular/angular.js/commit/a9352c19ce33f0393d6581547c7ea8dfc2a8b78f),
|
||||
[#9607](https://github.com/angular/angular.js/issues/9607), [#9678](https://github.com/angular/angular.js/issues/9678))
|
||||
- **$routeProvider:** allow setting caseInsensitiveMatch on the provider
|
||||
([0db573b7](https://github.com/angular/angular.js/commit/0db573b7493f76abd94ff65ce660017d617e865b),
|
||||
[#6477](https://github.com/angular/angular.js/issues/6477), [#9873](https://github.com/angular/angular.js/issues/9873))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **orderBy:** copy array with slice instead of for loop
|
||||
([8eabc546](https://github.com/angular/angular.js/commit/8eabc5463c795d87f37e5a9eacbbb14435024061),
|
||||
[#9942](https://github.com/angular/angular.js/issues/9942))
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$parse:** due to [fbad2805](https://github.com/angular/angular.js/commit/fbad2805703569058a4a860747b0e2d8aee36bdf),
|
||||
you can't use characters that have special meaning in AngularJS expressions (ex.: `.` or `-`)
|
||||
as part of filter's name. Before this commit custom filters could contain special characters
|
||||
(like a dot) in their name but this wasn't intentional.
|
||||
|
||||
<a name="1.3.2"></a>
|
||||
# 1.3.2 cardiovasculatory-magnification (2014-11-07)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** do not rebind parent bound transclude functions
|
||||
([841c0907](https://github.com/angular/angular.js/commit/841c0907556f525dbc4223609d808319fe0dd7e2),
|
||||
[#9413](https://github.com/angular/angular.js/issues/9413))
|
||||
- **$parse:**
|
||||
- stateful interceptors override an `undefined` expression
|
||||
([ed99821e](https://github.com/angular/angular.js/commit/ed99821e4dc621864f7e2d9a6b5305fca27fb7fa),
|
||||
[#9821](https://github.com/angular/angular.js/issues/9821), [#9825](https://github.com/angular/angular.js/issues/9825))
|
||||
- add quick check for Function constructor in fast path
|
||||
([e676d642](https://github.com/angular/angular.js/commit/e676d642f5feb8d3ba88944634afb479ba525c36))
|
||||
- **$parse, events:** prevent accidental misuse of properties on $event
|
||||
([e057a9aa](https://github.com/angular/angular.js/commit/e057a9aa398ead209bd6bbf76e22d2d5562904fb))
|
||||
- **ngRoute:** allow proto inherited properties in route params object
|
||||
([b4770582](https://github.com/angular/angular.js/commit/b4770582f84f26c8ff7f2320a36a6b0ceff6e6cc),
|
||||
[#8181](https://github.com/angular/angular.js/issues/8181), [#9731](https://github.com/angular/angular.js/issues/9731))
|
||||
- **select:** use strict comparison for isSelected with selectAs
|
||||
([9e305948](https://github.com/angular/angular.js/commit/9e305948e4965fb86b0c79985dc6e8c59a9c66af),
|
||||
[#9639](https://github.com/angular/angular.js/issues/9639), [#9949](https://github.com/angular/angular.js/issues/9949))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngAria:** announce ngMessages with aria-live
|
||||
([187e4318](https://github.com/angular/angular.js/commit/187e43185dfb1bce6a318d95958c73cfb789d33c),
|
||||
[#9834](https://github.com/angular/angular.js/issues/9834))
|
||||
- **ngMock:** decorator that adds Scope#$countChildScopes and Scope#$countWatchers
|
||||
([74981c9f](https://github.com/angular/angular.js/commit/74981c9f208b3617cbf00beafd61138d25c5d546),
|
||||
[#9926](https://github.com/angular/angular.js/issues/9926), [#9871](https://github.com/angular/angular.js/issues/9871))
|
||||
|
||||
|
||||
## Security Note
|
||||
|
||||
This release also contains security fixes for expression sandbox bypasses.
|
||||
|
||||
These issues affect only applications with known server-side XSS holes that are also using [CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP) to secure their client-side code. If your application falls into this rare category, we recommend updating your version of Angular.
|
||||
|
||||
We'd like to thank security researches [Sebastian Lekies](https://twitter.com/sebastianlekies), [Jann Horn](http://thejh.net/), and [Gábor Molnár](https://twitter.com/molnar_g) for reporting these issues to us.
|
||||
|
||||
We also added a documentation page focused on security, which contains some of the best practices, DOs and DON'Ts. Please check out [https://docs.angularjs.org/guide/security](https://docs.angularjs.org/guide/security).
|
||||
|
||||
|
||||
|
||||
<a name="1.3.1"></a>
|
||||
# 1.3.1 spectral-lobster (2014-10-31)
|
||||
@@ -2532,6 +3023,16 @@ jQuery. We don't expect that app code actually depends on this accidental featur
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [2ee29c5d](https://github.com/angular/angular.js/commit/2ee29c5da81ffacdc1cabb438f5d125d5e116cb9),
|
||||
|
||||
The isolated scope of a component directive no longer leaks into the template
|
||||
that contains the instance of the directive. This means that you can no longer
|
||||
access the isolated scope from attributes on the element where the isolated
|
||||
directive is defined.
|
||||
|
||||
See https://github.com/angular/angular.js/issues/10236 for an example.
|
||||
|
||||
|
||||
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
|
||||
|
||||
If you expected `$resource` to strip these types of properties before,
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ Before you submit your pull request consider the following guidelines:
|
||||
|
||||
```shell
|
||||
git rebase master -i
|
||||
git push -f
|
||||
git push origin my-fix-branch -f
|
||||
```
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
|
||||
@@ -30,14 +30,6 @@ module.exports = function(grunt) {
|
||||
benchmarksPath: 'benchmarks'
|
||||
}
|
||||
},
|
||||
parallel: {
|
||||
travis: {
|
||||
tasks: [
|
||||
util.parallelTask(['test:unit', 'test:promises-aplus', 'tests:docs'], {stream: true}),
|
||||
util.parallelTask(['test:e2e'])
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
connect: {
|
||||
devserver: {
|
||||
|
||||
Vendored
+2
@@ -5,6 +5,7 @@ var angularFiles = {
|
||||
'src/minErr.js',
|
||||
'src/Angular.js',
|
||||
'src/loader.js',
|
||||
'src/stringify.js',
|
||||
'src/AngularPublic.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
@@ -73,6 +74,7 @@ var angularFiles = {
|
||||
],
|
||||
|
||||
'angularLoader': [
|
||||
'stringify.js',
|
||||
'src/minErr.js',
|
||||
'src/loader.js'
|
||||
],
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<div>ngBind: <input type="radio" ng-model="benchmarkType" value="ngBind"></div>
|
||||
<div>ngBindOnce: <input type="radio" ng-model="benchmarkType" value="ngBindOnce"></div>
|
||||
<div>interpolation: <input type="radio" ng-model="benchmarkType" value="interpolation"></div>
|
||||
<div>attribute interpolation: <input type="radio" ng-model="benchmarkType" value="interpolationAttr"></div>
|
||||
<div>ngBind + fnInvocation: <input type="radio" ng-model="benchmarkType" value="ngBindFn"></div>
|
||||
<div>interpolation + fnInvocation: <input type="radio" ng-model="benchmarkType" value="interpolationFn"></div>
|
||||
<div>ngBind + filter: <input type="radio" ng-model="benchmarkType" value="ngBindFilter"></div>
|
||||
@@ -46,6 +47,12 @@
|
||||
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="interpolationAttr">
|
||||
<h2>attribute interpolation</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row" i="{{column.i}}" j="{{column.j}}">i,j attrs</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="ngBindFn">
|
||||
<h2>bindings with functions</h2>
|
||||
<div ng-repeat="row in data">
|
||||
|
||||
@@ -202,6 +202,7 @@ var generate = function(version, file) {
|
||||
|
||||
// publish for testing
|
||||
exports.parseRawCommit = parseRawCommit;
|
||||
exports.printSection = printSection;
|
||||
|
||||
// hacky start if not run by jasmine :-D
|
||||
if (process.argv.join('').indexOf('jasmine-node') === -1) {
|
||||
|
||||
+62
-1
@@ -1,4 +1,4 @@
|
||||
/* global describe: false, it: false, expect: false */
|
||||
/* global describe: false, beforeEach: false, afterEach: false, it: false, expect: false */
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -44,4 +44,65 @@ describe('changelog.js', function() {
|
||||
expect(msg.breaking).toEqual(' first breaking change\nsomething else\nanother line with more info\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('printSection', function() {
|
||||
var output;
|
||||
var streamMock = {
|
||||
write: function(str) {
|
||||
output += str;
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
output = '';
|
||||
});
|
||||
|
||||
it('should add a new line at the end of each breaking change list item ' +
|
||||
'when there is 1 item per component', function() {
|
||||
var title = 'test';
|
||||
var printCommitLinks = false;
|
||||
|
||||
var section = {
|
||||
module1: [{subject: 'breaking change 1'}],
|
||||
module2: [{subject: 'breaking change 2'}]
|
||||
};
|
||||
var expectedOutput =
|
||||
'\n' + '## test\n\n' +
|
||||
'- **module1:** breaking change 1\n' +
|
||||
'- **module2:** breaking change 2\n' +
|
||||
'\n';
|
||||
|
||||
ch.printSection(streamMock, title, section, printCommitLinks);
|
||||
expect(output).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should add a new line at the end of each breaking change list item ' +
|
||||
'when there are multiple items per component', function() {
|
||||
var title = 'test';
|
||||
var printCommitLinks = false;
|
||||
|
||||
var section = {
|
||||
module1: [
|
||||
{subject: 'breaking change 1.1'},
|
||||
{subject: 'breaking change 1.2'}
|
||||
],
|
||||
module2: [
|
||||
{subject: 'breaking change 2.1'},
|
||||
{subject: 'breaking change 2.2'}
|
||||
]
|
||||
};
|
||||
var expectedOutput =
|
||||
'\n' + '## test\n\n' +
|
||||
'- **module1:**\n' +
|
||||
' - breaking change 1.1\n' +
|
||||
' - breaking change 1.2\n' +
|
||||
'- **module2:**\n' +
|
||||
' - breaking change 2.1\n' +
|
||||
' - breaking change 2.2\n' +
|
||||
'\n';
|
||||
|
||||
ch.printSection(streamMock, title, section, printCommitLinks);
|
||||
expect(output).toBe(expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,6 +128,10 @@ h1,h2,h3,h4,h5,h6 {
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
.nav-index-group .nav-index-listing.current a {
|
||||
color: #B52E31;
|
||||
}
|
||||
|
||||
.nav-breadcrumb {
|
||||
margin:4px 0;
|
||||
padding:0;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -13,6 +13,7 @@ angular.module('DocsController', [])
|
||||
$scope.navClass = function(navItem) {
|
||||
return {
|
||||
active: navItem.href && this.currentPage && this.currentPage.path,
|
||||
current: this.currentPage && this.currentPage.path === navItem.href,
|
||||
'nav-index-section': navItem.type === 'section'
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
angular.module('versions', [])
|
||||
|
||||
.controller('DocsVersionsCtrl', ['$scope', '$location', '$window', 'NG_VERSIONS', function($scope, $location, $window, NG_VERSIONS) {
|
||||
$scope.docs_version = NG_VERSIONS[0];
|
||||
$scope.docs_versions = NG_VERSIONS;
|
||||
|
||||
for(var i=0, minor = NaN; i < NG_VERSIONS.length; i++) {
|
||||
var version = NG_VERSIONS[i];
|
||||
@@ -13,9 +16,8 @@ angular.module('versions', [])
|
||||
minor = version.minor;
|
||||
}
|
||||
|
||||
$scope.docs_versions = NG_VERSIONS;
|
||||
$scope.getGroupName = function(v) {
|
||||
return v.isLatest ? 'Latest' : (v.isStable ? 'Stable' : 'Unstable');
|
||||
return v.isLatest ? 'Latest' : ('v' + v.major + '.' + v.minor + '.x');
|
||||
};
|
||||
|
||||
$scope.jumpToDocsVersion = function(version) {
|
||||
|
||||
@@ -128,7 +128,7 @@ Use ngRoute to enable URL routing to your application. The ngRoute module suppor
|
||||
|
||||
## {@link ngAnimate ngAnimate}
|
||||
|
||||
Use ngAnimate to enable animation features into your application. Various core ng directives will provide
|
||||
Use ngAnimate to enable animation features within your application. Various core ng directives will provide
|
||||
animation hooks into your application when ngAnimate is included. Animations are defined by using CSS transitions/animations
|
||||
or JavaScript callbacks.
|
||||
|
||||
@@ -148,7 +148,7 @@ or JavaScript callbacks.
|
||||
{@link ngAnimate CSS-based animations}
|
||||
</td>
|
||||
<td>
|
||||
Follow ngAnimate’s CSS naming structure to reference CSS transitions / keyframe animations in AngularJS. Once defined the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
Follow ngAnimate’s CSS naming structure to reference CSS transitions / keyframe animations in AngularJS. Once defined, the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -156,11 +156,32 @@ or JavaScript callbacks.
|
||||
{@link ngAnimate JS-based animations}
|
||||
</td>
|
||||
<td>
|
||||
Use {@link angular.Module#animation module.animation()} to register a JavaScript animation. Once registered the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
Use {@link angular.Module#animation module.animation()} to register a JavaScript animation. Once registered, the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## {@link ngAria ngAria}
|
||||
|
||||
Use ngAria to inject common accessibility attributes into directives and improve the experience for users with disabilities.
|
||||
|
||||
<div class="alert alert-info">Include the **angular-aria.js** file and set ngAria as a dependency for this to work in your application.</div>
|
||||
|
||||
<table class="definition-table spaced">
|
||||
<tr>
|
||||
<td>
|
||||
{@link ngAria#service Services}
|
||||
</td>
|
||||
<td>
|
||||
<p>
|
||||
The {@link ngAria.$aria $aria} service contains helper methods for applying ARIA attributes to HTML.
|
||||
<p>
|
||||
<p>
|
||||
{@link ngAria.$ariaProvider $ariaProvider} is used for configuring ARIA attributes.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## {@link ngResource ngResource}
|
||||
|
||||
@@ -252,7 +273,7 @@ Use ngSanitize to securely parse and manipulate HTML data in your application.
|
||||
|
||||
## {@link ngMock ngMock}
|
||||
|
||||
Use ngMock to inject and mock modules, factories, services and providers within your unit tests
|
||||
Use ngMock to inject and mock modules, factories, services and providers within your unit tests.
|
||||
|
||||
<div class="alert alert-info">Include the **angular-mocks.js** file into your test runner for this to work.</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
@ngdoc error
|
||||
@name $http:badreq
|
||||
@fullName Bad Request Configuration
|
||||
@description
|
||||
|
||||
This error occurs when the request configuration parameter passed to the {@link ng.$http `$http`} service is not an object. `$http` expects a single parameter, the request configuration object, but received a parameter that was not an object. The error message should provide additional context such as the actual value of the parameter that was received. If you passed a string parameter, perhaps you meant to call one of the shorthand methods on `$http` such as `$http.get(…)`, etc.
|
||||
|
||||
To resolve this error, make sure you pass a valid request configuration object to `$http`.
|
||||
|
||||
For more information, see the {@link ng.$http `$http`} service API documentation.
|
||||
@@ -61,7 +61,7 @@ Attempting to inject one controller into another will also throw an `Unknown pro
|
||||
|
||||
```
|
||||
angular.module('myModule', [])
|
||||
.controller('MyFirstController', function() { /* ... */ });
|
||||
.controller('MyFirstController', function() { /* ... */ })
|
||||
.controller('MySecondController', ['MyFirstController', function(MyFirstController) {
|
||||
// This controller throws an unknown provider error because
|
||||
// MyFirstController cannot be injected.
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $location:ihshprfx
|
||||
@fullName Missing Hash Prefix
|
||||
@description
|
||||
|
||||
This error occurs when {@link ng.$location $location} service is configured to use a hash prefix but this prefix was not present in a url that the `$location` service was asked to parse.
|
||||
|
||||
For example if you configure `$location` service with prefix `'!'`:
|
||||
```
|
||||
myApp.config(function($locationProvider) {
|
||||
$locationProvider.prefix('!');
|
||||
});
|
||||
```
|
||||
|
||||
If you enter the app at url `http:/myapp.com/#/myView` this error will be throw.
|
||||
|
||||
The correct url for this configuration is `http:/myapp.com/#!/myView` (note the `'!'` after `'#'` symbol).
|
||||
@@ -3,7 +3,7 @@
|
||||
@fullName Response does not match configured parameter
|
||||
@description
|
||||
|
||||
This error occurs when the {@link ngResource.$resource `$resource`} service expects a response that can be deserialized as an array, receives an object, or vice versa.
|
||||
This error occurs when the {@link ngResource.$resource `$resource`} service expects a response that can be deserialized as an array but receives an object, or vice versa.
|
||||
By default, all resource actions expect objects, except `query` which expects arrays.
|
||||
|
||||
To resolve this error, make sure your `$resource` configuration matches the actual format of the data returned from the server.
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
@fullName Bad `hasOwnProperty` Name
|
||||
@description
|
||||
|
||||
Occurs when you try to use the name `hasOwnProperty` in a context where it is not allow.
|
||||
Occurs when you try to use the name `hasOwnProperty` in a context where it is not allowed.
|
||||
Generally, a name cannot be `hasOwnProperty` because it is used, internally, on a object
|
||||
and allowing such a name would break lookups on this object.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
@ngdoc error
|
||||
@name ng:test
|
||||
@fullName Testability Not Found
|
||||
@description
|
||||
|
||||
Angular's testability helper, getTestability, requires a root element to be
|
||||
passed in. This helps differentiate between different Angular apps on the same
|
||||
page. This error is thrown when no injector is found for root element. It is
|
||||
often because the root element is outside of the ng-app.
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
# What does it do?
|
||||
|
||||
The `$location` service parses the URL in the browser address bar (based on the [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL available to
|
||||
your application. Changes to the URL in the address bar are reflected into $location service and
|
||||
changes to $location are reflected into the browser address bar.
|
||||
The `$location` service parses the URL in the browser address bar (based on [`window.location`](https://developer.mozilla.org/en/window.location)) and makes the URL available to
|
||||
your application. Changes to the URL in the address bar are reflected into the `$location` service and
|
||||
changes to `$location` are reflected into the browser address bar.
|
||||
|
||||
**The $location service:**
|
||||
|
||||
@@ -21,7 +21,7 @@ changes to $location are reflected into the browser address bar.
|
||||
- Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
|
||||
|
||||
|
||||
## Comparing $location to window.location
|
||||
## Comparing `$location` to `window.location`
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
@@ -68,7 +68,7 @@ changes to $location are reflected into the browser address bar.
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## When should I use $location?
|
||||
## When should I use `$location`?
|
||||
Any time your application needs to react to a change in the current URL or if you want to change
|
||||
the current URL in the browser.
|
||||
|
||||
@@ -85,7 +85,7 @@ others customizing the configuration can enable new features.
|
||||
Once the `$location` service is instantiated, you can interact with it via jQuery-style getter and
|
||||
setter methods that allow you to get or change the current URL in the browser.
|
||||
|
||||
## $location service configuration
|
||||
## `$location` service configuration
|
||||
|
||||
To configure the `$location` service, retrieve the
|
||||
{@link ng.$locationProvider $locationProvider} and set the parameters as follows:
|
||||
@@ -344,7 +344,7 @@ to anchors on the same page without needing to know on which page the user curre
|
||||
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
|
||||
to entry point of your application (e.g. index.html). Requiring a `<base>` tag is also important for
|
||||
this case, as it allows Angular to differentiate between the part of the url that is the application
|
||||
base and the path that should be handeled by the application.
|
||||
base and the path that should be handled by the application.
|
||||
|
||||
### Sending links among different browsers
|
||||
|
||||
@@ -835,7 +835,7 @@ angular.module('locationExample', [])
|
||||
|
||||
# Related API
|
||||
|
||||
* {@link ng.$location $location API}
|
||||
* {@link ng.$location `$location` API}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ assistive technologies used by persons with disabilities.
|
||||
|
||||
##Including ngAria
|
||||
|
||||
Using ngAria is as simple as requiring the ngAria module in your application. ngAria hooks into
|
||||
Using {@link ngAria ngAria} is as simple as requiring the ngAria module in your application. ngAria hooks into
|
||||
standard AngularJS directives and quietly injects accessibility support into your application
|
||||
at runtime.
|
||||
|
||||
@@ -20,18 +20,27 @@ at runtime.
|
||||
angular.module('myApp', ['ngAria'])...
|
||||
```
|
||||
|
||||
###Using ngAria
|
||||
Most of what ngAria does is only visible "under the hood". To see the module in action, once you've
|
||||
added it as a dependency, you can test a few things:
|
||||
* Using your favorite element inspector, look for ngAria attributes in your own code.
|
||||
* Test using your keyboard to ensure `tabindex` is used correctly.
|
||||
* Fire up a screen reader such as VoiceOver to listen for ARIA support.
|
||||
[Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/)
|
||||
|
||||
##Supported directives
|
||||
Currently, ngAria interfaces with the following directives:
|
||||
|
||||
* <a href="#ngmodel">ngModel</a>
|
||||
* <a href="#ngdisabled">ngDisabled</a>
|
||||
* <a href="#ngshow">ngShow</a>
|
||||
* <a href="#nghide">ngHide</a>
|
||||
* <a href="#ngclick-and-ngdblclick">ngClick</a>
|
||||
* <a href="#ngclick-and-ngdblclick">ngDblClick</a>
|
||||
* {@link guide/accessibility#ngmodel ngModel}
|
||||
* {@link guide/accessibility#ngdisabled ngDisabled}
|
||||
* {@link guide/accessibility#ngshow ngShow}
|
||||
* {@link guide/accessibility#nghide ngHide}
|
||||
* {@link guide/accessibility#ngclick ngClick}
|
||||
* {@link guide/accessibility#ngdblclick ngDblClick}
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
Most of ngAria's heavy lifting happens in the [ngModel](https://docs.angularjs.org/api/ng/directive/ngModel)
|
||||
Most of ngAria's heavy lifting happens in the {@link ngModel ngModel}
|
||||
directive. For elements using ngModel, special attention is paid by ngAria if that element also
|
||||
has a a role or type of `checkbox`, `radio`, `range` or `textbox`.
|
||||
|
||||
@@ -47,15 +56,80 @@ attributes (if they have not been explicitly specified by the developer):
|
||||
|
||||
###Example
|
||||
|
||||
```html
|
||||
<md-checkbox ng-model="val" required>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<md-checkbox ng-model="val" required aria-required="true" tabIndex="0">
|
||||
```
|
||||
<example module="ngAria_ngModelExample" deps="angular-aria.js">
|
||||
<file name="index.html">
|
||||
<style>
|
||||
[role=checkbox] {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
[role=checkbox] .icon:before {
|
||||
content: '\2610';
|
||||
display: inline-block;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
speak: none;
|
||||
}
|
||||
[role=checkbox].active .icon:before {
|
||||
content: '\2611';
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<form ng-controller="formsController">
|
||||
<some-checkbox role="checkbox" ng-model="checked" ng-class="{active: checked}"
|
||||
ng-disabled="isDisabled" ng-click="toggleCheckbox()"
|
||||
aria-label="Custom Checkbox" show-attrs>
|
||||
<span class="icon" aria-hidden="true"></span>
|
||||
Custom Checkbox
|
||||
</some-checkbox>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
var app = angular.module('ngAria_ngModelExample', ['ngAria'])
|
||||
.controller('formsController', function($scope){
|
||||
$scope.checked = false;
|
||||
$scope.toggleCheckbox = function(){
|
||||
$scope.checked = !$scope.checked;
|
||||
}
|
||||
})
|
||||
.directive('someCheckbox', function(){
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, $el, $attrs) {
|
||||
$el.on('keypress', function(event){
|
||||
event.preventDefault();
|
||||
if(event.keyCode === 32 || event.keyCode === 13){
|
||||
$scope.toggleCheckbox();
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.directive('showAttrs', function() {
|
||||
return function($scope, $el, $attrs) {
|
||||
var pre = document.createElement('pre');
|
||||
$el.after(pre);
|
||||
$scope.$watch(function() {
|
||||
var $attrs = {};
|
||||
Array.prototype.slice.call($el[0].attributes, 0).forEach(function(item) {
|
||||
if (item.name !== 'show-$attrs') {
|
||||
$attrs[item.name] = item.value;
|
||||
}
|
||||
});
|
||||
return $attrs;
|
||||
}, function(newAttrs, oldAttrs) {
|
||||
pre.textContent = JSON.stringify(newAttrs, null, 2);
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</file>
|
||||
</example>
|
||||
|
||||
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from
|
||||
the keyboard. It is still up to **you** as a developer to **ensure custom controls will be
|
||||
@@ -106,6 +180,24 @@ screen reader users won't accidentally focus on "mystery elements". Managing tab
|
||||
child control can be complex and affect performance, so it’s best to just stick with the default
|
||||
`display: none` CSS. See the [fourth rule of ARIA use](http://www.w3.org/TR/aria-in-html/#fourth-rule-of-aria-use).
|
||||
|
||||
###Example
|
||||
```css
|
||||
.ng-hide {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
}
|
||||
```
|
||||
```html
|
||||
<div ng-show="false" class="ng-hide" aria-hidden="true"></div>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<div ng-show="true" aria-hidden="false"></div>
|
||||
```
|
||||
*Note: Child links, buttons or other interactive controls must also be removed from the tab order.*
|
||||
|
||||
<h2 id="nghide">ngHide</h2>
|
||||
|
||||
>The [ngHide](https://docs.angularjs.org/api/ng/directive/ngHide) directive shows or hides the
|
||||
@@ -114,28 +206,130 @@ shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
|
||||
|
||||
The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redundant. It toggles
|
||||
`aria-hidden` on the directive when it is hidden or shown, but the content is already hidden with
|
||||
`display: none`. See explanation for <a href="#ngshow">ngShow</a> when overriding the default CSS.
|
||||
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
|
||||
|
||||
<h2 id="ngclick">ngClick and ngDblClick</h2>
|
||||
If `ngClick` or `ngDblClick` is encountered, ngAria will add `tabIndex` if it isn't there already.
|
||||
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as
|
||||
`<div>` or `<taco-button>` to enable keyboard access. I have recommended this also bind
|
||||
`ng-keypress` to be more useful; the conversation is [currently ongoing](https://github.com/angular/angular.js/issues/9254).
|
||||
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex` if it isn't there already.
|
||||
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as `div`
|
||||
or `taco-button` to enable keyboard access. Conversation is currently ongoing about whether ngAria
|
||||
should also bind `ng-keypress`.
|
||||
|
||||
* * *
|
||||
<h3>Example</h3>
|
||||
```html
|
||||
<div ng-click="toggleMenu()"></div>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
```html
|
||||
<div ng-click="toggleMenu()" tabindex="0"></div>
|
||||
```
|
||||
*Note: ngAria still requires `ng-keypress` to be added manually to non-native controls like divs.*
|
||||
|
||||
<h2 id="ngmessages">ngMessages</h2>
|
||||
|
||||
The new ngMessages module makes it easy to display form validation or other messages with priority
|
||||
sequencing and animation. To expose these visual messages to screen readers,
|
||||
ngAria injects `aria-live="polite"`, causing them to be read aloud any time a message is shown,
|
||||
regardless of the user's focus location.
|
||||
###Example
|
||||
|
||||
```html
|
||||
<div ng-messages="myForm.myName.$error">
|
||||
<div ng-message="required">You did not enter a field</div>
|
||||
<div ng-message="maxlength">Your field is too long</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<div ng-messages="myForm.myName.$error" aria-live="polite">
|
||||
<div ng-message="required">You did not enter a field</div>
|
||||
<div ng-message="maxlength">Your field is too long</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
##Disabling attributes
|
||||
The attribute magic of ngAria may not work for every scenario. To disable individual attributes,
|
||||
you can use the `{@link ngAria.$ariaProvider#config config}` method:
|
||||
you can use the {@link ngAria.$ariaProvider#config config} method. Just keep in mind this will
|
||||
tell ngAria to ignore the attribute globally.
|
||||
|
||||
```
|
||||
angular.module('myApp', ['ngAria'], function config($ariaProvider) {
|
||||
$ariaProvider.config({
|
||||
tabindex: false
|
||||
<example module="ngAria_ngDisabledExample" deps="angular-aria.js">
|
||||
<file name="index.html">
|
||||
<style>
|
||||
[role=checkbox] {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
[role=checkbox] .icon:before {
|
||||
content: '\2610';
|
||||
display: inline-block;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
speak: none;
|
||||
}
|
||||
[role=checkbox].active .icon:before {
|
||||
content: '\2611';
|
||||
}
|
||||
</style>
|
||||
<form ng-controller="formsController">
|
||||
<div ng-model="someModel" show-attrs>
|
||||
Div with ngModel and aria-invalid disabled
|
||||
</div>
|
||||
<div role="checkbox" ng-model="checked" ng-class="{active: checked}"
|
||||
aria-label="Custom Checkbox" ng-click="toggleCheckbox()" some-checkbox show-attrs>
|
||||
<span class="icon" aria-hidden="true"></span>
|
||||
Custom Checkbox for comparison
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
angular.module('ngAria_ngDisabledExample', ['ngAria'], function config($ariaProvider) {
|
||||
$ariaProvider.config({
|
||||
ariaInvalid: false,
|
||||
tabindex: true
|
||||
});
|
||||
})
|
||||
.controller('formsController', function($scope){
|
||||
$scope.checked = false;
|
||||
$scope.toggleCheckbox = function(){
|
||||
$scope.checked = !$scope.checked;
|
||||
}
|
||||
})
|
||||
.directive('someCheckbox', function(){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $el, $attrs) {
|
||||
$el.on('keypress', function(event){
|
||||
event.preventDefault();
|
||||
if(event.keyCode === 32 || event.keyCode === 13){
|
||||
$scope.toggleCheckbox();
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.directive('showAttrs', function() {
|
||||
return function(scope, el, attrs) {
|
||||
var pre = document.createElement('pre');
|
||||
el.after(pre);
|
||||
scope.$watch(function() {
|
||||
var attrs = {};
|
||||
Array.prototype.slice.call(el[0].attributes, 0).forEach(function(item) {
|
||||
if (item.name !== 'show-attrs') {
|
||||
attrs[item.name] = item.value;
|
||||
}
|
||||
});
|
||||
return attrs;
|
||||
}, function(newAttrs, oldAttrs) {
|
||||
pre.textContent = JSON.stringify(newAttrs, null, 2);
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
* * *
|
||||
</script>
|
||||
</file>
|
||||
</example>
|
||||
|
||||
##Common Accessibility Patterns
|
||||
|
||||
@@ -164,6 +358,7 @@ Accessibility best practices that apply to web apps in general also apply to Ang
|
||||
|
||||
* [Using ARIA in HTML](http://www.w3.org/TR/aria-in-html/)
|
||||
* [AngularJS Accessibility at ngEurope](https://www.youtube.com/watch?v=dmYDggEgU-s&list=UUEGUP3TJJfMsEM_1y8iviSQ)
|
||||
* [Testing with Screen Readers](http://webaim.org/articles/screenreader_testing/)
|
||||
* [Chrome Accessibility Developer Tools](https://chrome.google.com/webstore/detail/accessibility-developer-t/fpkknkljclfencbdbgkenhalefipecmb?hl=en)
|
||||
* [W3C Accessibility Testing](http://www.w3.org/wiki/Accessibility_testing)
|
||||
* [WebAIM](http://webaim.org)
|
||||
|
||||
@@ -200,7 +200,7 @@ code is present, and match the CSS class name on the element, then AngularJS wil
|
||||
## Class and ngClass animation hooks
|
||||
|
||||
AngularJS also pays attention to CSS class changes on elements by triggering the **add** and **remove** hooks.
|
||||
This means that if a CSS class is added to or removed from an element then an animation can be executed in between
|
||||
This means that if a CSS class is added to or removed from an element then an animation can be executed in between,
|
||||
before the CSS class addition or removal is finalized. (Keep in mind that AngularJS will only be
|
||||
able to capture class changes if an **expression** or the **ng-class** directive is used on the element.)
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ This is the sequence that your code should follow:
|
||||
|
||||
## Deferred Bootstrap
|
||||
|
||||
This feature enables tools like [Batarang](github.com/angular/angularjs-batarang) and test runners
|
||||
This feature enables tools like [Batarang](https://github.com/angular/angularjs-batarang) and test runners
|
||||
to hook into angular's bootstrap process and sneak in more modules
|
||||
into the DI registry which can replace or augment DI services for
|
||||
the purpose of instrumentation or mocking out heavy dependencies.
|
||||
|
||||
@@ -332,7 +332,7 @@ The first issue we have to solve is that the dialog box template expects `title`
|
||||
But we would like the template's scope property `title` to be the result of interpolating the
|
||||
`<dialog>` element's `title` attribute (i.e. `"Hello {{username}}"`). Furthermore, the buttons expect
|
||||
the `onOk` and `onCancel` functions to be present in the scope. This limits the usefulness of the
|
||||
widget. To solve the mapping issue we use the `locals` to create local variables which the template
|
||||
widget. To solve the mapping issue we use the `scope` to create local variables which the template
|
||||
expects as follows:
|
||||
|
||||
```js
|
||||
|
||||
@@ -154,7 +154,7 @@ First, there is a new JavaScript file that contains a so-called <a name="control
|
||||
More exactly, the file contains a constructor function that creates the actual controller instance.
|
||||
The purpose of controllers is to expose variables and functionality to expressions and directives.
|
||||
|
||||
Besides the new file that contains the controller code we also added a
|
||||
Besides the new file that contains the controller code we also added an
|
||||
{@link ng.directive:ngController `ng-controller`} directive to the HTML.
|
||||
This directive tells Angular that the new `InvoiceController` is responsible for the element with the directive
|
||||
and all of the element's children.
|
||||
|
||||
@@ -326,7 +326,7 @@ describe('state', function() {
|
||||
expect(childScope.timeOfDay).toBe('morning');
|
||||
expect(childScope.name).toBe('Mattie');
|
||||
expect(grandChildScope.timeOfDay).toBe('evening');
|
||||
expect(grandChildScope.name).toBe('Gingerbreak Baby');
|
||||
expect(grandChildScope.name).toBe('Gingerbread Baby');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@@ -13,21 +13,26 @@ Angular sets these CSS classes. It is up to your application to provide useful s
|
||||
is defined. (see {@link guide/scope scope} guide for more information about scopes)
|
||||
|
||||
* `ng-isolate-scope`
|
||||
- **Usage:** angular applies this class to any element for which a new
|
||||
{@link guide/directive#isolating-the-scope-of-a-directive isolate scope} is defined.
|
||||
- **Usage:** angular applies this class to any element for which a new
|
||||
{@link guide/directive#isolating-the-scope-of-a-directive isolate scope} is defined.
|
||||
|
||||
* `ng-binding`
|
||||
- **Usage:** angular applies this class to any element that is attached to a data binding, via `ng-bind` or
|
||||
`{{}}` curly braces, for example. (see {@link guide/databinding databinding} guide)
|
||||
|
||||
* `ng-invalid`, `ng-valid`
|
||||
- **Usage:** angular applies this class to an input widget element if that element's input does
|
||||
- **Usage:** angular applies this class to a form control widget element if that element's input does
|
||||
not pass validation. (see {@link ng.directive:input input} directive)
|
||||
|
||||
* `ng-pristine`, `ng-dirty`
|
||||
- **Usage:** angular {@link ng.directive:input input} directive applies `ng-pristine` class
|
||||
to a new input widget element which did not have user interaction. Once the user interacts with
|
||||
the input widget the class is changed to `ng-dirty`.
|
||||
- **Usage:** angular {@link ng.directive:ngModel ngModel} directive applies `ng-pristine` class
|
||||
to a new form control widget which did not have user interaction. Once the user interacts with
|
||||
the form control, the class is changed to `ng-dirty`.
|
||||
|
||||
* `ng-touched`, `ng-untouched`
|
||||
- **Usage:** angular {@link ng.directive:ngModel ngModel} directive applies `ng-untouched` class
|
||||
to a new form control widget which has not been blurred. Once the user blurs the form control,
|
||||
the class is changed to `ng-touched`.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
@@ -174,6 +174,15 @@ For example, we could fix the example above by instead writing:
|
||||
</svg>
|
||||
```
|
||||
|
||||
If one wants to modify a camelcased attribute (SVG elements have valid camelcased attributes), such as `viewBox` on the `svg` element, one can use underscores to denote that the attribute to bind to is naturally camelcased.
|
||||
|
||||
For example, to bind to `viewBox`, we can write:
|
||||
|
||||
```html
|
||||
<svg ng-attr-view_box="{{viewBox}}">
|
||||
</svg>
|
||||
```
|
||||
|
||||
|
||||
## Creating Directives
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# E2E Testing
|
||||
|
||||
<div class="alert alert-danger">
|
||||
**Note:** In the past, end to end testing could be done with a deprecated tool called
|
||||
**Note:** In the past, end-to-end testing could be done with a deprecated tool called
|
||||
[Angular Scenario Runner](http://code.angularjs.org/1.2.16/docs/guide/e2e-testing). That tool
|
||||
is now in maintenance mode.
|
||||
</div>
|
||||
@@ -14,7 +14,7 @@ is now in maintenance mode.
|
||||
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
|
||||
between components which can't be captured in a unit test. End to end tests are made to find
|
||||
between components which can't be captured in a unit test. End-to-end tests are made to find
|
||||
these problems.
|
||||
|
||||
We have built [Protractor](https://github.com/angular/protractor), an end
|
||||
@@ -23,7 +23,7 @@ Angular application.
|
||||
|
||||
## Using Protractor
|
||||
|
||||
Protractor is a [Node.js](http://nodejs.org) program, and runs end to end tests that are also
|
||||
Protractor is a [Node.js](http://nodejs.org) program, and runs end-to-end tests that are also
|
||||
written in JavaScript and run with node. Protractor uses [WebDriver](https://code.google.com/p/selenium/wiki/GettingStarted)
|
||||
to control browsers and simulate user actions.
|
||||
|
||||
@@ -77,7 +77,7 @@ filter the list of items.
|
||||
## Example
|
||||
See the [angular-seed](https://github.com/angular/angular-seed) project for more examples, or look
|
||||
at the embedded examples in the Angular documentation (For example, {@link $http $http}
|
||||
has an end to end test in the example under the `protractor.js` tag).
|
||||
has an end-to-end test in the example under the `protractor.js` tag).
|
||||
|
||||
## Caveats
|
||||
|
||||
|
||||
@@ -26,8 +26,16 @@ Angular expressions are like JavaScript expressions with the following differenc
|
||||
* **Forgiving:** In JavaScript, trying to evaluate undefined properties generates `ReferenceError`
|
||||
or `TypeError`. In Angular, expression evaluation is forgiving to `undefined` and `null`.
|
||||
|
||||
* **No Control Flow Statements:** you cannot use the following in an Angular expression:
|
||||
* **No Control Flow Statements:** You cannot use the following in an Angular expression:
|
||||
conditionals, loops, or exceptions.
|
||||
|
||||
* **No Function Declarations:** You cannot declare functions in an Angular expression,
|
||||
even inside `ng-init` directive.
|
||||
|
||||
* **No RegExp Creation With Literal Notation:** You cannot create regular expressions
|
||||
in an Angular expression.
|
||||
|
||||
* **No Comma And Void Operators:** You cannot use `,` or `void` in an Angular expression.
|
||||
|
||||
* **Filters:** You can use {@link guide/filter filters} within expressions to format data before
|
||||
displaying it.
|
||||
@@ -164,6 +172,12 @@ expression. The reason behind this is core to the Angular philosophy that applic
|
||||
be in controllers, not the views. If you need a real conditional, loop, or to throw from a view
|
||||
expression, delegate to a JavaScript method instead.
|
||||
|
||||
## No function declarations or RegExp creation with literal notation
|
||||
|
||||
You can't declare functions or create regular expressions from within AngularJS expressions. This is
|
||||
to avoid complex model transformation logic inside templates. Such logic is better placed in a
|
||||
controller or in a dedicated filter where it can be tested properly.
|
||||
|
||||
## `$event`
|
||||
|
||||
Directives like {@link ng.directive:ngClick `ngClick`} and {@link ng.directive:ngFocus `ngFocus`}
|
||||
@@ -292,14 +306,16 @@ then the expression is not fulfilled and will remain watched.
|
||||
|
||||
### How to benefit from one-time binding
|
||||
|
||||
When interpolating text or attributes. If the expression, once set, will not change
|
||||
then it is a candidate for one-time expression.
|
||||
If the expression will not change once set, it is a candidate for one-time binding.
|
||||
Here are three example cases.
|
||||
|
||||
When interpolating text or attributes:
|
||||
|
||||
```html
|
||||
<div name="attr: {{::color}}">text: {{::name}}</div>
|
||||
```
|
||||
|
||||
When using a directive with bidirectional binding and the parameters will not change
|
||||
When using a directive with bidirectional binding and the parameters will not change:
|
||||
|
||||
```js
|
||||
someModule.directive('someDirective', function() {
|
||||
@@ -314,11 +330,11 @@ someModule.directive('someDirective', function() {
|
||||
```
|
||||
|
||||
```html
|
||||
<div some-directive name=“::myName” color=“My color is {{::myColor}}”></div>
|
||||
<div some-directive name="::myName" color="My color is {{::myColor}}"></div>
|
||||
```
|
||||
|
||||
|
||||
When using a directive that takes an expression
|
||||
When using a directive that takes an expression:
|
||||
|
||||
```html
|
||||
<ul>
|
||||
|
||||
@@ -91,6 +91,11 @@ The filter function should be a [pure function](http://en.wikipedia.org/wiki/Pur
|
||||
means that it should be stateless and idempotent. Angular relies on these properties and executes
|
||||
the filter only when the inputs to the function change.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** filter names must be valid angular expression identifiers, such as `uppercase` or `orderBy`.
|
||||
Names with special characters, such as hyphens and dots, are not allowed.
|
||||
</div>
|
||||
|
||||
The following sample filter reverses a text string. In addition, it conditionally makes the
|
||||
text upper-case.
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ for other directives to augment its behavior.
|
||||
E-mail: <input type="email" ng-model="user.email" /><br />
|
||||
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
||||
<button ng-click="reset()">RESET</button>
|
||||
<button ng-click="update(user)">SAVE</button>
|
||||
<input type="button" ng-click="reset()" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
<pre>form = {{user | json}}</pre>
|
||||
<pre>master = {{master | json}}</pre>
|
||||
@@ -77,29 +77,29 @@ To allow styling of form as well as controls, `ngModel` adds these CSS classes:
|
||||
|
||||
The following example uses the CSS to display validity of each form control.
|
||||
In the example both `user.name` and `user.email` are required, but are rendered
|
||||
with red background only when they are dirty. This ensures that the user is not distracted
|
||||
with an error until after interacting with the control, and failing to satisfy its validity.
|
||||
with red background only after the input is blurred (loses focus).
|
||||
This ensures that the user is not distracted with an error until after interacting with the control,
|
||||
and failing to satisfy its validity.
|
||||
|
||||
<example module="formExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ExampleController">
|
||||
<form novalidate class="css-form">
|
||||
Name:
|
||||
<input type="text" ng-model="user.name" required /><br />
|
||||
Name: <input type="text" ng-model="user.name" required /><br />
|
||||
E-mail: <input type="email" ng-model="user.email" required /><br />
|
||||
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
||||
<button ng-click="reset()">RESET</button>
|
||||
<button ng-click="update(user)">SAVE</button>
|
||||
<input type="button" ng-click="reset()" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style type="text/css">
|
||||
.css-form input.ng-invalid.ng-dirty {
|
||||
.css-form input.ng-invalid.ng-touched {
|
||||
background-color: #FA787E;
|
||||
}
|
||||
|
||||
.css-form input.ng-valid.ng-dirty {
|
||||
.css-form input.ng-valid.ng-touched {
|
||||
background-color: #78FA89;
|
||||
}
|
||||
</style>
|
||||
@@ -140,34 +140,45 @@ the view using the standard binding primitives.
|
||||
|
||||
This allows us to extend the above example with these features:
|
||||
|
||||
- RESET button is enabled only if form has some changes
|
||||
- SAVE button is enabled only if form has some changes and is valid
|
||||
- custom error messages for `user.email` and `user.agree`
|
||||
- Custom error message displayed after the user interacted with a control (i.e. when `$touched` is set)
|
||||
- Custom error message displayed upon submitting the form (`$submitted` is set), even if the user
|
||||
didn't interact with a control
|
||||
|
||||
|
||||
<example module="formExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ExampleController">
|
||||
<form name="form" class="css-form" novalidate>
|
||||
Name:
|
||||
<input type="text" ng-model="user.name" name="uName" required /><br />
|
||||
<input type="text" ng-model="user.name" name="uName" required="" />
|
||||
<br />
|
||||
<div ng-show="form.$submitted || form.uName.$touched">
|
||||
<div ng-show="form.uName.$error.required">Tell us your name.</div>
|
||||
</div>
|
||||
|
||||
E-mail:
|
||||
<input type="email" ng-model="user.email" name="uEmail" required/><br />
|
||||
<div ng-show="form.uEmail.$dirty && form.uEmail.$invalid">Invalid:
|
||||
<input type="email" ng-model="user.email" name="uEmail" required="" />
|
||||
<br />
|
||||
<div ng-show="form.$submitted || form.uEmail.$touched">
|
||||
<span ng-show="form.uEmail.$error.required">Tell us your email.</span>
|
||||
<span ng-show="form.uEmail.$error.email">This is not a valid email.</span>
|
||||
</div>
|
||||
|
||||
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
||||
Gender:
|
||||
<input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female
|
||||
<br />
|
||||
<input type="checkbox" ng-model="user.agree" name="userAgree" required="" />
|
||||
|
||||
<input type="checkbox" ng-model="user.agree" name="userAgree" required />
|
||||
I agree: <input ng-show="user.agree" type="text" ng-model="user.agreeSign"
|
||||
required /><br />
|
||||
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
|
||||
I agree:
|
||||
<input ng-show="user.agree" type="text" ng-model="user.agreeSign" required="" />
|
||||
<br />
|
||||
<div ng-show="form.$submitted || form.userAgree.$touched">
|
||||
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
|
||||
</div>
|
||||
|
||||
<button ng-click="reset()" ng-disabled="isUnchanged(user)">RESET</button>
|
||||
<button ng-click="update(user)"
|
||||
ng-disabled="form.$invalid || isUnchanged(user)">SAVE</button>
|
||||
<input type="button" ng-click="reset(form)" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
</file>
|
||||
@@ -181,14 +192,14 @@ This allows us to extend the above example with these features:
|
||||
$scope.master = angular.copy(user);
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.reset = function(form) {
|
||||
if (form) {
|
||||
form.$setPristine();
|
||||
form.$setUntouched();
|
||||
}
|
||||
$scope.user = angular.copy($scope.master);
|
||||
};
|
||||
|
||||
$scope.isUnchanged = function(user) {
|
||||
return angular.equals(user, $scope.master);
|
||||
};
|
||||
|
||||
$scope.reset();
|
||||
}]);
|
||||
</file>
|
||||
@@ -282,7 +293,7 @@ after last change.
|
||||
|
||||
Angular provides basic implementation for most common HTML5 {@link ng.directive:input input}
|
||||
types: ({@link input[text] text}, {@link input[number] number}, {@link input[url] url},
|
||||
{@link input[email] email}, {@link input[radio] radio}, {@link input[checkbox] checkbox}),
|
||||
{@link input[email] email}, {@link input[date] date}, {@link input[radio] radio}, {@link input[checkbox] checkbox}),
|
||||
as well as some directives for validation (`required`, `pattern`, `minlength`, `maxlength`,
|
||||
`min`, `max`).
|
||||
|
||||
|
||||
+5
-184
@@ -6,7 +6,7 @@
|
||||
# Internet Explorer Compatibility
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** AngularJS 1.3 is dropping support for IE8. Read more about it on
|
||||
**Note:** AngularJS 1.3 has dropped support for IE8. Read more about it on
|
||||
[our blog](http://blog.angularjs.org/2013/12/angularjs-13-new-release-approaches.html).
|
||||
AngularJS 1.2 will continue to support IE8, but the core team does not plan to spend time
|
||||
addressing issues specific to IE8 or earlier.
|
||||
@@ -14,7 +14,7 @@ addressing issues specific to IE8 or earlier.
|
||||
|
||||
This document describes the Internet Explorer (IE) idiosyncrasies when dealing with custom HTML
|
||||
attributes and tags. Read this document if you are planning on deploying your Angular application
|
||||
on IE8 or earlier.
|
||||
on IE.
|
||||
|
||||
The project currently supports and will attempt to fix bugs for IE9 and above. The continuous
|
||||
integration server runs all the tests against IE9, IE10, and IE11. See
|
||||
@@ -25,186 +25,7 @@ We do not run tests on IE8 and below. A subset of the AngularJS functionality ma
|
||||
browsers, but it is up to you to test and decide whether it works for your particular app.
|
||||
|
||||
|
||||
## Short Version
|
||||
|
||||
To make your Angular application work on IE please make sure that:
|
||||
|
||||
1. You polyfill JSON.stringify for IE7 and below. You can use
|
||||
[JSON2](https://github.com/douglascrockford/JSON-js) or
|
||||
[JSON3](http://bestiejs.github.com/json3/) polyfills for this.
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<!--[if lte IE 7]>
|
||||
<script src="/path/to/json2.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
2. add `id="ng-app"` to the root element in conjunction with `ng-app` attribute
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
|
||||
...
|
||||
</html>
|
||||
```
|
||||
|
||||
3. you **do not** use custom element tags such as `<ng:view>` (use the attribute version
|
||||
`<div ng-view>` instead), or
|
||||
|
||||
4. if you **do use** custom element tags, then you must take these steps to make IE 8 and below happy:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
|
||||
<head>
|
||||
<!--[if lte IE 8]>
|
||||
<script>
|
||||
document.createElement('ng-include');
|
||||
document.createElement('ng-pluralize');
|
||||
document.createElement('ng-view');
|
||||
|
||||
// Optionally these for CSS
|
||||
document.createElement('ng:include');
|
||||
document.createElement('ng:pluralize');
|
||||
document.createElement('ng:view');
|
||||
</script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
5. Use `ng-style` tags instead of `style="{{ someCss }}"`. The later works in Chrome and Firefox
|
||||
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
|
||||
|
||||
|
||||
The **important** parts are:
|
||||
|
||||
* `xmlns:ng` - *namespace* - you need one namespace for each custom tag you are planning on
|
||||
using.
|
||||
|
||||
* `document.createElement(yourTagName)` - *creation of custom tag names* - Since this is an
|
||||
issue only for older version of IE you need to load it conditionally. For each tag which does
|
||||
not have namespace and which is not defined in HTML you need to pre-declare it to make IE
|
||||
happy.
|
||||
|
||||
|
||||
## Long Version
|
||||
|
||||
IE has issues with element tag names which are not standard HTML tag names. These fall into two
|
||||
categories, and each category has its own fix.
|
||||
|
||||
* If the tag name starts with `my:` prefix then it is considered an XML namespace and must
|
||||
have corresponding namespace declaration on `<html xmlns:my="ignored">`
|
||||
|
||||
* If the tag has no `:` but it is not a standard HTML tag, then it must be pre-created using
|
||||
`document.createElement('my-tag')`
|
||||
|
||||
* If you are planning on styling the custom tag with CSS selectors, then it must be
|
||||
pre-created using `document.createElement('my-tag')` regardless of XML namespace.
|
||||
|
||||
|
||||
## The Good News
|
||||
|
||||
The good news is that these restrictions only apply to element tag names, and not to element
|
||||
attribute names. So this requires no special handling in IE: `<div my-tag your:tag></div>`.
|
||||
|
||||
|
||||
## What happens if I fail to do this?
|
||||
|
||||
Suppose you have HTML with unknown tag `mytag` (this could also be `my:tag` or `my-tag` with same
|
||||
result):
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<mytag>some text</mytag>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
It should parse into the following DOM:
|
||||
|
||||
```
|
||||
#document
|
||||
+- HTML
|
||||
+- BODY
|
||||
+- mytag
|
||||
+- #text: some text
|
||||
```
|
||||
|
||||
The expected behavior is that the `BODY` element has a child element `mytag`, which in turn has
|
||||
the text `some text`.
|
||||
|
||||
But this is not what IE does (if the above fixes are not included):
|
||||
|
||||
```
|
||||
#document
|
||||
+- HTML
|
||||
+- BODY
|
||||
+- mytag
|
||||
+- #text: some text
|
||||
+- /mytag
|
||||
```
|
||||
|
||||
In IE, the behavior is that the `BODY` element has three children:
|
||||
|
||||
1. A self closing `mytag`. Example of self closing tag is `<br/>`. The trailing `/` is optional,
|
||||
but the `<br>` tag is not allowed to have any children, and browsers consider `<br>some
|
||||
text</br>` as three siblings not a `<br>` with `some text` as child.
|
||||
|
||||
2. A text node with `some text`. This should have been a child of `mytag` above, not a sibling.
|
||||
|
||||
3. A corrupt self closing `/mytag`. This is corrupt since element names are not allowed to have
|
||||
the `/` character. Furthermore this closing element should not be part of the DOM since it is
|
||||
only used to delineate the structure of the DOM.
|
||||
|
||||
|
||||
## CSS Styling of Custom Tag Names
|
||||
|
||||
To make CSS selectors work with custom elements, the custom element name must be pre-created with
|
||||
`document.createElement('my-tag')` regardless of XML namespace.
|
||||
|
||||
```html
|
||||
<html xmlns:ng="needed for ng: namespace">
|
||||
<head>
|
||||
<!--[if lte IE 8]>
|
||||
<script>
|
||||
// needed to make ng-include parse properly
|
||||
document.createElement('ng-include');
|
||||
|
||||
// needed to enable CSS reference
|
||||
document.createElement('ng:view');
|
||||
</script>
|
||||
<![endif]-->
|
||||
<style>
|
||||
ng\:view {
|
||||
display: block;
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
ng-include {
|
||||
display: block;
|
||||
border: 1px solid blue;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ng:view></ng:view>
|
||||
<ng-include></ng-include>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
To ensure your Angular application works on IE please consider:
|
||||
|
||||
1. Use `ng-style` tags instead of `style="{{ someCss }}"`. The latter works in Chrome and Firefox
|
||||
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
|
||||
@@ -41,7 +41,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
### Other AngularJS Features
|
||||
|
||||
* **Animation:** {@link guide/animations Core concepts}, {@link ngAnimate ngAnimate API}, and [Animation in AngularJS 1.2](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html)
|
||||
* **Security:** {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
|
||||
* **Security:** {@link guide/security Security Docs}, {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
|
||||
* **Internationalization and Localization:** {@link guide/i18n Angular Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
|
||||
* **Mobile:** {@link ngTouch Touch events}
|
||||
* **Accessibility:** {@link guide/accessibility ngAria}
|
||||
@@ -108,6 +108,8 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
|
||||
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
|
||||
* [AngularJS : Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
|
||||
* [AngularJS UI Development](http://www.amazon.com/AngularJS-UI-Development-Amit-Ghart-ebook/dp/B00OXVAK7A) by Amit Gharat and Matthias Nehlsen
|
||||
* [Responsive Web Design with AngularJS](http://www.amazon.com/Responsive-Design-AngularJS-Sandeep-Kumar/dp/178439842X) by Sandeep Kumar Patel
|
||||
|
||||
###Videos:
|
||||
* [egghead.io](http://egghead.io/)
|
||||
@@ -116,12 +118,12 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
### Courses
|
||||
* **Free online:**
|
||||
[thinkster.io](http://thinkster.io),
|
||||
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1)
|
||||
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1),
|
||||
[CodeSchool](https://www.codeschool.com/courses/shaping-up-with-angular-js)
|
||||
* **Paid online:**
|
||||
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
|
||||
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
|
||||
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html)
|
||||
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html),
|
||||
[WintellectNOW (4 lessons)](http://www.wintellectnow.com/Course/Detail/mastering-angularjs)
|
||||
* **Paid onsite:**
|
||||
[angularbootcamp.com](http://angularbootcamp.com/)
|
||||
|
||||
+255
-141
@@ -15,28 +15,68 @@ which drives many of these changes.
|
||||
|
||||
# Migrating from 1.2 to 1.3
|
||||
|
||||
- **$parse:**
|
||||
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
|
||||
## Angular Expression Parsing (`$parse` + `$interpolate`)
|
||||
|
||||
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
|
||||
|
||||
You can no longer invoke .bind, .call or .apply on a function in angular expressions.
|
||||
This is to disallow changing the behaviour of existing functions
|
||||
in an unforseen fashion.
|
||||
|
||||
- due to [6081f207](https://github.com/angular/angular.js/commit/6081f20769e64a800ee8075c168412b21f026d99),
|
||||
|
||||
The (deprecated) __proto__ propery does not work inside angular expressions
|
||||
The (deprecated) __proto__ property does not work inside angular expressions
|
||||
anymore.
|
||||
- due to [48fa3aad](https://github.com/angular/angular.js/commit/48fa3aadd546036c7e69f71046f659ab1de244c6),
|
||||
|
||||
|
||||
- due to [48fa3aad](https://github.com/angular/angular.js/commit/48fa3aadd546036c7e69f71046f659ab1de244c6),
|
||||
|
||||
This prevents the use of __{define,lookup}{Getter,Setter}__ inside angular
|
||||
expressions. If you really need them for some reason, please wrap/bind them to make them
|
||||
less dangerous, then make them available through the scope object.
|
||||
- due to [528be29d](https://github.com/angular/angular.js/commit/528be29d1662122a34e204dd607e1c0bd9c16bbc),
|
||||
|
||||
|
||||
- due to [528be29d](https://github.com/angular/angular.js/commit/528be29d1662122a34e204dd607e1c0bd9c16bbc),
|
||||
|
||||
This prevents the use of `Object` inside angular expressions.
|
||||
If you need Object.keys, make it accessible in the scope.
|
||||
- **Angular.copy:** due to [b59b04f9](https://github.com/angular/angular.js/commit/b59b04f98a0b59eead53f6a53391ce1bbcbe9b57),
|
||||
|
||||
|
||||
- due to [bdfc9c02](https://github.com/angular/angular.js/commit/bdfc9c02d021e08babfbc966a007c71b4946d69d),
|
||||
values 'f', '0', 'false', 'no', 'n', '[]' are no longer
|
||||
treated as falsy. Only JavaScript falsy values are now treated as falsy by the
|
||||
expression parser; there are six of them: false, null, undefined, NaN, 0 and "".
|
||||
|
||||
|
||||
- due to [fa6e411d](https://github.com/angular/angular.js/commit/fa6e411da26824a5bae55f37ce7dbb859653276d),
|
||||
promise unwrapping has been removed. It has been deprecated since 1.2.0-rc.3.
|
||||
It can no longer be turned on.
|
||||
Two methods have been removed:
|
||||
* `$parseProvider.unwrapPromises`
|
||||
* `$parseProvider.logPromiseWarnings`
|
||||
|
||||
|
||||
- **$interpolate:** due to [88c2193c](https://github.com/angular/angular.js/commit/88c2193c71954b9e7e7e4bdf636a2b168d36300d),
|
||||
the function returned by `$interpolate`
|
||||
no longer has a `.parts` array set on it.
|
||||
|
||||
Instead it has two arrays:
|
||||
* `.expressions`, an array of the expressions in the
|
||||
interpolated text. The expressions are parsed with
|
||||
`$parse`, with an extra layer converting them to strings
|
||||
when computed
|
||||
* `.separators`, an array of strings representing the
|
||||
separations between interpolations in the text.
|
||||
This array is **always** 1 item longer than the
|
||||
`.expressions` array for easy merging with it
|
||||
|
||||
|
||||
|
||||
|
||||
## Miscellaneous Angular helpers
|
||||
|
||||
- **Angular.copy:** due to [b59b04f9](https://github.com/angular/angular.js/commit/b59b04f98a0b59eead53f6a53391ce1bbcbe9b57),
|
||||
|
||||
This changes `angular.copy` so that it applies the prototype of the original
|
||||
object to the copied object. Previously, `angular.copy` would copy properties
|
||||
of the original object's prototype chain directly onto the copied object.
|
||||
@@ -53,17 +93,54 @@ not filter them with `hasOwnProperty`.
|
||||
**Be aware that this change also uses a feature that is not compatible with
|
||||
IE8.** If you need this to work on IE8 then you would need to provide a polyfill
|
||||
for `Object.create` and `Object.getPrototypeOf`.
|
||||
- **core:** due to [bdfc9c02](https://github.com/angular/angular.js/commit/bdfc9c02d021e08babfbc966a007c71b4946d69d),
|
||||
values 'f', '0', 'false', 'no', 'n', '[]' are no longer
|
||||
treated as falsy. Only JavaScript falsy values are now treated as falsy by the
|
||||
expression parser; there are six of them: false, null, undefined, NaN, 0 and "".
|
||||
|
||||
Closes #3969
|
||||
Closes #4277
|
||||
Closes #7960
|
||||
|
||||
|
||||
- **$compile:** due to [2cde927e](https://github.com/angular/angular.js/commit/2cde927e58c8d1588569d94a797e43cdfbcedaf9),
|
||||
- **forEach:** due to [55991e33](https://github.com/angular/angular.js/commit/55991e33af6fece07ea347a059da061b76fc95f5),
|
||||
forEach will iterate only over the initial number of items in
|
||||
the array. So if items are added to the array during the iteration, these won't
|
||||
be iterated over during the initial forEach call.
|
||||
|
||||
This change also makes our forEach behave more like Array#forEach.
|
||||
|
||||
|
||||
- **angular.toJson:** due to [c054288c](https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251),
|
||||
|
||||
If you expected `toJson` to strip these types of properties before, you will have to
|
||||
manually do this yourself now.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## jqLite / JQuery
|
||||
|
||||
- **jqLite:** due to [a196c8bc](https://github.com/angular/angular.js/commit/a196c8bca82a28c08896d31f1863cf4ecd11401c),
|
||||
previously it was possible to set jqLite data on Text/Comment
|
||||
nodes, but now that is allowed only on Element and Document nodes just like in
|
||||
jQuery. We don't expect that app code actually depends on this accidental feature.
|
||||
|
||||
|
||||
- **jqLite:** due to [d71dbb1a](https://github.com/angular/angular.js/commit/d71dbb1ae50f174680533492ce4c7db3ff74df00),
|
||||
the jQuery `detach()` method does not trigger the `$destroy` event.
|
||||
If you want to destroy Angular data attached to the element, use `remove()`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Angular HTML Compiler (`$compile`)
|
||||
|
||||
|
||||
- due to [2ee29c5d](https://github.com/angular/angular.js/commit/2ee29c5da81ffacdc1cabb438f5d125d5e116cb9),
|
||||
|
||||
The isolated scope of a component directive no longer leaks into the template
|
||||
that contains the instance of the directive. This means that you can no longer
|
||||
access the isolated scope from attributes on the element where the isolated
|
||||
directive is defined.
|
||||
|
||||
See https://github.com/angular/angular.js/issues/10236 for an example.
|
||||
|
||||
- due to [2cde927e](https://github.com/angular/angular.js/commit/2cde927e58c8d1588569d94a797e43cdfbcedaf9),
|
||||
|
||||
|
||||
Requesting isolate scope and any other scope on a single element is an error.
|
||||
@@ -77,9 +154,50 @@ If you find that your code is now throwing a `$compile:multidir` error,
|
||||
check that you do not have directives on the same element that are trying
|
||||
to request both an isolate and a non-isolate scope and fix your code.
|
||||
|
||||
Closes #4402
|
||||
Closes #4421
|
||||
- **NgModel:** due to [1be9bb9d](https://github.com/angular/angular.js/commit/1be9bb9d3527e0758350c4f7417a4228d8571440),
|
||||
|
||||
- due to [eec6394a](https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb), The `replace` flag for defining directives that
|
||||
replace the element that they are on will be removed in the next major angular version.
|
||||
This feature has difficult semantics (e.g. how attributes are merged) and leads to more
|
||||
problems compared to what it solves. Also, with Web Components it is normal to have
|
||||
custom elements in the DOM.
|
||||
|
||||
|
||||
- due to [299b220f](https://github.com/angular/angular.js/commit/299b220f5e05e1d4e26bfd58d0b2fd7329ca76b1),
|
||||
calling `attr.$observe` no longer returns the observer function, but a
|
||||
deregistration function instead. To migrate the code follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = attr.$observe('someAttr', function(value) {
|
||||
console.log(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
After:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = function(value) {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
attr.$observe('someAttr', observer);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
## Forms, Inputs and ngModel
|
||||
|
||||
- due to [1be9bb9d](https://github.com/angular/angular.js/commit/1be9bb9d3527e0758350c4f7417a4228d8571440),
|
||||
|
||||
|
||||
If an expression is used on ng-pattern (such as `ng-pattern="exp"`) or on the
|
||||
@@ -95,45 +213,70 @@ this limitation, use a regular expression object as the value for the expression
|
||||
|
||||
//after
|
||||
$scope.exp = /abc/i;
|
||||
- **Scope:** due to [8c6a8171](https://github.com/angular/angular.js/commit/8c6a8171f9bdaa5cdabc0cc3f7d3ce10af7b434d),
|
||||
|
||||
|
||||
- **ngModelOptions:** due to [adfc322b](https://github.com/angular/angular.js/commit/adfc322b04a58158fb9697e5b99aab9ca63c80bb),
|
||||
|
||||
|
||||
This commit changes the API on `NgModelController`, both semantically and
|
||||
in terms of adding and renaming methods.
|
||||
|
||||
* `$setViewValue(value)` -
|
||||
This method still changes the `$viewValue` but does not immediately commit this
|
||||
change through to the `$modelValue` as it did previously.
|
||||
Now the value is committed only when a trigger specified in an associated
|
||||
`ngModelOptions` directive occurs. If `ngModelOptions` also has a `debounce` delay
|
||||
specified for the trigger then the change will also be debounced before being
|
||||
committed.
|
||||
In most cases this should not have a significant impact on how `NgModelController`
|
||||
is used: If `updateOn` includes `default` then `$setViewValue` will trigger
|
||||
a (potentially debounced) commit immediately.
|
||||
* `$cancelUpdate()` - is renamed to `$rollbackViewValue()` and has the same meaning,
|
||||
which is to revert the current `$viewValue` back to the `$lastCommittedViewValue`,
|
||||
to cancel any pending debounced updates and to re-render the input.
|
||||
|
||||
To migrate code that used `$cancelUpdate()` follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$cancelUpdate();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$rollbackViewValue();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- types date, time, datetime-local, month, week now always
|
||||
require a `Date` object as model ([46bd6dc8](https://github.com/angular/angular.js/commit/46bd6dc88de252886d75426efc2ce8107a5134e9),
|
||||
[#5864](https://github.com/angular/angular.js/issues/5864))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Scopes and Digests (`$scope`)
|
||||
|
||||
- due to [8c6a8171](https://github.com/angular/angular.js/commit/8c6a8171f9bdaa5cdabc0cc3f7d3ce10af7b434d),
|
||||
Scope#$id is now of type number rather than string. Since the
|
||||
id is primarily being used for debugging purposes this change should not affect
|
||||
anyone.
|
||||
- **forEach:** due to [55991e33](https://github.com/angular/angular.js/commit/55991e33af6fece07ea347a059da061b76fc95f5),
|
||||
forEach will iterate only over the initial number of items in
|
||||
the array. So if items are added to the array during the iteration, these won't
|
||||
be iterated over during the initial forEach call.
|
||||
|
||||
This change also makes our forEach behave more like Array#forEach.
|
||||
- **jqLite:** due to [a196c8bc](https://github.com/angular/angular.js/commit/a196c8bca82a28c08896d31f1863cf4ecd11401c),
|
||||
previously it was possible to set jqLite data on Text/Comment
|
||||
nodes, but now that is allowed only on Element and Document nodes just like in
|
||||
jQuery. We don't expect that app code actually depends on this accidental feature.
|
||||
|
||||
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
|
||||
|
||||
If you expected `$resource` to strip these types of properties before,
|
||||
you will have to manually do this yourself now.
|
||||
|
||||
- **angular.toJson:** due to [c054288c](https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251),
|
||||
|
||||
If you expected `toJson` to strip these types of properties before,
|
||||
you will have to manually do this yourself now.
|
||||
|
||||
- **$compile:** due to [eec6394a](https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb), The `replace` flag for defining directives that
|
||||
replace the element that they are on will be removed in the next major angular version.
|
||||
This feature has difficult semantics (e.g. how attributes are merged) and leads to more
|
||||
problems compared to what it solves. Also, with Web Components it is normal to have
|
||||
custom elements in the DOM.
|
||||
|
||||
- **$parse:** due to [fa6e411d](https://github.com/angular/angular.js/commit/fa6e411da26824a5bae55f37ce7dbb859653276d),
|
||||
promise unwrapping has been removed. It has been deprecated since 1.2.0-rc.3.
|
||||
It can no longer be turned on.
|
||||
Two methods have been removed:
|
||||
* `$parseProvider.unwrapPromises`
|
||||
* `$parseProvider.logPromiseWarnings`
|
||||
|
||||
- **Scope:** due to [82f45aee](https://github.com/angular/angular.js/commit/82f45aee5bd84d1cc53fb2e8f645d2263cdaacbc),
|
||||
- due to [82f45aee](https://github.com/angular/angular.js/commit/82f45aee5bd84d1cc53fb2e8f645d2263cdaacbc),
|
||||
[#7445](https://github.com/angular/angular.js/issues/7445),
|
||||
[#7523](https://github.com/angular/angular.js/issues/7523)
|
||||
`$broadcast` and `$emit` will now reset the `currentScope` property of the event to
|
||||
@@ -141,11 +284,11 @@ jQuery. We don't expect that app code actually depends on this accidental featur
|
||||
`currentScope` property, it should be migrated to use `targetScope` instead. All of these cases
|
||||
should be considered programming bugs.
|
||||
|
||||
- **jqLite:** due to [d71dbb1a](https://github.com/angular/angular.js/commit/d71dbb1ae50f174680533492ce4c7db3ff74df00),
|
||||
the jQuery `detach()` method does not trigger the `$destroy` event.
|
||||
If you want to destroy Angular data attached to the element, use `remove()`.
|
||||
|
||||
|
||||
|
||||
|
||||
## Server Requests (`$http`, `$resource`)
|
||||
- **$http:** due to [ad4336f9](https://github.com/angular/angular.js/commit/ad4336f9359a073e272930f8f9bcd36587a8648f),
|
||||
|
||||
|
||||
@@ -197,7 +340,24 @@ More details on the new interceptors API (which has been around as of v1.1.4) ca
|
||||
{@link $http#interceptors interceptors}
|
||||
|
||||
|
||||
- **injector:** due to [c0b4e2db](https://github.com/angular/angular.js/commit/c0b4e2db9cbc8bc3164cedc4646145d3ab72536e),
|
||||
|
||||
- **$httpBackend:** due to [6680b7b9](https://github.com/angular/angular.js/commit/6680b7b97c0326a80bdccaf0a35031e4af641e0e), the JSONP behavior for erroneous and empty responses changed:
|
||||
Previously, a JSONP response was regarded as erroneous if it was empty. Now Angular is listening to the
|
||||
correct events to detect errors, i.e. even empty responses can be successful.
|
||||
|
||||
|
||||
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
|
||||
|
||||
If you expected `$resource` to strip these types of properties before,
|
||||
you will have to manually do this yourself now.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Modules and Injector (`$inject`)
|
||||
|
||||
- due to [c0b4e2db](https://github.com/angular/angular.js/commit/c0b4e2db9cbc8bc3164cedc4646145d3ab72536e),
|
||||
|
||||
Previously, config blocks would be able to control behaviour of provider registration, due to being
|
||||
invoked prior to provider registration. Now, provider registration always occurs prior to configuration
|
||||
@@ -234,66 +394,13 @@ and "$dependentProvider" would have actually accomplished something and changed
|
||||
app. This is no longer possible within a single module.
|
||||
|
||||
|
||||
- **ngModelOptions:** due to [adfc322b](https://github.com/angular/angular.js/commit/adfc322b04a58158fb9697e5b99aab9ca63c80bb),
|
||||
|
||||
|
||||
This commit changes the API on `NgModelController`, both semantically and
|
||||
in terms of adding and renaming methods.
|
||||
|
||||
* `$setViewValue(value)` -
|
||||
This method still changes the `$viewValue` but does not immediately commit this
|
||||
change through to the `$modelValue` as it did previously.
|
||||
Now the value is committed only when a trigger specified in an associated
|
||||
`ngModelOptions` directive occurs. If `ngModelOptions` also has a `debounce` delay
|
||||
specified for the trigger then the change will also be debounced before being
|
||||
committed.
|
||||
In most cases this should not have a significant impact on how `NgModelController`
|
||||
is used: If `updateOn` includes `default` then `$setViewValue` will trigger
|
||||
a (potentially debounced) commit immediately.
|
||||
* `$cancelUpdate()` - is renamed to `$rollbackViewValue()` and has the same meaning,
|
||||
which is to revert the current `$viewValue` back to the `$lastCommittedViewValue`,
|
||||
to cancel any pending debounced updates and to re-render the input.
|
||||
|
||||
To migrate code that used `$cancelUpdate()` follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$cancelUpdate();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$rollbackViewValue();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **$interpolate:** due to [88c2193c](https://github.com/angular/angular.js/commit/88c2193c71954b9e7e7e4bdf636a2b168d36300d),
|
||||
the function returned by `$interpolate`
|
||||
no longer has a `.parts` array set on it.
|
||||
|
||||
Instead it has two arrays:
|
||||
* `.expressions`, an array of the expressions in the
|
||||
interpolated text. The expressions are parsed with
|
||||
`$parse`, with an extra layer converting them to strings
|
||||
when computed
|
||||
* `.separators`, an array of strings representing the
|
||||
separations between interpolations in the text.
|
||||
This array is **always** 1 item longer than the
|
||||
`.expressions` array for easy merging with it
|
||||
## Animation (`ngAnimate`)
|
||||
|
||||
|
||||
- **$animate:** due to [1cb8584e](https://github.com/angular/angular.js/commit/1cb8584e8490ecdb1b410a8846c4478c6c2c0e53),
|
||||
- due to [1cb8584e](https://github.com/angular/angular.js/commit/1cb8584e8490ecdb1b410a8846c4478c6c2c0e53),
|
||||
`$animate` will no longer default the after parameter to the last element of the parent
|
||||
container. Instead, when after is not specified, the new element will be inserted as the
|
||||
first child of the parent container.
|
||||
@@ -308,7 +415,7 @@ to:
|
||||
|
||||
|
||||
|
||||
- **$animate:** due to [1bebe36a](https://github.com/angular/angular.js/commit/1bebe36aa938890d61188762ed618b1b5e193634),
|
||||
- due to [1bebe36a](https://github.com/angular/angular.js/commit/1bebe36aa938890d61188762ed618b1b5e193634),
|
||||
|
||||
Any class-based animation code that makes use of transitions
|
||||
and uses the setup CSS classes (such as class-add and class-remove) must now
|
||||
@@ -343,45 +450,52 @@ After:
|
||||
Please view the documentation for ngAnimate for more info.
|
||||
|
||||
|
||||
- **$compile:** due to [299b220f](https://github.com/angular/angular.js/commit/299b220f5e05e1d4e26bfd58d0b2fd7329ca76b1),
|
||||
calling `attr.$observe` no longer returns the observer function, but a
|
||||
deregistration function instead. To migrate the code follow the example below:
|
||||
## Testing
|
||||
|
||||
- due to [85880a64](https://github.com/angular/angular.js/commit/85880a64900fa22a61feb926bf52de0965332ca5), some deprecated features of
|
||||
Protractor tests no longer work.
|
||||
|
||||
`by.binding(descriptor)` no longer allows using the surrounding interpolation
|
||||
markers in the descriptor (the default interpolation markers are `{{}}`).
|
||||
Previously, these were optional.
|
||||
|
||||
Before:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = attr.$observe('someAttr', function(value) {
|
||||
console.log(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
var el = element(by.binding('{{foo}}'));
|
||||
|
||||
After:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = function(value) {
|
||||
console.log(value);
|
||||
};
|
||||
var el = element(by.binding('foo'));
|
||||
|
||||
attr.$observe('someAttr', observer);
|
||||
}
|
||||
};
|
||||
});
|
||||
Prefixes `ng_` and `x-ng-` are no longer allowed for models. Use `ng-model`.
|
||||
|
||||
- **$httpBackend:** due to [6680b7b9](https://github.com/angular/angular.js/commit/6680b7b97c0326a80bdccaf0a35031e4af641e0e), the JSONP behavior for erroneous and empty responses changed:
|
||||
Previously, a JSONP response was regarded as erroneous if it was empty. Now Angular is listening to the
|
||||
correct events to detect errors, i.e. even empty responses can be successful.
|
||||
`by.repeater` cannot find elements by row and column which are not children of
|
||||
the row. For example, if your template is
|
||||
|
||||
- **build:** due to [eaa1d00b](https://github.com/angular/angular.js/commit/eaa1d00b24008f590b95ad099241b4003688cdda),
|
||||
<div ng-repeat="foo in foos">{{foo.name}}</div>
|
||||
|
||||
Before:
|
||||
|
||||
var el = element(by.repeater('foo in foos').row(2).column('foo.name'))
|
||||
|
||||
After:
|
||||
|
||||
You may either enclose `{{foo.name}}` in a child element
|
||||
|
||||
<div ng-repeat="foo in foos"><span>{{foo.name}}</span></div>
|
||||
|
||||
or simply use:
|
||||
|
||||
var el = element(by.repeater('foo in foos').row(2))
|
||||
|
||||
|
||||
## Internet Explorer 8
|
||||
|
||||
- due to [eaa1d00b](https://github.com/angular/angular.js/commit/eaa1d00b24008f590b95ad099241b4003688cdda),
|
||||
As communicated before, IE8 is no longer supported.
|
||||
- **input:** types date, time, datetime-local, month, week now always
|
||||
require a `Date` object as model ([46bd6dc8](https://github.com/angular/angular.js/commit/46bd6dc88de252886d75426efc2ce8107a5134e9),
|
||||
[#5864](https://github.com/angular/angular.js/issues/5864))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ The above is a suggestion. Tailor it to your needs.
|
||||
# Module Loading & Dependencies
|
||||
|
||||
A module is a collection of configuration and run blocks which get applied to the application
|
||||
during the bootstrap process. In its simplest form the module consist of collection of two kinds
|
||||
during the bootstrap process. In its simplest form the module consist of a collection of two kinds
|
||||
of blocks:
|
||||
|
||||
1. **Configuration blocks** - get executed during the provider registrations and configuration
|
||||
|
||||
@@ -177,7 +177,7 @@ for example, only a portion of the view needs to be controlled by Angular.
|
||||
|
||||
To examine the scope in the debugger:
|
||||
|
||||
1. right click on the element of interest in your browser and select 'inspect element'. You
|
||||
1. Right click on the element of interest in your browser and select 'inspect element'. You
|
||||
should see the browser debugger with the element you clicked on highlighted.
|
||||
|
||||
2. The debugger allows you to access the currently selected element in the console as `$0`
|
||||
@@ -345,7 +345,7 @@ access on JavaScript object.
|
||||
Dirty checking can be done with three strategies: By reference, by collection contents, and by value. The strategies differ in the kinds of changes they detect, and in their performance characteristics.
|
||||
|
||||
- Watching *by reference* ({@link
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient stategy.
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient strategy.
|
||||
- Watching *collection contents* ({@link
|
||||
ng.$rootScope.Scope#$watchCollection scope.$watchCollection} `(watchExpression, listener)`) detects changes that occur inside an array or an object: When items are added, removed, or reordered. The detection is shallow - it does not reach into nested collections. Watching collection contents is more expensive than watching by reference, because copies of the collection contents need to be maintained. However, the strategy attempts to minimize the amount of copying required.
|
||||
- Watching *by value* ({@link
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
@ngdoc overview
|
||||
@name Security
|
||||
@sortOrder 525
|
||||
@description
|
||||
|
||||
# Security
|
||||
|
||||
This document explains some of AngularJS's security features and best practices that you should
|
||||
keep in mind as you build your application.
|
||||
|
||||
|
||||
## Expression Sandboxing
|
||||
|
||||
AngularJS's expressions are sandboxed not for security reasons, but instead to maintain a proper
|
||||
separation of application responsibilities. For example, access to `window` is disallowed
|
||||
because it makes it easy to introduce brittle global state into your application.
|
||||
|
||||
However, this sandbox is not intended to stop attackers who can edit the template before it's
|
||||
processed by Angular. It may be possible to run arbitrary JavaScript inside double-curly bindings
|
||||
if an attacker can modify them.
|
||||
|
||||
But if an attacker can change arbitrary HTML templates, there's nothing stopping them from doing:
|
||||
|
||||
```html
|
||||
<script>somethingEvil();</script>
|
||||
```
|
||||
|
||||
It's better to design your application in such a way that users cannot change client-side templates.
|
||||
For instance:
|
||||
|
||||
* Do not mix client and server templates
|
||||
* Do not use user input to generate templates dynamically
|
||||
* Do not run user input through `$scope.$eval`
|
||||
* Consider using {@link ng.directive:ngCsp CSP} (but don't rely only on CSP)
|
||||
|
||||
## Mixing client-side and server-side templates
|
||||
|
||||
In general, we recommend against this because it can create unintended XSS vectors.
|
||||
|
||||
However, it's ok to mix server-side templating in the bootstrap template (`index.html`) as long
|
||||
as user input cannot be used on the server to output html that would then be processed by Angular
|
||||
in a way that would cause allow for arbitrary code execution.
|
||||
|
||||
For instance, you can use server-side templating to dynamically generate CSS, URLs, etc, but not
|
||||
for generating templates that are bootstrapped/compiled by Angular.
|
||||
|
||||
|
||||
## Reporting a security issue
|
||||
|
||||
Email us at [security@angularjs.org](mailto:security@angularjs.org) to report any potential
|
||||
security issues in AngularJS.
|
||||
|
||||
Please keep in mind the above points about Angular's expression language.
|
||||
|
||||
|
||||
## See also
|
||||
|
||||
* {@link ng.directive:ngCsp Content Security Policy}
|
||||
* {@link ng.$sce Strict Contextual Escaping}
|
||||
* {@link ngSanitize.$sanitize $sanitize}
|
||||
@@ -8,15 +8,15 @@ comes with almost no help from the compiler. For this reason we feel very strong
|
||||
written in JavaScript needs to come with a strong set of tests. We have built many features into
|
||||
Angular which makes testing your Angular applications easy. So there is no excuse for not testing.
|
||||
|
||||
# Separation of Concerns
|
||||
## Separation of Concerns
|
||||
|
||||
Unit testing as the name implies is about testing individual units of code. Unit tests try to
|
||||
Unit testing, as the name implies, is about testing individual units of code. Unit tests try to
|
||||
answer questions such as "Did I think about the logic correctly?" or "Does the sort function order
|
||||
the list in the right order?"
|
||||
|
||||
In order to answer such a question it is very important that we can isolate the unit of code under test.
|
||||
That is because when we are testing the sort function we don't want to be forced into creating
|
||||
related pieces such as the DOM elements, or making any XHR calls in getting the data to sort.
|
||||
related pieces such as the DOM elements, or making any XHR calls to fetch the data to sort.
|
||||
|
||||
While this may seem obvious it can be very difficult to call an individual function on a
|
||||
typical project. The reason is that the developers often mix concerns resulting in a
|
||||
@@ -24,12 +24,10 @@ piece of code which does everything. It makes an XHR request, it sorts the respo
|
||||
manipulates the DOM.
|
||||
|
||||
With Angular we try to make it easy for you to do the right thing, and so we
|
||||
provide dependency injection for your XHR (which you can mock out) and we created abstractions which
|
||||
allow you to sort your model without having to resort to manipulating the DOM. So that in the end,
|
||||
it is easy to write a sort function which sorts some data, so that your test can create a data set,
|
||||
apply the function, and assert that the resulting model is in the correct order. The test does not
|
||||
have to wait for the XHR response to arrive, create the right kind of test DOM, nor assert that your
|
||||
function has mutated the DOM in the right way.
|
||||
provide dependency injection for your XHR requests, which can be mocked, and we provide abstractions which
|
||||
allow you to test your model without having to resort to manipulating the DOM. The test can then
|
||||
assert that the data has been sorted without having to create or look at the state of the DOM or
|
||||
wait for any XHR requests to return data. The individual sort function can be tested in isolation.
|
||||
|
||||
## With great power comes great responsibility
|
||||
|
||||
@@ -38,230 +36,218 @@ We tried to make the right thing easy, but if you ignore these guidelines you ma
|
||||
untestable application.
|
||||
|
||||
## Dependency Injection
|
||||
There are several ways in which you can get a hold of a dependency. You can:
|
||||
1. Create it using the `new` operator.
|
||||
2. Look for it in a well-known place, also known as a global singleton.
|
||||
3. Ask a registry (also known as service registry) for it. (But how do you get a hold of
|
||||
the registry? Most likely by looking it up in a well known place. See #2.)
|
||||
4. Expect it to be handed to you.
|
||||
|
||||
Out of the four options in the list above, only the last one is testable. Let's look at why:
|
||||
Angular comes with {@link di dependency injection} built-in, which makes testing components much
|
||||
easier, because you can pass in a component's dependencies and stub or mock them as you wish.
|
||||
|
||||
### Using the `new` operator
|
||||
Components that have their dependencies injected allow them to be easily mocked on a test by
|
||||
test basis, without having to mess with any global variables that could inadvertently affect
|
||||
another test.
|
||||
|
||||
While there is nothing wrong with the `new` operator fundamentally, a problem arises when calling `new`
|
||||
on a constructor. This permanently binds the call site to the type. For example, let's say that we try to
|
||||
instantiate an `XHR` that will retrieve data from the server.
|
||||
## Additional tools for testing Angular applications
|
||||
|
||||
For testing Angular applications there are certain tools that you should use that will make testing much
|
||||
easier to set up and run.
|
||||
|
||||
### Karma
|
||||
|
||||
[Karma](http://karma-runner.github.io/) is a JavaScript command line tool that can be used to spawn
|
||||
a web server which loads your application's source code and executes your tests. You can configure
|
||||
Karma to run against a number of browsers, which is useful for being confident that your application
|
||||
works on all browsers you need to support. Karma is executed on the command line and will display
|
||||
the results of your tests on the command line once they have run in the browser.
|
||||
|
||||
Karma is a NodeJS application, and should be installed through npm. Full installation instructions
|
||||
are available on [the Karma website](http://karma-runner.github.io/0.12/intro/installation.html).
|
||||
|
||||
### Jasmine
|
||||
|
||||
[Jasmine](http://jasmine.github.io/1.3/introduction.html) is a test driven development framework for
|
||||
JavaScript that has become the most popular choice for testing Angular applications. Jasmine
|
||||
provides functions to help with structuring your tests and also making assertions. As your tests
|
||||
grow, keeping them well structured and documented is vital, and Jasmine helps achieve this.
|
||||
|
||||
In Jasmine we use the `describe` function to group our tests together:
|
||||
|
||||
```js
|
||||
function MyClass() {
|
||||
this.doWork = function() {
|
||||
var xhr = new XHR();
|
||||
xhr.open(method, url, true);
|
||||
xhr.onreadystatechange = function() {...}
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
describe("sorting the list of users", function() {
|
||||
// individual tests go here
|
||||
});
|
||||
```
|
||||
|
||||
A problem surfaces in tests when we would like to instantiate a `MockXHR` that would
|
||||
allow us to return fake data and simulate network failures. By calling `new XHR()` we are
|
||||
permanently bound to the actual XHR and there is no way to replace it. Yes, we could monkey
|
||||
patch, but that is a bad idea for many reasons which are outside the scope of this document.
|
||||
|
||||
Here's an example of how the class above becomes hard to test when resorting to monkey patching:
|
||||
And then each individual test is defined within a call to the `it` function:
|
||||
|
||||
```js
|
||||
var oldXHR = XHR;
|
||||
XHR = function MockXHR() {};
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that MockXHR got called with the right arguments
|
||||
XHR = oldXHR; // if you forget this bad things will happen
|
||||
describe('sorting the list of users', function() {
|
||||
it('sorts in descending order by default', function() {
|
||||
// your test assertion goes here
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Grouping related tests within `describe` blocks and describing each individual test within an
|
||||
`it` call keeps your tests self documenting.
|
||||
|
||||
### Global look-up:
|
||||
Another way to approach the problem is to look for the service in a well-known location.
|
||||
Finally, Jasmine provides matchers which let you make assertions:
|
||||
|
||||
```js
|
||||
function MyClass() {
|
||||
this.doWork = function() {
|
||||
global.xhr({
|
||||
method:'...',
|
||||
url:'...',
|
||||
complete:function(response){ ... }
|
||||
})
|
||||
}
|
||||
}
|
||||
describe('sorting the list of users', function() {
|
||||
it('sorts in descending order by default', function() {
|
||||
var users = ['jack', 'igor', 'jeff'];
|
||||
var sorted = sortUsers(users);
|
||||
expect(sorted).toEqual(['jeff', 'jack', 'igor']);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
While no new dependency instance is created, it is fundamentally the same as `new` in
|
||||
that no way exists to intercept the call to `global.xhr` for testing purposes, other than
|
||||
through monkey patching. The basic issue for testing is that a global variable needs to be mutated in
|
||||
order to replace it with call to a mock method. For further explanation of why this is bad see: [Brittle Global
|
||||
State & Singletons](http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/)
|
||||
Jasmine comes with a number of matchers that help you make a variety of assertions. You should [read
|
||||
the Jasmine documentation](http://jasmine.github.io/1.3/introduction.html#section-Matchers) to see
|
||||
what they are. To use Jasmine with Karma, we use the
|
||||
[karma-jasmine](https://github.com/karma-runner/karma-jasmine) test runner.
|
||||
|
||||
The class above is hard to test since we have to change the global state:
|
||||
### angular-mocks
|
||||
|
||||
Angular also provides the {@link ngMock} module, which provides mocking for your tests. This is used
|
||||
to inject and mock Angular services within unit tests. In addition, it is able to extend other
|
||||
modules so they are synchronous. Having tests synchronous keeps them much cleaner and easier to work
|
||||
with. One of the most useful parts of ngMock is {@link ngMock.$httpBackend}, which lets us mock XHR
|
||||
requests in tests, and return sample data instead.
|
||||
|
||||
## Testing a Controller
|
||||
|
||||
Because Angular separates logic from the view layer, it keeps controllers easy to test. Let's take a
|
||||
look at how we might test the controller below, which provides `$scope.grade`, which sets a property
|
||||
on the scope based on the length of the password.
|
||||
|
||||
```js
|
||||
var oldXHR = global.xhr;
|
||||
global.xhr = function mockXHR() {};
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that mockXHR got called with the right arguments
|
||||
global.xhr = oldXHR; // if you forget this bad things will happen
|
||||
angular.module('app', [])
|
||||
.controller('PasswordController', function PasswordController($scope) {
|
||||
$scope.password = '';
|
||||
$scope.grade = function() {
|
||||
var size = $scope.password.length;
|
||||
if (size > 8) {
|
||||
$scope.strength = 'strong';
|
||||
} else if (size > 3) {
|
||||
$scope.strength = 'medium';
|
||||
} else {
|
||||
$scope.strength = 'weak';
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
### Service Registry:
|
||||
|
||||
It may seem that this can be solved by having a registry of all the services and then
|
||||
having the tests replace the services as needed.
|
||||
Because controllers are not available on the global scope, we need to use {@link
|
||||
angular.mock.inject} to inject our controller first. The first step is to use the `module` function,
|
||||
which is provided by angular-mocks. This loads in the module it's given, so it is available in your
|
||||
tests. We pass this into `beforeEach`, which is a function Jasmine provides that lets us run code
|
||||
before each test. Then we can use `inject` to access `$controller`, the service that is responsible
|
||||
for instantiating controllers.
|
||||
|
||||
```js
|
||||
function MyClass() {
|
||||
var serviceRegistry = ????;
|
||||
this.doWork = function() {
|
||||
var xhr = serviceRegistry.get('xhr');
|
||||
xhr({
|
||||
method:'...',
|
||||
url:'...',
|
||||
complete:function(response){ ... }
|
||||
})
|
||||
}
|
||||
describe('PasswordController', function() {
|
||||
beforeEach(module('app'));
|
||||
|
||||
var $controller;
|
||||
|
||||
beforeEach(inject(function(_$controller_){
|
||||
// The injector unwraps the underscores (_) from around the parameter names when matching
|
||||
$controller = _$controller_;
|
||||
}));
|
||||
|
||||
describe('$scope.grade', function() {
|
||||
it('sets the strength to "strong" if the password length is >8 chars', function() {
|
||||
var $scope = {};
|
||||
var controller = $controller('PasswordController', { $scope: $scope });
|
||||
$scope.password = 'longerthaneightchars';
|
||||
$scope.grade();
|
||||
expect($scope.strength).toEqual('strong');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
However, where does the serviceRegistry come from? If it is:
|
||||
* `new`-ed up, the test has no chance to reset the services for testing.
|
||||
* a global look-up then the service returned is global as well (but resetting is easier, since
|
||||
only one global variable exists to be reset).
|
||||
|
||||
The class above is hard to test since we have to change the global state:
|
||||
Notice how by nesting the `describe` calls and being descriptive when calling them with strings, the
|
||||
test is very clear. It documents exactly what it is testing, and at a glance you can quickly see
|
||||
what is happening. Now let's add the test for when the password is less than three characters, which
|
||||
should see `$scope.strength` set to "weak":
|
||||
|
||||
```js
|
||||
var oldServiceLocator = global.serviceLocator;
|
||||
global.serviceLocator.set('xhr', function mockXHR() {});
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that mockXHR got called with the right arguments
|
||||
global.serviceLocator = oldServiceLocator; // if you forget this bad things will happen
|
||||
describe('PasswordController', function() {
|
||||
beforeEach(module('app'));
|
||||
|
||||
var $controller;
|
||||
|
||||
beforeEach(inject(function(_$controller_){
|
||||
// The injector unwraps the underscores (_) from around the parameter names when matching
|
||||
$controller = _$controller_;
|
||||
}));
|
||||
|
||||
describe('$scope.grade', function() {
|
||||
it('sets the strength to "strong" if the password length is >8 chars', function() {
|
||||
var $scope = {};
|
||||
var controller = $controller('PasswordController', { $scope: $scope });
|
||||
$scope.password = 'longerthaneightchars';
|
||||
$scope.grade();
|
||||
expect($scope.strength).toEqual('strong');
|
||||
});
|
||||
|
||||
it('sets the strength to "weak" if the password length <3 chars', function() {
|
||||
var $scope = {};
|
||||
var controller = $controller('PasswordController', { $scope: $scope });
|
||||
$scope.password = 'a';
|
||||
$scope.grade();
|
||||
expect($scope.strength).toEqual('weak');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
### Passing in Dependencies:
|
||||
Last, the dependency can be passed in.
|
||||
Now we have two tests, but notice the duplication between the tests. Both have to
|
||||
create the `$scope` variable and create the controller. As we add new tests, this duplication is
|
||||
only going to get worse. Thankfully, Jasmine provides `beforeEach`, which lets us run a function
|
||||
before each individual test. Let's see how that would tidy up our tests:
|
||||
|
||||
```js
|
||||
function MyClass(xhr) {
|
||||
this.doWork = function() {
|
||||
xhr({
|
||||
method:'...',
|
||||
url:'...',
|
||||
complete:function(response){ ... }
|
||||
})
|
||||
}
|
||||
describe('PasswordController', function() {
|
||||
beforeEach(module('app'));
|
||||
|
||||
var $controller;
|
||||
|
||||
beforeEach(inject(function(_$controller_){
|
||||
// The injector unwraps the underscores (_) from around the parameter names when matching
|
||||
$controller = _$controller_;
|
||||
}));
|
||||
|
||||
describe('$scope.grade', function() {
|
||||
var $scope, controller;
|
||||
|
||||
beforeEach(function() {
|
||||
$scope = {};
|
||||
controller = $controller('PasswordController', { $scope: $scope });
|
||||
});
|
||||
|
||||
it('sets the strength to "strong" if the password length is >8 chars', function() {
|
||||
$scope.password = 'longerthaneightchars';
|
||||
$scope.grade();
|
||||
expect($scope.strength).toEqual('strong');
|
||||
});
|
||||
|
||||
it('sets the strength to "weak" if the password length <3 chars', function() {
|
||||
$scope.password = 'a';
|
||||
$scope.grade();
|
||||
expect($scope.strength).toEqual('weak');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This is the preferred method since the code makes no assumptions about the origin of `xhr` and cares
|
||||
instead about whoever created the class responsible for passing it in. Since the creator of the
|
||||
class should be different code than the user of the class, it separates the responsibility of
|
||||
creation from the logic. This is dependency-injection in a nutshell.
|
||||
We've moved the duplication out and into the `beforeEach` block. Each individual test now
|
||||
only contains the code specific to that test, and not code that is general across all tests. As you
|
||||
expand your tests, keep an eye out for locations where you can use `beforeEach` to tidy up tests.
|
||||
`beforeEach` isn't the only function of this sort that Jasmine provides, and the [documentation
|
||||
lists the others](http://jasmine.github.io/1.3/introduction.html#section-Setup_and_Teardown).
|
||||
|
||||
The class above is testable, since in the test we can write:
|
||||
|
||||
```js
|
||||
function xhrMock(args) {...}
|
||||
var myClass = new MyClass(xhrMock);
|
||||
myClass.doWork();
|
||||
// assert that xhrMock got called with the right arguments
|
||||
```
|
||||
|
||||
Notice that no global variables were harmed in the writing of this test.
|
||||
|
||||
Angular comes with {@link di dependency injection} built-in, making the right thing
|
||||
easy to do, but you still need to do it if you wish to take advantage of the testability story.
|
||||
|
||||
## Controllers
|
||||
What makes each application unique is its logic, and the logic is what we would like to test. If the logic
|
||||
for your application contains DOM manipulation, it will be hard to test. See the example
|
||||
below:
|
||||
|
||||
```js
|
||||
function PasswordCtrl() {
|
||||
// get references to DOM elements
|
||||
var msg = $('.ex1 span');
|
||||
var input = $('.ex1 input');
|
||||
var strength;
|
||||
|
||||
this.grade = function() {
|
||||
msg.removeClass(strength);
|
||||
var pwd = input.val();
|
||||
password.text(pwd);
|
||||
if (pwd.length > 8) {
|
||||
strength = 'strong';
|
||||
} else if (pwd.length > 3) {
|
||||
strength = 'medium';
|
||||
} else {
|
||||
strength = 'weak';
|
||||
}
|
||||
msg
|
||||
.addClass(strength)
|
||||
.text(strength);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The code above is problematic from a testability point of view since it requires your test to have the right kind
|
||||
of DOM present when the code executes. The test would look like this:
|
||||
|
||||
```js
|
||||
var input = $('<input type="text"/>');
|
||||
var span = $('<span>');
|
||||
$('body').html('<div class="ex1">')
|
||||
.find('div')
|
||||
.append(input)
|
||||
.append(span);
|
||||
var pc = new PasswordCtrl();
|
||||
input.val('abc');
|
||||
pc.grade();
|
||||
expect(span.text()).toEqual('weak');
|
||||
$('body').empty();
|
||||
```
|
||||
|
||||
In angular the controllers are strictly separated from the DOM manipulation logic and this results in
|
||||
a much easier testability story as the following example shows:
|
||||
|
||||
```js
|
||||
function PasswordCtrl($scope) {
|
||||
$scope.password = '';
|
||||
$scope.grade = function() {
|
||||
var size = $scope.password.length;
|
||||
if (size > 8) {
|
||||
$scope.strength = 'strong';
|
||||
} else if (size > 3) {
|
||||
$scope.strength = 'medium';
|
||||
} else {
|
||||
$scope.strength = 'weak';
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
and the test is straight forward:
|
||||
|
||||
```js
|
||||
var $scope = {};
|
||||
var pc = $controller('PasswordCtrl', { $scope: $scope });
|
||||
$scope.password = 'abc';
|
||||
$scope.grade();
|
||||
expect($scope.strength).toEqual('weak');
|
||||
```
|
||||
|
||||
Notice that the test is not only much shorter, it is also easier to follow what is happening. We say
|
||||
that such a test tells a story, rather than asserting random bits which don't seem to be related.
|
||||
|
||||
## Filters
|
||||
## Testing Filters
|
||||
{@link ng.$filterProvider Filters} are functions which transform the data into a user readable
|
||||
format. They are important because they remove the formatting responsibility from the application
|
||||
logic, further simplifying the application logic.
|
||||
@@ -273,12 +259,20 @@ myModule.filter('length', function() {
|
||||
}
|
||||
});
|
||||
|
||||
var length = $filter('length');
|
||||
expect(length(null)).toEqual(0);
|
||||
expect(length('abc')).toEqual(3);
|
||||
describe('length filter', function() {
|
||||
it('returns 0 when given null', function() {
|
||||
var length = $filter('length');
|
||||
expect(length(null)).toEqual(0);
|
||||
});
|
||||
|
||||
it('returns the correct value when given a string of chars', function() {
|
||||
var length = $filter('length');
|
||||
expect(length('abc')).toEqual(3);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Directives
|
||||
## Testing Directives
|
||||
Directives in angular are responsible for encapsulating complex functionality within custom HTML tags,
|
||||
attributes, classes or comments. Unit tests are very important for directives because the components
|
||||
you create with directives may be used throughout your application and in many different contexts.
|
||||
@@ -309,28 +303,28 @@ verify this functionality. Note that the expression `{{1 + 1}}` times will also
|
||||
|
||||
```js
|
||||
describe('Unit testing great quotes', function() {
|
||||
var $compile;
|
||||
var $rootScope;
|
||||
var $compile,
|
||||
$rootScope;
|
||||
|
||||
// Load the myApp module, which contains the directive
|
||||
beforeEach(module('myApp'));
|
||||
// Load the myApp module, which contains the directive
|
||||
beforeEach(module('myApp'));
|
||||
|
||||
// Store references to $rootScope and $compile
|
||||
// so they are available to all tests in this describe block
|
||||
beforeEach(inject(function(_$compile_, _$rootScope_){
|
||||
// The injector unwraps the underscores (_) from around the parameter names when matching
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
// Store references to $rootScope and $compile
|
||||
// so they are available to all tests in this describe block
|
||||
beforeEach(inject(function(_$compile_, _$rootScope_){
|
||||
// The injector unwraps the underscores (_) from around the parameter names when matching
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
it('Replaces the element with the appropriate content', function() {
|
||||
// Compile a piece of HTML containing the directive
|
||||
var element = $compile("<a-great-eye></a-great-eye>")($rootScope);
|
||||
// fire all the watches, so the scope expression {{1 + 1}} will be evaluated
|
||||
$rootScope.$digest();
|
||||
// Check that the compiled element contains the templated content
|
||||
expect(element.html()).toContain("lidless, wreathed in flame, 2 times");
|
||||
});
|
||||
it('Replaces the element with the appropriate content', function() {
|
||||
// Compile a piece of HTML containing the directive
|
||||
var element = $compile("<a-great-eye></a-great-eye>")($rootScope);
|
||||
// fire all the watches, so the scope expression {{1 + 1}} will be evaluated
|
||||
$rootScope.$digest();
|
||||
// Check that the compiled element contains the templated content
|
||||
expect(element.html()).toContain("lidless, wreathed in flame, 2 times");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@@ -431,4 +425,3 @@ Otherwise you may run into issues if the test directory hierarchy differs from t
|
||||
|
||||
## Sample project
|
||||
See the [angular-seed](https://github.com/angular/angular-seed) project for an example.
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ npm --version
|
||||
```
|
||||
|
||||
|
||||
<div class="alert alert-info">If you need to run a different versions of node.js
|
||||
<div class="alert alert-info">If you need to run different versions of node.js
|
||||
in your local environment, consider installing
|
||||
<a href="https://github.com/creationix/nvm" title="Node Version Manager Github Repo link">
|
||||
Node Version Manager (nvm)
|
||||
@@ -160,7 +160,7 @@ globally and run directly from a terminal/command prompt. You don't need to do t
|
||||
tutorial, but if you decide you do want to run them directly, you can install these modules globally
|
||||
using, `sudo npm install -g ...`.
|
||||
|
||||
For instance to install the Bower command line executable you would do:
|
||||
For instance, to install the Bower command line executable you would do:
|
||||
|
||||
```
|
||||
sudo npm install -g bower
|
||||
|
||||
@@ -182,7 +182,7 @@ You can now rerun `npm run protractor` to see the tests run.
|
||||
# Experiments
|
||||
|
||||
* In the `PhoneListCtrl` controller, remove the statement that sets the `orderProp` value and
|
||||
you'll see that Angular will temporarily add a new "unknown" option to the drop-down list and the
|
||||
you'll see that Angular will temporarily add a new blank ("unknown") option to the drop-down list and the
|
||||
ordering will default to unordered/natural order.
|
||||
|
||||
* Add an `{{orderProp}}` binding into the `index.html` template to display its current value as
|
||||
|
||||
@@ -258,8 +258,8 @@ You should now see the following output in the Karma tab:
|
||||
|
||||
# Experiments
|
||||
|
||||
* At the bottom of `index.html`, add a `<pre>{{phones | json}}</pre>` binding to see the list of phones
|
||||
displayed in json format.
|
||||
* At the bottom of `index.html`, add a `<pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>`
|
||||
binding to see the list of phones displayed in json format.
|
||||
|
||||
* In the `PhoneListCtrl` controller, pre-process the http response by limiting the number of phones
|
||||
to the first 5 in the list. Use the following code in the `$http` callback:
|
||||
|
||||
@@ -16,8 +16,8 @@ about the phones in the catalog.
|
||||
|
||||
## Data
|
||||
|
||||
Note that the `phones.json` file contains unique ids and image urls for each of the phones. The
|
||||
urls point to the `app/img/phones/` directory.
|
||||
Note that the `phones.json` file contains unique IDs and image URLs for each of the phones. The
|
||||
URLs point to the `app/img/phones/` directory.
|
||||
|
||||
__`app/phones/phones.json`__ (sample snippet):
|
||||
|
||||
@@ -59,7 +59,7 @@ the element attribute.
|
||||
We also added phone images next to each record using an image tag with the {@link
|
||||
ng.directive:ngSrc ngSrc} directive. That directive prevents the
|
||||
browser from treating the Angular `{{ expression }}` markup literally, and initiating a request to
|
||||
invalid url `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
|
||||
invalid URL `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
|
||||
specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
|
||||
Using the `ngSrc` directive prevents the browser from making an http request to an invalid location.
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ fleshed out the `phone-detail.html` view template.
|
||||
|
||||
## Data
|
||||
|
||||
In addition to `phones.json`, the `app/phones/` directory also contains one json file for each
|
||||
In addition to `phones.json`, the `app/phones/` directory also contains one JSON file for each
|
||||
phone:
|
||||
|
||||
__`app/phones/nexus-s.json`:__ (sample snippet)
|
||||
@@ -53,7 +53,7 @@ show this data in the phone detail view.
|
||||
|
||||
## Controller
|
||||
|
||||
We'll expand the `PhoneDetailCtrl` by using the `$http` service to fetch the json files. This works
|
||||
We'll expand the `PhoneDetailCtrl` by using the `$http` service to fetch the JSON files. This works
|
||||
the same way as the phone list controller.
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
|
||||
@@ -267,7 +267,7 @@ goog.i18n.currency.adjustPrecision = function(pattern, currencyCode) {
|
||||
* 18: two decimals precision (2), currency sign last (16), no space (0)
|
||||
* 50: two decimals precision (2), currency sign last (16), space (32)
|
||||
*
|
||||
* @type {!Object.<!Array.<?>>}
|
||||
* @const {!Object<!Array<?>>}
|
||||
*/
|
||||
goog.i18n.currency.CurrencyInfo = {
|
||||
'AED': [2, 'dh', '\u062f.\u0625.', 'DH'],
|
||||
@@ -334,7 +334,7 @@ goog.i18n.currency.CurrencyInfo = {
|
||||
|
||||
/**
|
||||
* Tier 2 currency information.
|
||||
* @type {!Object.<!Array.<?>>}
|
||||
* @const {!Object<!Array<?>>}
|
||||
*/
|
||||
goog.i18n.currency.CurrencyInfoTier2 = {
|
||||
'AFN': [48, 'Af.', 'AFN'],
|
||||
|
||||
+354
-308
File diff suppressed because it is too large
Load Diff
+1820
-1710
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@
|
||||
* This file is autogenerated by script:
|
||||
* http://go/generate_number_constants.py
|
||||
* using the --for_closure flag.
|
||||
* File generated from CLDR ver. 25
|
||||
* File generated from CLDR ver. 26
|
||||
*
|
||||
* To reduce the file size (which may cause issues in some JS
|
||||
* developing environments), this file will only contain locales
|
||||
@@ -42,7 +42,6 @@ goog.provide('goog.i18n.NumberFormatSymbols_am_ET');
|
||||
goog.provide('goog.i18n.NumberFormatSymbols_ar');
|
||||
goog.provide('goog.i18n.NumberFormatSymbols_ar_001');
|
||||
goog.provide('goog.i18n.NumberFormatSymbols_az');
|
||||
goog.provide('goog.i18n.NumberFormatSymbols_az_Cyrl_AZ');
|
||||
goog.provide('goog.i18n.NumberFormatSymbols_az_Latn_AZ');
|
||||
goog.provide('goog.i18n.NumberFormatSymbols_bg');
|
||||
goog.provide('goog.i18n.NumberFormatSymbols_bg_BG');
|
||||
@@ -364,13 +363,6 @@ goog.i18n.NumberFormatSymbols_az = {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Number formatting symbols for locale az_Cyrl_AZ.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.i18n.NumberFormatSymbols_az_Cyrl_AZ = goog.i18n.NumberFormatSymbols_az;
|
||||
|
||||
|
||||
/**
|
||||
* Number formatting symbols for locale az_Latn_AZ.
|
||||
* @enum {string}
|
||||
@@ -455,8 +447,8 @@ goog.i18n.NumberFormatSymbols_br = {
|
||||
NAN: 'NaN',
|
||||
DECIMAL_PATTERN: '#,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
PERCENT_PATTERN: '#,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4\u00A0#,##0.00',
|
||||
PERCENT_PATTERN: '#,##0\u00A0%',
|
||||
CURRENCY_PATTERN: '#,##0.00\u00A0\u00A4',
|
||||
DEF_CURRENCY_CODE: 'EUR'
|
||||
};
|
||||
|
||||
@@ -1063,7 +1055,7 @@ goog.i18n.NumberFormatSymbols_es = {
|
||||
NAN: 'NaN',
|
||||
DECIMAL_PATTERN: '#,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
PERCENT_PATTERN: '#,##0%',
|
||||
PERCENT_PATTERN: '#,##0\u00A0%',
|
||||
CURRENCY_PATTERN: '#,##0.00\u00A0\u00A4',
|
||||
DEF_CURRENCY_CODE: 'EUR'
|
||||
};
|
||||
@@ -1086,7 +1078,7 @@ goog.i18n.NumberFormatSymbols_es_419 = {
|
||||
NAN: 'NaN',
|
||||
DECIMAL_PATTERN: '#,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
PERCENT_PATTERN: '#,##0%',
|
||||
PERCENT_PATTERN: '#,##0\u00A0%',
|
||||
CURRENCY_PATTERN: '\u00A4#,##0.00',
|
||||
DEF_CURRENCY_CODE: 'MXN'
|
||||
};
|
||||
@@ -1492,7 +1484,7 @@ goog.i18n.NumberFormatSymbols_gu = {
|
||||
INFINITY: '\u221E',
|
||||
NAN: 'NaN',
|
||||
DECIMAL_PATTERN: '#,##,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
SCIENTIFIC_PATTERN: '[#E0]',
|
||||
PERCENT_PATTERN: '#,##,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4#,##,##0.00',
|
||||
DEF_CURRENCY_CODE: 'INR'
|
||||
@@ -1582,7 +1574,7 @@ goog.i18n.NumberFormatSymbols_hi = {
|
||||
INFINITY: '\u221E',
|
||||
NAN: 'NaN',
|
||||
DECIMAL_PATTERN: '#,##,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
SCIENTIFIC_PATTERN: '[#E0]',
|
||||
PERCENT_PATTERN: '#,##,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4#,##,##0.00',
|
||||
DEF_CURRENCY_CODE: 'INR'
|
||||
@@ -1961,7 +1953,7 @@ goog.i18n.NumberFormatSymbols_kn = {
|
||||
ZERO_DIGIT: '0',
|
||||
PLUS_SIGN: '+',
|
||||
MINUS_SIGN: '-',
|
||||
EXP_SYMBOL: '\u0C88',
|
||||
EXP_SYMBOL: 'E',
|
||||
PERMILL: '\u2030',
|
||||
INFINITY: '\u221E',
|
||||
NAN: 'NaN',
|
||||
@@ -2085,7 +2077,7 @@ goog.i18n.NumberFormatSymbols_lo = {
|
||||
PERMILL: '\u2030',
|
||||
INFINITY: '\u221E',
|
||||
NAN:
|
||||
'\u0E9A\u0ECD\u0EC8\u0EC1\u0EA1\u0EC8\u0E99\u0EC2\u0E95\u0EC0\u0EA5\u0E81',
|
||||
'\u0E9A\u0ECD\u0EC8\u200B\u0EC1\u0EA1\u0EC8\u0E99\u200B\u0EC2\u0E95\u200B\u0EC0\u0EA5\u0E81',
|
||||
DECIMAL_PATTERN: '#,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#',
|
||||
PERCENT_PATTERN: '#,##0%',
|
||||
@@ -2149,7 +2141,7 @@ goog.i18n.NumberFormatSymbols_lv = {
|
||||
DECIMAL_PATTERN: '#,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
PERCENT_PATTERN: '#,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4#,##0.00',
|
||||
CURRENCY_PATTERN: '\u00A4#0.00',
|
||||
DEF_CURRENCY_CODE: 'EUR'
|
||||
};
|
||||
|
||||
@@ -2208,8 +2200,8 @@ goog.i18n.NumberFormatSymbols_ml = {
|
||||
NAN: 'NaN',
|
||||
DECIMAL_PATTERN: '#,##,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
PERCENT_PATTERN: '#,##,##0%',
|
||||
CURRENCY_PATTERN: '#,##,##0.00\u00A4',
|
||||
PERCENT_PATTERN: '#,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4#,##0.00',
|
||||
DEF_CURRENCY_CODE: 'INR'
|
||||
};
|
||||
|
||||
@@ -2266,7 +2258,7 @@ goog.i18n.NumberFormatSymbols_mr = {
|
||||
PERMILL: '\u2030',
|
||||
INFINITY: '\u221E',
|
||||
NAN: 'NaN',
|
||||
DECIMAL_PATTERN: '#,##0.###',
|
||||
DECIMAL_PATTERN: '#,##,##0.###',
|
||||
SCIENTIFIC_PATTERN: '[#E0]',
|
||||
PERCENT_PATTERN: '#,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4#,##0.00',
|
||||
@@ -2427,7 +2419,7 @@ goog.i18n.NumberFormatSymbols_ne = {
|
||||
DECIMAL_PATTERN: '#,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
PERCENT_PATTERN: '#,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4#,##0.00',
|
||||
CURRENCY_PATTERN: '\u00A4\u00A0#,##0.00',
|
||||
DEF_CURRENCY_CODE: 'NPR'
|
||||
};
|
||||
|
||||
@@ -2545,7 +2537,7 @@ goog.i18n.NumberFormatSymbols_pa = {
|
||||
INFINITY: '\u221E',
|
||||
NAN: 'NaN',
|
||||
DECIMAL_PATTERN: '#,##,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
SCIENTIFIC_PATTERN: '[#E0]',
|
||||
PERCENT_PATTERN: '#,##,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4#,##,##0.00',
|
||||
DEF_CURRENCY_CODE: 'INR'
|
||||
@@ -2718,7 +2710,7 @@ goog.i18n.NumberFormatSymbols_si = {
|
||||
INFINITY: '\u221E',
|
||||
NAN: 'NaN',
|
||||
DECIMAL_PATTERN: '#,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
SCIENTIFIC_PATTERN: '#',
|
||||
PERCENT_PATTERN: '#,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4#,##0.00',
|
||||
DEF_CURRENCY_CODE: 'LKR'
|
||||
@@ -2957,10 +2949,10 @@ goog.i18n.NumberFormatSymbols_te = {
|
||||
PERMILL: '\u2030',
|
||||
INFINITY: '\u221E',
|
||||
NAN: 'NaN',
|
||||
DECIMAL_PATTERN: '#,##0.###',
|
||||
DECIMAL_PATTERN: '#,##,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
PERCENT_PATTERN: '#,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4#,##0.00',
|
||||
CURRENCY_PATTERN: '\u00A4#,##,##0.00',
|
||||
DEF_CURRENCY_CODE: 'INR'
|
||||
};
|
||||
|
||||
@@ -3102,8 +3094,8 @@ goog.i18n.NumberFormatSymbols_ur = {
|
||||
NAN: 'NaN',
|
||||
DECIMAL_PATTERN: '#,##0.###',
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
PERCENT_PATTERN: '#,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4#,##0.00\u200E',
|
||||
PERCENT_PATTERN: '#,##,##0%',
|
||||
CURRENCY_PATTERN: '\u00A4\u00A0#,##,##0.00',
|
||||
DEF_CURRENCY_CODE: 'PKR'
|
||||
};
|
||||
|
||||
@@ -3321,10 +3313,6 @@ if (goog.LOCALE == 'az') {
|
||||
goog.i18n.NumberFormatSymbols = goog.i18n.NumberFormatSymbols_az;
|
||||
}
|
||||
|
||||
if (goog.LOCALE == 'az_Cyrl_AZ' || goog.LOCALE == 'az-Cyrl-AZ') {
|
||||
goog.i18n.NumberFormatSymbols = goog.i18n.NumberFormatSymbols_az;
|
||||
}
|
||||
|
||||
if (goog.LOCALE == 'az_Latn_AZ' || goog.LOCALE == 'az-Latn-AZ') {
|
||||
goog.i18n.NumberFormatSymbols = goog.i18n.NumberFormatSymbols_az;
|
||||
}
|
||||
|
||||
+531
-375
File diff suppressed because it is too large
Load Diff
+44
-27
@@ -17,7 +17,7 @@
|
||||
*
|
||||
* This file is autogenerated by script:
|
||||
* http://go/generate_pluralrules.py
|
||||
* File generated from CLDR ver. 25
|
||||
* File generated from CLDR ver. 26
|
||||
*
|
||||
* Before check in, this file could have been manually edited. This is to
|
||||
* incorporate changes before we could fix CLDR. All manual modification must be
|
||||
@@ -409,6 +409,23 @@ goog.i18n.pluralRules.srSelect_ = function(n, opt_precision) {
|
||||
return goog.i18n.pluralRules.Keyword.OTHER;
|
||||
};
|
||||
|
||||
/**
|
||||
* Plural select rules for mk locale
|
||||
*
|
||||
* @param {number} n The count of items.
|
||||
* @param {number=} opt_precision Precision for number formatting, if not default.
|
||||
* @return {goog.i18n.pluralRules.Keyword} Locale-specific plural value.
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.pluralRules.mkSelect_ = function(n, opt_precision) {
|
||||
var i = n | 0;
|
||||
var vf = goog.i18n.pluralRules.get_vf_(n, opt_precision);
|
||||
if (vf.v == 0 && i % 10 == 1 || vf.f % 10 == 1) {
|
||||
return goog.i18n.pluralRules.Keyword.ONE;
|
||||
}
|
||||
return goog.i18n.pluralRules.Keyword.OTHER;
|
||||
};
|
||||
|
||||
/**
|
||||
* Plural select rules for hi locale
|
||||
*
|
||||
@@ -426,17 +443,15 @@ goog.i18n.pluralRules.hiSelect_ = function(n, opt_precision) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Plural select rules for mk locale
|
||||
* Plural select rules for pt locale
|
||||
*
|
||||
* @param {number} n The count of items.
|
||||
* @param {number=} opt_precision Precision for number formatting, if not default.
|
||||
* @return {goog.i18n.pluralRules.Keyword} Locale-specific plural value.
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.pluralRules.mkSelect_ = function(n, opt_precision) {
|
||||
var i = n | 0;
|
||||
var vf = goog.i18n.pluralRules.get_vf_(n, opt_precision);
|
||||
if (vf.v == 0 && i % 10 == 1 || vf.f % 10 == 1) {
|
||||
goog.i18n.pluralRules.ptSelect_ = function(n, opt_precision) {
|
||||
if (n >= 0 && n <= 2 && n != 2) {
|
||||
return goog.i18n.pluralRules.Keyword.ONE;
|
||||
}
|
||||
return goog.i18n.pluralRules.Keyword.OTHER;
|
||||
@@ -562,24 +577,6 @@ goog.i18n.pluralRules.akSelect_ = function(n, opt_precision) {
|
||||
return goog.i18n.pluralRules.Keyword.OTHER;
|
||||
};
|
||||
|
||||
/**
|
||||
* Plural select rules for pt locale
|
||||
*
|
||||
* @param {number} n The count of items.
|
||||
* @param {number=} opt_precision Precision for number formatting, if not default.
|
||||
* @return {goog.i18n.pluralRules.Keyword} Locale-specific plural value.
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.pluralRules.ptSelect_ = function(n, opt_precision) {
|
||||
var i = n | 0;
|
||||
var vf = goog.i18n.pluralRules.get_vf_(n, opt_precision);
|
||||
var wt = goog.i18n.pluralRules.get_wt_(vf.v, vf.f);
|
||||
if (i == 1 && vf.v == 0 || i == 0 && wt.t == 1) {
|
||||
return goog.i18n.pluralRules.Keyword.ONE;
|
||||
}
|
||||
return goog.i18n.pluralRules.Keyword.OTHER;
|
||||
};
|
||||
|
||||
/**
|
||||
* Plural select rules for pl locale
|
||||
*
|
||||
@@ -748,6 +745,29 @@ goog.i18n.pluralRules.gdSelect_ = function(n, opt_precision) {
|
||||
return goog.i18n.pluralRules.Keyword.OTHER;
|
||||
};
|
||||
|
||||
/**
|
||||
* Plural select rules for dsb locale
|
||||
*
|
||||
* @param {number} n The count of items.
|
||||
* @param {number=} opt_precision Precision for number formatting, if not default.
|
||||
* @return {goog.i18n.pluralRules.Keyword} Locale-specific plural value.
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.pluralRules.dsbSelect_ = function(n, opt_precision) {
|
||||
var i = n | 0;
|
||||
var vf = goog.i18n.pluralRules.get_vf_(n, opt_precision);
|
||||
if (vf.v == 0 && i % 100 == 1 || vf.f % 100 == 1) {
|
||||
return goog.i18n.pluralRules.Keyword.ONE;
|
||||
}
|
||||
if (vf.v == 0 && i % 100 == 2 || vf.f % 100 == 2) {
|
||||
return goog.i18n.pluralRules.Keyword.TWO;
|
||||
}
|
||||
if (vf.v == 0 && i % 100 >= 3 && i % 100 <= 4 || vf.f % 100 >= 3 && vf.f % 100 <= 4) {
|
||||
return goog.i18n.pluralRules.Keyword.FEW;
|
||||
}
|
||||
return goog.i18n.pluralRules.Keyword.OTHER;
|
||||
};
|
||||
|
||||
/**
|
||||
* Plural select rules for lv locale
|
||||
*
|
||||
@@ -852,9 +872,6 @@ if (goog.LOCALE == 'en_IE' || goog.LOCALE == 'en-IE') {
|
||||
if (goog.LOCALE == 'en_IN' || goog.LOCALE == 'en-IN') {
|
||||
goog.i18n.pluralRules.select = goog.i18n.pluralRules.enSelect_;
|
||||
}
|
||||
if (goog.LOCALE == 'en_ISO' || goog.LOCALE == 'en-ISO') {
|
||||
goog.i18n.pluralRules.select = goog.i18n.pluralRules.enSelect_;
|
||||
}
|
||||
if (goog.LOCALE == 'en_SG' || goog.LOCALE == 'en-SG') {
|
||||
goog.i18n.pluralRules.select = goog.i18n.pluralRules.enSelect_;
|
||||
}
|
||||
|
||||
+10
-6
@@ -114,26 +114,30 @@ module.exports = function(config, specificOptions) {
|
||||
var buildLabel = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
|
||||
|
||||
config.logLevel = config.LOG_DEBUG;
|
||||
config.transports = ['websocket', 'xhr-polling'];
|
||||
config.captureTimeout = 0; // rely on SL timeout
|
||||
// Karma (with socket.io 1.x) buffers by 50 and 50 tests can take a long time on IEs;-)
|
||||
config.browserNoActivityTimeout = 120000;
|
||||
|
||||
config.browserStack.build = buildLabel;
|
||||
config.browserStack.startTunnel = false;
|
||||
config.browserStack.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
|
||||
|
||||
config.sauceLabs.build = buildLabel;
|
||||
config.sauceLabs.startConnect = false;
|
||||
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
|
||||
config.sauceLabs.recordScreenshots = true;
|
||||
|
||||
// TODO(vojta): remove once SauceLabs supports websockets.
|
||||
// This speeds up the capturing a bit, as browsers don't even try to use websocket.
|
||||
config.transports = ['xhr-polling'];
|
||||
|
||||
// Debug logging into a file, that we print out at the end of the build.
|
||||
config.loggers.push({
|
||||
type: 'file',
|
||||
filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log')
|
||||
});
|
||||
|
||||
if (process.env.BROWSER_PROVIDER === 'saucelabs' || !process.env.BROWSER_PROVIDER) {
|
||||
// Allocating a browser can take pretty long (eg. if we are out of capacity and need to wait
|
||||
// for another build to finish) and so the `captureTimeout` typically kills
|
||||
// an in-queue-pending request, which makes no sense.
|
||||
config.captureTimeout = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
node ./lib/browser-stack/start-tunnel.js &
|
||||
@@ -5,9 +5,10 @@ var http = require('http');
|
||||
var BrowserStackTunnel = require('browserstacktunnel-wrapper');
|
||||
|
||||
var HOSTNAME = 'localhost';
|
||||
var PORTS = require('../grunt/utils').availablePorts;
|
||||
var PORTS = [9876, 8000];
|
||||
var ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY;
|
||||
var READY_FILE = process.env.SAUCE_CONNECT_READY_FILE;
|
||||
var READY_FILE = process.env.BROWSER_PROVIDER_READY_FILE;
|
||||
var TUNNEL_IDENTIFIER = process.env.TRAVIS_JOB_NUMBER;
|
||||
|
||||
// We need to start fake servers, otherwise the tunnel does not start.
|
||||
var fakeServers = [];
|
||||
@@ -24,6 +25,7 @@ PORTS.forEach(function(port) {
|
||||
|
||||
var tunnel = new BrowserStackTunnel({
|
||||
key: ACCESS_KEY,
|
||||
tunnelIdentifier: TUNNEL_IDENTIFIER,
|
||||
hosts: hosts
|
||||
});
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
|
||||
|
||||
node ./lib/browserstack/start_tunnel.js &
|
||||
+1
-43
@@ -11,21 +11,6 @@ var _ = require('lodash');
|
||||
|
||||
var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n';
|
||||
|
||||
var PORT_MIN = 8000;
|
||||
var PORT_MAX = 9999;
|
||||
var TRAVIS_BUILD_NUMBER = parseInt(process.env.TRAVIS_BUILD_NUMBER, 10);
|
||||
var getRandomPorts = function() {
|
||||
if (!process.env.TRAVIS) {
|
||||
return [9876, 9877];
|
||||
}
|
||||
|
||||
// Generate two numbers between PORT_MIN and PORT_MAX, based on TRAVIS_BUILD_NUMBER.
|
||||
return [
|
||||
PORT_MIN + (TRAVIS_BUILD_NUMBER % (PORT_MAX - PORT_MIN)),
|
||||
PORT_MIN + ((TRAVIS_BUILD_NUMBER + 100) % (PORT_MAX - PORT_MIN))
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -312,32 +297,5 @@ module.exports = {
|
||||
}
|
||||
next();
|
||||
};
|
||||
},
|
||||
|
||||
parallelTask: function(args, options) {
|
||||
var task = {
|
||||
grunt: true,
|
||||
args: args,
|
||||
stream: options && options.stream
|
||||
};
|
||||
|
||||
args.push('--port=' + this.sauceLabsAvailablePorts.pop());
|
||||
|
||||
if (args.indexOf('test:e2e') !== -1 && grunt.option('e2e-browsers')) {
|
||||
args.push('--browsers=' + grunt.option('e2e-browsers'));
|
||||
} else if (grunt.option('browsers')) {
|
||||
args.push('--browsers=' + grunt.option('browsers'));
|
||||
}
|
||||
|
||||
if (grunt.option('reporters')) {
|
||||
args.push('--reporters=' + grunt.option('reporters'));
|
||||
}
|
||||
|
||||
return task;
|
||||
},
|
||||
|
||||
// see http://saucelabs.com/docs/connect#localhost
|
||||
sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876],
|
||||
// pseudo-random port numbers for BrowserStack
|
||||
availablePorts: getRandomPorts()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -91,15 +91,6 @@ var getTaggedVersion = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stable versions have an even minor version and have no prerelease
|
||||
* @param {SemVer} version The version to test
|
||||
* @return {Boolean} True if the version is stable
|
||||
*/
|
||||
var isStable = function(version) {
|
||||
return semver.satisfies(version, '1.0 || 1.2') && version.prerelease.length === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a collection of all the previous versions sorted by semantic version
|
||||
* @return {Array.<SemVer>} The collection of previous versions
|
||||
@@ -119,8 +110,6 @@ var getPreviousVersions = function() {
|
||||
})
|
||||
.filter()
|
||||
.map(function(version) {
|
||||
version.isStable = isStable(version);
|
||||
|
||||
version.docsUrl = 'http://code.angularjs.org/' + version.version + '/docs';
|
||||
// Versions before 1.0.2 had a different docs folder name
|
||||
if ( version.major < 1 || (version.major === 1 && version.minor === 0 && version.dot < 2 ) ) {
|
||||
|
||||
Generated
+7
-7
@@ -1395,7 +1395,7 @@
|
||||
}
|
||||
},
|
||||
"dgeni-packages": {
|
||||
"version": "0.10.5",
|
||||
"version": "0.10.7",
|
||||
"dependencies": {
|
||||
"catharsis": {
|
||||
"version": "0.7.1"
|
||||
@@ -1454,7 +1454,7 @@
|
||||
"version": "1.2.2"
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "1.5.1"
|
||||
"version": "1.7.1"
|
||||
},
|
||||
"glob": {
|
||||
"version": "3.2.11",
|
||||
@@ -1465,16 +1465,16 @@
|
||||
}
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.7.3",
|
||||
"version": "3.8.2",
|
||||
"dependencies": {
|
||||
"domhandler": {
|
||||
"version": "2.2.0"
|
||||
"version": "2.3.0"
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.5.0"
|
||||
},
|
||||
"domelementtype": {
|
||||
"version": "1.1.1"
|
||||
"version": "1.1.3"
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.13",
|
||||
@@ -1528,7 +1528,7 @@
|
||||
"dependencies": {
|
||||
"fsevents": {
|
||||
"version": "0.2.1",
|
||||
"from": "fsevents@git://github.com/pipobscure/fsevents#7dcdf9fa3f8956610fd6f69f72c67bace2de7138",
|
||||
"from": "git://github.com/pipobscure/fsevents#7dcdf9fa3f8956610fd6f69f72c67bace2de7138",
|
||||
"resolved": "git://github.com/pipobscure/fsevents#7dcdf9fa3f8956610fd6f69f72c67bace2de7138",
|
||||
"dependencies": {
|
||||
"nan": {
|
||||
@@ -1596,7 +1596,7 @@
|
||||
"version": "0.0.10",
|
||||
"dependencies": {
|
||||
"combined-stream": {
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.7",
|
||||
"dependencies": {
|
||||
"delayed-stream": {
|
||||
"version": "0.0.5"
|
||||
|
||||
+2
-3
@@ -25,7 +25,6 @@
|
||||
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"grunt-jscs": "~0.7.1",
|
||||
"grunt-merge-conflict": "~0.0.1",
|
||||
"grunt-parallel": "~0.3.1",
|
||||
"grunt-shell": "~1.1.1",
|
||||
"gulp": "~3.8.0",
|
||||
"gulp-concat": "^2.4.1",
|
||||
@@ -38,8 +37,8 @@
|
||||
"jasmine-node": "~1.14.5",
|
||||
"jasmine-reporters": "~1.0.1",
|
||||
"jshint-stylish": "~1.0.0",
|
||||
"karma": "^0.12.0",
|
||||
"karma-browserstack-launcher": "0.1.1",
|
||||
"karma": "vojtajina/karma#socketio_10",
|
||||
"karma-browserstack-launcher": "0.1.2",
|
||||
"karma-chrome-launcher": "0.1.5",
|
||||
"karma-firefox-launcher": "0.1.3",
|
||||
"karma-jasmine": "0.1.5",
|
||||
|
||||
+72
-23
@@ -2,32 +2,81 @@
|
||||
|
||||
var config = require('./protractor-shared-conf').config;
|
||||
|
||||
config.sauceUser = process.env.SAUCE_USERNAME;
|
||||
config.sauceKey = process.env.SAUCE_ACCESS_KEY;
|
||||
if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
// Using BrowserStack.
|
||||
config.seleniumAddress = 'http://hub.browserstack.com/wd/hub';
|
||||
config.multiCapabilities = [
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'chrome',
|
||||
platform: 'MAC',
|
||||
version: '34'
|
||||
}),
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'firefox',
|
||||
version: '28'
|
||||
}),
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'safari',
|
||||
platform: 'MAC',
|
||||
version: '7'
|
||||
})
|
||||
];
|
||||
} else {
|
||||
// Using SauceLabs.
|
||||
config.sauceUser = process.env.SAUCE_USERNAME;
|
||||
config.sauceKey = process.env.SAUCE_ACCESS_KEY;
|
||||
config.multiCapabilities = [
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'chrome',
|
||||
platform: 'OS X 10.9',
|
||||
version: '34'
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'firefox',
|
||||
version: '28'
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.9',
|
||||
version: '7'
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
config.multiCapabilities = [{
|
||||
'browserName': 'chrome',
|
||||
'platform': 'OS X 10.9',
|
||||
'name': 'Angular E2E',
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
'version': '34'
|
||||
}, {
|
||||
'browserName': 'firefox',
|
||||
'name': 'Angular E2E',
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
'version': '28'
|
||||
}, {
|
||||
browserName: 'safari',
|
||||
'platform': 'OS X 10.9',
|
||||
'version': '7',
|
||||
'name': 'Angular E2E',
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER
|
||||
}];
|
||||
|
||||
config.allScriptsTimeout = 30000;
|
||||
config.getPageTimeout = 30000;
|
||||
|
||||
exports.config = config;
|
||||
|
||||
|
||||
function capabilitiesForBrowserStack(capabilities) {
|
||||
return {
|
||||
'browserstack.user': process.env.BROWSER_STACK_USERNAME,
|
||||
'browserstack.key': process.env.BROWSER_STACK_ACCESS_KEY,
|
||||
'browserstack.local' : 'true',
|
||||
'browserstack.debug': 'true',
|
||||
'browserstack.tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
|
||||
'name': 'Angular E2E',
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
|
||||
'browserName': capabilities.browserName,
|
||||
'platform': capabilities.platform,
|
||||
'version': capabilities.version
|
||||
};
|
||||
}
|
||||
|
||||
function capabilitiesForSauceLabs(capabilities) {
|
||||
return {
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
|
||||
'name': 'Angular E2E',
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
|
||||
'browserName': capabilities.browserName,
|
||||
'platform': capabilities.platform,
|
||||
'version': capabilities.version
|
||||
};
|
||||
}
|
||||
|
||||
@@ -96,8 +96,14 @@ function publish {
|
||||
# don't publish every build to npm
|
||||
if [ "${NEW_VERSION/+sha}" = "$NEW_VERSION" ] ; then
|
||||
if [ "${NEW_VERSION/-}" = "$NEW_VERSION" ] ; then
|
||||
# publish releases as "latest"
|
||||
npm publish
|
||||
if [[ $NEW_VERSION =~ ^1\.2\.[0-9]+$ ]] ; then
|
||||
# publish 1.2.x releases with the appropriate tag
|
||||
# this ensures that `npm install` by default will not grab `1.2.x` releases
|
||||
npm publish --tag=old
|
||||
else
|
||||
# publish releases as "latest"
|
||||
npm publish
|
||||
fi
|
||||
else
|
||||
# publish prerelease builds with the beta tag
|
||||
npm publish --tag=beta
|
||||
|
||||
@@ -16,9 +16,11 @@ function init {
|
||||
REPOS=(
|
||||
angular
|
||||
angular-animate
|
||||
angular-aria
|
||||
angular-cookies
|
||||
angular-i18n
|
||||
angular-loader
|
||||
angular-messages
|
||||
angular-mocks
|
||||
angular-route
|
||||
angular-resource
|
||||
|
||||
@@ -2,13 +2,20 @@
|
||||
|
||||
set -e
|
||||
|
||||
export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
|
||||
export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
|
||||
|
||||
if [ $JOB = "unit" ]; then
|
||||
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
|
||||
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11"
|
||||
else
|
||||
BROWSERS="SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11"
|
||||
fi
|
||||
|
||||
grunt test:promises-aplus
|
||||
grunt test:unit --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
|
||||
grunt test:unit --browsers $BROWSERS --reporters dots
|
||||
grunt ci-checks
|
||||
grunt tests:docs --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
|
||||
grunt tests:docs --browsers $BROWSERS --reporters dots
|
||||
grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js"
|
||||
elif [ $JOB = "e2e" ]; then
|
||||
if [ $TEST_TARGET = "jquery" ]; then
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Has to be run from project root directory.
|
||||
|
||||
|
||||
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
|
||||
echo "Using BrowserStack"
|
||||
elif [ "$BROWSER_PROVIDER" == "saucelabs" ]; then
|
||||
echo "Using SauceLabs"
|
||||
else
|
||||
echo "Invalid BROWSER_PROVIDER. Please set env var BROWSER_PROVIDER to 'saucelabs' or 'browserstack'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./lib/${BROWSER_PROVIDER}/start_tunnel.sh
|
||||
@@ -19,6 +19,7 @@
|
||||
"angularModule": false,
|
||||
"nodeName_": false,
|
||||
"uid": false,
|
||||
"toDebugString": false,
|
||||
|
||||
"REGEX_STRING_REGEXP" : false,
|
||||
"lowercase": false,
|
||||
@@ -50,6 +51,7 @@
|
||||
"isWindow": false,
|
||||
"isScope": false,
|
||||
"isFile": false,
|
||||
"isFormData": false,
|
||||
"isBlob": false,
|
||||
"isBoolean": false,
|
||||
"isPromiseLike": false,
|
||||
|
||||
+34
-17
@@ -45,6 +45,7 @@
|
||||
isWindow: true,
|
||||
isScope: true,
|
||||
isFile: true,
|
||||
isFormData: true,
|
||||
isBlob: true,
|
||||
isBoolean: true,
|
||||
isPromiseLike: true,
|
||||
@@ -162,8 +163,8 @@ if ('i' !== 'I'.toLowerCase()) {
|
||||
}
|
||||
|
||||
|
||||
var /** holds major version number for IE or NaN for real browsers */
|
||||
msie,
|
||||
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.
|
||||
jQuery, // delay binding
|
||||
slice = [].slice,
|
||||
@@ -362,7 +363,7 @@ function int(str) {
|
||||
|
||||
|
||||
function inherit(parent, extra) {
|
||||
return extend(new (extend(function() {}, {prototype:parent}))(), extra);
|
||||
return extend(Object.create(parent), extra);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,6 +401,8 @@ noop.$inject = [];
|
||||
return (transformationFn || angular.identity)(value);
|
||||
};
|
||||
```
|
||||
* @param {*} value to be returned.
|
||||
* @returns {*} the value passed in.
|
||||
*/
|
||||
function identity($) {return $;}
|
||||
identity.$inject = [];
|
||||
@@ -566,6 +569,11 @@ function isFile(obj) {
|
||||
}
|
||||
|
||||
|
||||
function isFormData(obj) {
|
||||
return toString.call(obj) === '[object FormData]';
|
||||
}
|
||||
|
||||
|
||||
function isBlob(obj) {
|
||||
return toString.call(obj) === '[object Blob]';
|
||||
}
|
||||
@@ -625,7 +633,7 @@ function makeMap(str) {
|
||||
|
||||
|
||||
function nodeName_(element) {
|
||||
return lowercase(element.nodeName || element[0].nodeName);
|
||||
return lowercase(element.nodeName || (element[0] && element[0].nodeName));
|
||||
}
|
||||
|
||||
function includes(array, obj) {
|
||||
@@ -634,7 +642,7 @@ function includes(array, obj) {
|
||||
|
||||
function arrayRemove(array, value) {
|
||||
var index = array.indexOf(value);
|
||||
if (index >=0)
|
||||
if (index >= 0)
|
||||
array.splice(index, 1);
|
||||
return value;
|
||||
}
|
||||
@@ -649,7 +657,7 @@ function arrayRemove(array, value) {
|
||||
* Creates a deep copy of `source`, which should be an object or an array.
|
||||
*
|
||||
* * If no destination is supplied, a copy of the object or array is created.
|
||||
* * If a destination is provided, all of its elements (for array) or properties (for objects)
|
||||
* * If a destination is provided, all of its elements (for arrays) or properties (for objects)
|
||||
* are deleted and then all elements/properties from the source are copied to it.
|
||||
* * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
|
||||
* * If `source` is identical to 'destination' an exception will be thrown.
|
||||
@@ -835,7 +843,7 @@ function equals(o1, o2) {
|
||||
if (isArray(o1)) {
|
||||
if (!isArray(o2)) return false;
|
||||
if ((length = o1.length) == o2.length) {
|
||||
for (key=0; key<length; key++) {
|
||||
for (key = 0; key < length; key++) {
|
||||
if (!equals(o1[key], o2[key])) return false;
|
||||
}
|
||||
return true;
|
||||
@@ -921,7 +929,7 @@ function bind(self, fn) {
|
||||
return curryArgs.length
|
||||
? function() {
|
||||
return arguments.length
|
||||
? fn.apply(self, curryArgs.concat(slice.call(arguments, 0)))
|
||||
? fn.apply(self, concat(curryArgs, arguments, 0))
|
||||
: fn.apply(self, curryArgs);
|
||||
}
|
||||
: function() {
|
||||
@@ -964,12 +972,16 @@ function toJsonReplacer(key, value) {
|
||||
* stripped since angular uses this notation internally.
|
||||
*
|
||||
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
|
||||
* @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
|
||||
* @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
|
||||
* If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
|
||||
* @returns {string|undefined} JSON-ified string representing `obj`.
|
||||
*/
|
||||
function toJson(obj, pretty) {
|
||||
if (typeof obj === 'undefined') return undefined;
|
||||
return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
|
||||
if (!isNumber(pretty)) {
|
||||
pretty = pretty ? 2 : null;
|
||||
}
|
||||
return JSON.stringify(obj, toJsonReplacer, pretty);
|
||||
}
|
||||
|
||||
|
||||
@@ -983,7 +995,7 @@ function toJson(obj, pretty) {
|
||||
* Deserializes a JSON string.
|
||||
*
|
||||
* @param {string} json JSON string to deserialize.
|
||||
* @returns {Object|Array|string|number} Deserialized thingy.
|
||||
* @returns {Object|Array|string|number} Deserialized JSON string.
|
||||
*/
|
||||
function fromJson(json) {
|
||||
return isString(json)
|
||||
@@ -1121,7 +1133,7 @@ var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
|
||||
function getNgAttribute(element, ngAttr) {
|
||||
var attr, i, ii = ngAttrPrefixes.length;
|
||||
element = jqLite(element);
|
||||
for (i=0; i<ii; ++i) {
|
||||
for (i = 0; i < ii; ++i) {
|
||||
attr = ngAttrPrefixes[i] + ngAttr;
|
||||
if (isString(attr = element.attr(attr))) {
|
||||
return attr;
|
||||
@@ -1156,7 +1168,7 @@ function getNgAttribute(element, ngAttr) {
|
||||
* {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
|
||||
*
|
||||
* You can specify an **AngularJS module** to be used as the root module for the application. This
|
||||
* module will be loaded into the {@link auto.$injector} when the application is bootstrapped and
|
||||
* module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
|
||||
* should contain the application code needed or have dependencies on other modules that will
|
||||
* contain the code. See {@link angular.module} for more information.
|
||||
*
|
||||
@@ -1164,7 +1176,7 @@ function getNgAttribute(element, ngAttr) {
|
||||
* document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
|
||||
* would not be resolved to `3`.
|
||||
*
|
||||
* `ngApp` is the easiest, and most common, way to bootstrap an application.
|
||||
* `ngApp` is the easiest, and most common way to bootstrap an application.
|
||||
*
|
||||
<example module="ngAppDemo">
|
||||
<file name="index.html">
|
||||
@@ -1331,8 +1343,8 @@ function angularInit(element, bootstrap) {
|
||||
* @param {Object=} config an object for defining configuration options for the application. The
|
||||
* following keys are supported:
|
||||
*
|
||||
* - `strictDi`: disable automatic function annotation for the application. This is meant to
|
||||
* assist in finding bugs which break minified code.
|
||||
* * `strictDi` - disable automatic function annotation for the application. This is meant to
|
||||
* assist in finding bugs which break minified code. Defaults to `false`.
|
||||
*
|
||||
* @returns {auto.$injector} Returns the newly created injector for this app.
|
||||
*/
|
||||
@@ -1424,7 +1436,12 @@ function reloadWithDebugInfo() {
|
||||
* @param {DOMElement} element DOM element which is the root of angular application.
|
||||
*/
|
||||
function getTestability(rootElement) {
|
||||
return angular.element(rootElement).injector().get('$$testability');
|
||||
var injector = angular.element(rootElement).injector();
|
||||
if (!injector) {
|
||||
throw ngMinErr('test',
|
||||
'no injector found for element argument to getTestability');
|
||||
}
|
||||
return injector.get('$$testability');
|
||||
}
|
||||
|
||||
var SNAKE_CASE_REGEXP = /[A-Z]/g;
|
||||
|
||||
@@ -83,7 +83,8 @@
|
||||
$TimeoutProvider,
|
||||
$$RAFProvider,
|
||||
$$AsyncCallbackProvider,
|
||||
$WindowProvider
|
||||
$WindowProvider,
|
||||
$$jqLiteProvider
|
||||
*/
|
||||
|
||||
|
||||
@@ -236,7 +237,8 @@ function publishExternalAPI(angular) {
|
||||
$timeout: $TimeoutProvider,
|
||||
$window: $WindowProvider,
|
||||
$$rAF: $$RAFProvider,
|
||||
$$asyncCallback: $$AsyncCallbackProvider
|
||||
$$asyncCallback: $$AsyncCallbackProvider,
|
||||
$$jqLite: $$jqLiteProvider
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
+26
-18
@@ -10,9 +10,10 @@
|
||||
* Creates an injector object that can be used for retrieving services as well as for
|
||||
* dependency injection (see {@link guide/di dependency injection}).
|
||||
*
|
||||
|
||||
* @param {Array.<string|Function>} modules A list of module functions or their aliases. See
|
||||
* {@link angular.module}. The `ng` module must be explicitly added.
|
||||
* {@link angular.module}. The `ng` module must be explicitly added.
|
||||
* @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
|
||||
* disallows argument name annotation inference.
|
||||
* @returns {injector} Injector object. See {@link auto.$injector $injector}.
|
||||
*
|
||||
* @example
|
||||
@@ -158,8 +159,10 @@ function annotate(fn, strictDi, name) {
|
||||
* ## Inference
|
||||
*
|
||||
* In JavaScript calling `toString()` on a function returns the function definition. The definition
|
||||
* can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with
|
||||
* minification, and obfuscation tools since these tools change the argument names.
|
||||
* can then be parsed and the function arguments can be extracted. This method of discovering
|
||||
* annotations is disallowed when the injector is in strict mode.
|
||||
* *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
|
||||
* argument names.
|
||||
*
|
||||
* ## `$inject` Annotation
|
||||
* By adding an `$inject` property onto a function the injection parameters can be specified.
|
||||
@@ -176,6 +179,7 @@ function annotate(fn, strictDi, name) {
|
||||
* Return an instance of the service.
|
||||
*
|
||||
* @param {string} name The name of the instance to retrieve.
|
||||
* @param {string} caller An optional string to provide the origin of the function call for error messages.
|
||||
* @return {*} The instance.
|
||||
*/
|
||||
|
||||
@@ -244,6 +248,8 @@ function annotate(fn, strictDi, name) {
|
||||
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
|
||||
* ```
|
||||
*
|
||||
* You can disallow this method by using strict injection mode.
|
||||
*
|
||||
* This method does not work with code minification / obfuscation. For this reason the following
|
||||
* annotation strategies are supported.
|
||||
*
|
||||
@@ -296,6 +302,8 @@ function annotate(fn, strictDi, name) {
|
||||
* @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
|
||||
* be retrieved as described above.
|
||||
*
|
||||
* @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
|
||||
*
|
||||
* @returns {Array.<string>} The names of the services which the function requires.
|
||||
*/
|
||||
|
||||
@@ -622,14 +630,17 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
}
|
||||
},
|
||||
providerInjector = (providerCache.$injector =
|
||||
createInternalInjector(providerCache, function() {
|
||||
createInternalInjector(providerCache, function(serviceName, caller) {
|
||||
if (angular.isString(caller)) {
|
||||
path.push(caller);
|
||||
}
|
||||
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
|
||||
})),
|
||||
instanceCache = {},
|
||||
instanceInjector = (instanceCache.$injector =
|
||||
createInternalInjector(instanceCache, function(servicename) {
|
||||
var provider = providerInjector.get(servicename + providerSuffix);
|
||||
return instanceInjector.invoke(provider.$get, provider, undefined, servicename);
|
||||
createInternalInjector(instanceCache, function(serviceName, caller) {
|
||||
var provider = providerInjector.get(serviceName + providerSuffix, caller);
|
||||
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
|
||||
}));
|
||||
|
||||
|
||||
@@ -664,7 +675,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
|
||||
function enforceReturnValue(name, factory) {
|
||||
return function enforcedReturnValue() {
|
||||
var result = instanceInjector.invoke(factory, this, undefined, name);
|
||||
var result = instanceInjector.invoke(factory, this);
|
||||
if (isUndefined(result)) {
|
||||
throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
|
||||
}
|
||||
@@ -759,7 +770,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
|
||||
function createInternalInjector(cache, factory) {
|
||||
|
||||
function getService(serviceName) {
|
||||
function getService(serviceName, caller) {
|
||||
if (cache.hasOwnProperty(serviceName)) {
|
||||
if (cache[serviceName] === INSTANTIATING) {
|
||||
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
|
||||
@@ -770,7 +781,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
try {
|
||||
path.unshift(serviceName);
|
||||
cache[serviceName] = INSTANTIATING;
|
||||
return cache[serviceName] = factory(serviceName);
|
||||
return cache[serviceName] = factory(serviceName, caller);
|
||||
} catch (err) {
|
||||
if (cache[serviceName] === INSTANTIATING) {
|
||||
delete cache[serviceName];
|
||||
@@ -802,7 +813,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
args.push(
|
||||
locals && locals.hasOwnProperty(key)
|
||||
? locals[key]
|
||||
: getService(key)
|
||||
: getService(key, serviceName)
|
||||
);
|
||||
}
|
||||
if (isArray(fn)) {
|
||||
@@ -815,14 +826,11 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
}
|
||||
|
||||
function instantiate(Type, locals, serviceName) {
|
||||
var Constructor = function() {},
|
||||
instance, returnedValue;
|
||||
|
||||
// Check if Type is annotated and use just the given function at n-1 as parameter
|
||||
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
|
||||
Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
|
||||
instance = new Constructor();
|
||||
returnedValue = invoke(Type, instance, locals, serviceName);
|
||||
// Object creation: http://jsperf.com/create-constructor/2
|
||||
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
|
||||
var returnedValue = invoke(Type, instance, locals, serviceName);
|
||||
|
||||
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
|
||||
}
|
||||
|
||||
+25
-2
@@ -87,10 +87,12 @@
|
||||
* `'ngModel'`).
|
||||
* - `injector()` - retrieves the injector of the current element or its parent.
|
||||
* - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
|
||||
* element or its parent.
|
||||
* element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
|
||||
* be enabled.
|
||||
* - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
|
||||
* current element. This getter should be used only on elements that contain a directive which starts a new isolate
|
||||
* scope. Calling `scope()` on this element always returns the original non-isolate scope.
|
||||
* Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
|
||||
* - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
|
||||
* parent element is reached.
|
||||
*
|
||||
@@ -597,7 +599,7 @@ forEach({
|
||||
}
|
||||
} else {
|
||||
return (element[name] ||
|
||||
(element.attributes.getNamedItem(name)|| noop).specified)
|
||||
(element.attributes.getNamedItem(name) || noop).specified)
|
||||
? lowercasedName
|
||||
: undefined;
|
||||
}
|
||||
@@ -1001,3 +1003,24 @@ forEach({
|
||||
JQLite.prototype.bind = JQLite.prototype.on;
|
||||
JQLite.prototype.unbind = JQLite.prototype.off;
|
||||
});
|
||||
|
||||
|
||||
// Provider for private $$jqLite service
|
||||
function $$jqLiteProvider() {
|
||||
this.$get = function $$jqLite() {
|
||||
return extend(JQLite, {
|
||||
hasClass: function(node, classes) {
|
||||
if (node.attr) node = node[0];
|
||||
return jqLiteHasClass(node, classes);
|
||||
},
|
||||
addClass: function(node, classes) {
|
||||
if (node.attr) node = node[0];
|
||||
return jqLiteAddClass(node, classes);
|
||||
},
|
||||
removeClass: function(node, classes) {
|
||||
if (node.attr) node = node[0];
|
||||
return jqLiteRemoveClass(node, classes);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
+4
-21
@@ -37,31 +37,14 @@ function minErr(module, ErrorConstructor) {
|
||||
prefix = '[' + (module ? module + ':' : '') + code + '] ',
|
||||
template = arguments[1],
|
||||
templateArgs = arguments,
|
||||
stringify = function(obj) {
|
||||
if (typeof obj === 'function') {
|
||||
return obj.toString().replace(/ \{[\s\S]*$/, '');
|
||||
} else if (typeof obj === 'undefined') {
|
||||
return 'undefined';
|
||||
} else if (typeof obj !== 'string') {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
message, i;
|
||||
|
||||
message = prefix + template.replace(/\{\d+\}/g, function(match) {
|
||||
var index = +match.slice(1, -1), arg;
|
||||
|
||||
if (index + 2 < templateArgs.length) {
|
||||
arg = templateArgs[index + 2];
|
||||
if (typeof arg === 'function') {
|
||||
return arg.toString().replace(/ ?\{[\s\S]*$/, '');
|
||||
} else if (typeof arg === 'undefined') {
|
||||
return 'undefined';
|
||||
} else if (typeof arg !== 'string') {
|
||||
return toJson(arg);
|
||||
}
|
||||
return arg;
|
||||
return toDebugString(templateArgs[index + 2]);
|
||||
}
|
||||
return match;
|
||||
});
|
||||
@@ -69,8 +52,8 @@ function minErr(module, ErrorConstructor) {
|
||||
message = message + '\nhttp://errors.angularjs.org/"NG_VERSION_FULL"/' +
|
||||
(module ? module + '/' : '') + code;
|
||||
for (i = 2; i < arguments.length; i++) {
|
||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
|
||||
encodeURIComponent(stringify(arguments[i]));
|
||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
|
||||
encodeURIComponent(toDebugString(arguments[i]));
|
||||
}
|
||||
return new ErrorConstructor(message);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ function $AnchorScrollProvider() {
|
||||
* @name $anchorScrollProvider#disableAutoScrolling
|
||||
*
|
||||
* @description
|
||||
* By default, {@link ng.$anchorScroll $anchorScroll()} will automatically will detect changes to
|
||||
* By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
|
||||
* {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
|
||||
* Use this method to disable automatic scrolling.
|
||||
*
|
||||
|
||||
+12
-6
@@ -19,8 +19,7 @@
|
||||
/**
|
||||
* @param {object} window The global window object.
|
||||
* @param {object} document jQuery wrapped document.
|
||||
* @param {function()} XHR XMLHttpRequest constructor.
|
||||
* @param {object} $log console.log or an object with the same interface.
|
||||
* @param {object} $log window.console or an object with the same interface.
|
||||
* @param {object} $sniffer $sniffer service
|
||||
*/
|
||||
function Browser(window, document, $log, $sniffer) {
|
||||
@@ -62,6 +61,11 @@ function Browser(window, document, $log, $sniffer) {
|
||||
}
|
||||
}
|
||||
|
||||
function getHash(url) {
|
||||
var index = url.indexOf('#');
|
||||
return index === -1 ? '' : url.substr(index + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Note: this method is used only by scenario runner
|
||||
@@ -171,7 +175,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
// IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
|
||||
// See https://github.com/angular/angular.js/commit/ffb2701
|
||||
if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
|
||||
return;
|
||||
return self;
|
||||
}
|
||||
var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
|
||||
lastBrowserUrl = url;
|
||||
@@ -191,8 +195,10 @@ function Browser(window, document, $log, $sniffer) {
|
||||
}
|
||||
if (replace) {
|
||||
location.replace(url);
|
||||
} else {
|
||||
} else if (!sameBase) {
|
||||
location.href = url;
|
||||
} else {
|
||||
location.hash = getHash(url);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
@@ -370,8 +376,8 @@ function Browser(window, document, $log, $sniffer) {
|
||||
// - 20 cookies per unique domain
|
||||
// - 4096 bytes per cookie
|
||||
if (cookieLength > 4096) {
|
||||
$log.warn("Cookie '"+ name +
|
||||
"' possibly not set or overflowed because it was too large ("+
|
||||
$log.warn("Cookie '" + name +
|
||||
"' possibly not set or overflowed because it was too large (" +
|
||||
cookieLength + " > 4096 bytes)!");
|
||||
}
|
||||
}
|
||||
|
||||
+47
-33
@@ -115,7 +115,7 @@
|
||||
* #### `multiElement`
|
||||
* When this property is set to true, the HTML compiler will collect DOM nodes between
|
||||
* nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
|
||||
* together as the directive elements. It is recomended that this feature be used on directives
|
||||
* together as the directive elements. It is recommended that this feature be used on directives
|
||||
* which are not strictly behavioural (such as {@link ngClick}), and which
|
||||
* do not manipulate or replace child nodes (such as {@link ngInclude}).
|
||||
*
|
||||
@@ -180,7 +180,7 @@
|
||||
*
|
||||
*
|
||||
* #### `bindToController`
|
||||
* When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController` will
|
||||
* When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
|
||||
* allow a component to have its properties bound to the controller, rather than to scope. When the controller
|
||||
* is instantiated, the initial values of the isolate scope bindings are already available.
|
||||
*
|
||||
@@ -803,7 +803,7 @@ function $CompileProvider($provide, $$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 links.
|
||||
* The sanitization is a security measure aimed at preventing XSS attacks via html links.
|
||||
*
|
||||
* 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`
|
||||
@@ -870,7 +870,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
* * `ng-binding` CSS class
|
||||
* * `$binding` data property containing an array of the binding expressions
|
||||
*
|
||||
* You may want to use this in production for a significant performance boost. See
|
||||
* You may want to disable this in production for a significant performance boost. See
|
||||
* {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
|
||||
*
|
||||
* The default value is true.
|
||||
@@ -907,6 +907,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
};
|
||||
|
||||
Attributes.prototype = {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compile.directive.Attributes#$normalize
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
|
||||
* `data-`) to its normalized, camelCase form.
|
||||
*
|
||||
* Also there is special case for Moz prefix starting with upper case letter.
|
||||
*
|
||||
* For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
|
||||
*
|
||||
* @param {string} name Name to normalize
|
||||
*/
|
||||
$normalize: directiveNormalize,
|
||||
|
||||
|
||||
@@ -1029,16 +1044,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// for each tuples
|
||||
var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
|
||||
for (var i=0; i<nbrUrisWith2parts; i++) {
|
||||
var innerIdx = i*2;
|
||||
for (var i = 0; i < nbrUrisWith2parts; i++) {
|
||||
var innerIdx = i * 2;
|
||||
// sanitize the uri
|
||||
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/);
|
||||
var lastTuple = trim(rawUris[i * 2]).split(/\s/);
|
||||
|
||||
// sanitize the last uri
|
||||
result += $$sanitizeUri(trim(lastTuple[0]), true);
|
||||
@@ -1232,7 +1247,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (!node) {
|
||||
return 'html';
|
||||
} else {
|
||||
return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg': 'html';
|
||||
return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1405,7 +1420,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// support ngAttr attribute binding
|
||||
ngAttrName = directiveNormalize(name);
|
||||
if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
|
||||
name = snake_case(ngAttrName.substr(6), '-');
|
||||
name = name.replace(PREFIX_REGEXP, '')
|
||||
.substr(8).replace(/_(.)/g, function(match, letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
|
||||
@@ -2045,7 +2063,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var match = null;
|
||||
if (hasDirectives.hasOwnProperty(name)) {
|
||||
for (var directive, directives = $injector.get(name + Suffix),
|
||||
i = 0, ii = directives.length; i<ii; i++) {
|
||||
i = 0, ii = directives.length; i < ii; i++) {
|
||||
try {
|
||||
directive = directives[i];
|
||||
if ((maxPriority === undefined || maxPriority > directive.priority) &&
|
||||
@@ -2074,7 +2092,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
function directiveIsMultiElement(name) {
|
||||
if (hasDirectives.hasOwnProperty(name)) {
|
||||
for (var directive, directives = $injector.get(name + Suffix),
|
||||
i = 0, ii = directives.length; i<ii; i++) {
|
||||
i = 0, ii = directives.length; i < ii; i++) {
|
||||
directive = directives[i];
|
||||
if (directive.multiElement) {
|
||||
return true;
|
||||
@@ -2227,10 +2245,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var childBoundTranscludeFn = boundTranscludeFn;
|
||||
if (scope.$$destroyed) return;
|
||||
if (linkQueue) {
|
||||
linkQueue.push(scope);
|
||||
linkQueue.push(node);
|
||||
linkQueue.push(rootElement);
|
||||
linkQueue.push(childBoundTranscludeFn);
|
||||
linkQueue.push(scope,
|
||||
node,
|
||||
rootElement,
|
||||
childBoundTranscludeFn);
|
||||
} else {
|
||||
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
|
||||
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
|
||||
@@ -2293,7 +2311,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
case 'svg':
|
||||
case 'math':
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = '<'+type+'>'+template+'</'+type+'>';
|
||||
wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
|
||||
return wrapper.childNodes[0].childNodes;
|
||||
default:
|
||||
return template;
|
||||
@@ -2317,7 +2335,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
|
||||
function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
|
||||
var interpolateFn = $interpolate(value, true);
|
||||
var trustedContext = getTrustedContext(node, name);
|
||||
allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
|
||||
|
||||
var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
|
||||
|
||||
// no interpolation found -> ignore
|
||||
if (!interpolateFn) return;
|
||||
@@ -2342,16 +2363,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
"ng- versions (such as ng-click instead of onclick) instead.");
|
||||
}
|
||||
|
||||
// If the attribute was removed, then we are done
|
||||
if (!attr[name]) {
|
||||
return;
|
||||
// If the attribute has changed since last $interpolate()ed
|
||||
var newValue = attr[name];
|
||||
if (newValue !== value) {
|
||||
// we need to interpolate again since the attribute value has been updated
|
||||
// (e.g. by another directive's compile function)
|
||||
// ensure unset/empty values make interpolateFn falsy
|
||||
interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
|
||||
value = newValue;
|
||||
}
|
||||
|
||||
// we need to interpolate again, in case the attribute value has been updated
|
||||
// (e.g. by another directive's compile function)
|
||||
interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name),
|
||||
ALL_OR_NOTHING_ATTRS[name] || allOrNothing);
|
||||
|
||||
// if attribute was updated so that there is no interpolation going on we don't want to
|
||||
// register any observers
|
||||
if (!interpolateFn) return;
|
||||
@@ -2485,13 +2506,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
|
||||
/**
|
||||
* Converts all accepted directives format into proper directive name.
|
||||
* All of these will become 'myDirective':
|
||||
* my:Directive
|
||||
* my-directive
|
||||
* x-my-directive
|
||||
* data-my:directive
|
||||
*
|
||||
* Also there is special case for Moz prefix starting with upper case letter.
|
||||
* @param name Name to normalize
|
||||
*/
|
||||
function directiveNormalize(name) {
|
||||
|
||||
@@ -59,6 +59,10 @@ function $ControllerProvider() {
|
||||
* * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
|
||||
* `window` object (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
|
||||
* to work correctly.
|
||||
*
|
||||
* @param {Object} locals Injection locals for Controller.
|
||||
* @return {Object} Instance of given controller.
|
||||
*
|
||||
@@ -104,10 +108,10 @@ function $ControllerProvider() {
|
||||
//
|
||||
// This feature is not intended for use by applications, and is thus not documented
|
||||
// publicly.
|
||||
var Constructor = function() {};
|
||||
Constructor.prototype = (isArray(expression) ?
|
||||
// Object creation: http://jsperf.com/create-constructor/2
|
||||
var controllerPrototype = (isArray(expression) ?
|
||||
expression[expression.length - 1] : expression).prototype;
|
||||
instance = new Constructor();
|
||||
instance = Object.create(controllerPrototype);
|
||||
|
||||
if (identifier) {
|
||||
addIdentifier(locals, identifier, instance, constructor || expression.name);
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
* make the link go to the wrong URL if the user clicks it before
|
||||
* Angular has a chance to replace the `{{hash}}` markup with its
|
||||
* value. Until Angular replaces the markup the link will be broken
|
||||
* and will most likely return a 404 error.
|
||||
*
|
||||
* The `ngHref` directive solves this problem.
|
||||
* and will most likely return a 404 error. The `ngHref` directive
|
||||
* solves this problem.
|
||||
*
|
||||
* The wrong way to write it:
|
||||
* ```html
|
||||
|
||||
@@ -471,9 +471,7 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
controller.$setSubmitted();
|
||||
});
|
||||
|
||||
event.preventDefault
|
||||
? event.preventDefault()
|
||||
: event.returnValue = false; // IE
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
|
||||
|
||||
+217
-119
@@ -41,10 +41,16 @@ var inputType = {
|
||||
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
||||
* minlength.
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
||||
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
* patterns defined as scope expressions.
|
||||
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
|
||||
* any length.
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object then this is used directly.
|
||||
* If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
|
||||
* characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
|
||||
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
||||
* interaction with the input element.
|
||||
* @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
|
||||
@@ -584,10 +590,16 @@ var inputType = {
|
||||
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
||||
* minlength.
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
||||
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
* patterns defined as scope expressions.
|
||||
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
|
||||
* any length.
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object then this is used directly.
|
||||
* If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
|
||||
* characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
|
||||
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
||||
* interaction with the input element.
|
||||
*
|
||||
@@ -666,10 +678,16 @@ var inputType = {
|
||||
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
||||
* minlength.
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
||||
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
* patterns defined as scope expressions.
|
||||
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
|
||||
* any length.
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object then this is used directly.
|
||||
* If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
|
||||
* characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
|
||||
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
||||
* interaction with the input element.
|
||||
*
|
||||
@@ -749,10 +767,16 @@ var inputType = {
|
||||
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
||||
* minlength.
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
||||
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
* patterns defined as scope expressions.
|
||||
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
|
||||
* any length.
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object then this is used directly.
|
||||
* If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
|
||||
* characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
|
||||
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
||||
* interaction with the input element.
|
||||
*
|
||||
@@ -930,7 +954,6 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
var placeholder = element[0].placeholder, noevent = {};
|
||||
var type = lowercase(element[0].type);
|
||||
|
||||
// In composition mode, users are still inputing intermediate text buffer,
|
||||
@@ -950,19 +973,14 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
var listener = function(ev) {
|
||||
if (timeout) {
|
||||
$browser.defer.cancel(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
if (composing) return;
|
||||
var value = element.val(),
|
||||
event = ev && ev.type;
|
||||
|
||||
// IE (11 and under) seem to emit an 'input' event if the placeholder value changes.
|
||||
// We don't want to dirty the value when this happens, so we abort here. Unfortunately,
|
||||
// IE also sends input events for other non-input-related things, (such as focusing on a
|
||||
// form control), so this change is not entirely enough to solve this.
|
||||
if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) {
|
||||
placeholder = element[0].placeholder;
|
||||
return;
|
||||
}
|
||||
|
||||
// By default we will trim the value
|
||||
// If the attribute ng-trim exists we will avoid trimming
|
||||
// If input type is 'password', the value is never trimmed
|
||||
@@ -985,11 +1003,13 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
} else {
|
||||
var timeout;
|
||||
|
||||
var deferListener = function(ev) {
|
||||
var deferListener = function(ev, input, origValue) {
|
||||
if (!timeout) {
|
||||
timeout = $browser.defer(function() {
|
||||
listener(ev);
|
||||
timeout = null;
|
||||
if (!input || input.value !== origValue) {
|
||||
listener(ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1001,7 +1021,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
// command modifiers arrows
|
||||
if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
|
||||
|
||||
deferListener(event);
|
||||
deferListener(event, this, this.value);
|
||||
});
|
||||
|
||||
// if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
|
||||
@@ -1015,7 +1035,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
element.on('change', listener);
|
||||
|
||||
ctrl.$render = function() {
|
||||
element.val(ctrl.$isEmpty(ctrl.$modelValue) ? '' : ctrl.$viewValue);
|
||||
element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1063,8 +1083,8 @@ function createDateParser(regexp, mapping) {
|
||||
// When a date is JSON'ified to wraps itself inside of an extra
|
||||
// set of double quotes. This makes the date parsing code unable
|
||||
// to match the date string and parse it as a date.
|
||||
if (iso.charAt(0) == '"' && iso.charAt(iso.length-1) == '"') {
|
||||
iso = iso.substring(1, iso.length-1);
|
||||
if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
|
||||
iso = iso.substring(1, iso.length - 1);
|
||||
}
|
||||
if (ISO_DATE_REGEXP.test(iso)) {
|
||||
return new Date(iso);
|
||||
@@ -1125,10 +1145,10 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
});
|
||||
|
||||
ctrl.$formatters.push(function(value) {
|
||||
if (!ctrl.$isEmpty(value)) {
|
||||
if (!isDate(value)) {
|
||||
throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
|
||||
}
|
||||
if (value && !isDate(value)) {
|
||||
throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
|
||||
}
|
||||
if (isValidDate(value)) {
|
||||
previousDate = value;
|
||||
if (previousDate && timezone === 'UTC') {
|
||||
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
|
||||
@@ -1137,14 +1157,14 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
return $filter('date')(value, format, timezone);
|
||||
} else {
|
||||
previousDate = null;
|
||||
return '';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
if (isDefined(attr.min) || attr.ngMin) {
|
||||
var minVal;
|
||||
ctrl.$validators.min = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(minVal) || parseDate(value) >= minVal;
|
||||
return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
|
||||
};
|
||||
attr.$observe('min', function(val) {
|
||||
minVal = parseObservedDateValue(val);
|
||||
@@ -1155,18 +1175,18 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
if (isDefined(attr.max) || attr.ngMax) {
|
||||
var maxVal;
|
||||
ctrl.$validators.max = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
|
||||
return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
|
||||
};
|
||||
attr.$observe('max', function(val) {
|
||||
maxVal = parseObservedDateValue(val);
|
||||
ctrl.$validate();
|
||||
});
|
||||
}
|
||||
// Override the standard $isEmpty to detect invalid dates as well
|
||||
ctrl.$isEmpty = function(value) {
|
||||
|
||||
function isValidDate(value) {
|
||||
// Invalid Date: getTime() returns NaN
|
||||
return !value || (value.getTime && value.getTime() !== value.getTime());
|
||||
};
|
||||
return value && !(value.getTime && value.getTime() !== value.getTime());
|
||||
}
|
||||
|
||||
function parseObservedDateValue(val) {
|
||||
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
|
||||
@@ -1250,7 +1270,8 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
stringBasedInputType(ctrl);
|
||||
|
||||
ctrl.$$parserName = 'url';
|
||||
ctrl.$validators.url = function(value) {
|
||||
ctrl.$validators.url = function(modelValue, viewValue) {
|
||||
var value = modelValue || viewValue;
|
||||
return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
|
||||
};
|
||||
}
|
||||
@@ -1262,7 +1283,8 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
stringBasedInputType(ctrl);
|
||||
|
||||
ctrl.$$parserName = 'email';
|
||||
ctrl.$validators.email = function(value) {
|
||||
ctrl.$validators.email = function(modelValue, viewValue) {
|
||||
var value = modelValue || viewValue;
|
||||
return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
|
||||
};
|
||||
}
|
||||
@@ -1316,9 +1338,11 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
|
||||
element[0].checked = ctrl.$viewValue;
|
||||
};
|
||||
|
||||
// Override the standard `$isEmpty` because an empty checkbox is never equal to the trueValue
|
||||
// Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
|
||||
// This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
|
||||
// it to a boolean.
|
||||
ctrl.$isEmpty = function(value) {
|
||||
return value !== trueValue;
|
||||
return value === false;
|
||||
};
|
||||
|
||||
ctrl.$formatters.push(function(value) {
|
||||
@@ -1350,7 +1374,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
|
||||
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
||||
* minlength.
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength.
|
||||
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
|
||||
* length.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
||||
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
* patterns defined as scope expressions.
|
||||
@@ -1382,7 +1407,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
|
||||
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
||||
* minlength.
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength.
|
||||
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
|
||||
* length.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
||||
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
* patterns defined as scope expressions.
|
||||
@@ -1511,12 +1537,17 @@ var VALID_CLASS = 'ng-valid',
|
||||
* @property {string} $viewValue Actual string value in the view.
|
||||
* @property {*} $modelValue The value in the model that the control is bound to.
|
||||
* @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
|
||||
the control reads value from the DOM. The functions are called in array order, each passing the value
|
||||
through to the next. The last return value is forwarded to the $validators collection.
|
||||
Used to sanitize / convert the value.
|
||||
Returning undefined from a parser means a parse error occurred. No $validators will
|
||||
run and the 'ngModel' will not be updated until the parse error is resolved. The parse error is stored
|
||||
in 'ngModel.$error.parse'.
|
||||
the control reads value from the DOM. The functions are called in array order, each passing
|
||||
its return value through to the next. The last return value is forwarded to the
|
||||
{@link ngModel.NgModelController#$validators `$validators`} collection.
|
||||
|
||||
Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
|
||||
`$viewValue`}.
|
||||
|
||||
Returning `undefined` from a parser means a parse error occurred. In that case,
|
||||
no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
|
||||
will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
|
||||
is set to `true`. The parse error is stored in `ngModel.$error.parse`.
|
||||
|
||||
*
|
||||
* @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
|
||||
@@ -1593,13 +1624,18 @@ var VALID_CLASS = 'ng-valid',
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* `NgModelController` provides API for the `ng-model` directive. The controller contains
|
||||
* services for data-binding, validation, CSS updates, and value formatting and parsing. It
|
||||
* purposefully does not contain any logic which deals with DOM rendering or listening to
|
||||
* DOM events. Such DOM related logic should be provided by other directives which make use of
|
||||
* `NgModelController` for data-binding.
|
||||
* `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
|
||||
* The controller contains services for data-binding, validation, CSS updates, and value formatting
|
||||
* and parsing. It purposefully does not contain any logic which deals with DOM rendering or
|
||||
* listening to DOM events.
|
||||
* Such DOM related logic should be provided by other directives which make use of
|
||||
* `NgModelController` for data-binding to control elements.
|
||||
* Angular provides this DOM logic for most {@link input `input`} elements.
|
||||
* At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
|
||||
* custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
|
||||
*
|
||||
* ## Custom Control Example
|
||||
* @example
|
||||
* ### Custom Control Example
|
||||
* This example shows how to use `NgModelController` with a custom control to achieve
|
||||
* data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
|
||||
* collaborate together to achieve the desired result.
|
||||
@@ -1641,7 +1677,7 @@ var VALID_CLASS = 'ng-valid',
|
||||
|
||||
// Listen for change events to enable binding
|
||||
element.on('blur keyup change', function() {
|
||||
scope.$apply(read);
|
||||
scope.$evalAsync(read);
|
||||
});
|
||||
read(); // initialize
|
||||
|
||||
@@ -1696,6 +1732,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
|
||||
this.$viewValue = Number.NaN;
|
||||
this.$modelValue = Number.NaN;
|
||||
this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
|
||||
this.$validators = {};
|
||||
this.$asyncValidators = {};
|
||||
this.$parsers = [];
|
||||
@@ -1714,32 +1751,33 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
|
||||
var parsedNgModel = $parse($attr.ngModel),
|
||||
parsedNgModelAssign = parsedNgModel.assign,
|
||||
ngModelGet = parsedNgModel,
|
||||
ngModelSet = parsedNgModelAssign,
|
||||
pendingDebounce = null,
|
||||
ctrl = this;
|
||||
|
||||
var ngModelGet = function ngModelGet() {
|
||||
var modelValue = parsedNgModel($scope);
|
||||
if (ctrl.$options && ctrl.$options.getterSetter && isFunction(modelValue)) {
|
||||
modelValue = modelValue();
|
||||
}
|
||||
return modelValue;
|
||||
};
|
||||
|
||||
var ngModelSet = function ngModelSet(newValue) {
|
||||
var getterSetter;
|
||||
if (ctrl.$options && ctrl.$options.getterSetter &&
|
||||
isFunction(getterSetter = parsedNgModel($scope))) {
|
||||
|
||||
getterSetter(ctrl.$modelValue);
|
||||
} else {
|
||||
parsedNgModel.assign($scope, ctrl.$modelValue);
|
||||
}
|
||||
};
|
||||
|
||||
this.$$setOptions = function(options) {
|
||||
ctrl.$options = options;
|
||||
if (options && options.getterSetter) {
|
||||
var invokeModelGetter = $parse($attr.ngModel + '()'),
|
||||
invokeModelSetter = $parse($attr.ngModel + '($$$p)');
|
||||
|
||||
if (!parsedNgModel.assign && (!options || !options.getterSetter)) {
|
||||
ngModelGet = function($scope) {
|
||||
var modelValue = parsedNgModel($scope);
|
||||
if (isFunction(modelValue)) {
|
||||
modelValue = invokeModelGetter($scope);
|
||||
}
|
||||
return modelValue;
|
||||
};
|
||||
ngModelSet = function($scope, newValue) {
|
||||
if (isFunction(parsedNgModel($scope))) {
|
||||
invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
|
||||
} else {
|
||||
parsedNgModelAssign($scope, ctrl.$modelValue);
|
||||
}
|
||||
};
|
||||
} else if (!parsedNgModel.assign) {
|
||||
throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
|
||||
$attr.ngModel, startingTag($element));
|
||||
}
|
||||
@@ -1772,17 +1810,18 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* @name ngModel.NgModelController#$isEmpty
|
||||
*
|
||||
* @description
|
||||
* This is called when we need to determine if the value of the input is empty.
|
||||
* This is called when we need to determine if the value of an input is empty.
|
||||
*
|
||||
* For instance, the required directive does this to work out if the input has data or not.
|
||||
*
|
||||
* The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
|
||||
*
|
||||
* You can override this for input directives whose concept of being empty is different to the
|
||||
* default. The `checkboxInputType` directive does this because in its case a value of `false`
|
||||
* implies empty.
|
||||
*
|
||||
* @param {*} value Model value to check.
|
||||
* @returns {boolean} True if `value` is empty.
|
||||
* @param {*} value The value of the input to check for emptiness.
|
||||
* @returns {boolean} True if `value` is "empty".
|
||||
*/
|
||||
this.$isEmpty = function(value) {
|
||||
return isUndefined(value) || value === '' || value === null || value !== value;
|
||||
@@ -1833,9 +1872,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* @description
|
||||
* Sets the control to its pristine state.
|
||||
*
|
||||
* This method can be called to remove the 'ng-dirty' class and set the control to its pristine
|
||||
* state (ng-pristine class). A model is considered to be pristine when the model has not been changed
|
||||
* from when first compiled within then form.
|
||||
* This method can be called to remove the `ng-dirty` class and set the control to its pristine
|
||||
* state (`ng-pristine` class). A model is considered to be pristine when the control
|
||||
* has not been changed from when first compiled.
|
||||
*/
|
||||
this.$setPristine = function() {
|
||||
ctrl.$dirty = false;
|
||||
@@ -1844,6 +1883,25 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
$animate.addClass($element, PRISTINE_CLASS);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngModel.NgModelController#$setDirty
|
||||
*
|
||||
* @description
|
||||
* Sets the control to its dirty state.
|
||||
*
|
||||
* This method can be called to remove the `ng-pristine` class and set the control to its dirty
|
||||
* state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
|
||||
* from when first compiled.
|
||||
*/
|
||||
this.$setDirty = function() {
|
||||
ctrl.$dirty = true;
|
||||
ctrl.$pristine = false;
|
||||
$animate.removeClass($element, PRISTINE_CLASS);
|
||||
$animate.addClass($element, DIRTY_CLASS);
|
||||
parentForm.$setDirty();
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngModel.NgModelController#$setUntouched
|
||||
@@ -1851,8 +1909,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* @description
|
||||
* Sets the control to its untouched state.
|
||||
*
|
||||
* This method can be called to remove the 'ng-touched' class and set the control to its
|
||||
* untouched state (ng-untouched class). Upon compilation, a model is set as untouched
|
||||
* This method can be called to remove the `ng-touched` class and set the control to its
|
||||
* untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
|
||||
* by default, however this function can be used to restore that state if the model has
|
||||
* already been touched by the user.
|
||||
*/
|
||||
@@ -1869,10 +1927,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* @description
|
||||
* Sets the control to its touched state.
|
||||
*
|
||||
* This method can be called to remove the 'ng-untouched' class and set the control to its
|
||||
* touched state (ng-touched class). A model is considered to be touched when the user has
|
||||
* first interacted (focussed) on the model input element and then shifted focus away (blurred)
|
||||
* from the input element.
|
||||
* This method can be called to remove the `ng-untouched` class and set the control to its
|
||||
* touched state (`ng-touched` class). A model is considered to be touched when the user has
|
||||
* first focused the control element and then shifted focus away from the control (blur event).
|
||||
*/
|
||||
this.$setTouched = function() {
|
||||
ctrl.$touched = true;
|
||||
@@ -1950,14 +2007,51 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* @name ngModel.NgModelController#$validate
|
||||
*
|
||||
* @description
|
||||
* Runs each of the registered validators (first synchronous validators and then asynchronous validators).
|
||||
* Runs each of the registered validators (first synchronous validators and then
|
||||
* asynchronous validators).
|
||||
* If the validity changes to invalid, the model will be set to `undefined`,
|
||||
* unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
|
||||
* If the validity changes to valid, it will set the model to the last available valid
|
||||
* modelValue, i.e. either the last parsed value or the last value set from the scope.
|
||||
*/
|
||||
this.$validate = function() {
|
||||
// ignore $validate before model is initialized
|
||||
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
|
||||
return;
|
||||
}
|
||||
this.$$parseAndValidate();
|
||||
|
||||
var viewValue = ctrl.$$lastCommittedViewValue;
|
||||
// Note: we use the $$rawModelValue as $modelValue might have been
|
||||
// set to undefined during a view -> model update that found validation
|
||||
// errors. We can't parse the view here, since that could change
|
||||
// the model although neither viewValue nor the model on the scope changed
|
||||
var modelValue = ctrl.$$rawModelValue;
|
||||
|
||||
// Check if the there's a parse error, so we don't unset it accidentially
|
||||
var parserName = ctrl.$$parserName || 'parse';
|
||||
var parserValid = ctrl.$error[parserName] ? false : undefined;
|
||||
|
||||
var prevValid = ctrl.$valid;
|
||||
var prevModelValue = ctrl.$modelValue;
|
||||
|
||||
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
|
||||
|
||||
ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
|
||||
// If there was no change in validity, don't update the model
|
||||
// This prevents changing an invalid modelValue to undefined
|
||||
if (!allowInvalid && prevValid !== allValid) {
|
||||
// Note: Don't check ctrl.$valid here, as we could have
|
||||
// external validators (e.g. calculated on the server),
|
||||
// that just call $setValidity and need the model value
|
||||
// to calculate their validity.
|
||||
ctrl.$modelValue = allValid ? modelValue : undefined;
|
||||
|
||||
if (ctrl.$modelValue !== prevModelValue) {
|
||||
ctrl.$$writeModelToScope();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) {
|
||||
@@ -2076,11 +2170,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
// change to dirty
|
||||
if (ctrl.$pristine) {
|
||||
ctrl.$dirty = true;
|
||||
ctrl.$pristine = false;
|
||||
$animate.removeClass($element, PRISTINE_CLASS);
|
||||
$animate.addClass($element, DIRTY_CLASS);
|
||||
parentForm.$setDirty();
|
||||
this.$setDirty();
|
||||
}
|
||||
this.$$parseAndValidate();
|
||||
};
|
||||
@@ -2101,15 +2191,20 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
}
|
||||
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
|
||||
// ctrl.$modelValue has not been touched yet...
|
||||
ctrl.$modelValue = ngModelGet();
|
||||
ctrl.$modelValue = ngModelGet($scope);
|
||||
}
|
||||
var prevModelValue = ctrl.$modelValue;
|
||||
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
|
||||
ctrl.$$rawModelValue = modelValue;
|
||||
|
||||
if (allowInvalid) {
|
||||
ctrl.$modelValue = modelValue;
|
||||
writeToModelIfNeeded();
|
||||
}
|
||||
ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
|
||||
|
||||
// Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
|
||||
// This can happen if e.g. $setViewValue is called from inside a parser
|
||||
ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
|
||||
if (!allowInvalid) {
|
||||
// Note: Don't check ctrl.$valid here, as we could have
|
||||
// external validators (e.g. calculated on the server),
|
||||
@@ -2128,7 +2223,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
};
|
||||
|
||||
this.$$writeModelToScope = function() {
|
||||
ngModelSet(ctrl.$modelValue);
|
||||
ngModelSet($scope, ctrl.$modelValue);
|
||||
forEach(ctrl.$viewChangeListeners, function(listener) {
|
||||
try {
|
||||
listener();
|
||||
@@ -2224,12 +2319,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
// ng-change executes in apply phase
|
||||
// 4. view should be changed back to 'a'
|
||||
$scope.$watch(function ngModelWatch() {
|
||||
var modelValue = ngModelGet();
|
||||
var modelValue = ngModelGet($scope);
|
||||
|
||||
// if scope model value and ngModel value are out of sync
|
||||
// TODO(perf): why not move this to the action fn?
|
||||
if (modelValue !== ctrl.$modelValue) {
|
||||
ctrl.$modelValue = modelValue;
|
||||
ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
|
||||
|
||||
var formatters = ctrl.$formatters,
|
||||
idx = formatters.length;
|
||||
@@ -2414,7 +2509,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
</file>
|
||||
* </example>
|
||||
*/
|
||||
var ngModelDirective = function() {
|
||||
var ngModelDirective = ['$rootScope', function($rootScope) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: ['ngModel', '^?form', '^?ngModelOptions'],
|
||||
@@ -2458,15 +2553,17 @@ var ngModelDirective = function() {
|
||||
element.on('blur', function(ev) {
|
||||
if (modelCtrl.$touched) return;
|
||||
|
||||
scope.$apply(function() {
|
||||
modelCtrl.$setTouched();
|
||||
});
|
||||
if ($rootScope.$$phase) {
|
||||
scope.$evalAsync(modelCtrl.$setTouched);
|
||||
} else {
|
||||
scope.$apply(modelCtrl.$setTouched);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
|
||||
/**
|
||||
@@ -2555,8 +2652,8 @@ var requiredDirective = function() {
|
||||
if (!ctrl) return;
|
||||
attr.required = true; // force truthy in case we are on non input element
|
||||
|
||||
ctrl.$validators.required = function(value) {
|
||||
return !attr.required || !ctrl.$isEmpty(value);
|
||||
ctrl.$validators.required = function(modelValue, viewValue) {
|
||||
return !attr.required || !ctrl.$isEmpty(viewValue);
|
||||
};
|
||||
|
||||
attr.$observe('required', function() {
|
||||
@@ -2577,7 +2674,7 @@ var patternDirective = function() {
|
||||
var regexp, patternExp = attr.ngPattern || attr.pattern;
|
||||
attr.$observe('pattern', function(regex) {
|
||||
if (isString(regex) && regex.length > 0) {
|
||||
regex = new RegExp(regex);
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
}
|
||||
|
||||
if (regex && !regex.test) {
|
||||
@@ -2605,13 +2702,14 @@ var maxlengthDirective = function() {
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var maxlength = 0;
|
||||
var maxlength = -1;
|
||||
attr.$observe('maxlength', function(value) {
|
||||
maxlength = int(value) || 0;
|
||||
var intVal = int(value);
|
||||
maxlength = isNaN(intVal) ? -1 : intVal;
|
||||
ctrl.$validate();
|
||||
});
|
||||
ctrl.$validators.maxlength = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(modelValue) || viewValue.length <= maxlength;
|
||||
return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength);
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -2630,7 +2728,7 @@ var minlengthDirective = function() {
|
||||
ctrl.$validate();
|
||||
});
|
||||
ctrl.$validators.minlength = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(modelValue) || viewValue.length >= minlength;
|
||||
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -2868,7 +2966,7 @@ var ngValueDirective = function() {
|
||||
* `ngModelOptions` has an effect on the element it's declared on and its descendants.
|
||||
*
|
||||
* @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
|
||||
* - `updateOn`: string specifying which event should be the input bound to. You can set several
|
||||
* - `updateOn`: string specifying which event should the input be bound to. You can set several
|
||||
* events using an space delimited list. There is a special event called `default` that
|
||||
* matches the default events belonging of the control.
|
||||
* - `debounce`: integer value which contains the debounce model update value in milliseconds. A
|
||||
|
||||
@@ -141,12 +141,11 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
|
||||
* @name ngBindHtml
|
||||
*
|
||||
* @description
|
||||
* Creates a binding that will innerHTML the result of evaluating the `expression` into the current
|
||||
* element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link
|
||||
* ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize`
|
||||
* is available, for example, by including {@link ngSanitize} in your module's dependencies (not in
|
||||
* core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to
|
||||
* include "angular-sanitize.js" in your application.
|
||||
* Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
|
||||
* the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
|
||||
* To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
|
||||
* ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
|
||||
* in your module's dependencies, you need to include "angular-sanitize.js" in your application.
|
||||
*
|
||||
* You may also bypass sanitization for values you know are safe. To do so, bind to
|
||||
* an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
|
||||
|
||||
@@ -173,7 +173,9 @@
|
||||
</example>
|
||||
*/
|
||||
var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
|
||||
var BRACE = /{}/g;
|
||||
var BRACE = /{}/g,
|
||||
IS_WHEN = /^when(Minus)?(.+)$/;
|
||||
|
||||
return {
|
||||
restrict: 'EA',
|
||||
link: function(scope, element, attr) {
|
||||
@@ -184,34 +186,44 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
|
||||
whensExpFns = {},
|
||||
startSymbol = $interpolate.startSymbol(),
|
||||
endSymbol = $interpolate.endSymbol(),
|
||||
isWhen = /^when(Minus)?(.+)$/;
|
||||
braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
|
||||
watchRemover = angular.noop,
|
||||
lastCount;
|
||||
|
||||
forEach(attr, function(expression, attributeName) {
|
||||
if (isWhen.test(attributeName)) {
|
||||
whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] =
|
||||
element.attr(attr.$attr[attributeName]);
|
||||
var tmpMatch = IS_WHEN.exec(attributeName);
|
||||
if (tmpMatch) {
|
||||
var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
|
||||
whens[whenKey] = element.attr(attr.$attr[attributeName]);
|
||||
}
|
||||
});
|
||||
forEach(whens, function(expression, key) {
|
||||
whensExpFns[key] =
|
||||
$interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' +
|
||||
offset + endSymbol));
|
||||
whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
|
||||
|
||||
});
|
||||
|
||||
scope.$watch(function ngPluralizeWatch() {
|
||||
var value = parseFloat(scope.$eval(numberExp));
|
||||
scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
|
||||
var count = parseFloat(newVal);
|
||||
var countIsNaN = isNaN(count);
|
||||
|
||||
if (!isNaN(value)) {
|
||||
//if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
|
||||
//check it against pluralization rules in $locale service
|
||||
if (!(value in whens)) value = $locale.pluralCat(value - offset);
|
||||
return whensExpFns[value](scope);
|
||||
} else {
|
||||
return '';
|
||||
if (!countIsNaN && !(count in whens)) {
|
||||
// If an explicit number rule such as 1, 2, 3... is defined, just use it.
|
||||
// Otherwise, check it against pluralization rules in $locale service.
|
||||
count = $locale.pluralCat(count - offset);
|
||||
}
|
||||
|
||||
// If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
|
||||
// In JS `NaN !== NaN`, so we have to exlicitly check.
|
||||
if ((count !== lastCount) && !(countIsNaN && isNaN(lastCount))) {
|
||||
watchRemover();
|
||||
watchRemover = scope.$watch(whensExpFns[count], updateElementText);
|
||||
lastCount = count;
|
||||
}
|
||||
}, function ngPluralizeWatchAction(newVal) {
|
||||
element.text(newVal);
|
||||
});
|
||||
|
||||
function updateElementText(newText) {
|
||||
element.text(newText || '');
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
@@ -257,7 +257,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
var aliasAs = match[3];
|
||||
var trackByExp = match[4];
|
||||
|
||||
match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
|
||||
match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
|
||||
|
||||
if (!match) {
|
||||
throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
|
||||
@@ -366,7 +366,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
});
|
||||
throw ngRepeatMinErr('dupes',
|
||||
"Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
|
||||
expression, trackById, toJson(value));
|
||||
expression, trackById, value);
|
||||
} else {
|
||||
// new never before seen block
|
||||
nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
|
||||
|
||||
@@ -38,17 +38,17 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
*
|
||||
* ### Overriding `.ng-hide`
|
||||
*
|
||||
* By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change
|
||||
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
|
||||
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
|
||||
* class in CSS:
|
||||
*
|
||||
* ```css
|
||||
* .ng-hide {
|
||||
* /* this is just another form of hiding an element */
|
||||
* display:block!important;
|
||||
* position:absolute;
|
||||
* top:-9999px;
|
||||
* left:-9999px;
|
||||
* display: block!important;
|
||||
* position: absolute;
|
||||
* top: -9999px;
|
||||
* left: -9999px;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
@@ -68,13 +68,13 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
* .my-element.ng-hide-add, .my-element.ng-hide-remove {
|
||||
* /* this is required as of 1.3x to properly
|
||||
* apply all styling in a show/hide animation */
|
||||
* transition:0s linear all;
|
||||
* transition: 0s linear all;
|
||||
* }
|
||||
*
|
||||
* .my-element.ng-hide-add-active,
|
||||
* .my-element.ng-hide-remove-active {
|
||||
* /* the transition is defined in the active class */
|
||||
* transition:1s linear all;
|
||||
* transition: 1s linear all;
|
||||
* }
|
||||
*
|
||||
* .my-element.ng-hide-add { ... }
|
||||
@@ -116,29 +116,29 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
</file>
|
||||
<file name="animations.css">
|
||||
.animate-show {
|
||||
line-height:20px;
|
||||
opacity:1;
|
||||
padding:10px;
|
||||
border:1px solid black;
|
||||
background:white;
|
||||
line-height: 20px;
|
||||
opacity: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid black;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.animate-show.ng-hide-add.ng-hide-add-active,
|
||||
.animate-show.ng-hide-remove.ng-hide-remove-active {
|
||||
-webkit-transition:all linear 0.5s;
|
||||
transition:all linear 0.5s;
|
||||
-webkit-transition: all linear 0.5s;
|
||||
transition: all linear 0.5s;
|
||||
}
|
||||
|
||||
.animate-show.ng-hide {
|
||||
line-height:0;
|
||||
opacity:0;
|
||||
padding:0 10px;
|
||||
line-height: 0;
|
||||
opacity: 0;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.check-element {
|
||||
padding:10px;
|
||||
border:1px solid black;
|
||||
background:white;
|
||||
padding: 10px;
|
||||
border: 1px solid black;
|
||||
background: white;
|
||||
}
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
@@ -212,17 +212,17 @@ var ngShowDirective = ['$animate', function($animate) {
|
||||
*
|
||||
* ### Overriding `.ng-hide`
|
||||
*
|
||||
* By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change
|
||||
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
|
||||
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
|
||||
* class in CSS:
|
||||
*
|
||||
* ```css
|
||||
* .ng-hide {
|
||||
* /* this is just another form of hiding an element */
|
||||
* display:block!important;
|
||||
* position:absolute;
|
||||
* top:-9999px;
|
||||
* left:-9999px;
|
||||
* display: block!important;
|
||||
* position: absolute;
|
||||
* top: -9999px;
|
||||
* left: -9999px;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
@@ -239,7 +239,7 @@ var ngShowDirective = ['$animate', function($animate) {
|
||||
* //a working example can be found at the bottom of this page
|
||||
* //
|
||||
* .my-element.ng-hide-add, .my-element.ng-hide-remove {
|
||||
* transition:0.5s linear all;
|
||||
* transition: 0.5s linear all;
|
||||
* }
|
||||
*
|
||||
* .my-element.ng-hide-add { ... }
|
||||
@@ -281,25 +281,25 @@ var ngShowDirective = ['$animate', function($animate) {
|
||||
</file>
|
||||
<file name="animations.css">
|
||||
.animate-hide {
|
||||
-webkit-transition:all linear 0.5s;
|
||||
transition:all linear 0.5s;
|
||||
line-height:20px;
|
||||
opacity:1;
|
||||
padding:10px;
|
||||
border:1px solid black;
|
||||
background:white;
|
||||
-webkit-transition: all linear 0.5s;
|
||||
transition: all linear 0.5s;
|
||||
line-height: 20px;
|
||||
opacity: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid black;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.animate-hide.ng-hide {
|
||||
line-height:0;
|
||||
opacity:0;
|
||||
padding:0 10px;
|
||||
line-height: 0;
|
||||
opacity: 0;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.check-element {
|
||||
padding:10px;
|
||||
border:1px solid black;
|
||||
background:white;
|
||||
padding: 10px;
|
||||
border: 1px solid black;
|
||||
background: white;
|
||||
}
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
<input ng-model="title"><br>
|
||||
<input ng-model="title"> <br/>
|
||||
<textarea ng-model="text"></textarea> <br/>
|
||||
<pane title="{{title}}">{{text}}</pane>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,6 @@ var scriptDirective = ['$templateCache', function($templateCache) {
|
||||
compile: function(element, attr) {
|
||||
if (attr.type == 'text/ng-template') {
|
||||
var templateUrl = attr.id,
|
||||
// IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
|
||||
text = element[0].text;
|
||||
|
||||
$templateCache.put(templateUrl, text);
|
||||
|
||||
+66
-33
@@ -13,14 +13,15 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
*
|
||||
* The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
|
||||
* elements for the `<select>` element using the array or object obtained by evaluating the
|
||||
* `ngOptions` comprehension_expression.
|
||||
* `ngOptions` comprehension expression.
|
||||
*
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
|
||||
* similar result. However, the `ngOptions` provides some benefits such as reducing memory and
|
||||
* similar result. However, `ngOptions` provides some benefits such as reducing memory and
|
||||
* increasing speed by not creating a new scope for each repeated instance, as well as providing
|
||||
* more flexibility in how the `select`'s model is assigned via `select as`. `ngOptions should be
|
||||
* used when the `select` model needs to be bound to a non-string value. This is because an option
|
||||
* element can only be bound to string values at present.
|
||||
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
|
||||
* comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
|
||||
* to a non-string value. This is because an option element can only be bound to string values at
|
||||
* present.
|
||||
*
|
||||
* When an item in the `<select>` menu is selected, the array element or object property
|
||||
* represented by the selected option will be bound to the model identified by the `ngModel`
|
||||
@@ -35,28 +36,51 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
|
||||
* </div>
|
||||
*
|
||||
* ## `select as`
|
||||
* ## `select` **`as`**
|
||||
*
|
||||
* Using `select as` will bind the result of the `select as` expression to the model, but
|
||||
* Using `select` **`as`** will bind the result of the `select` expression to the model, but
|
||||
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
|
||||
* or property name (for object data sources) of the value within the collection. If a `track by` expression
|
||||
* or property name (for object data sources) of the value within the collection. If a **`track by`** expression
|
||||
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
|
||||
*
|
||||
* ### `select as` with `trackexpr`
|
||||
*
|
||||
* Using `select as` together with `trackexpr` is not recommended. Reasoning:
|
||||
* ### `select` **`as`** and **`track by`**
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
|
||||
* </div>
|
||||
*
|
||||
* Consider the following example:
|
||||
*
|
||||
* ```html
|
||||
* <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
* $scope.values = [{
|
||||
* id: 1,
|
||||
* label: 'aLabel',
|
||||
* subItem: { name: 'aSubItem' }
|
||||
* }, {
|
||||
* id: 2,
|
||||
* label: 'bLabel',
|
||||
* subItem: { name: 'bSubItem' }
|
||||
* }];
|
||||
*
|
||||
* $scope.selected = { name: 'aSubItem' };
|
||||
* ```
|
||||
*
|
||||
* With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
|
||||
* of the data source (to `item` in this example). To calculate whether an element is selected, we do the
|
||||
* following:
|
||||
*
|
||||
* 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
|
||||
* 2. Apply **`track by`** to the already selected value in `ngModel`.
|
||||
* In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
|
||||
* value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
|
||||
* a wrong object, the selected element can't be found, `<select>` is always reset to the "not
|
||||
* selected" option.
|
||||
*
|
||||
* - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
|
||||
* values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}],
|
||||
* $scope.selected = {name: 'aSubItem'};
|
||||
* - track by is always applied to `value`, with the purpose of preserving the selection,
|
||||
* (to `item` in this case)
|
||||
* - to calculate whether an item is selected we do the following:
|
||||
* 1. apply `track by` to the values in the array, e.g.
|
||||
* In the example: [1,2]
|
||||
* 2. apply `track by` to the already selected value in `ngModel`:
|
||||
* In the example: this is not possible, as `track by` refers to `item.id`, but the selected
|
||||
* value from `ngModel` is `{name: aSubItem}`.
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
@@ -69,8 +93,10 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* * for array data sources:
|
||||
* * `label` **`for`** `value` **`in`** `array`
|
||||
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
|
||||
* * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
||||
* * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
|
||||
* (for including a filter with `track by`)
|
||||
* * for object data sources:
|
||||
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
@@ -212,7 +238,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
self.removeOption = function(value) {
|
||||
if (this.hasOption(value)) {
|
||||
delete optionsMap[value];
|
||||
if (ngModelCtrl.$viewValue == value) {
|
||||
if (ngModelCtrl.$viewValue === value) {
|
||||
this.renderUnknownOption(value);
|
||||
}
|
||||
}
|
||||
@@ -616,13 +642,14 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
lastElement = null; // start at the beginning
|
||||
for (index = 0, length = optionGroup.length; index < length; index++) {
|
||||
option = optionGroup[index];
|
||||
if ((existingOption = existingOptions[index+1])) {
|
||||
if ((existingOption = existingOptions[index + 1])) {
|
||||
// reuse elements
|
||||
lastElement = existingOption.element;
|
||||
if (existingOption.label !== option.label) {
|
||||
updateLabelMap(labelMap, existingOption.label, false);
|
||||
updateLabelMap(labelMap, option.label, true);
|
||||
lastElement.text(existingOption.label = option.label);
|
||||
lastElement.prop('label', existingOption.label);
|
||||
}
|
||||
if (existingOption.id !== option.id) {
|
||||
lastElement.val(existingOption.id = option.id);
|
||||
@@ -652,6 +679,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
.val(option.id)
|
||||
.prop('selected', option.selected)
|
||||
.attr('selected', option.selected)
|
||||
.prop('label', option.label)
|
||||
.text(option.label);
|
||||
}
|
||||
|
||||
@@ -677,18 +705,23 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
updateLabelMap(labelMap, option.label, false);
|
||||
option.element.remove();
|
||||
}
|
||||
forEach(labelMap, function(count, label) {
|
||||
if (count > 0) {
|
||||
selectCtrl.addOption(label);
|
||||
} else if (count < 0) {
|
||||
selectCtrl.removeOption(label);
|
||||
}
|
||||
});
|
||||
}
|
||||
// remove any excessive OPTGROUPs from select
|
||||
while (optionGroupsCache.length > groupIndex) {
|
||||
optionGroupsCache.pop()[0].element.remove();
|
||||
// remove all the labels in the option group
|
||||
optionGroup = optionGroupsCache.pop();
|
||||
for (index = 1; index < optionGroup.length; ++index) {
|
||||
updateLabelMap(labelMap, optionGroup[index].label, false);
|
||||
}
|
||||
optionGroup[0].element.remove();
|
||||
}
|
||||
forEach(labelMap, function(count, label) {
|
||||
if (count > 0) {
|
||||
selectCtrl.addOption(label);
|
||||
} else if (count < 0) {
|
||||
selectCtrl.removeOption(label);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+106
-98
@@ -14,19 +14,26 @@
|
||||
*
|
||||
* Can be one of:
|
||||
*
|
||||
* - `string`: The string is evaluated as an expression and the resulting value is used for substring match against
|
||||
* the contents of the `array`. All strings or objects with string properties in `array` that contain this string
|
||||
* will be returned. The predicate can be negated by prefixing the string with `!`.
|
||||
* - `string`: The string is used for matching against the contents of the `array`. All strings or
|
||||
* objects with string properties in `array` that match this string will be returned. This also
|
||||
* applies to nested object properties.
|
||||
* The predicate can be negated by prefixing the string with `!`.
|
||||
*
|
||||
* - `Object`: A pattern object can be used to filter specific properties on objects contained
|
||||
* by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
|
||||
* which have property `name` containing "M" and property `phone` containing "1". A special
|
||||
* property name `$` can be used (as in `{$:"text"}`) to accept a match against any
|
||||
* property of the object. That's equivalent to the simple substring match with a `string`
|
||||
* as described above. The predicate can be negated by prefixing the string with `!`.
|
||||
* For Example `{name: "!M"}` predicate will return an array of items which have property `name`
|
||||
* property of the object or its nested object properties. That's equivalent to the simple
|
||||
* substring match with a `string` as described above. The predicate can be negated by prefixing
|
||||
* the string with `!`.
|
||||
* For example `{name: "!M"}` predicate will return an array of items which have property `name`
|
||||
* not containing "M".
|
||||
*
|
||||
* Note that a named property will match properties on the same level only, while the special
|
||||
* `$` property will match properties on the same level or deeper. E.g. an array item like
|
||||
* `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
|
||||
* **will** be matched by `{$: 'John'}`.
|
||||
*
|
||||
* - `function(value, index)`: A predicate function can be used to write arbitrary filters. The
|
||||
* function is called for each element of `array`. The final result is an array of those
|
||||
* elements that the predicate returned true for.
|
||||
@@ -39,10 +46,10 @@
|
||||
*
|
||||
* - `function(actual, expected)`:
|
||||
* The function will be given the object value and the predicate value to compare and
|
||||
* should return true if the item should be included in filtered result.
|
||||
* should return true if both values should be considered equal.
|
||||
*
|
||||
* - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`.
|
||||
* this is essentially strict comparison of expected and actual.
|
||||
* - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
|
||||
* This is essentially strict comparison of expected and actual.
|
||||
*
|
||||
* - `false|undefined`: A short hand for a function which will look for a substring match in case
|
||||
* insensitive way.
|
||||
@@ -119,104 +126,105 @@ function filterFilter() {
|
||||
return function(array, expression, comparator) {
|
||||
if (!isArray(array)) return array;
|
||||
|
||||
var comparatorType = typeof(comparator),
|
||||
predicates = [];
|
||||
var predicateFn;
|
||||
var matchAgainstAnyProp;
|
||||
|
||||
predicates.check = function(value, index) {
|
||||
for (var j = 0; j < predicates.length; j++) {
|
||||
if (!predicates[j](value, index)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (comparatorType !== 'function') {
|
||||
if (comparatorType === 'boolean' && comparator) {
|
||||
comparator = function(obj, text) {
|
||||
return angular.equals(obj, text);
|
||||
};
|
||||
} else {
|
||||
comparator = function(obj, text) {
|
||||
if (obj && text && typeof obj === 'object' && typeof text === 'object') {
|
||||
for (var objKey in obj) {
|
||||
if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
|
||||
comparator(obj[objKey], text[objKey])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
text = (''+text).toLowerCase();
|
||||
return (''+obj).toLowerCase().indexOf(text) > -1;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var search = function(obj, text) {
|
||||
if (typeof text === 'string' && text.charAt(0) === '!') {
|
||||
return !search(obj, text.substr(1));
|
||||
}
|
||||
switch (typeof obj) {
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
return comparator(obj, text);
|
||||
case 'object':
|
||||
switch (typeof text) {
|
||||
case 'object':
|
||||
return comparator(obj, text);
|
||||
default:
|
||||
for (var objKey in obj) {
|
||||
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
case 'array':
|
||||
for (var i = 0; i < obj.length; i++) {
|
||||
if (search(obj[i], text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
switch (typeof expression) {
|
||||
case 'function':
|
||||
predicateFn = expression;
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
// Set up expression object and fall through
|
||||
expression = {$:expression};
|
||||
// jshint -W086
|
||||
matchAgainstAnyProp = true;
|
||||
//jshint -W086
|
||||
case 'object':
|
||||
// jshint +W086
|
||||
for (var key in expression) {
|
||||
(function(path) {
|
||||
if (typeof expression[path] === 'undefined') return;
|
||||
predicates.push(function(value) {
|
||||
return search(path == '$' ? value : (value && value[path]), expression[path]);
|
||||
});
|
||||
})(key);
|
||||
}
|
||||
break;
|
||||
case 'function':
|
||||
predicates.push(expression);
|
||||
//jshint +W086
|
||||
predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
|
||||
break;
|
||||
default:
|
||||
return array;
|
||||
}
|
||||
var filtered = [];
|
||||
for (var j = 0; j < array.length; j++) {
|
||||
var value = array[j];
|
||||
if (predicates.check(value, j)) {
|
||||
filtered.push(value);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
|
||||
return array.filter(predicateFn);
|
||||
};
|
||||
}
|
||||
|
||||
// Helper functions for `filterFilter`
|
||||
function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
|
||||
var predicateFn;
|
||||
|
||||
if (comparator === true) {
|
||||
comparator = equals;
|
||||
} else if (!isFunction(comparator)) {
|
||||
comparator = function(actual, expected) {
|
||||
if (isObject(actual) || isObject(expected)) {
|
||||
// Prevent an object to be considered equal to a string like `'[object'`
|
||||
return false;
|
||||
}
|
||||
|
||||
actual = lowercase('' + actual);
|
||||
expected = lowercase('' + expected);
|
||||
return actual.indexOf(expected) !== -1;
|
||||
};
|
||||
}
|
||||
|
||||
predicateFn = function(item) {
|
||||
if (shouldMatchPrimitives && !isObject(item)) {
|
||||
return deepCompare(item, expression.$, comparator, false);
|
||||
}
|
||||
return deepCompare(item, expression, comparator, matchAgainstAnyProp);
|
||||
};
|
||||
|
||||
return predicateFn;
|
||||
}
|
||||
|
||||
function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
|
||||
var actualType = typeof actual;
|
||||
var expectedType = typeof expected;
|
||||
|
||||
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
|
||||
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
|
||||
} else if (actualType === 'array') {
|
||||
// In case `actual` is an array, consider it a match
|
||||
// if ANY of it's items matches `expected`
|
||||
return actual.some(function(item) {
|
||||
return deepCompare(item, expected, comparator, matchAgainstAnyProp);
|
||||
});
|
||||
}
|
||||
|
||||
switch (actualType) {
|
||||
case 'object':
|
||||
var key;
|
||||
if (matchAgainstAnyProp) {
|
||||
for (key in actual) {
|
||||
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
|
||||
} else if (expectedType === 'object') {
|
||||
for (key in expected) {
|
||||
var expectedVal = expected[key];
|
||||
if (isFunction(expectedVal)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchAnyProperty = key === '$';
|
||||
var actualVal = matchAnyProperty ? actual : actual[key];
|
||||
if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return comparator(actual, expected);
|
||||
}
|
||||
break;
|
||||
case 'function':
|
||||
return false;
|
||||
default:
|
||||
return comparator(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
+29
-25
@@ -11,7 +11,7 @@
|
||||
*
|
||||
* @param {number} amount Input to filter.
|
||||
* @param {string=} symbol Currency symbol or identifier to be displayed.
|
||||
* @param {number=} fractionSize Number of decimal places to round the amount to.
|
||||
* @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
|
||||
* @returns {string} Formatted number.
|
||||
*
|
||||
*
|
||||
@@ -61,8 +61,7 @@ function currencyFilter($locale) {
|
||||
}
|
||||
|
||||
if (isUndefined(fractionSize)) {
|
||||
// TODO: read the default value from the locale file
|
||||
fractionSize = 2;
|
||||
fractionSize = formats.PATTERNS[1].maxFrac;
|
||||
}
|
||||
|
||||
// if null or undefined pass it through
|
||||
@@ -151,7 +150,6 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
if (numStr.indexOf('e') !== -1) {
|
||||
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
|
||||
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
|
||||
numStr = '0';
|
||||
number = 0;
|
||||
} else {
|
||||
formatedText = numStr;
|
||||
@@ -172,10 +170,6 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
|
||||
number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
|
||||
|
||||
if (number === 0) {
|
||||
isNegative = false;
|
||||
}
|
||||
|
||||
var fraction = ('' + number).split(DECIMAL_SEP);
|
||||
var whole = fraction[0];
|
||||
fraction = fraction[1] || '';
|
||||
@@ -187,7 +181,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
if (whole.length >= (lgroup + group)) {
|
||||
pos = whole.length - lgroup;
|
||||
for (i = 0; i < pos; i++) {
|
||||
if ((pos - i)%group === 0 && i !== 0) {
|
||||
if ((pos - i) % group === 0 && i !== 0) {
|
||||
formatedText += groupSep;
|
||||
}
|
||||
formatedText += whole.charAt(i);
|
||||
@@ -195,7 +189,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
}
|
||||
|
||||
for (i = pos; i < whole.length; i++) {
|
||||
if ((whole.length - i)%lgroup === 0 && i !== 0) {
|
||||
if ((whole.length - i) % lgroup === 0 && i !== 0) {
|
||||
formatedText += groupSep;
|
||||
}
|
||||
formatedText += whole.charAt(i);
|
||||
@@ -208,15 +202,19 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
|
||||
if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
|
||||
} else {
|
||||
|
||||
if (fractionSize > 0 && number > -1 && number < 1) {
|
||||
if (fractionSize > 0 && number < 1) {
|
||||
formatedText = number.toFixed(fractionSize);
|
||||
number = parseFloat(formatedText);
|
||||
}
|
||||
}
|
||||
|
||||
parts.push(isNegative ? pattern.negPre : pattern.posPre);
|
||||
parts.push(formatedText);
|
||||
parts.push(isNegative ? pattern.negSuf : pattern.posSuf);
|
||||
if (number === 0) {
|
||||
isNegative = false;
|
||||
}
|
||||
|
||||
parts.push(isNegative ? pattern.negPre : pattern.posPre,
|
||||
formatedText,
|
||||
isNegative ? pattern.negSuf : pattern.posSuf);
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
@@ -358,8 +356,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
|
||||
* * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
|
||||
* * `'a'`: AM/PM marker
|
||||
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
|
||||
* * `'ww'`: ISO-8601 week of year (00-53)
|
||||
* * `'w'`: ISO-8601 week of year (0-53)
|
||||
* * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
|
||||
* * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
|
||||
*
|
||||
* `format` string can also be one of the following predefined
|
||||
* {@link guide/i18n localizable formats}:
|
||||
@@ -435,10 +433,10 @@ function dateFilter($locale) {
|
||||
tzMin = int(match[9] + match[11]);
|
||||
}
|
||||
dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3]));
|
||||
var h = int(match[4]||0) - tzHour;
|
||||
var m = int(match[5]||0) - tzMin;
|
||||
var s = int(match[6]||0);
|
||||
var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000);
|
||||
var h = int(match[4] || 0) - tzHour;
|
||||
var m = int(match[5] || 0) - tzMin;
|
||||
var s = int(match[6] || 0);
|
||||
var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
|
||||
timeSetter.call(date, h, m, s, ms);
|
||||
return date;
|
||||
}
|
||||
@@ -503,25 +501,31 @@ function dateFilter($locale) {
|
||||
* the binding is automatically converted to JSON.
|
||||
*
|
||||
* @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
|
||||
* @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
|
||||
* @returns {string} JSON string.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
<pre>{{ {'name':'value'} | json }}</pre>
|
||||
<pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
|
||||
<pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should jsonify filtered objects', function() {
|
||||
expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
|
||||
expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
|
||||
expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
*/
|
||||
function jsonFilter() {
|
||||
return function(object) {
|
||||
return toJson(object, true);
|
||||
return function(object, spacing) {
|
||||
if (isUndefined(spacing)) {
|
||||
spacing = 2;
|
||||
}
|
||||
return toJson(object, spacing);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -106,8 +106,7 @@ function limitToFilter() {
|
||||
}
|
||||
}
|
||||
|
||||
var out = [],
|
||||
i, n;
|
||||
var i, n;
|
||||
|
||||
// if abs(limit) exceeds maximum length, trim it
|
||||
if (limit > input.length)
|
||||
@@ -119,14 +118,13 @@ function limitToFilter() {
|
||||
i = 0;
|
||||
n = limit;
|
||||
} else {
|
||||
// zero and NaN check on limit - return empty array
|
||||
if (!limit) return [];
|
||||
|
||||
i = input.length + limit;
|
||||
n = input.length;
|
||||
}
|
||||
|
||||
for (; i<n; i++) {
|
||||
out.push(input[i]);
|
||||
}
|
||||
|
||||
return out;
|
||||
return input.slice(i, n);
|
||||
};
|
||||
}
|
||||
|
||||
+34
-13
@@ -119,7 +119,7 @@ orderByFilter.$inject = ['$parse'];
|
||||
function orderByFilter($parse) {
|
||||
return function(array, sortPredicate, reverseOrder) {
|
||||
if (!(isArrayLike(array))) return array;
|
||||
sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
|
||||
sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate];
|
||||
if (sortPredicate.length === 0) { sortPredicate = ['+']; }
|
||||
sortPredicate = sortPredicate.map(function(predicate) {
|
||||
var descending = false, get = predicate || identity;
|
||||
@@ -130,9 +130,7 @@ function orderByFilter($parse) {
|
||||
}
|
||||
if (predicate === '') {
|
||||
// Effectively no predicate was passed so we compare identity
|
||||
return reverseComparator(function(a, b) {
|
||||
return compare(a, b);
|
||||
}, descending);
|
||||
return reverseComparator(compare, descending);
|
||||
}
|
||||
get = $parse(predicate);
|
||||
if (get.constant) {
|
||||
@@ -146,9 +144,7 @@ function orderByFilter($parse) {
|
||||
return compare(get(a),get(b));
|
||||
}, descending);
|
||||
});
|
||||
var arrayCopy = [];
|
||||
for (var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
|
||||
return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
|
||||
return slice.call(array).sort(reverseComparator(comparator, reverseOrder));
|
||||
|
||||
function comparator(o1, o2) {
|
||||
for (var i = 0; i < sortPredicate.length; i++) {
|
||||
@@ -162,15 +158,40 @@ function orderByFilter($parse) {
|
||||
? function(a, b) {return comp(b,a);}
|
||||
: comp;
|
||||
}
|
||||
|
||||
function isPrimitive(value) {
|
||||
switch (typeof value) {
|
||||
case 'number': /* falls through */
|
||||
case 'boolean': /* falls through */
|
||||
case 'string':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function objectToString(value) {
|
||||
if (value === null) return 'null';
|
||||
if (typeof value.valueOf === 'function') {
|
||||
value = value.valueOf();
|
||||
if (isPrimitive(value)) return value;
|
||||
}
|
||||
if (typeof value.toString === 'function') {
|
||||
value = value.toString();
|
||||
if (isPrimitive(value)) return value;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function compare(v1, v2) {
|
||||
var t1 = typeof v1;
|
||||
var t2 = typeof v2;
|
||||
if (t1 == t2) {
|
||||
if (isDate(v1) && isDate(v2)) {
|
||||
v1 = v1.valueOf();
|
||||
v2 = v2.valueOf();
|
||||
}
|
||||
if (t1 == "string") {
|
||||
if (t1 === t2 && t1 === "object") {
|
||||
v1 = objectToString(v1);
|
||||
v2 = objectToString(v2);
|
||||
}
|
||||
if (t1 === t2) {
|
||||
if (t1 === "string") {
|
||||
v1 = v1.toLowerCase();
|
||||
v2 = v2.toLowerCase();
|
||||
}
|
||||
|
||||
+95
-49
@@ -2,23 +2,34 @@
|
||||
|
||||
var APPLICATION_JSON = 'application/json';
|
||||
var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
|
||||
var JSON_START = /^\s*(\[|\{[^\{])/;
|
||||
var JSON_END = /[\}\]]\s*$/;
|
||||
var JSON_START = /^\[|^\{(?!\{)/;
|
||||
var JSON_ENDS = {
|
||||
'[': /]$/,
|
||||
'{': /}$/
|
||||
};
|
||||
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
|
||||
|
||||
function defaultHttpResponseTransform(data, headers) {
|
||||
if (isString(data)) {
|
||||
// strip json vulnerability protection prefix
|
||||
data = data.replace(JSON_PROTECTION_PREFIX, '');
|
||||
var contentType = headers('Content-Type');
|
||||
if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) ||
|
||||
(JSON_START.test(data) && JSON_END.test(data))) {
|
||||
data = fromJson(data);
|
||||
// Strip json vulnerability protection prefix and trim whitespace
|
||||
var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
|
||||
|
||||
if (tempData) {
|
||||
var contentType = headers('Content-Type');
|
||||
if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
|
||||
data = fromJson(tempData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function isJsonLike(str) {
|
||||
var jsonStart = str.match(JSON_START);
|
||||
return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse headers into key value object
|
||||
*
|
||||
@@ -26,7 +37,7 @@ function defaultHttpResponseTransform(data, headers) {
|
||||
* @returns {Object} Parsed headers as key value object
|
||||
*/
|
||||
function parseHeaders(headers) {
|
||||
var parsed = {}, key, val, i;
|
||||
var parsed = createMap(), key, val, i;
|
||||
|
||||
if (!headers) return parsed;
|
||||
|
||||
@@ -63,7 +74,11 @@ function headersGetter(headers) {
|
||||
if (!headersObj) headersObj = parseHeaders(headers);
|
||||
|
||||
if (name) {
|
||||
return headersObj[lowercase(name)] || null;
|
||||
var value = headersObj[lowercase(name)];
|
||||
if (value === void 0) {
|
||||
value = null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
return headersObj;
|
||||
@@ -77,16 +92,17 @@ function headersGetter(headers) {
|
||||
* This function is used for both request and response transforming
|
||||
*
|
||||
* @param {*} data Data to transform.
|
||||
* @param {function(string=)} headers Http headers getter fn.
|
||||
* @param {function(string=)} headers HTTP headers getter fn.
|
||||
* @param {number} status HTTP status code of the response.
|
||||
* @param {(Function|Array.<Function>)} fns Function or an array of functions.
|
||||
* @returns {*} Transformed data.
|
||||
*/
|
||||
function transformData(data, headers, fns) {
|
||||
function transformData(data, headers, status, fns) {
|
||||
if (isFunction(fns))
|
||||
return fns(data, headers);
|
||||
return fns(data, headers, status);
|
||||
|
||||
forEach(fns, function(fn) {
|
||||
data = fn(data, headers);
|
||||
data = fn(data, headers, status);
|
||||
});
|
||||
|
||||
return data;
|
||||
@@ -112,6 +128,11 @@ function $HttpProvider() {
|
||||
*
|
||||
* Object containing default values for all {@link ng.$http $http} requests.
|
||||
*
|
||||
* - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
|
||||
* that will provide the cache for all requests who set their `cache` property to `true`.
|
||||
* If you set the `default.cache = false` then only requests that specify their own custom
|
||||
* cache object will be cached. See {@link $http#caching $http Caching} for more information.
|
||||
*
|
||||
* - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
|
||||
* Defaults value is `'XSRF-TOKEN'`.
|
||||
*
|
||||
@@ -125,6 +146,7 @@ function $HttpProvider() {
|
||||
* - **`defaults.headers.post`**
|
||||
* - **`defaults.headers.put`**
|
||||
* - **`defaults.headers.patch`**
|
||||
*
|
||||
**/
|
||||
var defaults = this.defaults = {
|
||||
// transform incoming response data
|
||||
@@ -132,7 +154,7 @@ function $HttpProvider() {
|
||||
|
||||
// transform outgoing request data
|
||||
transformRequest: [function(d) {
|
||||
return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d;
|
||||
return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
|
||||
}],
|
||||
|
||||
// default headers
|
||||
@@ -339,12 +361,27 @@ function $HttpProvider() {
|
||||
* In addition, you can supply a `headers` property in the config object passed when
|
||||
* calling `$http(config)`, which overrides the defaults without changing them globally.
|
||||
*
|
||||
* To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
|
||||
* Use the `headers` property, setting the desired header to `undefined`. For example:
|
||||
*
|
||||
* ```js
|
||||
* var req = {
|
||||
* method: 'POST',
|
||||
* url: 'http://example.com',
|
||||
* headers: {
|
||||
* 'Content-Type': undefined
|
||||
* },
|
||||
* data: { test: 'test' },
|
||||
* }
|
||||
*
|
||||
* $http(req).success(function(){...}).error(function(){...});
|
||||
* ```
|
||||
*
|
||||
* ## Transforming Requests and Responses
|
||||
*
|
||||
* Both requests and responses can be transformed using transformation functions: `transformRequest`
|
||||
* and `transformResponse`. These properties can be a single function that returns
|
||||
* the transformed value (`{function(data, headersGetter)`) or an array of such transformation functions,
|
||||
* the transformed value (`{function(data, headersGetter, status)`) or an array of such transformation functions,
|
||||
* which allows you to `push` or `unshift` a new transformation function into the transformation chain.
|
||||
*
|
||||
* ### Default Transformations
|
||||
@@ -585,12 +622,14 @@ function $HttpProvider() {
|
||||
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||||
* 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.
|
||||
* See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations}
|
||||
* See {@link ng.$http#overriding-the-default-transformations-per-request
|
||||
* Overriding the Default Transformations}
|
||||
* - **transformResponse** –
|
||||
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||||
* `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
|
||||
* transform function or an array of such functions. The transform function takes the http
|
||||
* response body and headers and returns its transformed (typically deserialized) version.
|
||||
* See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations}
|
||||
* response body, headers and status and returns its transformed (typically deserialized) version.
|
||||
* See {@link ng.$http#overriding-the-default-transformations-per-request
|
||||
* Overriding the Default Transformations}
|
||||
* - **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}, this cache will be used for
|
||||
@@ -711,20 +750,23 @@ function $HttpProvider() {
|
||||
</example>
|
||||
*/
|
||||
function $http(requestConfig) {
|
||||
var config = {
|
||||
|
||||
if (!angular.isObject(requestConfig)) {
|
||||
throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
|
||||
}
|
||||
|
||||
var config = extend({
|
||||
method: 'get',
|
||||
transformRequest: defaults.transformRequest,
|
||||
transformResponse: defaults.transformResponse
|
||||
};
|
||||
var headers = mergeHeaders(requestConfig);
|
||||
}, requestConfig);
|
||||
|
||||
extend(config, requestConfig);
|
||||
config.headers = headers;
|
||||
config.headers = mergeHeaders(requestConfig);
|
||||
config.method = uppercase(config.method);
|
||||
|
||||
var serverRequest = function(config) {
|
||||
headers = config.headers;
|
||||
var reqData = transformData(config.data, headersGetter(headers), config.transformRequest);
|
||||
var headers = config.headers;
|
||||
var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
|
||||
|
||||
// strip content-type if data is undefined
|
||||
if (isUndefined(reqData)) {
|
||||
@@ -740,7 +782,7 @@ function $HttpProvider() {
|
||||
}
|
||||
|
||||
// send request
|
||||
return sendReq(config, reqData, headers).then(transformResponse, transformResponse);
|
||||
return sendReq(config, reqData).then(transformResponse, transformResponse);
|
||||
};
|
||||
|
||||
var chain = [serverRequest, undefined];
|
||||
@@ -785,13 +827,30 @@ function $HttpProvider() {
|
||||
if (!response.data) {
|
||||
resp.data = response.data;
|
||||
} else {
|
||||
resp.data = transformData(response.data, response.headers, config.transformResponse);
|
||||
resp.data = transformData(response.data, response.headers, response.status, config.transformResponse);
|
||||
}
|
||||
return (isSuccess(response.status))
|
||||
? resp
|
||||
: $q.reject(resp);
|
||||
}
|
||||
|
||||
function executeHeaderFns(headers) {
|
||||
var headerContent, processedHeaders = {};
|
||||
|
||||
forEach(headers, function(headerFn, header) {
|
||||
if (isFunction(headerFn)) {
|
||||
headerContent = headerFn();
|
||||
if (headerContent != null) {
|
||||
processedHeaders[header] = headerContent;
|
||||
}
|
||||
} else {
|
||||
processedHeaders[header] = headerFn;
|
||||
}
|
||||
});
|
||||
|
||||
return processedHeaders;
|
||||
}
|
||||
|
||||
function mergeHeaders(config) {
|
||||
var defHeaders = defaults.headers,
|
||||
reqHeaders = extend({}, config.headers),
|
||||
@@ -814,23 +873,7 @@ function $HttpProvider() {
|
||||
}
|
||||
|
||||
// execute if header value is a function for merged headers
|
||||
execHeaders(reqHeaders);
|
||||
return reqHeaders;
|
||||
|
||||
function execHeaders(headers) {
|
||||
var headerContent;
|
||||
|
||||
forEach(headers, function(headerFn, header) {
|
||||
if (isFunction(headerFn)) {
|
||||
headerContent = headerFn();
|
||||
if (headerContent != null) {
|
||||
headers[header] = headerContent;
|
||||
} else {
|
||||
delete headers[header];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return executeHeaderFns(reqHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -973,11 +1016,12 @@ function $HttpProvider() {
|
||||
* !!! ACCESSES CLOSURE VARS:
|
||||
* $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
|
||||
*/
|
||||
function sendReq(config, reqData, reqHeaders) {
|
||||
function sendReq(config, reqData) {
|
||||
var deferred = $q.defer(),
|
||||
promise = deferred.promise,
|
||||
cache,
|
||||
cachedResp,
|
||||
reqHeaders = config.headers,
|
||||
url = buildUrl(config.url, config.params);
|
||||
|
||||
$http.pendingRequests.push(config);
|
||||
@@ -996,8 +1040,7 @@ function $HttpProvider() {
|
||||
if (isDefined(cachedResp)) {
|
||||
if (isPromiseLike(cachedResp)) {
|
||||
// cached request has already been sent, but there is no response yet
|
||||
cachedResp.then(removePendingReq, removePendingReq);
|
||||
return cachedResp;
|
||||
cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
|
||||
} else {
|
||||
// serving from cache
|
||||
if (isArray(cachedResp)) {
|
||||
@@ -1075,6 +1118,9 @@ function $HttpProvider() {
|
||||
});
|
||||
}
|
||||
|
||||
function resolvePromiseWithResult(result) {
|
||||
resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
|
||||
}
|
||||
|
||||
function removePendingReq() {
|
||||
var idx = $http.pendingRequests.indexOf(config);
|
||||
|
||||
@@ -126,7 +126,9 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
|
||||
function completeRequest(callback, status, response, headersString, statusText) {
|
||||
// cancel timeout and subsequent timeout promise resolution
|
||||
timeoutId && $browserDefer.cancel(timeoutId);
|
||||
if (timeoutId !== undefined) {
|
||||
$browserDefer.cancel(timeoutId);
|
||||
}
|
||||
jsonpDone = xhr = null;
|
||||
|
||||
callback(status, response, headersString, statusText);
|
||||
|
||||
@@ -301,7 +301,8 @@ function $InterpolateProvider() {
|
||||
|
||||
function parseStringifyInterceptor(value) {
|
||||
try {
|
||||
return stringify(getValue(value));
|
||||
value = getValue(value);
|
||||
return allOrNothing && !isDefined(value) ? value : stringify(value);
|
||||
} catch (err) {
|
||||
var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
|
||||
err.toString());
|
||||
|
||||
+24
-24
@@ -57,33 +57,33 @@ function $IntervalProvider() {
|
||||
* // Don't start a new fight if we are already fighting
|
||||
* if ( angular.isDefined(stop) ) return;
|
||||
*
|
||||
* stop = $interval(function() {
|
||||
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
|
||||
* $scope.blood_1 = $scope.blood_1 - 3;
|
||||
* $scope.blood_2 = $scope.blood_2 - 4;
|
||||
* } else {
|
||||
* $scope.stopFight();
|
||||
* stop = $interval(function() {
|
||||
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
|
||||
* $scope.blood_1 = $scope.blood_1 - 3;
|
||||
* $scope.blood_2 = $scope.blood_2 - 4;
|
||||
* } else {
|
||||
* $scope.stopFight();
|
||||
* }
|
||||
* }, 100);
|
||||
* };
|
||||
*
|
||||
* $scope.stopFight = function() {
|
||||
* if (angular.isDefined(stop)) {
|
||||
* $interval.cancel(stop);
|
||||
* stop = undefined;
|
||||
* }
|
||||
* }, 100);
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* $scope.stopFight = function() {
|
||||
* if (angular.isDefined(stop)) {
|
||||
* $interval.cancel(stop);
|
||||
* stop = undefined;
|
||||
* }
|
||||
* };
|
||||
* $scope.resetFight = function() {
|
||||
* $scope.blood_1 = 100;
|
||||
* $scope.blood_2 = 120;
|
||||
* };
|
||||
*
|
||||
* $scope.resetFight = function() {
|
||||
* $scope.blood_1 = 100;
|
||||
* $scope.blood_2 = 120;
|
||||
* };
|
||||
*
|
||||
* $scope.$on('$destroy', function() {
|
||||
* // Make sure that the interval is destroyed too
|
||||
* $scope.stopFight();
|
||||
* });
|
||||
* }])
|
||||
* $scope.$on('$destroy', function() {
|
||||
* // Make sure that the interval is destroyed too
|
||||
* $scope.stopFight();
|
||||
* });
|
||||
* }])
|
||||
* // Register the 'myCurrentTime' directive factory method.
|
||||
* // We inject $interval and dateFilter service since the factory method is DI.
|
||||
* .directive('myCurrentTime', ['$interval', 'dateFilter',
|
||||
|
||||
+107
-30
@@ -22,8 +22,8 @@ function encodePath(path) {
|
||||
return segments.join('/');
|
||||
}
|
||||
|
||||
function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
|
||||
var parsedUrl = urlResolve(absoluteUrl, appBase);
|
||||
function parseAbsoluteUrl(absoluteUrl, locationObj) {
|
||||
var parsedUrl = urlResolve(absoluteUrl);
|
||||
|
||||
locationObj.$$protocol = parsedUrl.protocol;
|
||||
locationObj.$$host = parsedUrl.hostname;
|
||||
@@ -31,12 +31,12 @@ function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
|
||||
}
|
||||
|
||||
|
||||
function parseAppUrl(relativeUrl, locationObj, appBase) {
|
||||
function parseAppUrl(relativeUrl, locationObj) {
|
||||
var prefixed = (relativeUrl.charAt(0) !== '/');
|
||||
if (prefixed) {
|
||||
relativeUrl = '/' + relativeUrl;
|
||||
}
|
||||
var match = urlResolve(relativeUrl, appBase);
|
||||
var match = urlResolve(relativeUrl);
|
||||
locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
|
||||
match.pathname.substring(1) : match.pathname);
|
||||
locationObj.$$search = parseKeyValue(match.search);
|
||||
@@ -68,6 +68,10 @@ function stripHash(url) {
|
||||
return index == -1 ? url : url.substr(0, index);
|
||||
}
|
||||
|
||||
function trimEmptyHash(url) {
|
||||
return url.replace(/(#.+)|#$/, '$1');
|
||||
}
|
||||
|
||||
|
||||
function stripFile(url) {
|
||||
return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
|
||||
@@ -91,7 +95,7 @@ function LocationHtml5Url(appBase, basePrefix) {
|
||||
this.$$html5 = true;
|
||||
basePrefix = basePrefix || '';
|
||||
var appBaseNoFile = stripFile(appBase);
|
||||
parseAbsoluteUrl(appBase, this, appBase);
|
||||
parseAbsoluteUrl(appBase, this);
|
||||
|
||||
|
||||
/**
|
||||
@@ -106,7 +110,7 @@ function LocationHtml5Url(appBase, basePrefix) {
|
||||
appBaseNoFile);
|
||||
}
|
||||
|
||||
parseAppUrl(pathUrl, this, appBase);
|
||||
parseAppUrl(pathUrl, this);
|
||||
|
||||
if (!this.$$path) {
|
||||
this.$$path = '/';
|
||||
@@ -169,7 +173,7 @@ function LocationHtml5Url(appBase, basePrefix) {
|
||||
function LocationHashbangUrl(appBase, hashPrefix) {
|
||||
var appBaseNoFile = stripFile(appBase);
|
||||
|
||||
parseAbsoluteUrl(appBase, this, appBase);
|
||||
parseAbsoluteUrl(appBase, this);
|
||||
|
||||
|
||||
/**
|
||||
@@ -179,17 +183,26 @@ function LocationHashbangUrl(appBase, hashPrefix) {
|
||||
*/
|
||||
this.$$parse = function(url) {
|
||||
var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
|
||||
var withoutHashUrl = withoutBaseUrl.charAt(0) == '#'
|
||||
? beginsWith(hashPrefix, withoutBaseUrl)
|
||||
: (this.$$html5)
|
||||
? withoutBaseUrl
|
||||
: '';
|
||||
var withoutHashUrl;
|
||||
|
||||
if (!isString(withoutHashUrl)) {
|
||||
throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url,
|
||||
hashPrefix);
|
||||
if (withoutBaseUrl.charAt(0) === '#') {
|
||||
|
||||
// The rest of the url starts with a hash so we have
|
||||
// got either a hashbang path or a plain hash fragment
|
||||
withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
|
||||
if (isUndefined(withoutHashUrl)) {
|
||||
// There was no hashbang prefix so we just have a hash fragment
|
||||
withoutHashUrl = withoutBaseUrl;
|
||||
}
|
||||
|
||||
} else {
|
||||
// There was no hashbang path nor hash fragment:
|
||||
// If we are in HTML5 mode we use what is left as the path;
|
||||
// Otherwise we ignore what is left
|
||||
withoutHashUrl = this.$$html5 ? withoutBaseUrl : '';
|
||||
}
|
||||
parseAppUrl(withoutHashUrl, this, appBase);
|
||||
|
||||
parseAppUrl(withoutHashUrl, this);
|
||||
|
||||
this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
|
||||
|
||||
@@ -327,6 +340,13 @@ var locationPrototype = {
|
||||
* Return full url representation with all segments encoded according to rules specified in
|
||||
* [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
|
||||
*
|
||||
*
|
||||
* ```js
|
||||
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
|
||||
* var absUrl = $location.absUrl();
|
||||
* // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
|
||||
* ```
|
||||
*
|
||||
* @return {string} full url
|
||||
*/
|
||||
absUrl: locationGetter('$$absUrl'),
|
||||
@@ -342,6 +362,13 @@ var locationPrototype = {
|
||||
*
|
||||
* Change path, search and hash, when called with parameter and return `$location`.
|
||||
*
|
||||
*
|
||||
* ```js
|
||||
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
|
||||
* var url = $location.url();
|
||||
* // => "/some/path?foo=bar&baz=xoxo"
|
||||
* ```
|
||||
*
|
||||
* @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
|
||||
* @return {string} url
|
||||
*/
|
||||
@@ -350,8 +377,8 @@ var locationPrototype = {
|
||||
return this.$$url;
|
||||
|
||||
var match = PATH_MATCH.exec(url);
|
||||
if (match[1]) this.path(decodeURIComponent(match[1]));
|
||||
if (match[2] || match[1]) this.search(match[3] || '');
|
||||
if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
|
||||
if (match[2] || match[1] || url === '') this.search(match[3] || '');
|
||||
this.hash(match[5] || '');
|
||||
|
||||
return this;
|
||||
@@ -366,6 +393,13 @@ var locationPrototype = {
|
||||
*
|
||||
* Return protocol of current url.
|
||||
*
|
||||
*
|
||||
* ```js
|
||||
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
|
||||
* var protocol = $location.protocol();
|
||||
* // => "http"
|
||||
* ```
|
||||
*
|
||||
* @return {string} protocol of current url
|
||||
*/
|
||||
protocol: locationGetter('$$protocol'),
|
||||
@@ -379,6 +413,13 @@ var locationPrototype = {
|
||||
*
|
||||
* Return host of current url.
|
||||
*
|
||||
*
|
||||
* ```js
|
||||
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
|
||||
* var host = $location.host();
|
||||
* // => "example.com"
|
||||
* ```
|
||||
*
|
||||
* @return {string} host of current url.
|
||||
*/
|
||||
host: locationGetter('$$host'),
|
||||
@@ -392,6 +433,13 @@ var locationPrototype = {
|
||||
*
|
||||
* Return port of current url.
|
||||
*
|
||||
*
|
||||
* ```js
|
||||
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
|
||||
* var port = $location.port();
|
||||
* // => 80
|
||||
* ```
|
||||
*
|
||||
* @return {Number} port
|
||||
*/
|
||||
port: locationGetter('$$port'),
|
||||
@@ -410,6 +458,13 @@ var locationPrototype = {
|
||||
* Note: Path should always begin with forward slash (/), this method will add the forward slash
|
||||
* if it is missing.
|
||||
*
|
||||
*
|
||||
* ```js
|
||||
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
|
||||
* var path = $location.path();
|
||||
* // => "/some/path"
|
||||
* ```
|
||||
*
|
||||
* @param {(string|number)=} path New path
|
||||
* @return {string} path
|
||||
*/
|
||||
@@ -435,10 +490,9 @@ var locationPrototype = {
|
||||
* var searchObject = $location.search();
|
||||
* // => {foo: 'bar', baz: 'xoxo'}
|
||||
*
|
||||
*
|
||||
* // set foo to 'yipee'
|
||||
* $location.search('foo', 'yipee');
|
||||
* // => $location
|
||||
* // $location.search() => {foo: 'yipee', baz: 'xoxo'}
|
||||
* ```
|
||||
*
|
||||
* @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
|
||||
@@ -508,6 +562,13 @@ var locationPrototype = {
|
||||
*
|
||||
* Change hash fragment when called with parameter and return `$location`.
|
||||
*
|
||||
*
|
||||
* ```js
|
||||
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
|
||||
* var hash = $location.hash();
|
||||
* // => "hashValue"
|
||||
* ```
|
||||
*
|
||||
* @param {(string|number)=} hash New hash fragment
|
||||
* @return {string} hash
|
||||
*/
|
||||
@@ -727,8 +788,8 @@ function $LocationProvider() {
|
||||
* @param {string=} oldState History state object that was before it was changed.
|
||||
*/
|
||||
|
||||
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
|
||||
function($rootScope, $browser, $sniffer, $rootElement) {
|
||||
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
|
||||
function($rootScope, $browser, $sniffer, $rootElement, $window) {
|
||||
var $location,
|
||||
LocationMode,
|
||||
baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
|
||||
@@ -810,7 +871,7 @@ function $LocationProvider() {
|
||||
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;
|
||||
$window.angular['ff-684208-preventDefault'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -829,11 +890,19 @@ function $LocationProvider() {
|
||||
$rootScope.$evalAsync(function() {
|
||||
var oldUrl = $location.absUrl();
|
||||
var oldState = $location.$$state;
|
||||
var defaultPrevented;
|
||||
|
||||
$location.$$parse(newUrl);
|
||||
$location.$$state = newState;
|
||||
if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
|
||||
newState, oldState).defaultPrevented) {
|
||||
|
||||
defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
|
||||
newState, oldState).defaultPrevented;
|
||||
|
||||
// if the location was changed by a `$locationChangeStart` handler then stop
|
||||
// processing this location change
|
||||
if ($location.absUrl() !== newUrl) return;
|
||||
|
||||
if (defaultPrevented) {
|
||||
$location.$$parse(oldUrl);
|
||||
$location.$$state = oldState;
|
||||
setBrowserUrlWithFallback(oldUrl, false, oldState);
|
||||
@@ -847,23 +916,31 @@ function $LocationProvider() {
|
||||
|
||||
// update browser
|
||||
$rootScope.$watch(function $locationWatch() {
|
||||
var oldUrl = $browser.url();
|
||||
var oldUrl = trimEmptyHash($browser.url());
|
||||
var newUrl = trimEmptyHash($location.absUrl());
|
||||
var oldState = $browser.state();
|
||||
var currentReplace = $location.$$replace;
|
||||
var urlOrStateChanged = oldUrl !== $location.absUrl() ||
|
||||
var urlOrStateChanged = oldUrl !== newUrl ||
|
||||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
|
||||
|
||||
if (initializing || urlOrStateChanged) {
|
||||
initializing = false;
|
||||
|
||||
$rootScope.$evalAsync(function() {
|
||||
if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl,
|
||||
$location.$$state, oldState).defaultPrevented) {
|
||||
var newUrl = $location.absUrl();
|
||||
var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
|
||||
$location.$$state, oldState).defaultPrevented;
|
||||
|
||||
// if the location was changed by a `$locationChangeStart` handler then stop
|
||||
// processing this location change
|
||||
if ($location.absUrl() !== newUrl) return;
|
||||
|
||||
if (defaultPrevented) {
|
||||
$location.$$parse(oldUrl);
|
||||
$location.$$state = oldState;
|
||||
} else {
|
||||
if (urlOrStateChanged) {
|
||||
setBrowserUrlWithFallback($location.absUrl(), currentReplace,
|
||||
setBrowserUrlWithFallback(newUrl, currentReplace,
|
||||
oldState === $location.$$state ? null : $location.$$state);
|
||||
}
|
||||
afterLocationChange(oldUrl, oldState);
|
||||
|
||||
+130
-155
@@ -5,7 +5,7 @@ var $parseMinErr = minErr('$parse');
|
||||
// Sandboxing Angular Expressions
|
||||
// ------------------------------
|
||||
// Angular expressions are generally considered safe because these expressions only have direct
|
||||
// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by
|
||||
// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
|
||||
// obtaining a reference to native JS functions such as the Function constructor.
|
||||
//
|
||||
// As an example, consider the following Angular expression:
|
||||
@@ -14,7 +14,7 @@ var $parseMinErr = minErr('$parse');
|
||||
//
|
||||
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
|
||||
// against the expression language, but not to prevent exploits that were enabled by exposing
|
||||
// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good
|
||||
// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
|
||||
// practice and therefore we are not even trying to protect against interaction with an object
|
||||
// explicitly exposed in this way.
|
||||
//
|
||||
@@ -22,6 +22,8 @@ var $parseMinErr = minErr('$parse');
|
||||
// window or some DOM object that has a reference to window is published onto a Scope.
|
||||
// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
|
||||
// native objects.
|
||||
//
|
||||
// See https://docs.angularjs.org/guide/security
|
||||
|
||||
|
||||
function ensureSafeMemberName(name, fullExpression) {
|
||||
@@ -30,7 +32,7 @@ function ensureSafeMemberName(name, fullExpression) {
|
||||
|| name === "__proto__") {
|
||||
throw $parseMinErr('isecfld',
|
||||
'Attempting to access a disallowed field in Angular expressions! '
|
||||
+'Expression: {0}', fullExpression);
|
||||
+ 'Expression: {0}', fullExpression);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@@ -107,24 +109,24 @@ var OPERATORS = extend(createMap(), {
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return isDefined(b)?b:undefined;},
|
||||
return isDefined(b) ? b : undefined;},
|
||||
'-':function(self, locals, a, b) {
|
||||
a=a(self, locals); b=b(self, locals);
|
||||
return (isDefined(a)?a:0)-(isDefined(b)?b:0);
|
||||
return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0);
|
||||
},
|
||||
'*':function(self, locals, a, b) {return a(self, locals)*b(self, locals);},
|
||||
'/':function(self, locals, a, b) {return a(self, locals)/b(self, locals);},
|
||||
'%':function(self, locals, a, b) {return a(self, locals)%b(self, locals);},
|
||||
'===':function(self, locals, a, b) {return a(self, locals)===b(self, locals);},
|
||||
'!==':function(self, locals, a, b) {return a(self, locals)!==b(self, locals);},
|
||||
'==':function(self, locals, a, b) {return a(self, locals)==b(self, locals);},
|
||||
'!=':function(self, locals, a, b) {return a(self, locals)!=b(self, locals);},
|
||||
'<':function(self, locals, a, b) {return a(self, locals)<b(self, locals);},
|
||||
'>':function(self, locals, a, b) {return a(self, locals)>b(self, locals);},
|
||||
'<=':function(self, locals, a, b) {return a(self, locals)<=b(self, locals);},
|
||||
'>=':function(self, locals, a, b) {return a(self, locals)>=b(self, locals);},
|
||||
'&&':function(self, locals, a, b) {return a(self, locals)&&b(self, locals);},
|
||||
'||':function(self, locals, a, b) {return a(self, locals)||b(self, locals);},
|
||||
'*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);},
|
||||
'/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);},
|
||||
'%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);},
|
||||
'===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);},
|
||||
'!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);},
|
||||
'==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);},
|
||||
'!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);},
|
||||
'<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);},
|
||||
'>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);},
|
||||
'<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);},
|
||||
'>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);},
|
||||
'&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);},
|
||||
'||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);},
|
||||
'!':function(self, locals, a) {return !a(self, locals);},
|
||||
|
||||
//Tokenized as operators but parsed as assignment/filters
|
||||
@@ -150,44 +152,31 @@ Lexer.prototype = {
|
||||
lex: function(text) {
|
||||
this.text = text;
|
||||
this.index = 0;
|
||||
this.ch = undefined;
|
||||
this.tokens = [];
|
||||
|
||||
while (this.index < this.text.length) {
|
||||
this.ch = this.text.charAt(this.index);
|
||||
if (this.is('"\'')) {
|
||||
this.readString(this.ch);
|
||||
} else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) {
|
||||
var ch = this.text.charAt(this.index);
|
||||
if (ch === '"' || ch === "'") {
|
||||
this.readString(ch);
|
||||
} else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
|
||||
this.readNumber();
|
||||
} else if (this.isIdent(this.ch)) {
|
||||
} else if (this.isIdent(ch)) {
|
||||
this.readIdent();
|
||||
} else if (this.is('(){}[].,;:?')) {
|
||||
this.tokens.push({
|
||||
index: this.index,
|
||||
text: this.ch
|
||||
});
|
||||
} else if (this.is(ch, '(){}[].,;:?')) {
|
||||
this.tokens.push({index: this.index, text: ch});
|
||||
this.index++;
|
||||
} else if (this.isWhitespace(this.ch)) {
|
||||
} else if (this.isWhitespace(ch)) {
|
||||
this.index++;
|
||||
} else {
|
||||
var ch2 = this.ch + this.peek();
|
||||
var ch2 = ch + this.peek();
|
||||
var ch3 = ch2 + this.peek(2);
|
||||
var fn = OPERATORS[this.ch];
|
||||
var fn2 = OPERATORS[ch2];
|
||||
var fn3 = OPERATORS[ch3];
|
||||
if (fn3) {
|
||||
this.tokens.push({index: this.index, text: ch3, fn: fn3});
|
||||
this.index += 3;
|
||||
} else if (fn2) {
|
||||
this.tokens.push({index: this.index, text: ch2, fn: fn2});
|
||||
this.index += 2;
|
||||
} else if (fn) {
|
||||
this.tokens.push({
|
||||
index: this.index,
|
||||
text: this.ch,
|
||||
fn: fn
|
||||
});
|
||||
this.index += 1;
|
||||
var op1 = OPERATORS[ch];
|
||||
var op2 = OPERATORS[ch2];
|
||||
var op3 = OPERATORS[ch3];
|
||||
if (op1 || op2 || op3) {
|
||||
var token = op3 ? ch3 : (op2 ? ch2 : ch);
|
||||
this.tokens.push({index: this.index, text: token, operator: true});
|
||||
this.index += token.length;
|
||||
} else {
|
||||
this.throwError('Unexpected next character ', this.index, this.index + 1);
|
||||
}
|
||||
@@ -196,8 +185,8 @@ Lexer.prototype = {
|
||||
return this.tokens;
|
||||
},
|
||||
|
||||
is: function(chars) {
|
||||
return chars.indexOf(this.ch) !== -1;
|
||||
is: function(ch, chars) {
|
||||
return chars.indexOf(ch) !== -1;
|
||||
},
|
||||
|
||||
peek: function(i) {
|
||||
@@ -206,7 +195,7 @@ Lexer.prototype = {
|
||||
},
|
||||
|
||||
isNumber: function(ch) {
|
||||
return ('0' <= ch && ch <= '9');
|
||||
return ('0' <= ch && ch <= '9') && typeof ch === "string";
|
||||
},
|
||||
|
||||
isWhitespace: function(ch) {
|
||||
@@ -259,79 +248,28 @@ Lexer.prototype = {
|
||||
}
|
||||
this.index++;
|
||||
}
|
||||
number = 1 * number;
|
||||
this.tokens.push({
|
||||
index: start,
|
||||
text: number,
|
||||
constant: true,
|
||||
fn: function() { return number; }
|
||||
value: Number(number)
|
||||
});
|
||||
},
|
||||
|
||||
readIdent: function() {
|
||||
var expression = this.text;
|
||||
|
||||
var ident = '';
|
||||
var start = this.index;
|
||||
|
||||
var lastDot, peekIndex, methodName, ch;
|
||||
|
||||
while (this.index < this.text.length) {
|
||||
ch = this.text.charAt(this.index);
|
||||
if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) {
|
||||
if (ch === '.') lastDot = this.index;
|
||||
ident += ch;
|
||||
} else {
|
||||
var ch = this.text.charAt(this.index);
|
||||
if (!(this.isIdent(ch) || this.isNumber(ch))) {
|
||||
break;
|
||||
}
|
||||
this.index++;
|
||||
}
|
||||
|
||||
//check if the identifier ends with . and if so move back one char
|
||||
if (lastDot && ident[ident.length - 1] === '.') {
|
||||
this.index--;
|
||||
ident = ident.slice(0, -1);
|
||||
lastDot = ident.lastIndexOf('.');
|
||||
if (lastDot === -1) {
|
||||
lastDot = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
//check if this is not a method invocation and if it is back out to last dot
|
||||
if (lastDot) {
|
||||
peekIndex = this.index;
|
||||
while (peekIndex < this.text.length) {
|
||||
ch = this.text.charAt(peekIndex);
|
||||
if (ch === '(') {
|
||||
methodName = ident.substr(lastDot - start + 1);
|
||||
ident = ident.substr(0, lastDot - start);
|
||||
this.index = peekIndex;
|
||||
break;
|
||||
}
|
||||
if (this.isWhitespace(ch)) {
|
||||
peekIndex++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.tokens.push({
|
||||
index: start,
|
||||
text: ident,
|
||||
fn: CONSTANTS[ident] || getterFn(ident, this.options, expression)
|
||||
text: this.text.slice(start, this.index),
|
||||
identifier: true
|
||||
});
|
||||
|
||||
if (methodName) {
|
||||
this.tokens.push({
|
||||
index: lastDot,
|
||||
text: '.'
|
||||
});
|
||||
this.tokens.push({
|
||||
index: lastDot + 1,
|
||||
text: methodName
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
readString: function(quote) {
|
||||
@@ -362,9 +300,8 @@ Lexer.prototype = {
|
||||
this.tokens.push({
|
||||
index: start,
|
||||
text: rawString,
|
||||
string: string,
|
||||
constant: true,
|
||||
fn: function() { return string; }
|
||||
value: string
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
@@ -425,16 +362,14 @@ Parser.prototype = {
|
||||
primary = this.arrayDeclaration();
|
||||
} else if (this.expect('{')) {
|
||||
primary = this.object();
|
||||
} else if (this.peek().identifier && this.peek().text in CONSTANTS) {
|
||||
primary = CONSTANTS[this.consume().text];
|
||||
} else if (this.peek().identifier) {
|
||||
primary = this.identifier();
|
||||
} else if (this.peek().constant) {
|
||||
primary = this.constant();
|
||||
} else {
|
||||
var token = this.expect();
|
||||
primary = token.fn;
|
||||
if (!primary) {
|
||||
this.throwError('not a primary expression', token);
|
||||
}
|
||||
if (token.constant) {
|
||||
primary.constant = true;
|
||||
primary.literal = true;
|
||||
}
|
||||
this.throwError('not a primary expression', this.peek());
|
||||
}
|
||||
|
||||
var next, context;
|
||||
@@ -468,8 +403,11 @@ Parser.prototype = {
|
||||
},
|
||||
|
||||
peek: function(e1, e2, e3, e4) {
|
||||
if (this.tokens.length > 0) {
|
||||
var token = this.tokens[0];
|
||||
return this.peekAhead(0, e1, e2, e3, e4);
|
||||
},
|
||||
peekAhead: function(i, e1, e2, e3, e4) {
|
||||
if (this.tokens.length > i) {
|
||||
var token = this.tokens[i];
|
||||
var t = token.text;
|
||||
if (t === e1 || t === e2 || t === e3 || t === e4 ||
|
||||
(!e1 && !e2 && !e3 && !e4)) {
|
||||
@@ -489,12 +427,19 @@ Parser.prototype = {
|
||||
},
|
||||
|
||||
consume: function(e1) {
|
||||
if (!this.expect(e1)) {
|
||||
if (this.tokens.length === 0) {
|
||||
throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
|
||||
}
|
||||
|
||||
var token = this.expect(e1);
|
||||
if (!token) {
|
||||
this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
|
||||
}
|
||||
return token;
|
||||
},
|
||||
|
||||
unaryFn: function(fn, right) {
|
||||
unaryFn: function(op, right) {
|
||||
var fn = OPERATORS[op];
|
||||
return extend(function $parseUnaryFn(self, locals) {
|
||||
return fn(self, locals, right);
|
||||
}, {
|
||||
@@ -503,7 +448,8 @@ Parser.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
binaryFn: function(left, fn, right, isBranching) {
|
||||
binaryFn: function(left, op, right, isBranching) {
|
||||
var fn = OPERATORS[op];
|
||||
return extend(function $parseBinaryFn(self, locals) {
|
||||
return fn(self, locals, left, right);
|
||||
}, {
|
||||
@@ -512,6 +458,28 @@ Parser.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
identifier: function() {
|
||||
var id = this.consume().text;
|
||||
|
||||
//Continue reading each `.identifier` unless it is a method invocation
|
||||
while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) {
|
||||
id += this.consume().text + this.consume().text;
|
||||
}
|
||||
|
||||
return getterFn(id, this.options, this.text);
|
||||
},
|
||||
|
||||
constant: function() {
|
||||
var value = this.consume().value;
|
||||
|
||||
return extend(function $parseConstant() {
|
||||
return value;
|
||||
}, {
|
||||
constant: true,
|
||||
literal: true
|
||||
});
|
||||
},
|
||||
|
||||
statements: function() {
|
||||
var statements = [];
|
||||
while (true) {
|
||||
@@ -543,8 +511,7 @@ Parser.prototype = {
|
||||
},
|
||||
|
||||
filter: function(inputFn) {
|
||||
var token = this.expect();
|
||||
var fn = this.$filter(token.text);
|
||||
var fn = this.$filter(this.consume().text);
|
||||
var argsFn;
|
||||
var args;
|
||||
|
||||
@@ -607,7 +574,7 @@ Parser.prototype = {
|
||||
var token;
|
||||
if ((token = this.expect('?'))) {
|
||||
middle = this.assignment();
|
||||
if ((token = this.expect(':'))) {
|
||||
if (this.consume(':')) {
|
||||
var right = this.assignment();
|
||||
|
||||
return extend(function $parseTernary(self, locals) {
|
||||
@@ -615,9 +582,6 @@ Parser.prototype = {
|
||||
}, {
|
||||
constant: left.constant && middle.constant && right.constant
|
||||
});
|
||||
|
||||
} else {
|
||||
this.throwError('expected :', token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,7 +592,7 @@ Parser.prototype = {
|
||||
var left = this.logicalAND();
|
||||
var token;
|
||||
while ((token = this.expect('||'))) {
|
||||
left = this.binaryFn(left, token.fn, this.logicalAND(), true);
|
||||
left = this.binaryFn(left, token.text, this.logicalAND(), true);
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -636,8 +600,8 @@ Parser.prototype = {
|
||||
logicalAND: function() {
|
||||
var left = this.equality();
|
||||
var token;
|
||||
if ((token = this.expect('&&'))) {
|
||||
left = this.binaryFn(left, token.fn, this.logicalAND(), true);
|
||||
while ((token = this.expect('&&'))) {
|
||||
left = this.binaryFn(left, token.text, this.equality(), true);
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -645,8 +609,8 @@ Parser.prototype = {
|
||||
equality: function() {
|
||||
var left = this.relational();
|
||||
var token;
|
||||
if ((token = this.expect('==','!=','===','!=='))) {
|
||||
left = this.binaryFn(left, token.fn, this.equality());
|
||||
while ((token = this.expect('==','!=','===','!=='))) {
|
||||
left = this.binaryFn(left, token.text, this.relational());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -654,8 +618,8 @@ Parser.prototype = {
|
||||
relational: function() {
|
||||
var left = this.additive();
|
||||
var token;
|
||||
if ((token = this.expect('<', '>', '<=', '>='))) {
|
||||
left = this.binaryFn(left, token.fn, this.relational());
|
||||
while ((token = this.expect('<', '>', '<=', '>='))) {
|
||||
left = this.binaryFn(left, token.text, this.additive());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -664,7 +628,7 @@ Parser.prototype = {
|
||||
var left = this.multiplicative();
|
||||
var token;
|
||||
while ((token = this.expect('+','-'))) {
|
||||
left = this.binaryFn(left, token.fn, this.multiplicative());
|
||||
left = this.binaryFn(left, token.text, this.multiplicative());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -673,7 +637,7 @@ Parser.prototype = {
|
||||
var left = this.unary();
|
||||
var token;
|
||||
while ((token = this.expect('*','/','%'))) {
|
||||
left = this.binaryFn(left, token.fn, this.unary());
|
||||
left = this.binaryFn(left, token.text, this.unary());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -683,26 +647,25 @@ Parser.prototype = {
|
||||
if (this.expect('+')) {
|
||||
return this.primary();
|
||||
} else if ((token = this.expect('-'))) {
|
||||
return this.binaryFn(Parser.ZERO, token.fn, this.unary());
|
||||
return this.binaryFn(Parser.ZERO, token.text, this.unary());
|
||||
} else if ((token = this.expect('!'))) {
|
||||
return this.unaryFn(token.fn, this.unary());
|
||||
return this.unaryFn(token.text, this.unary());
|
||||
} else {
|
||||
return this.primary();
|
||||
}
|
||||
},
|
||||
|
||||
fieldAccess: function(object) {
|
||||
var expression = this.text;
|
||||
var field = this.expect().text;
|
||||
var getter = getterFn(field, this.options, expression);
|
||||
var getter = this.identifier();
|
||||
|
||||
return extend(function $parseFieldAccess(scope, locals, self) {
|
||||
return getter(self || object(scope, locals));
|
||||
var o = self || object(scope, locals);
|
||||
return (o == null) ? undefined : getter(o);
|
||||
}, {
|
||||
assign: function(scope, value, locals) {
|
||||
var o = object(scope, locals);
|
||||
if (!o) object.assign(scope, o = {});
|
||||
return setter(o, field, value, expression);
|
||||
return getter.assign(o, value);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -747,7 +710,7 @@ Parser.prototype = {
|
||||
var args = argsFn.length ? [] : null;
|
||||
|
||||
return function $parseFunctionCall(scope, locals) {
|
||||
var context = contextGetter ? contextGetter(scope, locals) : scope;
|
||||
var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope;
|
||||
var fn = fnGetter(scope, locals, context) || noop;
|
||||
|
||||
if (args) {
|
||||
@@ -760,13 +723,13 @@ Parser.prototype = {
|
||||
ensureSafeObject(context, expressionText);
|
||||
ensureSafeFunction(fn, expressionText);
|
||||
|
||||
// IE stupidity! (IE doesn't have apply for some native functions)
|
||||
// IE doesn't have apply for some native functions
|
||||
var v = fn.apply
|
||||
? fn.apply(context, args)
|
||||
: fn(args[0], args[1], args[2], args[3], args[4]);
|
||||
|
||||
return ensureSafeObject(v, expressionText);
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
// This is used with json array declaration
|
||||
@@ -778,8 +741,7 @@ Parser.prototype = {
|
||||
// Support trailing commas per ES5.1.
|
||||
break;
|
||||
}
|
||||
var elementFn = this.expression();
|
||||
elementFns.push(elementFn);
|
||||
elementFns.push(this.expression());
|
||||
} while (this.expect(','));
|
||||
}
|
||||
this.consume(']');
|
||||
@@ -805,11 +767,16 @@ Parser.prototype = {
|
||||
// Support trailing commas per ES5.1.
|
||||
break;
|
||||
}
|
||||
var token = this.expect();
|
||||
keys.push(token.string || token.text);
|
||||
var token = this.consume();
|
||||
if (token.constant) {
|
||||
keys.push(token.value);
|
||||
} else if (token.identifier) {
|
||||
keys.push(token.text);
|
||||
} else {
|
||||
this.throwError("invalid key", token);
|
||||
}
|
||||
this.consume(':');
|
||||
var value = this.expression();
|
||||
valueFns.push(value);
|
||||
valueFns.push(this.expression());
|
||||
} while (this.expect(','));
|
||||
}
|
||||
this.consume('}');
|
||||
@@ -1251,13 +1218,21 @@ function $ParseProvider() {
|
||||
|
||||
function addInterceptor(parsedExpression, interceptorFn) {
|
||||
if (!interceptorFn) return parsedExpression;
|
||||
var watchDelegate = parsedExpression.$$watchDelegate;
|
||||
|
||||
var fn = function interceptedExpression(scope, locals) {
|
||||
var regularWatch =
|
||||
watchDelegate !== oneTimeLiteralWatchDelegate &&
|
||||
watchDelegate !== oneTimeWatchDelegate;
|
||||
|
||||
var fn = regularWatch ? function regularInterceptedExpression(scope, locals) {
|
||||
var value = parsedExpression(scope, locals);
|
||||
return interceptorFn(value, scope, locals);
|
||||
} : function oneTimeInterceptedExpression(scope, locals) {
|
||||
var value = parsedExpression(scope, locals);
|
||||
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) || interceptorFn.$stateful ? result : value;
|
||||
return isDefined(value) ? result : value;
|
||||
};
|
||||
|
||||
// Propagate $$watchDelegates other then inputsWatchDelegate
|
||||
|
||||
+6
-6
@@ -6,7 +6,11 @@
|
||||
* @requires $rootScope
|
||||
*
|
||||
* @description
|
||||
* A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
|
||||
* A service that helps you run functions asynchronously, and use their return values (or exceptions)
|
||||
* when they are done processing.
|
||||
*
|
||||
* This is an implementation of promises/deferred objects inspired by
|
||||
* [Kris Kowal's Q](https://github.com/kriskowal/q).
|
||||
*
|
||||
* $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
|
||||
* implementations, and the other which resembles ES6 promises to some degree.
|
||||
@@ -142,16 +146,12 @@
|
||||
*
|
||||
* - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
|
||||
*
|
||||
* - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise,
|
||||
* - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
|
||||
* but to do so without modifying the final value. This is useful to release resources or do some
|
||||
* clean-up that needs to be done whether the promise was rejected or resolved. See the [full
|
||||
* specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
|
||||
* more information.
|
||||
*
|
||||
* Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as
|
||||
* property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to
|
||||
* make your code IE8 and Android 2.x compatible.
|
||||
*
|
||||
* # Chaining promises
|
||||
*
|
||||
* Because calling the `then` method of a promise returns a new derived promise, it is easily
|
||||
|
||||
+1
-3
@@ -3,12 +3,10 @@
|
||||
function $$RAFProvider() { //rAF
|
||||
this.$get = ['$window', '$timeout', function($window, $timeout) {
|
||||
var requestAnimationFrame = $window.requestAnimationFrame ||
|
||||
$window.webkitRequestAnimationFrame ||
|
||||
$window.mozRequestAnimationFrame;
|
||||
$window.webkitRequestAnimationFrame;
|
||||
|
||||
var cancelAnimationFrame = $window.cancelAnimationFrame ||
|
||||
$window.webkitCancelAnimationFrame ||
|
||||
$window.mozCancelAnimationFrame ||
|
||||
$window.webkitCancelRequestAnimationFrame;
|
||||
|
||||
var rafSupported = !!requestAnimationFrame;
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
* @description
|
||||
* The root element of Angular application. This is either the element where {@link
|
||||
* ng.directive:ngApp ngApp} was declared or the element passed into
|
||||
* {@link angular.bootstrap}. The element represent the root element of application. It is also the
|
||||
* location where the applications {@link auto.$injector $injector} service gets
|
||||
* published, it can be retrieved using `$rootElement.injector()`.
|
||||
* {@link angular.bootstrap}. The element represents the root element of application. It is also the
|
||||
* location where the application's {@link auto.$injector $injector} service gets
|
||||
* published, and can be retrieved using `$rootElement.injector()`.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
+12
-12
@@ -105,7 +105,6 @@ function $RootScopeProvider() {
|
||||
var child = parent.$new();
|
||||
|
||||
parent.salutation = "Hello";
|
||||
child.name = "World";
|
||||
expect(child.salutation).toEqual('Hello');
|
||||
|
||||
child.salutation = "Welcome";
|
||||
@@ -743,7 +742,7 @@ function $RootScopeProvider() {
|
||||
while (asyncQueue.length) {
|
||||
try {
|
||||
asyncTask = asyncQueue.shift();
|
||||
asyncTask.scope.$eval(asyncTask.expression);
|
||||
asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
|
||||
} catch (e) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
@@ -773,11 +772,11 @@ function $RootScopeProvider() {
|
||||
if (ttl < 5) {
|
||||
logIdx = 4 - ttl;
|
||||
if (!watchLog[logIdx]) watchLog[logIdx] = [];
|
||||
logMsg = (isFunction(watch.exp))
|
||||
? 'fn: ' + (watch.exp.name || watch.exp.toString())
|
||||
: watch.exp;
|
||||
logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
|
||||
watchLog[logIdx].push(logMsg);
|
||||
watchLog[logIdx].push({
|
||||
msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
|
||||
newVal: value,
|
||||
oldVal: last
|
||||
});
|
||||
}
|
||||
} else if (watch === lastDirtyWatch) {
|
||||
// If the most recently dirty watcher is now clean, short circuit since the remaining watchers
|
||||
@@ -810,7 +809,7 @@ function $RootScopeProvider() {
|
||||
throw $rootScopeMinErr('infdig',
|
||||
'{0} $digest() iterations reached. Aborting!\n' +
|
||||
'Watchers fired in the last 5 iterations: {1}',
|
||||
TTL, toJson(watchLog));
|
||||
TTL, watchLog);
|
||||
}
|
||||
|
||||
} while (dirty || asyncQueue.length);
|
||||
@@ -958,8 +957,9 @@ function $RootScopeProvider() {
|
||||
* - `string`: execute using the rules as defined in {@link guide/expression expression}.
|
||||
* - `function(scope)`: execute the function with the current `scope` parameter.
|
||||
*
|
||||
* @param {(object)=} locals Local variables object, useful for overriding values in scope.
|
||||
*/
|
||||
$evalAsync: function(expr) {
|
||||
$evalAsync: function(expr, locals) {
|
||||
// if we are outside of an $digest loop and this is the first time we are scheduling async
|
||||
// task also schedule async auto-flush
|
||||
if (!$rootScope.$$phase && !asyncQueue.length) {
|
||||
@@ -970,7 +970,7 @@ function $RootScopeProvider() {
|
||||
});
|
||||
}
|
||||
|
||||
asyncQueue.push({scope: this, expression: expr});
|
||||
asyncQueue.push({scope: this, expression: expr, locals: locals});
|
||||
},
|
||||
|
||||
$$postDigest: function(fn) {
|
||||
@@ -1161,7 +1161,7 @@ function $RootScopeProvider() {
|
||||
do {
|
||||
namedListeners = scope.$$listeners[name] || empty;
|
||||
event.currentScope = scope;
|
||||
for (i=0, length=namedListeners.length; i<length; i++) {
|
||||
for (i = 0, length = namedListeners.length; i < length; i++) {
|
||||
|
||||
// if listeners were deregistered, defragment the array
|
||||
if (!namedListeners[i]) {
|
||||
@@ -1235,7 +1235,7 @@ function $RootScopeProvider() {
|
||||
while ((current = next)) {
|
||||
event.currentScope = current;
|
||||
listeners = current.$$listeners[name] || [];
|
||||
for (i=0, length = listeners.length; i<length; i++) {
|
||||
for (i = 0, length = listeners.length; i < length; i++) {
|
||||
// if listeners were deregistered, defragment the array
|
||||
if (!listeners[i]) {
|
||||
listeners.splice(i, 1);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user