Compare commits
241 Commits
v1.3.6
...
v1.4.0-beta.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d53e5a38d | |||
| 030a42e79d | |||
| 898714df9e | |||
| 966f6d831f | |||
| 28114faff4 | |||
| cc8755cda6 | |||
| 7b7b56d36d | |||
| 9278ae67c6 | |||
| a509e9aa14 | |||
| 09ee82d84d | |||
| c1cf053f37 | |||
| 2e5a7e52a0 | |||
| 5dfa630555 | |||
| 452d1cd66e | |||
| c6d8512a1d | |||
| 8a2c80ce7f | |||
| 2f3633d68f | |||
| 6c6a4086b7 | |||
| 0d424263ea | |||
| 2003fcf0de | |||
| 3130a82b21 | |||
| 6a38dbfd3c | |||
| 30e5b52344 | |||
| ebde4681bd | |||
| ad68a41e88 | |||
| 1f3ab484a7 | |||
| d9498a173c | |||
| 29ad3b7f36 | |||
| c1500ea775 | |||
| 4bc89bfe6d | |||
| 54097f63d5 | |||
| 2264413beb | |||
| 8a6cbb3c94 | |||
| bfcf9946e1 | |||
| b462f5dcf2 | |||
| bb1c379b36 | |||
| 62d514b069 | |||
| 35498d7045 | |||
| 630b80fc00 | |||
| a80d9449b7 | |||
| dda65e992b | |||
| aa0f64496a | |||
| e61eae1b1f | |||
| 400fbbf1d6 | |||
| 27bf2ce40c | |||
| c69caa7bee | |||
| 8c46919199 | |||
| d729fcf030 | |||
| 0baa17a3b7 | |||
| 2ece1c927b | |||
| 7602cd5e7e | |||
| 560951e988 | |||
| 1735d5e8d5 | |||
| c88b119ef5 | |||
| 6cf6a1b975 | |||
| 76df116574 | |||
| 440c122556 | |||
| f7114d0c1c | |||
| 2958cd308b | |||
| 9f7c5ceba7 | |||
| 2f32614378 | |||
| 8b33de6fd0 | |||
| d40749caab | |||
| e13224b2df | |||
| 36a14e65d1 | |||
| 3228d3b499 | |||
| 6d173aeb5d | |||
| d0ceeaa37e | |||
| 301663e734 | |||
| dca5fa7b81 | |||
| d557875a8d | |||
| b146af1127 | |||
| cea8e75144 | |||
| b3a9bd3ae0 | |||
| 31a5b8353a | |||
| 301e7aae24 | |||
| f2e2b31ece | |||
| 331cac233f | |||
| 7e3557e96b | |||
| d435464c51 | |||
| c2031b1e9e | |||
| d17fbc3862 | |||
| 0db5b21b1d | |||
| c260e73863 | |||
| e5ad6d6ecd | |||
| bd9bc3f828 | |||
| 2b8baf7e10 | |||
| b8e8c5587f | |||
| 37dd419478 | |||
| 8a433f3cc5 | |||
| 1476810a2c | |||
| 833ea05abf | |||
| ca5fcc6f7a | |||
| 2408f2ded5 | |||
| 1bf1a6203c | |||
| b7117afa2f | |||
| 3ae79c0105 | |||
| 2b64f6e318 | |||
| 23c8a90d22 | |||
| 0504395c14 | |||
| 3831de8a05 | |||
| 40abdaf407 | |||
| 3422cbac80 | |||
| 08035545ed | |||
| 9580bc2c2e | |||
| 5a9dde1c27 | |||
| 3a17799098 | |||
| 1a7e9de8d8 | |||
| 299b3e7e01 | |||
| 54cae0f1d0 | |||
| 4af7cdaf4d | |||
| 593b18c66a | |||
| f2e1a930aa | |||
| babc20b43d | |||
| ba90261b75 | |||
| fc21db8a15 | |||
| b4bdec35cb | |||
| 933591d69c | |||
| cf9331ac66 | |||
| 02977c5bab | |||
| 9f5ac048d7 | |||
| 408f89d8e6 | |||
| 7fda214c4f | |||
| eb6cb785df | |||
| aa798f1236 | |||
| 5a60302389 | |||
| 034fade3e8 | |||
| e24f22bdb1 | |||
| 371c1e19d8 | |||
| b5e00cf615 | |||
| 5765061652 | |||
| b146cae02c | |||
| 3353afbb59 | |||
| 40cb57c8f6 | |||
| f06f28e018 | |||
| f3b088a4e4 | |||
| ef1a9d2cda | |||
| 9c9c6b3fe4 | |||
| 51d6774286 | |||
| e079111b33 | |||
| e1132f53b0 | |||
| ab4b632dbf | |||
| 1b704071c8 | |||
| 647d93338f | |||
| 1334b8c832 | |||
| d2a9a163fb | |||
| e24d968276 | |||
| a01ce6b81c | |||
| c66b4b6a13 | |||
| 66ceecc295 | |||
| 349742b3f0 | |||
| 2ff7edfdd1 | |||
| 1e5e527c84 | |||
| 1c76bf7e94 | |||
| 6018f5da3f | |||
| 3616b9b07c | |||
| d224fe8172 | |||
| e9bf93d510 | |||
| 2e721a7914 | |||
| 1eb6036d29 | |||
| 4836dacae6 | |||
| 0e2ac3cd70 | |||
| 0f9fd2f642 | |||
| 3e42b22b0e | |||
| deb3cb4dae | |||
| b43fa3bb30 | |||
| 521c12c265 | |||
| 7f5051bb2a | |||
| 25623b709f | |||
| e4f23c4d25 | |||
| 8928d02345 | |||
| 5bb2636aac | |||
| c95e38c603 | |||
| a3c3bf3332 | |||
| 2caec44632 | |||
| eae848a712 | |||
| 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 |
@@ -0,0 +1,5 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# JS files must always use LF for tools to work
|
||||
*.js eol=lf
|
||||
+9
-2
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"excludeFiles": ["src/ngLocale/**"],
|
||||
"disallowKeywords": ["with"],
|
||||
"disallowKeywordsOnNewLine": ["else"],
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowMultipleLineStrings": true,
|
||||
"disallowNewlineBeforeBlockStatements": true,
|
||||
@@ -11,6 +12,7 @@
|
||||
"disallowSpacesInAnonymousFunctionExpression": {
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
"disallowSpacesInCallExpression": true,
|
||||
"disallowSpacesInFunctionDeclaration": {
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
@@ -18,6 +20,11 @@
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
"disallowSpacesInsideArrayBrackets": true,
|
||||
"requireSpaceBeforeKeywords": [
|
||||
"else",
|
||||
"while",
|
||||
"catch"
|
||||
],
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
"disallowTrailingComma": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
@@ -33,9 +40,9 @@
|
||||
"afterConsequent": true,
|
||||
"beforeAlternate": true
|
||||
},
|
||||
"requireSpacesInForStatement": true,
|
||||
"requireSpacesInFunction": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
"validateLineBreaks": "LF",
|
||||
"validateParameterSeparator": ", "
|
||||
"validateLineBreaks": "LF"
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,10 +4,10 @@
|
||||
// that correct the existing code base issues and make the new check pass.
|
||||
|
||||
{
|
||||
"validateParameterSeparator": ", ", // Re-assert this rule when JSCS allows multiple spaces
|
||||
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
|
||||
"disallowImplicitTypeConversion": ["string"],
|
||||
"disallowMultipleLineBreaks": true,
|
||||
"disallowKeywordsOnNewLine": ["else"],
|
||||
"validateJSDoc": {
|
||||
"checkParamNames": true,
|
||||
"requireParamTypes": true
|
||||
|
||||
@@ -9,8 +9,13 @@ branches:
|
||||
env:
|
||||
matrix:
|
||||
- JOB=unit BROWSER_PROVIDER=saucelabs
|
||||
- JOB=docs-e2e 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=docs-e2e 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
|
||||
@@ -19,6 +24,10 @@ env:
|
||||
- LOGS_DIR=/tmp/angular-build/logs
|
||||
- BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: "JOB=unit BROWSER_PROVIDER=browserstack"
|
||||
|
||||
install:
|
||||
# - npm config set registry http://23.251.144.68
|
||||
# Disable the spinner, it looks bad on Travis
|
||||
|
||||
+606
@@ -1,3 +1,609 @@
|
||||
<a name="1.4.0-beta.3"></a>
|
||||
# 1.4.0-beta.3 substance-mimicry (2015-02-02)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- do not initialize optional '&' binding if attribute not specified
|
||||
([6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
|
||||
[#6404](https://github.com/angular/angular.js/issues/6404), [#9216](https://github.com/angular/angular.js/issues/9216))
|
||||
- respect return value from controller constructor
|
||||
([62d514b0](https://github.com/angular/angular.js/commit/62d514b06937cc7dd86e973ea11165c88343b42d))
|
||||
- **$controller:** throw better error when controller expression is bad
|
||||
([dda65e99](https://github.com/angular/angular.js/commit/dda65e992b72044c0fa0c8f5f33184028c0e3ad7),
|
||||
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
|
||||
- **$parse:**
|
||||
- handle null targets at assign
|
||||
([2e5a7e52](https://github.com/angular/angular.js/commit/2e5a7e52a0385575bbb55a801471b009afafeca3))
|
||||
- remove references to last arguments to a fn call
|
||||
([e61eae1b](https://github.com/angular/angular.js/commit/e61eae1b1f2351c51bcfe4142749a4e68a2806ff),
|
||||
[#10894](https://github.com/angular/angular.js/issues/10894))
|
||||
- **a:** don't reload if there is only a name attribute
|
||||
([d729fcf0](https://github.com/angular/angular.js/commit/d729fcf030be1d3ef37196d36ea3bf3249ee3318),
|
||||
[#6273](https://github.com/angular/angular.js/issues/6273), [#10880](https://github.com/angular/angular.js/issues/10880))
|
||||
- **angular.copy:** support copying `TypedArray`s
|
||||
([aa0f6449](https://github.com/angular/angular.js/commit/aa0f64496a66d2a5d1a4d033f2eb075a8b084a78),
|
||||
[#10745](https://github.com/angular/angular.js/issues/10745))
|
||||
- **filter:** format timezone correctly in the case that UTC timezone is used
|
||||
([8c469191](https://github.com/angular/angular.js/commit/8c46919199090a05634789774124b38983430c76),
|
||||
[#9359](https://github.com/angular/angular.js/issues/9359))
|
||||
- **ngRoute:** dont duplicate optional params into query
|
||||
([27bf2ce4](https://github.com/angular/angular.js/commit/27bf2ce40c5adfb1494d69c9d0ac9cf433834a12),
|
||||
[#10689](https://github.com/angular/angular.js/issues/10689))
|
||||
- **ngScenario:** allow ngScenario to handle lazy-loaded and manually bootstrapped applications
|
||||
([c69caa7b](https://github.com/angular/angular.js/commit/c69caa7beee4e920f8f587eb3e943be99864a14f),
|
||||
[#10723](https://github.com/angular/angular.js/issues/10723))
|
||||
- **validators:** maxlength should use viewValue for $isEmpty
|
||||
([bfcf9946](https://github.com/angular/angular.js/commit/bfcf9946e16d21b55dde50d4d21c71c898b10215),
|
||||
[#10898](https://github.com/angular/angular.js/issues/10898))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** allow using bindToController as object, support both new/isolate scopes
|
||||
([35498d70](https://github.com/angular/angular.js/commit/35498d7045ba9138016464a344e2c145ce5264c1),
|
||||
[#10420](https://github.com/angular/angular.js/issues/10420), [#10467](https://github.com/angular/angular.js/issues/10467))
|
||||
- **filter:** support conversion to timezone other than UTC
|
||||
([c6d8512a](https://github.com/angular/angular.js/commit/c6d8512a1d7345516d1bd9a039d81821b9518bff),
|
||||
[#10858](https://github.com/angular/angular.js/issues/10858))
|
||||
- **ngMocks:** cleanup $inject annotations after each test
|
||||
([0baa17a3](https://github.com/angular/angular.js/commit/0baa17a3b7ad2b242df2b277b81cebdf75b04287))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$scope:** Add a property $$watchersCount to scope
|
||||
([c1500ea7](https://github.com/angular/angular.js/commit/c1500ea775c4cb130088b7d5bb5fb872bda50bae))
|
||||
- **$parse** new and more performant parser
|
||||
([0d42426](https://github.com/angular/angular.js/commit/0d424263ead16635afb582affab2b147f8e71626))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
|
||||
Previously, '&' expressions would always set up a function in the isolate scope. Now, if the binding
|
||||
is marked as optional and the attribute is not specified, no function will be added to the isolate scope.
|
||||
|
||||
|
||||
<a name="1.3.12"></a>
|
||||
# 1.3.12 outlandish-knitting (2015-02-02)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$controller:** throw better error when controller expression is bad
|
||||
([632b2ddd](https://github.com/angular/angular.js/commit/632b2ddd34c07b3b5a207bd83ca3a5e6e613e63b),
|
||||
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
|
||||
- **$parse:** remove references to last arguments to a fn call
|
||||
([7caad220](https://github.com/angular/angular.js/commit/7caad2205a6e9927890192a3638f55532bdaaf75),
|
||||
[#10894](https://github.com/angular/angular.js/issues/10894))
|
||||
- **ngRoute:** dont duplicate optional params into query
|
||||
([f41ca4a5](https://github.com/angular/angular.js/commit/f41ca4a53ed53f172fb334911be56e42aad58794),
|
||||
[#10689](https://github.com/angular/angular.js/issues/10689))
|
||||
- **ngScenario:** Allow ngScenario to handle lazy-loaded and manually bootstrapped applications
|
||||
([0bcd0872](https://github.com/angular/angular.js/commit/0bcd0872d8d2e37e6cb7aa5bc5cb0c742b4294f9),
|
||||
[#10723](https://github.com/angular/angular.js/issues/10723))
|
||||
- **validators:** maxlength should use viewValue for $isEmpty
|
||||
([abd8e2a9](https://github.com/angular/angular.js/commit/abd8e2a9eb2d21ac67989c2f7b64c4c6547a1585),
|
||||
[#10898](https://github.com/angular/angular.js/issues/10898))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngMocks:** cleanup $inject annotations after each test
|
||||
([6ec59460](https://github.com/angular/angular.js/commit/6ec5946094ee92b820bbacc886fa2367715e60b4))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.4.0-beta.2"></a>
|
||||
# 1.4.0-beta.2 holographic-rooster (2015-01-26)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** don't rewrite when link is shift-clicked
|
||||
([8b33de6f](https://github.com/angular/angular.js/commit/8b33de6fd0ec0eb785fed697f062763b5c1d8d23),
|
||||
[#9904](https://github.com/angular/angular.js/issues/9904), [#9906](https://github.com/angular/angular.js/issues/9906))
|
||||
- **$templateRequest:** cache downloaded templates as strings
|
||||
([b3a9bd3a](https://github.com/angular/angular.js/commit/b3a9bd3ae043e3042ea7ccfe08e3b36a84feb35e),
|
||||
[#10630](https://github.com/angular/angular.js/issues/10630), [#10646](https://github.com/angular/angular.js/issues/10646))
|
||||
- **filterFilter:** throw error if input is not an array
|
||||
([cea8e751](https://github.com/angular/angular.js/commit/cea8e75144e6910b806b63a6ec2a6d118316fddd),
|
||||
[#9992](https://github.com/angular/angular.js/issues/9992), [#10352](https://github.com/angular/angular.js/issues/10352))
|
||||
- **htmlAnchorDirective:**
|
||||
- remove "element !== target element" check
|
||||
([2958cd30](https://github.com/angular/angular.js/commit/2958cd308b5ebaf223a3e5df3fb5bf0f23408447),
|
||||
[#10866](https://github.com/angular/angular.js/issues/10866))
|
||||
- don't add event listener if replaced, ignore event if target is different element
|
||||
([b146af11](https://github.com/angular/angular.js/commit/b146af11271de8fa4c51c6db87df104269f41a33),
|
||||
[#4262](https://github.com/angular/angular.js/issues/4262), [#10849](https://github.com/angular/angular.js/issues/10849))
|
||||
- **ngPluralize:** fix wrong text content when count is null/undefined
|
||||
([3228d3b4](https://github.com/angular/angular.js/commit/3228d3b4991af681e57de5ab079c1e1c11cf35cb),
|
||||
[#10836](https://github.com/angular/angular.js/issues/10836), [#10841](https://github.com/angular/angular.js/issues/10841))
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **filterFilter:** due to [cea8e751](https://github.com/angular/angular.js/commit/cea8e75144e6910b806b63a6ec2a6d118316fddd),
|
||||
Previously, the filter was not applied if used with a non array.
|
||||
Now, it throws an error. This can be worked around by converting an object to an array, using
|
||||
a filter such as https://github.com/petebacondarwin/angular-toArrayFilter
|
||||
|
||||
Closes #9992
|
||||
Closes #10352
|
||||
|
||||
|
||||
<a name="1.3.11"></a>
|
||||
# 1.3.11 spiffy-manatee (2015-01-26)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** don't rewrite when link is shift-clicked
|
||||
([939ca37c](https://github.com/angular/angular.js/commit/939ca37cfe5f6fc35b09b6705caabd1fcc3cf9d3),
|
||||
[#9904](https://github.com/angular/angular.js/issues/9904), [#9906](https://github.com/angular/angular.js/issues/9906))
|
||||
- **htmlAnchorDirective:**
|
||||
- remove "element !== target element" check
|
||||
([779e3f6b](https://github.com/angular/angular.js/commit/779e3f6b5f8d2550e758cb0c5f64187ba8e00e29),
|
||||
[#10866](https://github.com/angular/angular.js/issues/10866))
|
||||
- don't add event listener if replaced, ignore event if target is different element
|
||||
([837a0775](https://github.com/angular/angular.js/commit/837a077578081bbd07863bef85241537d19fa652),
|
||||
[#4262](https://github.com/angular/angular.js/issues/4262), [#10849](https://github.com/angular/angular.js/issues/10849))
|
||||
|
||||
|
||||
<a name="1.4.0-beta.1"></a>
|
||||
# 1.4.0-beta.1 trepidatious-salamander (2015-01-20)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** ensure no transitions are applied when an empty inline style object is provided
|
||||
([0db5b21b](https://github.com/angular/angular.js/commit/0db5b21b1d09431535e0c0bf8ac63d4b5b24d349),
|
||||
[#10613](https://github.com/angular/angular.js/issues/10613), [#10770](https://github.com/angular/angular.js/issues/10770))
|
||||
- **$compile:** support class directives on SVG elements
|
||||
([23c8a90d](https://github.com/angular/angular.js/commit/23c8a90d22f7c7b41b5a756b89498ffac828980a),
|
||||
[#10736](https://github.com/angular/angular.js/issues/10736), [#10756](https://github.com/angular/angular.js/issues/10756))
|
||||
- **form:** clean up success state of controls when they are removed
|
||||
([2408f2de](https://github.com/angular/angular.js/commit/2408f2ded5ead6e678c241e38ef474c1fadff92b),
|
||||
[#10509](https://github.com/angular/angular.js/issues/10509))
|
||||
- **ngController:** allow bound constructor fns as controllers
|
||||
([d17fbc38](https://github.com/angular/angular.js/commit/d17fbc3862e0a2e646db1222f184dbe663da4a1f),
|
||||
[#10784](https://github.com/angular/angular.js/issues/10784), [#10790](https://github.com/angular/angular.js/issues/10790))
|
||||
- **ngRepeat:** do not sort object keys alphabetically
|
||||
([c260e738](https://github.com/angular/angular.js/commit/c260e7386391877625eda086480de73e8a0ba921),
|
||||
[#6210](https://github.com/angular/angular.js/issues/6210), [#10538](https://github.com/angular/angular.js/issues/10538))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$http:** provide a config object as an argument to header functions
|
||||
([d435464c](https://github.com/angular/angular.js/commit/d435464c51d3912f56cfc830d86bfc64a1578327),
|
||||
[#7235](https://github.com/angular/angular.js/issues/7235), [#10622](https://github.com/angular/angular.js/issues/10622))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **ngRepeat:** due to [c260e738](https://github.com/angular/angular.js/commit/c260e7386391877625eda086480de73e8a0ba921),
|
||||
|
||||
|
||||
Previously, the order of items when using ngRepeat to iterate
|
||||
over object properties was guaranteed to be consistent by sorting the
|
||||
keys into alphabetic order.
|
||||
|
||||
Now, the order of the items is browser dependent based on the order returned
|
||||
from iterating over the object using the `for key in obj` syntax.
|
||||
|
||||
It seems that browsers generally follow the strategy of providing
|
||||
keys in the order in which they were defined, although there are exceptions
|
||||
when keys are deleted and reinstated. See
|
||||
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues
|
||||
|
||||
The best approach is to convert Objects into Arrays by a filter such as
|
||||
https://github.com/petebacondarwin/angular-toArrayFilter
|
||||
or some other mechanism, and then sort them manually in the order you need.
|
||||
|
||||
Closes #6210
|
||||
Closes #10538
|
||||
|
||||
|
||||
|
||||
<a name="1.3.10"></a>
|
||||
# 1.3.10 heliotropic-sundial (2015-01-20)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** ensure no transitions are applied when an empty inline style object is provided
|
||||
([9b8df52a](https://github.com/angular/angular.js/commit/9b8df52aa960b9b6288fc150d55ea2e35f56555e),
|
||||
[#10613](https://github.com/angular/angular.js/issues/10613), [#10770](https://github.com/angular/angular.js/issues/10770))
|
||||
- **$compile:** support class directives on SVG elements
|
||||
([7a9e3360](https://github.com/angular/angular.js/commit/7a9e3360284d58197a1fe34de57f5e0f6d1f4a76),
|
||||
[#10736](https://github.com/angular/angular.js/issues/10736), [#10756](https://github.com/angular/angular.js/issues/10756))
|
||||
- **form:** clean up success state of controls when they are removed
|
||||
([cdc7280d](https://github.com/angular/angular.js/commit/cdc7280dd3d5a2ded784c06dd55fe36c2053fb6f),
|
||||
[#10509](https://github.com/angular/angular.js/issues/10509))
|
||||
- **ngController:** allow bound constructor fns as controllers
|
||||
([d015c8a8](https://github.com/angular/angular.js/commit/d015c8a80b28754633c846fc50d11c9437519486),
|
||||
[#10784](https://github.com/angular/angular.js/issues/10784), [#10790](https://github.com/angular/angular.js/issues/10790))
|
||||
|
||||
|
||||
|
||||
<a name="1.4.0-beta.0"></a>
|
||||
# 1.4.0-beta.0 photonic-umbrakinesis (2015-01-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** support right button click on anchors in firefox
|
||||
([aa798f12](https://github.com/angular/angular.js/commit/aa798f123658cb78b5581513d26577016195cafe),
|
||||
[#7984](https://github.com/angular/angular.js/issues/7984))
|
||||
- **$templateRequest:** propagate HTTP status on failed requests
|
||||
([e24f22bd](https://github.com/angular/angular.js/commit/e24f22bdb1740388938d58778aa24d307a79a796),
|
||||
[#10514](https://github.com/angular/angular.js/issues/10514), [#10628](https://github.com/angular/angular.js/issues/10628))
|
||||
- **dateFilter:** ignore invalid dates
|
||||
([1334b8c8](https://github.com/angular/angular.js/commit/1334b8c8326b93e0ca016c85516627900c7a9fd3),
|
||||
[#10640](https://github.com/angular/angular.js/issues/10640))
|
||||
- **filterFilter:** use isArray() to determine array type
|
||||
([a01ce6b8](https://github.com/angular/angular.js/commit/a01ce6b81c197b0a4a1057981e8e9c1b74f37587),
|
||||
[#10621](https://github.com/angular/angular.js/issues/10621))
|
||||
- **ngChecked:** ensure that ngChecked doesn't interfere with ngModel
|
||||
([e079111b](https://github.com/angular/angular.js/commit/e079111b33bf36be21c0941718b41cc9ca67bea0),
|
||||
[#10662](https://github.com/angular/angular.js/issues/10662), [#10664](https://github.com/angular/angular.js/issues/10664))
|
||||
- **ngClass:** handle multi-class definitions as an element of an array
|
||||
([e1132f53](https://github.com/angular/angular.js/commit/e1132f53b03a5a71aa9b6eded24d64e3bc83929b),
|
||||
[#8578](https://github.com/angular/angular.js/issues/8578), [#10651](https://github.com/angular/angular.js/issues/10651))
|
||||
- **ngModelOptions:** allow sharing options between multiple inputs
|
||||
([9c9c6b3f](https://github.com/angular/angular.js/commit/9c9c6b3fe4edfe78ae275c413ee3eefb81f1ebf6),
|
||||
[#10667](https://github.com/angular/angular.js/issues/10667))
|
||||
- **ngOptions:**
|
||||
- support one-time binding on the option values
|
||||
([ba90261b](https://github.com/angular/angular.js/commit/ba90261b7586b519483883800ea876510faf5c21),
|
||||
[#10687](https://github.com/angular/angular.js/issues/10687), [#10694](https://github.com/angular/angular.js/issues/10694))
|
||||
- prevent infinite digest if track by expression is stable
|
||||
([fc21db8a](https://github.com/angular/angular.js/commit/fc21db8a15545fad53124fc941b3c911a8d57067),
|
||||
[#9464](https://github.com/angular/angular.js/issues/9464))
|
||||
- update model if selected option is removed
|
||||
([933591d6](https://github.com/angular/angular.js/commit/933591d69cee2c5580da1d8522ba90a7d924da0e),
|
||||
[#7736](https://github.com/angular/angular.js/issues/7736))
|
||||
- ensure that the correct option is selected when options are loaded async
|
||||
([7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
[#8019](https://github.com/angular/angular.js/issues/8019), [#9714](https://github.com/angular/angular.js/issues/9714), [#10639](https://github.com/angular/angular.js/issues/10639))
|
||||
- **ngPluralize:** generate a warning when using a not defined rule
|
||||
([c66b4b6a](https://github.com/angular/angular.js/commit/c66b4b6a133f7215d50c23db516986cfc1f0a985))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$filter:** display Infinity symbol when number is Infinity
|
||||
([51d67742](https://github.com/angular/angular.js/commit/51d6774286202b55ade402ca097e417e70fd546b),
|
||||
[#10421](https://github.com/angular/angular.js/issues/10421))
|
||||
- **$timeout:** allow `fn` to be an optional parameter
|
||||
([5a603023](https://github.com/angular/angular.js/commit/5a60302389162c6ef45f311c1aaa65a00d538c66),
|
||||
[#9176](https://github.com/angular/angular.js/issues/9176))
|
||||
- **limitTo:** ignore limit when invalid
|
||||
([a3c3bf33](https://github.com/angular/angular.js/commit/a3c3bf3332e5685dc319c46faef882cb6ac246e1),
|
||||
[#10510](https://github.com/angular/angular.js/issues/10510))
|
||||
- **ngMock/$exceptionHandler:** log errors when rethrowing
|
||||
([deb3cb4d](https://github.com/angular/angular.js/commit/deb3cb4daef0054457bd9fb8995829fff0e8f1e4),
|
||||
[#10540](https://github.com/angular/angular.js/issues/10540), [#10564](https://github.com/angular/angular.js/issues/10564))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **ngStyleDirective:** use $watchCollection
|
||||
([8928d023](https://github.com/angular/angular.js/commit/8928d0234551a272992d0eccef73b3ad6cb8bfd1),
|
||||
[#10535](https://github.com/angular/angular.js/issues/10535))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **limitTo:** due to [a3c3bf33](https://github.com/angular/angular.js/commit/a3c3bf3332e5685dc319c46faef882cb6ac246e1),
|
||||
limitTo changed behavior when limit value is invalid.
|
||||
Instead of returning empty object/array it returns unchanged input.
|
||||
|
||||
|
||||
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
|
||||
|
||||
When using `ngOptions`: the directive applies a surrogate key as the value of the `<option>` element.
|
||||
This commit changes the actual string used as the surrogate key. We now store a string that is computed
|
||||
by calling `hashKey` on the item in the options collection; previously it was the index or key of the
|
||||
item in the collection.
|
||||
|
||||
(This is in keeping with the way that the unknown option value is represented in the select directive.)
|
||||
|
||||
Before you might have seen:
|
||||
|
||||
```
|
||||
<select ng-model="x" ng-option="i in items">
|
||||
<option value="1">a</option>
|
||||
<option value="2">b</option>
|
||||
<option value="3">c</option>
|
||||
<option value="4">d</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
Now it will be something like:
|
||||
|
||||
```
|
||||
<select ng-model="x" ng-option="i in items">
|
||||
<option value="string:a">a</option>
|
||||
<option value="string:b">b</option>
|
||||
<option value="string:c">c</option>
|
||||
<option value="string:d">d</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
If your application code relied on this value, which it shouldn't, then you will need to modify your
|
||||
application to accommodate this. You may find that you can use the `track by` feaure of `ngOptions`
|
||||
as this provides the ability to specify the key that is stored.
|
||||
|
||||
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
|
||||
When iterating over an object's properties using the `(key, value) in obj` syntax
|
||||
the order of the elements used to be sorted alphabetically. This was an artificial
|
||||
attempt to create a deterministic ordering since browsers don't guarantee the order.
|
||||
But in practice this is not what people want and so this change iterates over properties
|
||||
in the order they are returned by Object.keys(obj), which is almost always the order
|
||||
in which the properties were defined.
|
||||
|
||||
|
||||
|
||||
<a name="1.3.9"></a>
|
||||
# 1.3.9 multidimensional-awareness (2015-01-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$parse:** allow use of locals in assignments
|
||||
([86900814](https://github.com/angular/angular.js/commit/869008140a96e0e9e0d9774cc2e5fdd66ada7ba9))
|
||||
- **filterFilter:** use isArray() to determine array type
|
||||
([d4b60ada](https://github.com/angular/angular.js/commit/d4b60ada1ecff5afdb3210caa44e149e9f3d4c1b),
|
||||
[#10621](https://github.com/angular/angular.js/issues/10621))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngMock/$exceptionHandler:** log errors when rethrowing
|
||||
([2b97854b](https://github.com/angular/angular.js/commit/2b97854bf4786fe8579974e2b9d6b4adee8a3dc3),
|
||||
[#10540](https://github.com/angular/angular.js/issues/10540), [#10564](https://github.com/angular/angular.js/issues/10564))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **ngStyleDirective:** use $watchCollection
|
||||
([4c8d8ad5](https://github.com/angular/angular.js/commit/4c8d8ad5083d9dd17c0b8480339d5f95943f1b71),
|
||||
[#10535](https://github.com/angular/angular.js/issues/10535))
|
||||
|
||||
|
||||
|
||||
|
||||
<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)
|
||||
|
||||
|
||||
+3
-3
@@ -87,7 +87,7 @@ Before you submit your pull request consider the following guidelines:
|
||||
that relates to your submission. You don't want to duplicate effort.
|
||||
* Please sign our [Contributor License Agreement (CLA)](#cla) before sending pull
|
||||
requests. We cannot accept code without this.
|
||||
* Make your changes in a new git branch
|
||||
* Make your changes in a new git branch:
|
||||
|
||||
```shell
|
||||
git checkout -b my-fix-branch master
|
||||
@@ -107,7 +107,7 @@ Before you submit your pull request consider the following guidelines:
|
||||
```
|
||||
Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
|
||||
|
||||
* Build your changes locally to ensure all the tests pass
|
||||
* Build your changes locally to ensure all the tests pass:
|
||||
|
||||
```shell
|
||||
grunt test
|
||||
@@ -120,7 +120,7 @@ Before you submit your pull request consider the following guidelines:
|
||||
```
|
||||
|
||||
* In GitHub, send a pull request to `angular:master`.
|
||||
* If we suggest changes then
|
||||
* If we suggest changes then:
|
||||
* Make the required updates.
|
||||
* Re-run the Angular test suite to ensure tests are still passing.
|
||||
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
+2
-2
@@ -316,9 +316,9 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']);
|
||||
grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']);
|
||||
grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']);
|
||||
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['tests:modules']);
|
||||
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['build', 'tests:modules']);
|
||||
grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']);
|
||||
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['tests:jqlite', 'tests:jquery', 'tests:modules']);
|
||||
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['test:jqlite', 'test:jquery', 'test:modules']);
|
||||
grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:normal']);
|
||||
grunt.registerTask('test:travis-protractor', 'Run the end to end tests with Protractor for Travis CI builds', ['connect:testserver', 'protractor:travis']);
|
||||
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor for Jenkins CI builds', ['webdriver', 'connect:testserver', 'protractor:jenkins']);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
Vendored
+6
-1
@@ -53,6 +53,7 @@ var angularFiles = {
|
||||
'src/ng/directive/form.js',
|
||||
'src/ng/directive/input.js',
|
||||
'src/ng/directive/ngBind.js',
|
||||
'src/ng/directive/ngChange.js',
|
||||
'src/ng/directive/ngClass.js',
|
||||
'src/ng/directive/ngCloak.js',
|
||||
'src/ng/directive/ngController.js',
|
||||
@@ -61,7 +62,10 @@ var angularFiles = {
|
||||
'src/ng/directive/ngIf.js',
|
||||
'src/ng/directive/ngInclude.js',
|
||||
'src/ng/directive/ngInit.js',
|
||||
'src/ng/directive/ngList.js',
|
||||
'src/ng/directive/ngModel.js',
|
||||
'src/ng/directive/ngNonBindable.js',
|
||||
'src/ng/directive/ngOptions.js',
|
||||
'src/ng/directive/ngPluralize.js',
|
||||
'src/ng/directive/ngRepeat.js',
|
||||
'src/ng/directive/ngShowHide.js',
|
||||
@@ -70,7 +74,8 @@ var angularFiles = {
|
||||
'src/ng/directive/ngTransclude.js',
|
||||
'src/ng/directive/script.js',
|
||||
'src/ng/directive/select.js',
|
||||
'src/ng/directive/style.js'
|
||||
'src/ng/directive/style.js',
|
||||
'src/ng/directive/validators.js'
|
||||
],
|
||||
|
||||
'angularLoader': [
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<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>interpolation + bind-once: <input type="radio" ng-model="benchmarkType" value="bindOnceInterpolation"></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>
|
||||
@@ -34,7 +36,7 @@
|
||||
</div>
|
||||
<div ng-switch-when="ngBindOnce">
|
||||
<h2>baseline binding once</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<div ng-repeat="row in ::data">
|
||||
<span ng-repeat="column in ::row">
|
||||
<span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|
|
||||
</span>
|
||||
@@ -46,6 +48,18 @@
|
||||
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="bindOnceInterpolation">
|
||||
<h2>baseline one-time interpolation</h2>
|
||||
<div ng-repeat="row in ::data">
|
||||
<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">
|
||||
@@ -73,4 +87,4 @@
|
||||
</ng-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
"use strict";
|
||||
|
||||
/* globals angular, benchmarkSteps */
|
||||
|
||||
var app = angular.module('ngOptionsBenchmark', []);
|
||||
|
||||
app.config(function($compileProvider) {
|
||||
if ($compileProvider.debugInfoEnabled) {
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
app.controller('DataController', function($scope, $element) {
|
||||
$scope.items = [];
|
||||
$scope.count = 10000;
|
||||
|
||||
function changeOptions() {
|
||||
$scope.items = [];
|
||||
for (var i = 0; i < $scope.count; ++i) {
|
||||
$scope.items.push({
|
||||
id: i,
|
||||
label: 'item-' + i,
|
||||
group: 'group-' + i % 100
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var selectElement = $element.find('select');
|
||||
console.log(selectElement);
|
||||
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'add-options',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
$scope.count = 10000;
|
||||
changeOptions();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'set-model-1',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
$scope.x = $scope.items[1000];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'set-model-2',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
$scope.x = $scope.items[10];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'remove-options',
|
||||
fn: function() {
|
||||
$scope.count = 100;
|
||||
changeOptions();
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'add-options',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
$scope.count = 10000;
|
||||
changeOptions();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'set-view-1',
|
||||
fn: function() {
|
||||
selectElement.val('2000');
|
||||
selectElement.triggerHandler('change');
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'set-view-2',
|
||||
fn: function() {
|
||||
selectElement.val('1000');
|
||||
selectElement.triggerHandler('change');
|
||||
}
|
||||
});
|
||||
});
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
scripts: [ {
|
||||
id: 'angular',
|
||||
src: '/build/angular.js'
|
||||
},
|
||||
{
|
||||
src: 'app.js',
|
||||
}]
|
||||
});
|
||||
};
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
<div ng-app="ngOptionsBenchmark" ng-cloak>
|
||||
<div ng-controller="DataController">
|
||||
<div class="container-fluid">
|
||||
<p>
|
||||
Tests the execution of ng-options for rendering during model and option updates.
|
||||
</p>
|
||||
<select ng-model="x" ng-options="a as a.label group by a.group for a in items track by a.id"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,10 +103,10 @@ then(function (tags) {
|
||||
sort(semver.rcompare);
|
||||
}).
|
||||
then(function (tags) {
|
||||
var major = tags[0].split('.')[0] + '.x';
|
||||
var major = tags[0].split('.')[0];
|
||||
return tags.
|
||||
filter(function (ver) {
|
||||
return semver.satisfies(ver, major);
|
||||
return semver(ver).major == major;
|
||||
});
|
||||
}).
|
||||
then(function (tags) {
|
||||
|
||||
@@ -546,10 +546,10 @@ h4 {
|
||||
margin-left:10px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color:black!important;
|
||||
.btn:hover, .btn:focus {
|
||||
color: black!important;
|
||||
border: 1px solid #ddd!important;
|
||||
background:white!important;
|
||||
background: white!important;
|
||||
}
|
||||
|
||||
.view-source, .improve-docs {
|
||||
@@ -631,6 +631,7 @@ ul.events > li {
|
||||
}
|
||||
.main-body-grid .side-navigation {
|
||||
display:block!important;
|
||||
padding-bottom:50px;
|
||||
}
|
||||
.main-body-grid .side-navigation.ng-hide {
|
||||
display:none!important;
|
||||
@@ -656,14 +657,14 @@ ul.events > li {
|
||||
}
|
||||
.toc-close {
|
||||
position: absolute;
|
||||
bottom: -50px;
|
||||
bottom: 5px;
|
||||
left: 50%;
|
||||
margin-left: -50%;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
background: #eee;
|
||||
border-radius: 5px;
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
border:1px solid #ddd;
|
||||
box-shadow:0 0 10px #bbb;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
angular.module('examples', [])
|
||||
|
||||
.factory('formPostData', ['$document', function($document) {
|
||||
return function(url, fields) {
|
||||
return function(url, newWindow, fields) {
|
||||
/**
|
||||
* Form previously posted to target="_blank", but pop-up blockers were causing this to not work.
|
||||
* If a user chose to bypass pop-up blocker one time and click the link, they would arrive at
|
||||
* a new default plnkr, not a plnkr with the desired template.
|
||||
* If the form posts to target="_blank", pop-up blockers can cause it not to work.
|
||||
* If a user choses to bypass pop-up blocker one time and click the link, they will arrive at
|
||||
* a new default plnkr, not a plnkr with the desired template. Given this undesired behavior,
|
||||
* some may still want to open the plnk in a new window by opting-in via ctrl+click. The
|
||||
* newWindow param allows for this possibility.
|
||||
*/
|
||||
var form = angular.element('<form style="display: none;" method="post" action="' + url + '"></form>');
|
||||
var target = newWindow ? '_blank' : '_self';
|
||||
var form = angular.element('<form style="display: none;" method="post" action="' + url + '" target="' + target + '"></form>');
|
||||
angular.forEach(fields, function(value, name) {
|
||||
var input = angular.element('<input type="hidden" name="' + name + '">');
|
||||
input.attr('value', value);
|
||||
@@ -21,9 +24,10 @@ angular.module('examples', [])
|
||||
|
||||
|
||||
.factory('openPlunkr', ['formPostData', '$http', '$q', function(formPostData, $http, $q) {
|
||||
return function(exampleFolder) {
|
||||
return function(exampleFolder, clickEvent) {
|
||||
|
||||
var exampleName = 'AngularJS Example';
|
||||
var newWindow = clickEvent.ctrlKey || clickEvent.metaKey;
|
||||
|
||||
// Load the manifest for the example
|
||||
$http.get(exampleFolder + '/manifest.json')
|
||||
@@ -71,7 +75,7 @@ angular.module('examples', [])
|
||||
postData.private = true;
|
||||
postData.description = exampleName;
|
||||
|
||||
formPostData('http://plnkr.co/edit/?p=preview', postData);
|
||||
formPostData('http://plnkr.co/edit/?p=preview', newWindow, postData);
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
<p class="pull-right"><a back-to-top>Back to top</a></p>
|
||||
|
||||
<p>
|
||||
Super-powered by Google ©2010-2014
|
||||
Super-powered by Google ©2010-2015
|
||||
( <a id="version"
|
||||
ng-href="https://github.com/angular/angular.js/blob/master/CHANGELOG.md#{{versionNumber}}"
|
||||
ng-bind-template="v{{version}}">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
is HTML and wrap each line in a <p> - thus breaking the HTML #}
|
||||
|
||||
<div>
|
||||
<a ng-click="openPlunkr('{$ doc.path $}')" class="btn pull-right">
|
||||
<a ng-click="openPlunkr('{$ doc.path $}', $event)" class="btn pull-right">
|
||||
<i class="glyphicon glyphicon-edit"> </i>
|
||||
Edit in Plunker</a>
|
||||
|
||||
|
||||
@@ -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,7 +156,7 @@ 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>
|
||||
@@ -273,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,12 @@
|
||||
@ngdoc error
|
||||
@name $compile:noctrl
|
||||
@fullName Controller is required.
|
||||
@description
|
||||
|
||||
When using the `bindToController` feature of AngularJS, a directive is required
|
||||
to have a Controller. A controller may be specified by adding a "controller"
|
||||
property to the directive definition object. Its value should be either a
|
||||
string, or an invokable object (a function, or an array whose last element is a
|
||||
function).
|
||||
|
||||
For more information, see the {@link guide/directive directives guide}.
|
||||
@@ -0,0 +1,71 @@
|
||||
@ngdoc error
|
||||
@name $compile:noident
|
||||
@fullName Controller identifier is required.
|
||||
@description
|
||||
|
||||
When using the `bindToController` feature of AngularJS, a directive is required
|
||||
to have a Controller identifier, which is initialized in scope with the value of
|
||||
the controller instance. This can be supplied using the "controllerAs" property
|
||||
of the directive object, or alternatively by adding " as IDENTIFIER" to the controller
|
||||
name.
|
||||
|
||||
For example, the following directives are valid:
|
||||
|
||||
```js
|
||||
// OKAY, because controller is a string with an identifier component.
|
||||
directive("okay", function() {
|
||||
return {
|
||||
bindToController: true,
|
||||
controller: "myCtrl as $ctrl"
|
||||
scope: {
|
||||
text: "@text"
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// OKAY, because the directive uses the controllerAs property to override
|
||||
// the controller identifier.
|
||||
directive("okay2", function() {
|
||||
return {
|
||||
bindToController: true,
|
||||
controllerAs: "$ctrl",
|
||||
controller: function() {
|
||||
|
||||
},
|
||||
scope: {
|
||||
text: "@text"
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
While the following are invalid:
|
||||
|
||||
```js
|
||||
// BAD, because the controller property is a string with no identifier.
|
||||
directive("bad", function() {
|
||||
return {
|
||||
bindToController: true,
|
||||
controller: "noIdentCtrl",
|
||||
scope: {
|
||||
text: "@text"
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// BAD because the controller is not a string (therefore has no identifier),
|
||||
// and there is no controllerAs property.
|
||||
directive("bad2", function() {
|
||||
return {
|
||||
bindToController: true,
|
||||
controller: function noControllerAs() {
|
||||
|
||||
},
|
||||
scope: {
|
||||
text: "@text"
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
@ngdoc error
|
||||
@name $controller:ctrlfmt
|
||||
@fullName Badly formed controller string
|
||||
@description
|
||||
|
||||
This error occurs when {@link ng.$controller $controller} service is called
|
||||
with a string that does not match the supported controller string formats.
|
||||
|
||||
Supported formats:
|
||||
|
||||
1. `__name__`
|
||||
2. `__name__ as __identifier__`
|
||||
|
||||
N'either `__name__` or `__identifier__` may contain spaces.
|
||||
|
||||
Example of incorrect usage that leads to this error:
|
||||
```html
|
||||
<!-- unclosed ng-controller attribute messes up the format -->
|
||||
<div ng-controller="myController>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
// does not match `__name__` or `__name__ as __identifier__`
|
||||
var myCtrl = $controller("mY contRoller", { $scope: newScope });
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
directive("myDirective", function() {
|
||||
return {
|
||||
// does not match `__name__` or `__name__ as __identifier__`
|
||||
controller: "mY contRoller",
|
||||
link: function() {}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
To fix the examples above, ensure that the controller string matches the supported
|
||||
formats, and that any html attributes which are used as controller expressions are
|
||||
closed.
|
||||
|
||||
|
||||
Please consult the {@link ng.$controller $controller} service api docs to learn more.
|
||||
@@ -35,7 +35,7 @@ URL of the subcontext:
|
||||
|
||||
```html
|
||||
<head>
|
||||
<base href="/subapp">
|
||||
<base href="/subapp/">
|
||||
...
|
||||
</head>
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -35,7 +35,7 @@ var users = [ { name: 'Hank' }, { name: 'Francisco' } ];
|
||||
|
||||
$scope.getUsers = function() {
|
||||
return users;
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
The maximum number of allowed iterations of the `$digest` cycle is controlled via TTL setting which can be configured via {@link ng.$rootScopeProvider $rootScopeProvider}.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
@ngdoc error
|
||||
@name filter:notarray
|
||||
@fullName Not an array
|
||||
@description
|
||||
|
||||
This error occurs when {@link ng.filter filter} is not used with an array:
|
||||
```html
|
||||
<input ng-model="search">
|
||||
<div ng-repeat="(key, value) in myObj | filter:search">
|
||||
{{ key }} : {{ value }}
|
||||
</div>
|
||||
```
|
||||
|
||||
Filter must be used with an array so a subset of items can be returned.
|
||||
The array can be initialized asynchronously and therefore null or undefined won't throw this error.
|
||||
|
||||
To filter an object by the value of its properties you can create your own custom filter:
|
||||
```js
|
||||
angular.module('customFilter',[])
|
||||
.filter('custom', function() {
|
||||
return function(input, search) {
|
||||
if (!input) return input;
|
||||
if (!search) return input;
|
||||
var expected = ('' + search).toLowerCase();
|
||||
var result = {};
|
||||
angular.forEach(input, function(value, key) {
|
||||
var actual = ('' + value).toLowerCase();
|
||||
if (actual.indexOf(expected) !== -1) {
|
||||
result[key] = value;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
});
|
||||
```
|
||||
That can be used as:
|
||||
```html
|
||||
<input ng-model="search">
|
||||
<div ng-repeat="(key, value) in myObj | custom:search">
|
||||
{{ key }} : {{ value }}
|
||||
</div>
|
||||
```
|
||||
|
||||
You could as well convert the object to an array using a filter such as
|
||||
[toArrayFilter](https://github.com/petebacondarwin/angular-toArrayFilter):
|
||||
```html
|
||||
<input ng-model="search">
|
||||
<div ng-repeat="item in myObj | toArray:false | filter:search">
|
||||
{{ item }}
|
||||
</div>
|
||||
```
|
||||
@@ -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,7 @@
|
||||
@ngdoc error
|
||||
@name ng:cpta
|
||||
@fullName Copying TypedArray
|
||||
@description
|
||||
|
||||
Copying TypedArray's with a destination is not supported because TypedArray
|
||||
objects can not be mutated, they are fixed length.
|
||||
@@ -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.
|
||||
@@ -16,6 +16,8 @@ Reserved names include:
|
||||
- `this`
|
||||
- `undefined`
|
||||
- `$parent`
|
||||
- `$id`
|
||||
- `$root`
|
||||
- `$even`
|
||||
- `$odd`
|
||||
- `$first`
|
||||
|
||||
@@ -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:
|
||||
@@ -165,7 +165,7 @@ encoded.
|
||||
|
||||
`$location` service has two configuration modes which control the format of the URL in the browser
|
||||
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
|
||||
HTML5 [History API](http://www.w3.org/TR/html5/introduction.html#history-0). Applications use the same API in
|
||||
[HTML5 History API](https://html.spec.whatwg.org/multipage/browsers.html#the-history-interface). Applications use the same API in
|
||||
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
|
||||
facilitate the browser URL change and history management.
|
||||
|
||||
@@ -358,7 +358,7 @@ legacy browsers and hashbang links in modern browser:
|
||||
|
||||
Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so
|
||||
that you can see the differences. These `$location` services are connected to a fake browsers. Each
|
||||
input represents address bar of the browser.
|
||||
input represents the address bar of the browser.
|
||||
|
||||
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
|
||||
redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
|
||||
@@ -835,7 +835,7 @@ angular.module('locationExample', [])
|
||||
|
||||
# Related API
|
||||
|
||||
* {@link ng.$location $location API}
|
||||
* {@link ng.$location `$location` API}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ Currently, ngAria interfaces with the following directives:
|
||||
* {@link guide/accessibility#nghide ngHide}
|
||||
* {@link guide/accessibility#ngclick ngClick}
|
||||
* {@link guide/accessibility#ngdblclick ngDblClick}
|
||||
* {@link guide/accessibility#ngmessages ngMessages}
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
@@ -209,10 +210,14 @@ The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redun
|
||||
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
|
||||
|
||||
<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`.
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` if it isn't there
|
||||
already.
|
||||
|
||||
For `ng-click`, keypress will also be bound to `div` and `li` elements. You can turn this
|
||||
functionality on or off with the `bindKeypress` configuration option.
|
||||
|
||||
For `ng-dblclick`, you must manually add `ng-keypress` to non-interactive elements such as `div`
|
||||
or `taco-button` to enable keyboard access.
|
||||
|
||||
<h3>Example</h3>
|
||||
```html
|
||||
@@ -223,7 +228,6 @@ 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>
|
||||
|
||||
|
||||
@@ -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.)
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ As a best practice, consider adding an `ng-strict-di` directive on the same elem
|
||||
```
|
||||
|
||||
This will ensure that all services in your application are properly annotated.
|
||||
See the {@link guide/di#using-strict-dependency-injection dependancy injection strict mode} docs
|
||||
See the {@link guide/di#using-strict-dependency-injection dependency injection strict mode} docs
|
||||
for more.
|
||||
|
||||
|
||||
@@ -156,4 +156,4 @@ until `angular.resumeBootstrap()` is called.
|
||||
|
||||
`angular.resumeBootstrap()` takes an optional array of modules that
|
||||
should be added to the original list of modules that the app was
|
||||
about to be bootstrapped with.
|
||||
about to be bootstrapped with.
|
||||
@@ -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
|
||||
|
||||
@@ -742,9 +751,12 @@ own behavior to it.
|
||||
angular.module('docsIsoFnBindExample', [])
|
||||
.controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
|
||||
$scope.name = 'Tobias';
|
||||
$scope.hideDialog = function () {
|
||||
$scope.message = '';
|
||||
$scope.hideDialog = function (message) {
|
||||
$scope.message = message;
|
||||
$scope.dialogIsHidden = true;
|
||||
$timeout(function () {
|
||||
$scope.message = '';
|
||||
$scope.dialogIsHidden = false;
|
||||
}, 2000);
|
||||
};
|
||||
@@ -762,14 +774,15 @@ own behavior to it.
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Controller">
|
||||
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
|
||||
{{message}}
|
||||
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)">
|
||||
Check out the contents, {{name}}!
|
||||
</my-dialog>
|
||||
</div>
|
||||
</file>
|
||||
<file name="my-dialog-close.html">
|
||||
<div class="alert">
|
||||
<a href class="close" ng-click="close()">×</a>
|
||||
<a href class="close" ng-click="close({message: 'closing for now'})">×</a>
|
||||
<div ng-transclude></div>
|
||||
</div>
|
||||
</file>
|
||||
@@ -786,9 +799,15 @@ callback functions to directive behaviors.
|
||||
|
||||
When the user clicks the `x` in the dialog, the directive's `close` function is called, thanks to
|
||||
`ng-click.` This call to `close` on the isolated scope actually evaluates the expression
|
||||
`hideDialog()` in the context of the original scope, thus running `Controller`'s `hideDialog`
|
||||
`hideDialog(message)` in the context of the original scope, thus running `Controller`'s `hideDialog`
|
||||
function.
|
||||
|
||||
Often it's desirable to pass data from the isolate scope via an expression to the
|
||||
parent scope, this can be done by passing a map of local variable names and values into the expression
|
||||
wrapper fn. For example, the hideDialog function takes a message to display when the dialog is hidden.
|
||||
This is specified in the directive by calling `close({message: 'closing for now'})`. Then the local
|
||||
variable `message` will be available within the `on-close` expression.
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** use `&attr` in the `scope` option when you want your directive
|
||||
to expose an API for binding to behaviors.
|
||||
|
||||
@@ -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,11 +172,11 @@ 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 RegExp creation with literal notation
|
||||
## No function declarations or RegExp creation with literal notation
|
||||
|
||||
You can't 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.
|
||||
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`
|
||||
|
||||
@@ -298,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() {
|
||||
@@ -324,7 +334,7 @@ someModule.directive('someDirective', function() {
|
||||
```
|
||||
|
||||
|
||||
When using a directive that takes an expression
|
||||
When using a directive that takes an expression:
|
||||
|
||||
```html
|
||||
<ul>
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
Controls (`input`, `select`, `textarea`) are ways for a user to enter data.
|
||||
A Form is a collection of controls for the purpose of grouping related controls together.
|
||||
|
||||
Form and controls provide validation services, so that the user can be notified of invalid input.
|
||||
This provides a better user experience, because the user gets instant feedback on how to
|
||||
correct the error. Keep in mind that while client-side validation plays an important role
|
||||
in providing good user experience, it can easily be circumvented and thus can not be trusted.
|
||||
Server-side validation is still necessary for a secure application.
|
||||
Form and controls provide validation services, so that the user can be notified of invalid input
|
||||
before submitting a form. This provides a better user experience than server-side validation alone
|
||||
because the user gets instant feedback on how to correct the error. Keep in mind that while
|
||||
client-side validation plays an important role in providing good user experience, it can easily
|
||||
be circumvented and thus can not be trusted. Server-side validation is still necessary for a
|
||||
secure application.
|
||||
|
||||
|
||||
# Simple form
|
||||
@@ -131,7 +132,7 @@ A form is an instance of {@link form.FormController FormController}.
|
||||
The form instance can optionally be published into the scope using the `name` attribute.
|
||||
|
||||
Similarly, an input control that has the {@link ng.directive:ngModel ngModel} directive holds an
|
||||
instance of {@link ngModel.NgModelController NgModelController}.Such a control instance
|
||||
instance of {@link ngModel.NgModelController NgModelController}. Such a control instance
|
||||
can be published as a property of the form instance using the `name` attribute on the input control.
|
||||
The name attribute specifies the name of the property on the form instance.
|
||||
|
||||
@@ -293,7 +294,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`).
|
||||
|
||||
@@ -339,7 +340,7 @@ In the following example we create two directives:
|
||||
<div>
|
||||
Username:
|
||||
<input type="text" ng-model="name" name="name" username />{{name}}<br />
|
||||
<span ng-show="form.name.$pending.username">Checking if this name is available ...</span>
|
||||
<span ng-show="form.name.$pending.username">Checking if this name is available...</span>
|
||||
<span ng-show="form.name.$error.username">This username is already taken!</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
|
||||
## Specific Topics
|
||||
|
||||
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
|
||||
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [AngularJS Faceb0ok library](https://github.com/pc035860/angular-easyfb), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
|
||||
* **Mobile:** [Angular on Mobile Guide](http://www.ng-newsletter.com/posts/angular-on-mobile.html), [PhoneGap](http://devgirl.org/2013/06/10/quick-start-guide-phonegap-and-angularjs/)
|
||||
* **Other Languages:** [CoffeeScript](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html), [Dart](https://github.com/angular/angular.dart.tutorial/wiki)
|
||||
* **Realtime: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder)
|
||||
@@ -62,6 +62,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
|
||||
## Tools
|
||||
|
||||
* **Getting Started:** [Comparison of the options for starting a new project](http://www.dancancro.com/comparison-of-angularjs-application-starters/)
|
||||
* **Debugging:** [Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en)
|
||||
* **Testing:** [Karma](http://karma-runner.github.io), [Protractor](https://github.com/angular/protractor)
|
||||
* **Editor support:** [Webstorm](http://plugins.jetbrains.com/plugin/6971) (and [video](http://www.youtube.com/watch?v=LJOyrSh1kDU)), [Sublime Text](https://github.com/angular-ui/AngularJS-sublime-package), [Visual Studio](http://madskristensen.net/post/angularjs-intellisense-in-visual-studio-2012)
|
||||
@@ -101,7 +102,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
## Learning Resources
|
||||
|
||||
###Books
|
||||
* [AngularJS](http://www.amazon.com/AngularJS-Brad-Green/dp/1449344852) by Brad Green and Shyam Seshadri
|
||||
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
|
||||
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
|
||||
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
|
||||
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
|
||||
@@ -109,6 +110,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* [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/)
|
||||
@@ -117,12 +119,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/)
|
||||
|
||||
@@ -12,7 +12,7 @@ succinctly. Angular's data binding and dependency injection eliminate much of th
|
||||
would otherwise have to write. And it all happens within the browser, making it
|
||||
an ideal partner with any server technology.
|
||||
|
||||
Angular is what HTML would have been had it been designed for applications. HTML is a great
|
||||
Angular is what HTML would have been, had it been designed for applications. HTML is a great
|
||||
declarative language for static documents. It does not contain much in the way of creating
|
||||
applications, and as a result building web applications is an exercise in *what do I have to do
|
||||
to trick the browser into doing what I want?*
|
||||
@@ -28,10 +28,10 @@ The impedance mismatch between dynamic applications and static documents is ofte
|
||||
|
||||
Angular takes another approach. It attempts to minimize the impedance mismatch between document
|
||||
centric HTML and what an application needs by creating new HTML constructs. Angular teaches the
|
||||
browser new syntax through a construct we call directives. Examples include:
|
||||
browser new syntax through a construct we call *directives*. Examples include:
|
||||
|
||||
* Data binding, as in `{{}}`.
|
||||
* DOM control structures for repeating/hiding DOM fragments.
|
||||
* DOM control structures for repeating, showing and hiding DOM fragments.
|
||||
* Support for forms and form validation.
|
||||
* Attaching new behavior to DOM elements, such as DOM event handling.
|
||||
* Grouping of HTML into reusable components.
|
||||
@@ -42,20 +42,20 @@ browser new syntax through a construct we call directives. Examples include:
|
||||
|
||||
Angular is not a single piece in the overall puzzle of building the client-side of a web
|
||||
application. It handles all of the DOM and AJAX glue code you once wrote by hand and puts it in a
|
||||
well-defined structure. This makes Angular opinionated about how a CRUD application should be
|
||||
built. But while it is opinionated, it also tries to make sure that its opinion is just a
|
||||
starting point you can easily change. Angular comes with the following out-of-the-box:
|
||||
well-defined structure. This makes Angular opinionated about how a CRUD (Create, Read, Update, Delete)
|
||||
application should be built. But while it is opinionated, it also tries to make sure that its opinion
|
||||
is just a starting point you can easily change. Angular comes with the following out-of-the-box:
|
||||
|
||||
* Everything you need to build a CRUD app in a cohesive set: data-binding, basic templating
|
||||
directives, form validation, routing, deep-linking, reusable components, dependency injection.
|
||||
* Testability story: unit-testing, end-to-end testing, mocks, test harnesses.
|
||||
* Everything you need to build a CRUD app in a cohesive set: Data-binding, basic templating
|
||||
directives, form validation, routing, deep-linking, reusable components and dependency injection.
|
||||
* Testability story: Unit-testing, end-to-end testing, mocks and test harnesses.
|
||||
* Seed application with directory layout and test scripts as a starting point.
|
||||
|
||||
|
||||
## Angular Sweet Spot
|
||||
## Angular's sweet spot
|
||||
|
||||
Angular simplifies application development by presenting a higher level of abstraction to the
|
||||
developer. Like any abstraction, it comes at a cost of flexibility. In other words not every app
|
||||
developer. Like any abstraction, it comes at a cost of flexibility. In other words, not every app
|
||||
is a good fit for Angular. Angular was built with the CRUD application in mind. Luckily CRUD
|
||||
applications represent the majority of web applications. To understand what Angular is
|
||||
good at, though, it helps to understand when an app is not a good fit for Angular.
|
||||
@@ -78,7 +78,7 @@ expressing business logic.
|
||||
* It is an excellent idea to decouple the client side of an app from the server side. This
|
||||
allows development work to progress in parallel, and allows for reuse of both sides.
|
||||
* It is very helpful indeed if the framework guides developers through the entire journey of
|
||||
building an app: from designing the UI, through writing the business logic, to testing.
|
||||
building an app: From designing the UI, through writing the business logic, to testing.
|
||||
* It is always good to make common tasks trivial and difficult tasks possible.
|
||||
|
||||
|
||||
|
||||
@@ -15,13 +15,51 @@ which drives many of these changes.
|
||||
|
||||
# Migrating from 1.2 to 1.3
|
||||
|
||||
## Controllers
|
||||
|
||||
Due to [3f2232b5](https://github.com/angular/angular.js/commit/3f2232b5a181512fac23775b1df4a6ebda67d018),
|
||||
`$controller` will no longer look for controllers on `window`.
|
||||
The old behavior of looking on `window` for controllers was originally intended
|
||||
for use in examples, demos, and toy apps. We found that allowing global controller
|
||||
functions encouraged poor practices, so we resolved to disable this behavior by
|
||||
default.
|
||||
|
||||
To migrate, register your controllers with modules rather than exposing them
|
||||
as globals:
|
||||
|
||||
Before:
|
||||
|
||||
```javascript
|
||||
function MyController() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```javascript
|
||||
angular.module('myApp', []).controller('MyController', [function() {
|
||||
// ...
|
||||
}]);
|
||||
```
|
||||
|
||||
Although it's not recommended, you can re-enable the old behavior like this:
|
||||
|
||||
```javascript
|
||||
angular.module('myModule').config(['$controllerProvider', function($controllerProvider) {
|
||||
// this option might be handy for migrating old apps, but please don't use it
|
||||
// in new ones!
|
||||
$controllerProvider.allowGlobals();
|
||||
}]);
|
||||
```
|
||||
|
||||
## 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.
|
||||
in an unforeseen fashion.
|
||||
|
||||
- due to [6081f207](https://github.com/angular/angular.js/commit/6081f20769e64a800ee8075c168412b21f026d99),
|
||||
|
||||
@@ -450,6 +488,45 @@ After:
|
||||
Please view the documentation for ngAnimate for more info.
|
||||
|
||||
|
||||
## 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:
|
||||
|
||||
var el = element(by.binding('{{foo}}'));
|
||||
|
||||
After:
|
||||
|
||||
var el = element(by.binding('foo'));
|
||||
|
||||
Prefixes `ng_` and `x-ng-` are no longer allowed for models. Use `ng-model`.
|
||||
|
||||
`by.repeater` cannot find elements by row and column which are not children of
|
||||
the row. For example, if your template is
|
||||
|
||||
<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),
|
||||
@@ -838,7 +915,7 @@ of `$sce.trustAsHtml(string)`. When bound to a plain string, the string is sanit
|
||||
module is not loaded) and the bound expression evaluates to a value that is not trusted an
|
||||
exception is thrown.
|
||||
|
||||
When using this directive you can either include `ngSanitize` in your module's dependencis (See the
|
||||
When using this directive you can either include `ngSanitize` in your module's dependencies (See the
|
||||
example at the {@link ngBindHtml} reference) or use the {@link $sce} service to set the value as
|
||||
trusted.
|
||||
|
||||
@@ -1095,10 +1172,10 @@ freely available to JavaScript code (as before).
|
||||
|
||||
Angular expressions execute in a limited context. They do not have
|
||||
direct access to the global scope, `window`, `document` or the Function
|
||||
constructor. However, they have direct access to names/properties on
|
||||
the scope chain. It has been a long standing best practice to keep
|
||||
constructor. However, they have direct access to names/properties on
|
||||
the scope chain. It has been a long standing best practice to keep
|
||||
sensitive APIs outside of the scope chain (in a closure or your
|
||||
controller.) That's easier said that done for two reasons:
|
||||
controller.) That's easier said than done for two reasons:
|
||||
|
||||
1. JavaScript does not have a notion of private properties so if you need
|
||||
someone on the scope chain for JavaScript use, you also expose it to
|
||||
|
||||
@@ -197,14 +197,14 @@ Then Angular applies configuration blocks in the same order they were registered
|
||||
## Run Blocks
|
||||
|
||||
Run blocks are the closest thing in Angular to the main method. A run block is the code which
|
||||
needs to run to kickstart the application. It is executed after all of the service have been
|
||||
needs to run to kickstart the application. It is executed after all of the services have been
|
||||
configured and the injector has been created. Run blocks typically contain code which is hard
|
||||
to unit-test, and for this reason should be declared in isolated modules, so that they can be
|
||||
ignored in the unit-tests.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Modules can list other modules as their dependencies. Depending on a module implies that required
|
||||
Modules can list other modules as their dependencies. Depending on a module implies that the required
|
||||
module needs to be loaded before the requiring module is loaded. In other words the configuration
|
||||
blocks of the required modules execute before the configuration blocks of the requiring module.
|
||||
The same is true for the run blocks. Each module can only be loaded once, even if multiple other
|
||||
|
||||
@@ -10,13 +10,13 @@ There are a few things you might consider when running your AngularJS applicatio
|
||||
|
||||
## Disabling Debug Data
|
||||
|
||||
By default AngularJS attaches information about scopes to DOM nodes, and adds CSS classes
|
||||
to data-bound elements. The information that is not included is:
|
||||
By default AngularJS attaches information about binding and scopes to DOM nodes,
|
||||
and adds CSS classes to data-bound elements:
|
||||
|
||||
As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
|
||||
`ng-class` is attached to the corresponding element.
|
||||
- As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
|
||||
`ng-binding` are attached to the corresponding element.
|
||||
|
||||
Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
|
||||
- Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
|
||||
CSS class are attached to the corresponding element. These scope references can then be accessed via
|
||||
`element.scope()` and `element.isolateScope()`.
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -15,14 +15,14 @@ development.
|
||||
production.
|
||||
|
||||
To point your code to an angular script on the Google CDN server, use the following template. This
|
||||
example points to the minified version 1.2.0:
|
||||
example points to the minified version 1.3.12:
|
||||
|
||||
```
|
||||
<!doctype html>
|
||||
<html ng-app>
|
||||
<head>
|
||||
<title>My Angular App</title>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.12/angular.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
@@ -33,7 +33,7 @@ templating systems.
|
||||
### Do I need to worry about security holes in AngularJS?
|
||||
|
||||
Like any other technology, AngularJS is not impervious to attack. Angular does, however, provide
|
||||
built-in protection from basic security holes including cross-site scripting and HTML injection
|
||||
built-in protection from basic security holes, including cross-site scripting and HTML injection
|
||||
attacks. AngularJS does round-trip escaping on all strings for you and even offers XSRF protection
|
||||
for server-side communication.
|
||||
|
||||
@@ -52,7 +52,7 @@ Yes. See instructions in {@link downloading}.
|
||||
|
||||
We run our extensive test suite against the following browsers: Safari, Chrome, Firefox, Opera 15,
|
||||
IE9 and mobile browsers (Android, Chrome Mobile, iOS Safari). See {@link guide/ie Internet
|
||||
Explorer Compatibility} for more details in supporting legacy IE browsers.
|
||||
Explorer Compatibility} for more details on supporting legacy IE browsers.
|
||||
|
||||
|
||||
### What's Angular's performance like?
|
||||
@@ -61,8 +61,8 @@ The startup time heavily depends on your network connection, state of the cache,
|
||||
available hardware, but typically we measure bootstrap time in tens or hundreds of milliseconds.
|
||||
|
||||
The runtime performance will vary depending on the number and complexity of bindings on the page
|
||||
as well as the speed of your backend (for apps that fetch data from the backend). Just for an
|
||||
illustration we typically build snappy apps with hundreds or thousands of active bindings.
|
||||
as well as the speed of your backend (for apps that fetch data from the backend). For an
|
||||
illustration, we typically build snappy apps with hundreds or thousands of active bindings.
|
||||
|
||||
|
||||
### How big is the angular.js file that I need to include?
|
||||
@@ -88,7 +88,7 @@ but we don't guarantee that.
|
||||
|
||||
### What is testability like in Angular?
|
||||
|
||||
Very testable and designed this way from ground up. It has an integrated dependency injection
|
||||
Very testable and designed this way from the ground up. It has an integrated dependency injection
|
||||
framework, provides mocks for many heavy dependencies (server-side communication). See
|
||||
{@link ngMock} for details.
|
||||
|
||||
@@ -189,7 +189,7 @@ Then whenever a value on a scope changes, all `$watch`es observing that element
|
||||
|
||||
Sometimes, usually when you're writing a custom directive, you will have to define your own `$watch` on a scope value to make the directive react to changes.
|
||||
|
||||
On the flip side, sometimes you change a scope value in some code but the app doesn't react to it.
|
||||
On the flip side, sometimes you change a scope value in some code, but the app doesn't react to it.
|
||||
Angular checks for scope variable changes after pieces of your code have finished running; for example, when `ng-click` calls a function on your scope, Angular will check for changes and react.
|
||||
However, some code is outside of Angular and you'll have to call `scope.$apply()` yourself to trigger the update.
|
||||
This is most commonly seen in event handlers in custom directives.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,6 +33,9 @@ To see the app running in a browser, open a *separate* terminal/command line tab
|
||||
run `npm start` to start the web server. Now, open a browser window for the app and navigate to
|
||||
<a href="http://localhost:8000/app/" target="_blank">`http://localhost:8000/app/`</a>
|
||||
|
||||
Note that if you already ran the master branch app prior to checking out step-0, you may see the cached
|
||||
master version of the app in your browser window at this point. Just hit refresh to re-load the page.
|
||||
|
||||
You can now see the page in your browser. It's not very exciting, but that's OK.
|
||||
|
||||
The HTML page that displays "Nothing here yet!" was constructed with the HTML code shown below.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ multiple views by adding routing, using an Angular module called 'ngRoute'.
|
||||
|
||||
* When you now navigate to `app/index.html`, you are redirected to `app/index.html/#/phones`
|
||||
and the phone list appears in the browser.
|
||||
* When you click on a phone link the url changes to one specific to that phone and the stub of a
|
||||
* When you click on a phone link, the url changes to that specific phone and the stub of a
|
||||
phone detail page is displayed.
|
||||
|
||||
<div doc-tutorial-reset="7"></div>
|
||||
@@ -21,7 +21,7 @@ multiple views by adding routing, using an Angular module called 'ngRoute'.
|
||||
The routing functionality added by this step is provided by angular in the `ngRoute` module, which
|
||||
is distributed separately from the core Angular framework.
|
||||
|
||||
We are using [Bower][bower] to install client side dependencies. This step updates the
|
||||
We are using [Bower][bower] to install client-side dependencies. This step updates the
|
||||
`bower.json` configuration file to include the new dependency:
|
||||
|
||||
```json
|
||||
@@ -46,7 +46,7 @@ The new dependency `"angular-route": "~1.3.0"` tells bower to install a version
|
||||
angular-route component that is compatible with version 1.3.x. We must tell bower to download
|
||||
and install this dependency.
|
||||
|
||||
If you have bower installed globally then you can run `bower install` but for this project we have
|
||||
If you have bower installed globally, then you can run `bower install` but for this project, we have
|
||||
preconfigured npm to run bower install for us:
|
||||
|
||||
```
|
||||
@@ -70,7 +70,7 @@ the current "route" — the view that is currently displayed to the user.
|
||||
Application routes in Angular are declared via the {@link ngRoute.$routeProvider $routeProvider},
|
||||
which is the provider of the {@link ngRoute.$route $route service}. This service makes it easy to
|
||||
wire together controllers, view templates, and the current URL location in the browser. Using this
|
||||
feature we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us
|
||||
feature, we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us
|
||||
utilize the browser's history (back and forward navigation) and bookmarks.
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ AngularJS, so it's important for you to understand a thing or two about how it w
|
||||
|
||||
When the application bootstraps, Angular creates an injector that will be used to find and inject all
|
||||
of the services that are required by your app. The injector itself doesn't know anything about what
|
||||
`$http` or `$route` services do, in fact it doesn't even know about the existence of these services
|
||||
`$http` or `$route` services do. In fact, the injector doesn't even know about the existence of these services
|
||||
unless it is configured with proper module definitions.
|
||||
|
||||
The injector only carries out the following steps :
|
||||
@@ -295,7 +295,7 @@ phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
|
||||
|
||||
Again, note that we created a new module called `phonecatControllers`. For small AngularJS
|
||||
applications, it's common to create just one module for all of your controllers if there are just a
|
||||
few. As your application grows it is quite common to refactor your code into additional modules.
|
||||
few. As your application grows, it is quite common to refactor your code into additional modules.
|
||||
For larger apps, you will probably want to create separate modules for each major feature of
|
||||
your app.
|
||||
|
||||
@@ -349,7 +349,7 @@ the same binding into the `phone-list.html` template, the binding will work as e
|
||||
|
||||
<div style="display: none">
|
||||
* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In
|
||||
`PhoneListCtrl` let's shadow it with `this.hero = 'Batman'`, and in `PhoneDetailCtrl` we'll use
|
||||
`PhoneListCtrl`, let's shadow it with `this.hero = 'Batman'`. In `PhoneDetailCtrl`, we'll use
|
||||
`this.hero = "Captain Proton"`. Then add the `<p>hero = {{hero}}</p>` to all three of our templates
|
||||
(`index.html`, `phone-list.html`, and `phone-detail.html`). Open the app and you'll see scope
|
||||
inheritance and model property shadowing do some wonders.
|
||||
|
||||
@@ -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`:__
|
||||
|
||||
@@ -31,7 +31,7 @@ phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$h
|
||||
|
||||
$scope.setImage = function(imageUrl) {
|
||||
$scope.mainImageUrl = imageUrl;
|
||||
}
|
||||
};
|
||||
}]);
|
||||
```
|
||||
|
||||
|
||||
@@ -288,5 +288,5 @@ learn how to improve this application with animations.
|
||||
<ul doc-tutorial-nav="11"></ul>
|
||||
|
||||
[restful]: http://en.wikipedia.org/wiki/Representational_State_Transfer
|
||||
[jasmine-matchers]: https://github.com/pivotal/jasmine/wiki/Matchers
|
||||
[jasmine-matchers]: http://jasmine.github.io/1.3/introduction.html#section-Matchers
|
||||
[bower]: http://bower.io/
|
||||
|
||||
@@ -97,7 +97,7 @@ __`app/index.html`.__
|
||||
...
|
||||
|
||||
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
|
||||
<script src="bower_components/jquery/jquery.js"></script>
|
||||
<script src="bower_components/jquery/dist/jquery.js"></script>
|
||||
|
||||
...
|
||||
|
||||
|
||||
@@ -2912,16 +2912,12 @@ goog.i18n.DateTimeSymbols_my = {
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
|
||||
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
|
||||
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
|
||||
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
|
||||
'ဒီဇင်ဘာ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
|
||||
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
|
||||
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
|
||||
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
|
||||
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
|
||||
@@ -2945,7 +2941,7 @@ goog.i18n.DateTimeSymbols_my = {
|
||||
'တတိယ သုံးလပတ်',
|
||||
'စတုတ္ထ သုံးလပတ်'],
|
||||
AMPMS: ['နံနက်', 'ညနေ'],
|
||||
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
|
||||
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
|
||||
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
|
||||
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
|
||||
FIRSTDAYOFWEEK: 6,
|
||||
|
||||
@@ -14493,16 +14493,12 @@ goog.i18n.DateTimeSymbols_my_MM = {
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
|
||||
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
|
||||
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
|
||||
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
|
||||
'ဒီဇင်ဘာ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
|
||||
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
|
||||
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
|
||||
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
|
||||
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
|
||||
@@ -14526,7 +14522,7 @@ goog.i18n.DateTimeSymbols_my_MM = {
|
||||
'တတိယ သုံးလပတ်',
|
||||
'စတုတ္ထ သုံးလပတ်'],
|
||||
AMPMS: ['နံနက်', 'ညနေ'],
|
||||
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
|
||||
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
|
||||
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
|
||||
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
|
||||
FIRSTDAYOFWEEK: 6,
|
||||
|
||||
@@ -2112,7 +2112,7 @@ goog.i18n.NumberFormatSymbols_lt = {
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
PERCENT_PATTERN: '#,##0\u00A0%',
|
||||
CURRENCY_PATTERN: '#,##0.00\u00A0\u00A4',
|
||||
DEF_CURRENCY_CODE: 'LTL'
|
||||
DEF_CURRENCY_CODE: 'EUR'
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -35,18 +35,18 @@ module.exports = function(config, specificOptions) {
|
||||
'SL_Chrome': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'chrome',
|
||||
version: '34'
|
||||
version: '39'
|
||||
},
|
||||
'SL_Firefox': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'firefox',
|
||||
version: '26'
|
||||
version: '31'
|
||||
},
|
||||
'SL_Safari': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.9',
|
||||
version: '7'
|
||||
platform: 'OS X 10.10',
|
||||
version: '8'
|
||||
},
|
||||
'SL_IE_9': {
|
||||
base: 'SauceLabs',
|
||||
@@ -71,13 +71,13 @@ module.exports = function(config, specificOptions) {
|
||||
base: 'BrowserStack',
|
||||
browser: 'chrome',
|
||||
os: 'OS X',
|
||||
os_version: 'Mountain Lion'
|
||||
os_version: 'Yosemite'
|
||||
},
|
||||
'BS_Safari': {
|
||||
base: 'BrowserStack',
|
||||
browser: 'safari',
|
||||
os: 'OS X',
|
||||
os_version: 'Mountain Lion'
|
||||
os_version: 'Yosemite'
|
||||
},
|
||||
'BS_Firefox': {
|
||||
base: 'BrowserStack',
|
||||
|
||||
@@ -16,9 +16,9 @@ var currentPackage, previousVersions, cdnVersion, gitRepoInfo;
|
||||
var getPackage = function() {
|
||||
// Search up the folder hierarchy for the first package.json
|
||||
var packageFolder = path.resolve('.');
|
||||
while ( !fs.existsSync(path.join(packageFolder, 'package.json')) ) {
|
||||
while (!fs.existsSync(path.join(packageFolder, 'package.json'))) {
|
||||
var parent = path.dirname(packageFolder);
|
||||
if ( parent === packageFolder) { break; }
|
||||
if (parent === packageFolder) { break; }
|
||||
packageFolder = parent;
|
||||
}
|
||||
return JSON.parse(fs.readFileSync(path.join(packageFolder,'package.json'), 'UTF-8'));
|
||||
@@ -48,11 +48,11 @@ var getGitRepoInfo = function() {
|
||||
* @return {String} The codename if found, otherwise null/undefined
|
||||
*/
|
||||
var getCodeName = function(tagName) {
|
||||
var gitCatOutput = shell.exec('git cat-file -p '+ tagName, {silent:true}).output;
|
||||
var gitCatOutput = shell.exec('git cat-file -p ' + tagName, {silent:true}).output;
|
||||
var tagMessage = gitCatOutput.match(/^.*codename.*$/mg)[0];
|
||||
var codeName = tagMessage && tagMessage.match(/codename\((.*)\)/)[1];
|
||||
if (!codeName) {
|
||||
throw new Error("Could not extract release code name. The message of tag "+tagName+
|
||||
throw new Error("Could not extract release code name. The message of tag " + tagName +
|
||||
" must match '*codename(some release name)*'");
|
||||
}
|
||||
return codeName;
|
||||
@@ -65,7 +65,7 @@ var getCodeName = function(tagName) {
|
||||
*/
|
||||
function getBuild() {
|
||||
var hash = shell.exec('git rev-parse --short HEAD', {silent: true}).output.replace('\n', '');
|
||||
return 'sha.'+hash;
|
||||
return 'sha.' + hash;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,14 +76,14 @@ function getBuild() {
|
||||
var getTaggedVersion = function() {
|
||||
var gitTagResult = shell.exec('git describe --exact-match', {silent:true});
|
||||
|
||||
if ( gitTagResult.code === 0 ) {
|
||||
if (gitTagResult.code === 0) {
|
||||
var tag = gitTagResult.output.trim();
|
||||
var version = semver.parse(tag);
|
||||
|
||||
if ( version && semver.satisfies(version, currentPackage.branchVersion)) {
|
||||
if (version && semver.satisfies(version, currentPackage.branchVersion)) {
|
||||
version.codeName = getCodeName(tag);
|
||||
version.full = version.version;
|
||||
version.branch = 'v' + currentPackage.branchVersion.replace('*', 'x');
|
||||
version.branch = 'v' + currentPackage.branchPattern.replace('*', 'x');
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ var getPreviousVersions = function() {
|
||||
var repo_url = currentPackage.repository.url;
|
||||
var tagResults = shell.exec('git ls-remote --tags ' + repo_url,
|
||||
{silent: true});
|
||||
if ( tagResults.code === 0 ) {
|
||||
if (tagResults.code === 0) {
|
||||
return _(tagResults.output.match(/v[0-9].*[0-9]$/mg))
|
||||
.map(function(tag) {
|
||||
var version = semver.parse(tag);
|
||||
@@ -112,7 +112,7 @@ var getPreviousVersions = function() {
|
||||
.map(function(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 ) ) {
|
||||
if (version.major < 1 || (version.major === 1 && version.minor === 0 && version.dot < 2)) {
|
||||
version.docsUrl += '-' + version.version;
|
||||
}
|
||||
return version;
|
||||
@@ -134,10 +134,10 @@ var getCdnVersion = function() {
|
||||
if (!cdnVersion) {
|
||||
// Note: need to use shell.exec and curl here
|
||||
// as version-infos returns its result synchronously...
|
||||
var cdnResult = shell.exec('curl http://ajax.googleapis.com/ajax/libs/angularjs/'+version+'/angular.min.js '+
|
||||
var cdnResult = shell.exec('curl http://ajax.googleapis.com/ajax/libs/angularjs/' + version + '/angular.min.js ' +
|
||||
'--head --write-out "%{http_code}" -o /dev/null -silent',
|
||||
{silent: true});
|
||||
if ( cdnResult.code === 0 ) {
|
||||
if (cdnResult.code === 0) {
|
||||
var statusCode = cdnResult.output.trim();
|
||||
if (statusCode === '200') {
|
||||
cdnVersion = version;
|
||||
@@ -159,9 +159,9 @@ var getSnapshotVersion = function() {
|
||||
})
|
||||
.last();
|
||||
|
||||
if ( !version ) {
|
||||
if (!version) {
|
||||
// a snapshot version before the first tag on the branch
|
||||
version = semver(currentPackage.branchVersion.replace('*','0-beta.1'));
|
||||
version = semver(currentPackage.branchPattern.replace('*','0-beta.1'));
|
||||
}
|
||||
|
||||
// We need to clone to ensure that we are not modifying another version
|
||||
|
||||
Generated
+108
-84
@@ -1395,66 +1395,66 @@
|
||||
}
|
||||
},
|
||||
"dgeni-packages": {
|
||||
"version": "0.10.7",
|
||||
"version": "0.10.8",
|
||||
"dependencies": {
|
||||
"catharsis": {
|
||||
"version": "0.7.1"
|
||||
},
|
||||
"change-case": {
|
||||
"version": "2.1.5",
|
||||
"version": "2.2.0",
|
||||
"dependencies": {
|
||||
"camel-case": {
|
||||
"version": "1.0.2"
|
||||
"version": "1.1.1"
|
||||
},
|
||||
"constant-case": {
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"dot-case": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"is-lower-case": {
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"is-upper-case": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"lower-case": {
|
||||
"version": "1.0.2"
|
||||
},
|
||||
"param-case": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"pascal-case": {
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"path-case": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"sentence-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"dot-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"is-lower-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"is-upper-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"lower-case": {
|
||||
"version": "1.1.1"
|
||||
},
|
||||
"param-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"pascal-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"path-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"sentence-case": {
|
||||
"version": "1.1.1"
|
||||
},
|
||||
"snake-case": {
|
||||
"version": "1.0.1"
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"swap-case": {
|
||||
"version": "1.0.2"
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"title-case": {
|
||||
"version": "1.0.1"
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"upper-case": {
|
||||
"version": "1.0.3"
|
||||
"version": "1.1.1"
|
||||
},
|
||||
"upper-case-first": {
|
||||
"version": "1.0.1"
|
||||
"version": "1.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
"version": "1.2.2"
|
||||
"version": "1.2.3"
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "1.7.1"
|
||||
"version": "1.9.1"
|
||||
},
|
||||
"glob": {
|
||||
"version": "3.2.11",
|
||||
@@ -1626,7 +1626,7 @@
|
||||
}
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.1"
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"cookie-jar": {
|
||||
"version": "0.2.0"
|
||||
@@ -2342,26 +2342,32 @@
|
||||
},
|
||||
"grunt-jasmine-node": {
|
||||
"version": "0.1.0",
|
||||
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7",
|
||||
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"resolved": "git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7"
|
||||
},
|
||||
"grunt-jscs": {
|
||||
"version": "0.7.1",
|
||||
"version": "1.2.0",
|
||||
"dependencies": {
|
||||
"hooker": {
|
||||
"version": "0.2.3"
|
||||
},
|
||||
"jscs": {
|
||||
"version": "1.6.2",
|
||||
"version": "1.10.0",
|
||||
"dependencies": {
|
||||
"colors": {
|
||||
"version": "0.6.2"
|
||||
"version": "1.0.3"
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.3.0"
|
||||
"version": "2.5.1"
|
||||
},
|
||||
"esprima": {
|
||||
"version": "1.2.2"
|
||||
"version": "1.2.3"
|
||||
},
|
||||
"esprima-harmony-jscs": {
|
||||
"version": "1.1.0-regex-token-fix"
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "1.9.1"
|
||||
},
|
||||
"exit": {
|
||||
"version": "0.1.2"
|
||||
@@ -2370,11 +2376,22 @@
|
||||
"version": "4.0.6",
|
||||
"dependencies": {
|
||||
"graceful-fs": {
|
||||
"version": "3.0.4"
|
||||
"version": "3.0.5"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.5.0"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.3.1",
|
||||
"dependencies": {
|
||||
@@ -2386,54 +2403,61 @@
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.1",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.5.0"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.0"
|
||||
"brace-expansion": {
|
||||
"version": "1.1.0",
|
||||
"dependencies": {
|
||||
"balanced-match": {
|
||||
"version": "0.2.0"
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "1.0.1"
|
||||
"version": "1.0.2"
|
||||
},
|
||||
"vow-fs": {
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.4",
|
||||
"dependencies": {
|
||||
"node-uuid": {
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"vow": {
|
||||
"version": "0.4.4"
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"vow-queue": {
|
||||
"version": "0.3.1"
|
||||
"version": "0.4.1"
|
||||
},
|
||||
"glob": {
|
||||
"version": "3.2.8",
|
||||
"version": "4.3.5",
|
||||
"dependencies": {
|
||||
"minimatch": {
|
||||
"version": "0.2.14",
|
||||
"inflight": {
|
||||
"version": "1.0.4",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.5.0"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.0"
|
||||
"wrappy": {
|
||||
"version": "1.0.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
},
|
||||
"once": {
|
||||
"version": "1.3.1",
|
||||
"dependencies": {
|
||||
"wrappy": {
|
||||
"version": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.6",
|
||||
"dependencies": {
|
||||
"lodash-node": {
|
||||
"version": "2.4.1"
|
||||
@@ -2441,29 +2465,18 @@
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vow": {
|
||||
"version": "0.4.5"
|
||||
"version": "0.4.8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"grunt-merge-conflict": {
|
||||
"version": "0.0.2"
|
||||
},
|
||||
"grunt-parallel": {
|
||||
"version": "0.3.1",
|
||||
"dependencies": {
|
||||
"q": {
|
||||
"version": "0.8.12"
|
||||
},
|
||||
"lpad": {
|
||||
"version": "0.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"grunt-shell": {
|
||||
"version": "1.1.1",
|
||||
"dependencies": {
|
||||
@@ -4810,7 +4823,7 @@
|
||||
}
|
||||
},
|
||||
"protractor": {
|
||||
"version": "1.4.0",
|
||||
"version": "1.6.0",
|
||||
"dependencies": {
|
||||
"request": {
|
||||
"version": "2.36.0",
|
||||
@@ -4828,7 +4841,7 @@
|
||||
"version": "0.5.2"
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.1"
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "0.12.1",
|
||||
@@ -4858,16 +4871,16 @@
|
||||
"version": "0.4.0"
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "0.10.0",
|
||||
"version": "0.10.1",
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "0.1.2"
|
||||
"version": "0.1.5"
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.1.11"
|
||||
},
|
||||
"ctype": {
|
||||
"version": "0.5.2"
|
||||
"version": "0.5.3"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4909,7 +4922,7 @@
|
||||
"version": "0.6.1"
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.5",
|
||||
"dependencies": {
|
||||
"lodash-node": {
|
||||
"version": "2.4.1"
|
||||
@@ -4926,6 +4939,17 @@
|
||||
"jasminewd": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"jasminewd2": {
|
||||
"version": "0.0.2"
|
||||
},
|
||||
"jasmine": {
|
||||
"version": "2.1.1",
|
||||
"dependencies": {
|
||||
"jasmine-core": {
|
||||
"version": "2.1.3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saucelabs": {
|
||||
"version": "0.1.1"
|
||||
},
|
||||
@@ -4966,7 +4990,7 @@
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.9",
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.1.32",
|
||||
|
||||
+6
-5
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"branchVersion": "1.3.*",
|
||||
"branchVersion": "^1.4.0-beta.0",
|
||||
"branchPattern": "1.4.*",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
@@ -11,6 +12,7 @@
|
||||
"bower": "~1.3.9",
|
||||
"browserstacktunnel-wrapper": "~1.3.1",
|
||||
"canonical-path": "0.0.2",
|
||||
"cheerio": "^0.17.0",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.10.0",
|
||||
"event-stream": "~3.1.0",
|
||||
@@ -23,7 +25,7 @@
|
||||
"grunt-contrib-jshint": "~0.10.0",
|
||||
"grunt-ddescribe-iit": "~0.0.1",
|
||||
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"grunt-jscs": "~0.7.1",
|
||||
"grunt-jscs": "~1.2.0",
|
||||
"grunt-merge-conflict": "~0.0.1",
|
||||
"grunt-shell": "~1.1.1",
|
||||
"gulp": "~3.8.0",
|
||||
@@ -51,7 +53,7 @@
|
||||
"marked": "~0.3.0",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"promises-aplus-tests": "~2.1.0",
|
||||
"protractor": "1.4.0",
|
||||
"protractor": "^1.6.0",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
@@ -59,8 +61,7 @@
|
||||
"semver": "~4.0.3",
|
||||
"shelljs": "~0.3.0",
|
||||
"sorted-object": "^1.0.0",
|
||||
"stringmap": "^0.2.2",
|
||||
"cheerio": "^0.17.0"
|
||||
"stringmap": "^0.2.2"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ ARG_DEFS=(
|
||||
)
|
||||
|
||||
function checkVersionNumber() {
|
||||
BRANCH_PATTERN=$(readJsonProp "package.json" "branchVersion")
|
||||
BRANCH_PATTERN=$(readJsonProp "package.json" "branchPattern")
|
||||
if [[ $VERSION_NUMBER != $BRANCH_PATTERN ]]; then
|
||||
echo "version-number needs to match $BRANCH_PATTERN on this branch"
|
||||
usage
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,6 +16,7 @@ if [ $JOB = "unit" ]; then
|
||||
grunt test:unit --browsers $BROWSERS --reporters dots
|
||||
grunt ci-checks
|
||||
grunt tests:docs --browsers $BROWSERS --reporters dots
|
||||
elif [ $JOB = "docs-e2e" ]; then
|
||||
grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js"
|
||||
elif [ $JOB = "e2e" ]; then
|
||||
if [ $TEST_TARGET = "jquery" ]; then
|
||||
|
||||
+5
-2
@@ -28,13 +28,12 @@
|
||||
"manualUppercase": false,
|
||||
"isArrayLike": false,
|
||||
"forEach": false,
|
||||
"sortedKeys": false,
|
||||
"forEachSorted": false,
|
||||
"reverseParams": false,
|
||||
"nextUid": false,
|
||||
"setHashKey": false,
|
||||
"extend": false,
|
||||
"int": false,
|
||||
"toInt": false,
|
||||
"inherit": false,
|
||||
"noop": false,
|
||||
"identity": false,
|
||||
@@ -51,6 +50,7 @@
|
||||
"isWindow": false,
|
||||
"isScope": false,
|
||||
"isFile": false,
|
||||
"isFormData": false,
|
||||
"isBlob": false,
|
||||
"isBoolean": false,
|
||||
"isPromiseLike": false,
|
||||
@@ -152,6 +152,9 @@
|
||||
"urlResolve": false,
|
||||
"urlIsSameOrigin": false,
|
||||
|
||||
/* ng/controller.js */
|
||||
"identifierForController": false,
|
||||
|
||||
/* ng/compile.js */
|
||||
"directiveNormalize": false,
|
||||
|
||||
|
||||
+107
-22
@@ -22,13 +22,12 @@
|
||||
nodeName_: true,
|
||||
isArrayLike: true,
|
||||
forEach: true,
|
||||
sortedKeys: true,
|
||||
forEachSorted: true,
|
||||
reverseParams: true,
|
||||
nextUid: true,
|
||||
setHashKey: true,
|
||||
extend: true,
|
||||
int: true,
|
||||
toInt: true,
|
||||
inherit: true,
|
||||
noop: true,
|
||||
identity: true,
|
||||
@@ -45,6 +44,7 @@
|
||||
isWindow: true,
|
||||
isScope: true,
|
||||
isFile: true,
|
||||
isFormData: true,
|
||||
isBlob: true,
|
||||
isBoolean: true,
|
||||
isPromiseLike: true,
|
||||
@@ -58,6 +58,7 @@
|
||||
shallowCopy: true,
|
||||
equals: true,
|
||||
csp: true,
|
||||
jq: true,
|
||||
concat: true,
|
||||
sliceArgs: true,
|
||||
bind: true,
|
||||
@@ -271,12 +272,8 @@ function forEach(obj, iterator, context) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function sortedKeys(obj) {
|
||||
return Object.keys(obj).sort();
|
||||
}
|
||||
|
||||
function forEachSorted(obj, iterator, context) {
|
||||
var keys = sortedKeys(obj);
|
||||
var keys = Object.keys(obj).sort();
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
iterator.call(context, obj[keys[i]], keys[i]);
|
||||
}
|
||||
@@ -316,8 +313,7 @@ function nextUid() {
|
||||
function setHashKey(obj, h) {
|
||||
if (h) {
|
||||
obj.$$hashKey = h;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
delete obj.$$hashKey;
|
||||
}
|
||||
}
|
||||
@@ -356,7 +352,7 @@ function extend(dst) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
function int(str) {
|
||||
function toInt(str) {
|
||||
return parseInt(str, 10);
|
||||
}
|
||||
|
||||
@@ -400,6 +396,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 +564,11 @@ function isFile(obj) {
|
||||
}
|
||||
|
||||
|
||||
function isFormData(obj) {
|
||||
return toString.call(obj) === '[object FormData]';
|
||||
}
|
||||
|
||||
|
||||
function isBlob(obj) {
|
||||
return toString.call(obj) === '[object Blob]';
|
||||
}
|
||||
@@ -581,6 +584,12 @@ function isPromiseLike(obj) {
|
||||
}
|
||||
|
||||
|
||||
var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/;
|
||||
function isTypedArray(value) {
|
||||
return TYPED_ARRAY_REGEXP.test(toString.call(value));
|
||||
}
|
||||
|
||||
|
||||
var trim = function(value) {
|
||||
return isString(value) ? value.trim() : value;
|
||||
};
|
||||
@@ -618,8 +627,9 @@ function isElement(node) {
|
||||
*/
|
||||
function makeMap(str) {
|
||||
var obj = {}, items = str.split(","), i;
|
||||
for (i = 0; i < items.length; i++)
|
||||
obj[ items[i] ] = true;
|
||||
for (i = 0; i < items.length; i++) {
|
||||
obj[items[i]] = true;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -634,9 +644,10 @@ 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;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -649,7 +660,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.
|
||||
@@ -702,12 +713,18 @@ function copy(source, destination, stackSource, stackDest) {
|
||||
throw ngMinErr('cpws',
|
||||
"Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
}
|
||||
if (isTypedArray(destination)) {
|
||||
throw ngMinErr('cpta',
|
||||
"Can't copy! TypedArray destination cannot be mutated.");
|
||||
}
|
||||
|
||||
if (!destination) {
|
||||
destination = source;
|
||||
if (source) {
|
||||
if (isArray(source)) {
|
||||
destination = copy(source, [], stackSource, stackDest);
|
||||
} else if (isTypedArray(source)) {
|
||||
destination = new source.constructor(source);
|
||||
} else if (isDate(source)) {
|
||||
destination = new Date(source.getTime());
|
||||
} else if (isRegExp(source)) {
|
||||
@@ -885,7 +902,61 @@ var csp = function() {
|
||||
return (csp.isActive_ = active);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @module ng
|
||||
* @name ngJq
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string=} the name of the library available under `window`
|
||||
* to be used for angular.element
|
||||
* @description
|
||||
* Use this directive to force the angular.element library. This should be
|
||||
* used to force either jqLite by leaving ng-jq blank or setting the name of
|
||||
* the jquery variable under window (eg. jQuery).
|
||||
*
|
||||
* Since this directive is global for the angular library, it is recommended
|
||||
* that it's added to the same element as ng-app or the HTML element, but it is not mandatory.
|
||||
* It needs to be noted that only the first instance of `ng-jq` will be used and all others
|
||||
* ignored.
|
||||
*
|
||||
* @example
|
||||
* This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
|
||||
```html
|
||||
<!doctype html>
|
||||
<html ng-app ng-jq>
|
||||
...
|
||||
...
|
||||
</html>
|
||||
```
|
||||
* @example
|
||||
* This example shows how to use a jQuery based library of a different name.
|
||||
* The library name must be available at the top most 'window'.
|
||||
```html
|
||||
<!doctype html>
|
||||
<html ng-app ng-jq="jQueryLib">
|
||||
...
|
||||
...
|
||||
</html>
|
||||
```
|
||||
*/
|
||||
var jq = function() {
|
||||
if (isDefined(jq.name_)) return jq.name_;
|
||||
var el;
|
||||
var i, ii = ngAttrPrefixes.length;
|
||||
for (i = 0; i < ii; ++i) {
|
||||
if (el = document.querySelector('[' + ngAttrPrefixes[i].replace(':', '\\:') + 'jq]')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var name;
|
||||
if (el) {
|
||||
name = getNgAttribute(el, "jq");
|
||||
}
|
||||
|
||||
return (jq.name_ = name);
|
||||
};
|
||||
|
||||
function concat(array1, array2, index) {
|
||||
return array1.concat(slice.call(array2, index));
|
||||
@@ -987,7 +1058,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)
|
||||
@@ -1160,7 +1231,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.
|
||||
*
|
||||
@@ -1168,7 +1239,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">
|
||||
@@ -1330,7 +1401,7 @@ function angularInit(element, bootstrap) {
|
||||
* @param {DOMElement} element DOM element which is the root of angular application.
|
||||
* @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
|
||||
* Each item in the array should be the name of a predefined module or a (DI annotated)
|
||||
* function that will be invoked by the injector as a run block.
|
||||
* function that will be invoked by the injector as a `config` block.
|
||||
* See: {@link angular.module modules}
|
||||
* @param {Object=} config an object for defining configuration options for the application. The
|
||||
* following keys are supported:
|
||||
@@ -1400,8 +1471,12 @@ function bootstrap(element, modules, config) {
|
||||
forEach(extraModules, function(module) {
|
||||
modules.push(module);
|
||||
});
|
||||
doBootstrap();
|
||||
return doBootstrap();
|
||||
};
|
||||
|
||||
if (isFunction(angular.resumeDeferredBootstrap)) {
|
||||
angular.resumeDeferredBootstrap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1428,7 +1503,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;
|
||||
@@ -1449,7 +1529,12 @@ function bindJQuery() {
|
||||
}
|
||||
|
||||
// bind to jQuery if present;
|
||||
jQuery = window.jQuery;
|
||||
var jqName = jq();
|
||||
jQuery = window.jQuery; // use default jQuery.
|
||||
if (isDefined(jqName)) { // `ngJq` present
|
||||
jQuery = jqName === null ? undefined : window[jqName]; // if empty; use jqLite. if not empty, use jQuery specified by `ngJq`.
|
||||
}
|
||||
|
||||
// Use jQuery if it exists with proper functionality, otherwise default to us.
|
||||
// Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
|
||||
// Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, document, undefined) {
|
||||
|
||||
@@ -800,7 +800,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
}
|
||||
|
||||
var args = [],
|
||||
$inject = annotate(fn, strictDi, serviceName),
|
||||
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
|
||||
length, i,
|
||||
key;
|
||||
|
||||
@@ -829,7 +829,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
// 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) {}]);
|
||||
// Object creation: http://jsperf.com/create-constructor/2
|
||||
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
|
||||
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
|
||||
var returnedValue = invoke(Type, instance, locals, serviceName);
|
||||
|
||||
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
|
||||
@@ -839,7 +839,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
invoke: invoke,
|
||||
instantiate: instantiate,
|
||||
get: getService,
|
||||
annotate: annotate,
|
||||
annotate: createInjector.$$annotate,
|
||||
has: function(name) {
|
||||
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
+2
-1
@@ -851,8 +851,9 @@ forEach({
|
||||
children: function(element) {
|
||||
var children = [];
|
||||
forEach(element.childNodes, function(element) {
|
||||
if (element.nodeType === NODE_TYPE_ELEMENT)
|
||||
if (element.nodeType === NODE_TYPE_ELEMENT) {
|
||||
children.push(element);
|
||||
}
|
||||
});
|
||||
return children;
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
+18
-13
@@ -33,28 +33,33 @@
|
||||
function minErr(module, ErrorConstructor) {
|
||||
ErrorConstructor = ErrorConstructor || Error;
|
||||
return function() {
|
||||
var code = arguments[0],
|
||||
prefix = '[' + (module ? module + ':' : '') + code + '] ',
|
||||
template = arguments[1],
|
||||
templateArgs = arguments,
|
||||
var SKIP_INDEXES = 2;
|
||||
|
||||
message, i;
|
||||
var templateArgs = arguments,
|
||||
code = templateArgs[0],
|
||||
message = '[' + (module ? module + ':' : '') + code + '] ',
|
||||
template = templateArgs[1],
|
||||
paramPrefix, i;
|
||||
|
||||
message = prefix + template.replace(/\{\d+\}/g, function(match) {
|
||||
var index = +match.slice(1, -1), arg;
|
||||
message += template.replace(/\{\d+\}/g, function(match) {
|
||||
var index = +match.slice(1, -1),
|
||||
shiftedIndex = index + SKIP_INDEXES;
|
||||
|
||||
if (index + 2 < templateArgs.length) {
|
||||
return toDebugString(templateArgs[index + 2]);
|
||||
if (shiftedIndex < templateArgs.length) {
|
||||
return toDebugString(templateArgs[shiftedIndex]);
|
||||
}
|
||||
|
||||
return match;
|
||||
});
|
||||
|
||||
message = message + '\nhttp://errors.angularjs.org/"NG_VERSION_FULL"/' +
|
||||
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(toDebugString(arguments[i]));
|
||||
|
||||
for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
|
||||
message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
|
||||
encodeURIComponent(toDebugString(templateArgs[i]));
|
||||
}
|
||||
|
||||
return new ErrorConstructor(message);
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
|
||||
@@ -159,13 +159,13 @@ function $CacheFactoryProvider() {
|
||||
* @returns {*} the value stored.
|
||||
*/
|
||||
put: function(key, value) {
|
||||
if (isUndefined(value)) return;
|
||||
if (capacity < Number.MAX_VALUE) {
|
||||
var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
|
||||
|
||||
refresh(lruEntry);
|
||||
}
|
||||
|
||||
if (isUndefined(value)) return;
|
||||
if (!(key in data)) size++;
|
||||
data[key] = value;
|
||||
|
||||
|
||||
+211
-105
@@ -64,7 +64,7 @@
|
||||
* templateNamespace: 'html',
|
||||
* scope: false,
|
||||
* controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
|
||||
* controllerAs: 'stringAlias',
|
||||
* controllerAs: 'stringIdentifier',
|
||||
* require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
|
||||
* compile: function compile(tElement, tAttrs, transclude) {
|
||||
* return {
|
||||
@@ -224,9 +224,10 @@
|
||||
*
|
||||
*
|
||||
* #### `controllerAs`
|
||||
* Controller alias at the directive scope. An alias for the controller so it
|
||||
* can be referenced at the directive template. The directive needs to define a scope for this
|
||||
* configuration to be used. Useful in the case when directive is used as component.
|
||||
* Identifier name for a reference to the controller in the directive's scope.
|
||||
* This allows the controller to be referenced from the directive template. The directive
|
||||
* needs to define a scope for this configuration to be used. Useful in the case when
|
||||
* directive is used as component.
|
||||
*
|
||||
*
|
||||
* #### `restrict`
|
||||
@@ -477,7 +478,7 @@
|
||||
*
|
||||
* <div class="alert alert-info">
|
||||
* **Best Practice**: if you intend to add and remove transcluded content manually in your directive
|
||||
* (by calling the transclude function to get the DOM and and calling `element.remove()` to remove it),
|
||||
* (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
|
||||
* then you are also responsible for calling `$destroy` on the transclusion scope.
|
||||
* </div>
|
||||
*
|
||||
@@ -712,7 +713,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// 'on' and be composed of only English letters.
|
||||
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
|
||||
|
||||
function parseIsolateBindings(scope, directiveName) {
|
||||
function parseIsolateBindings(scope, directiveName, isController) {
|
||||
var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
|
||||
|
||||
var bindings = {};
|
||||
@@ -722,9 +723,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
if (!match) {
|
||||
throw $compileMinErr('iscp',
|
||||
"Invalid isolate scope definition for directive '{0}'." +
|
||||
"Invalid {3} for directive '{0}'." +
|
||||
" Definition: {... {1}: '{2}' ...}",
|
||||
directiveName, scopeName, definition);
|
||||
directiveName, scopeName, definition,
|
||||
(isController ? "controller bindings definition" :
|
||||
"isolate scope definition"));
|
||||
}
|
||||
|
||||
bindings[scopeName] = {
|
||||
@@ -738,6 +741,43 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
function parseDirectiveBindings(directive, directiveName) {
|
||||
var bindings = {
|
||||
isolateScope: null,
|
||||
bindToController: null
|
||||
};
|
||||
if (isObject(directive.scope)) {
|
||||
if (directive.bindToController === true) {
|
||||
bindings.bindToController = parseIsolateBindings(directive.scope,
|
||||
directiveName, true);
|
||||
bindings.isolateScope = {};
|
||||
} else {
|
||||
bindings.isolateScope = parseIsolateBindings(directive.scope,
|
||||
directiveName, false);
|
||||
}
|
||||
}
|
||||
if (isObject(directive.bindToController)) {
|
||||
bindings.bindToController =
|
||||
parseIsolateBindings(directive.bindToController, directiveName, true);
|
||||
}
|
||||
if (isObject(bindings.bindToController)) {
|
||||
var controller = directive.controller;
|
||||
var controllerAs = directive.controllerAs;
|
||||
if (!controller) {
|
||||
// There is no controller, there may or may not be a controllerAs property
|
||||
throw $compileMinErr('noctrl',
|
||||
"Cannot bind to controller without directive '{0}'s controller.",
|
||||
directiveName);
|
||||
} else if (!identifierForController(controller, controllerAs)) {
|
||||
// There is a controller, but no identifier or controllerAs property
|
||||
throw $compileMinErr('noident',
|
||||
"Cannot bind to controller without identifier for directive '{0}'.",
|
||||
directiveName);
|
||||
}
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compileProvider#directive
|
||||
@@ -775,8 +815,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
directive.name = directive.name || name;
|
||||
directive.require = directive.require || (directive.controller && directive.name);
|
||||
directive.restrict = directive.restrict || 'EA';
|
||||
if (isObject(directive.scope)) {
|
||||
directive.$$isolateBindings = parseIsolateBindings(directive.scope, directive.name);
|
||||
var bindings = directive.$$bindings =
|
||||
parseDirectiveBindings(directive, directive.name);
|
||||
if (isObject(bindings.isolateScope)) {
|
||||
directive.$$isolateBindings = bindings.isolateScope;
|
||||
}
|
||||
directives.push(directive);
|
||||
} catch (e) {
|
||||
@@ -1338,6 +1380,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (nodeLinkFn.scope) {
|
||||
childScope = scope.$new();
|
||||
compile.$$addScopeInfo(jqLite(node), childScope);
|
||||
var destroyBindings = nodeLinkFn.$$destroyBindings;
|
||||
if (destroyBindings) {
|
||||
nodeLinkFn.$$destroyBindings = null;
|
||||
childScope.$on('$destroyed', destroyBindings);
|
||||
}
|
||||
} else {
|
||||
childScope = scope;
|
||||
}
|
||||
@@ -1357,7 +1404,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
childBoundTranscludeFn = null;
|
||||
}
|
||||
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn,
|
||||
nodeLinkFn);
|
||||
|
||||
} else if (childLinkFn) {
|
||||
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
|
||||
@@ -1420,7 +1468,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)$/, '');
|
||||
@@ -1447,6 +1498,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// use class as directive
|
||||
className = node.className;
|
||||
if (isObject(className)) {
|
||||
// Maybe SVGAnimatedString
|
||||
className = className.animVal;
|
||||
}
|
||||
if (isString(className) && className !== '') {
|
||||
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
|
||||
nName = directiveNormalize(match[2]);
|
||||
@@ -1831,7 +1886,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
|
||||
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
|
||||
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn,
|
||||
thisLinkFn) {
|
||||
var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element,
|
||||
attrs;
|
||||
|
||||
@@ -1888,91 +1944,42 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
if (newIsolateScopeDirective) {
|
||||
// Initialize isolate scope bindings for new isolate scope directive.
|
||||
compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
|
||||
templateDirective === newIsolateScopeDirective.$$originalDirective)));
|
||||
compile.$$addScopeClass($element, true);
|
||||
|
||||
var isolateScopeController = controllers && controllers[newIsolateScopeDirective.name];
|
||||
var isolateBindingContext = isolateScope;
|
||||
if (isolateScopeController && isolateScopeController.identifier &&
|
||||
newIsolateScopeDirective.bindToController === true) {
|
||||
isolateBindingContext = isolateScopeController.instance;
|
||||
}
|
||||
|
||||
forEach(isolateScope.$$isolateBindings = newIsolateScopeDirective.$$isolateBindings, function(definition, scopeName) {
|
||||
var attrName = definition.attrName,
|
||||
optional = definition.optional,
|
||||
mode = definition.mode, // @, =, or &
|
||||
lastValue,
|
||||
parentGet, parentSet, compare;
|
||||
|
||||
switch (mode) {
|
||||
|
||||
case '@':
|
||||
attrs.$observe(attrName, function(value) {
|
||||
isolateBindingContext[scopeName] = value;
|
||||
});
|
||||
attrs.$$observers[attrName].$$scope = scope;
|
||||
if (attrs[attrName]) {
|
||||
// If the attribute has been provided then we trigger an interpolation to ensure
|
||||
// the value is there for use in the link fn
|
||||
isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope);
|
||||
}
|
||||
break;
|
||||
|
||||
case '=':
|
||||
if (optional && !attrs[attrName]) {
|
||||
return;
|
||||
}
|
||||
parentGet = $parse(attrs[attrName]);
|
||||
if (parentGet.literal) {
|
||||
compare = equals;
|
||||
} else {
|
||||
compare = function(a, b) { return a === b || (a !== a && b !== b); };
|
||||
}
|
||||
parentSet = parentGet.assign || function() {
|
||||
// reset the change, or we will throw this exception on every $digest
|
||||
lastValue = isolateBindingContext[scopeName] = parentGet(scope);
|
||||
throw $compileMinErr('nonassign',
|
||||
"Expression '{0}' used with directive '{1}' is non-assignable!",
|
||||
attrs[attrName], newIsolateScopeDirective.name);
|
||||
};
|
||||
lastValue = isolateBindingContext[scopeName] = parentGet(scope);
|
||||
var parentValueWatch = function parentValueWatch(parentValue) {
|
||||
if (!compare(parentValue, isolateBindingContext[scopeName])) {
|
||||
// we are out of sync and need to copy
|
||||
if (!compare(parentValue, lastValue)) {
|
||||
// parent changed and it has precedence
|
||||
isolateBindingContext[scopeName] = parentValue;
|
||||
} else {
|
||||
// if the parent can be assigned then do so
|
||||
parentSet(scope, parentValue = isolateBindingContext[scopeName]);
|
||||
}
|
||||
}
|
||||
return lastValue = parentValue;
|
||||
};
|
||||
parentValueWatch.$stateful = true;
|
||||
var unwatch;
|
||||
if (definition.collection) {
|
||||
unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
|
||||
} else {
|
||||
unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
|
||||
}
|
||||
isolateScope.$on('$destroy', unwatch);
|
||||
break;
|
||||
|
||||
case '&':
|
||||
parentGet = $parse(attrs[attrName]);
|
||||
isolateBindingContext[scopeName] = function(locals) {
|
||||
return parentGet(scope, locals);
|
||||
};
|
||||
break;
|
||||
}
|
||||
});
|
||||
isolateScope.$$isolateBindings =
|
||||
newIsolateScopeDirective.$$isolateBindings;
|
||||
initializeDirectiveBindings(scope, attrs, isolateScope,
|
||||
isolateScope.$$isolateBindings,
|
||||
newIsolateScopeDirective, isolateScope);
|
||||
}
|
||||
if (controllers) {
|
||||
// Initialize bindToController bindings for new/isolate scopes
|
||||
var scopeDirective = newIsolateScopeDirective || newScopeDirective;
|
||||
var bindings;
|
||||
var controllerForBindings;
|
||||
if (scopeDirective && controllers[scopeDirective.name]) {
|
||||
bindings = scopeDirective.$$bindings.bindToController;
|
||||
controller = controllers[scopeDirective.name];
|
||||
|
||||
if (controller && controller.identifier && bindings) {
|
||||
controllerForBindings = controller;
|
||||
thisLinkFn.$$destroyBindings =
|
||||
initializeDirectiveBindings(scope, attrs, controller.instance,
|
||||
bindings, scopeDirective);
|
||||
}
|
||||
}
|
||||
forEach(controllers, function(controller) {
|
||||
controller();
|
||||
var result = controller();
|
||||
if (result !== controller.instance &&
|
||||
controller === controllerForBindings) {
|
||||
// Remove and re-install bindToController bindings
|
||||
thisLinkFn.$$destroyBindings();
|
||||
thisLinkFn.$$destroyBindings =
|
||||
initializeDirectiveBindings(scope, attrs, result,
|
||||
bindings, scopeDirective);
|
||||
}
|
||||
});
|
||||
controllers = null;
|
||||
}
|
||||
@@ -2148,8 +2155,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
afterTemplateChildLinkFn,
|
||||
beforeTemplateCompileNode = $compileNode[0],
|
||||
origAsyncDirective = directives.shift(),
|
||||
// The fact that we have to copy and patch the directive seems wrong!
|
||||
derivedSyncDirective = extend({}, origAsyncDirective, {
|
||||
derivedSyncDirective = inherit(origAsyncDirective, {
|
||||
templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
|
||||
}),
|
||||
templateUrl = (isFunction(origAsyncDirective.templateUrl))
|
||||
@@ -2233,7 +2239,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
childBoundTranscludeFn = boundTranscludeFn;
|
||||
}
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
|
||||
childBoundTranscludeFn);
|
||||
childBoundTranscludeFn, afterTemplateNodeLinkFn);
|
||||
}
|
||||
linkQueue = null;
|
||||
});
|
||||
@@ -2250,7 +2256,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
|
||||
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
|
||||
}
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn,
|
||||
afterTemplateNodeLinkFn);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2332,7 +2339,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;
|
||||
@@ -2357,16 +2367,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;
|
||||
@@ -2494,6 +2504,102 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
$exceptionHandler(e, startingTag($element));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set up $watches for isolate scope and controller bindings. This process
|
||||
// only occurs for isolate scopes and new scopes with controllerAs.
|
||||
function initializeDirectiveBindings(scope, attrs, destination, bindings,
|
||||
directive, newScope) {
|
||||
var onNewScopeDestroyed;
|
||||
forEach(bindings, function(definition, scopeName) {
|
||||
var attrName = definition.attrName,
|
||||
optional = definition.optional,
|
||||
mode = definition.mode, // @, =, or &
|
||||
lastValue,
|
||||
parentGet, parentSet, compare;
|
||||
|
||||
switch (mode) {
|
||||
|
||||
case '@':
|
||||
attrs.$observe(attrName, function(value) {
|
||||
destination[scopeName] = value;
|
||||
});
|
||||
attrs.$$observers[attrName].$$scope = scope;
|
||||
if (attrs[attrName]) {
|
||||
// If the attribute has been provided then we trigger an interpolation to ensure
|
||||
// the value is there for use in the link fn
|
||||
destination[scopeName] = $interpolate(attrs[attrName])(scope);
|
||||
}
|
||||
break;
|
||||
|
||||
case '=':
|
||||
if (optional && !attrs[attrName]) {
|
||||
return;
|
||||
}
|
||||
parentGet = $parse(attrs[attrName]);
|
||||
if (parentGet.literal) {
|
||||
compare = equals;
|
||||
} else {
|
||||
compare = function(a, b) { return a === b || (a !== a && b !== b); };
|
||||
}
|
||||
parentSet = parentGet.assign || function() {
|
||||
// reset the change, or we will throw this exception on every $digest
|
||||
lastValue = destination[scopeName] = parentGet(scope);
|
||||
throw $compileMinErr('nonassign',
|
||||
"Expression '{0}' used with directive '{1}' is non-assignable!",
|
||||
attrs[attrName], directive.name);
|
||||
};
|
||||
lastValue = destination[scopeName] = parentGet(scope);
|
||||
var parentValueWatch = function parentValueWatch(parentValue) {
|
||||
if (!compare(parentValue, destination[scopeName])) {
|
||||
// we are out of sync and need to copy
|
||||
if (!compare(parentValue, lastValue)) {
|
||||
// parent changed and it has precedence
|
||||
destination[scopeName] = parentValue;
|
||||
} else {
|
||||
// if the parent can be assigned then do so
|
||||
parentSet(scope, parentValue = destination[scopeName]);
|
||||
}
|
||||
}
|
||||
return lastValue = parentValue;
|
||||
};
|
||||
parentValueWatch.$stateful = true;
|
||||
var unwatch;
|
||||
if (definition.collection) {
|
||||
unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
|
||||
} else {
|
||||
unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
|
||||
}
|
||||
onNewScopeDestroyed = (onNewScopeDestroyed || []);
|
||||
onNewScopeDestroyed.push(unwatch);
|
||||
break;
|
||||
|
||||
case '&':
|
||||
// Don't assign Object.prototype method to scope
|
||||
if (!attrs.hasOwnProperty(attrName) && optional) break;
|
||||
|
||||
parentGet = $parse(attrs[attrName]);
|
||||
|
||||
// Don't assign noop to destination if expression is not valid
|
||||
if (parentGet === noop && optional) break;
|
||||
|
||||
destination[scopeName] = function(locals) {
|
||||
return parentGet(scope, locals);
|
||||
};
|
||||
break;
|
||||
}
|
||||
});
|
||||
var destroyBindings = onNewScopeDestroyed ? function destroyBindings() {
|
||||
for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) {
|
||||
onNewScopeDestroyed[i]();
|
||||
}
|
||||
} : noop;
|
||||
if (newScope && destroyBindings !== noop) {
|
||||
newScope.$on('$destroy', destroyBindings);
|
||||
return noop;
|
||||
}
|
||||
return destroyBindings;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
+31
-7
@@ -1,5 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
var $controllerMinErr = minErr('$controller');
|
||||
|
||||
|
||||
var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
|
||||
function identifierForController(controller, ident) {
|
||||
if (ident && isString(ident)) return ident;
|
||||
if (isString(controller)) {
|
||||
var match = CNTRL_REG.exec(controller);
|
||||
if (match) return match[3];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc provider
|
||||
* @name $controllerProvider
|
||||
@@ -12,9 +25,7 @@
|
||||
*/
|
||||
function $ControllerProvider() {
|
||||
var controllers = {},
|
||||
globals = false,
|
||||
CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
|
||||
|
||||
globals = false;
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -87,7 +98,12 @@ function $ControllerProvider() {
|
||||
}
|
||||
|
||||
if (isString(expression)) {
|
||||
match = expression.match(CNTRL_REG),
|
||||
match = expression.match(CNTRL_REG);
|
||||
if (!match) {
|
||||
throw $controllerMinErr('ctrlfmt',
|
||||
"Badly formed controller string '{0}'. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.", expression);
|
||||
}
|
||||
constructor = match[1],
|
||||
identifier = identifier || match[3];
|
||||
expression = controllers.hasOwnProperty(constructor)
|
||||
@@ -111,14 +127,22 @@ function $ControllerProvider() {
|
||||
// Object creation: http://jsperf.com/create-constructor/2
|
||||
var controllerPrototype = (isArray(expression) ?
|
||||
expression[expression.length - 1] : expression).prototype;
|
||||
instance = Object.create(controllerPrototype);
|
||||
instance = Object.create(controllerPrototype || null);
|
||||
|
||||
if (identifier) {
|
||||
addIdentifier(locals, identifier, instance, constructor || expression.name);
|
||||
}
|
||||
|
||||
return extend(function() {
|
||||
$injector.invoke(expression, instance, locals, constructor);
|
||||
var instantiate;
|
||||
return instantiate = extend(function() {
|
||||
var result = $injector.invoke(expression, instance, locals, constructor);
|
||||
if (result !== instance && (isObject(result) || isFunction(result))) {
|
||||
instance = result;
|
||||
if (identifier) {
|
||||
// If result changed, re-assign controllerAs value to scope.
|
||||
addIdentifier(locals, identifier, instance, constructor || expression.name);
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}, {
|
||||
instance: instance,
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
var htmlAnchorDirective = valueFn({
|
||||
restrict: 'E',
|
||||
compile: function(element, attr) {
|
||||
if (!attr.href && !attr.xlinkHref && !attr.name) {
|
||||
if (!attr.href && !attr.xlinkHref) {
|
||||
return function(scope, element) {
|
||||
// If the linked element is not an anchor tag anymore, do nothing
|
||||
if (element[0].nodeName.toLowerCase() !== 'a') return;
|
||||
|
||||
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
|
||||
var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
|
||||
'xlink:href' : 'href';
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
}, 5000, 'page should navigate to /123');
|
||||
});
|
||||
|
||||
xit('should execute ng-click but not reload when href empty string and name specified', function() {
|
||||
it('should execute ng-click but not reload when href empty string and name specified', function() {
|
||||
element(by.id('link-4')).click();
|
||||
expect(element(by.model('value')).getAttribute('value')).toEqual('4');
|
||||
expect(element(by.id('link-4')).getAttribute('href')).toBe('');
|
||||
@@ -341,22 +341,34 @@
|
||||
|
||||
var ngAttributeAliasDirectives = {};
|
||||
|
||||
|
||||
// boolean attrs are evaluated
|
||||
forEach(BOOLEAN_ATTR, function(propName, attrName) {
|
||||
// binding to multiple is not supported
|
||||
if (propName == "multiple") return;
|
||||
|
||||
function defaultLinkFn(scope, element, attr) {
|
||||
scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
|
||||
attr.$set(attrName, !!value);
|
||||
});
|
||||
}
|
||||
|
||||
var normalized = directiveNormalize('ng-' + attrName);
|
||||
var linkFn = defaultLinkFn;
|
||||
|
||||
if (propName === 'checked') {
|
||||
linkFn = function(scope, element, attr) {
|
||||
// ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
|
||||
if (attr.ngModel !== attr[normalized]) {
|
||||
defaultLinkFn(scope, element, attr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ngAttributeAliasDirectives[normalized] = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority: 100,
|
||||
link: function(scope, element, attr) {
|
||||
scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
|
||||
attr.$set(attrName, !!value);
|
||||
});
|
||||
}
|
||||
link: linkFn
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@@ -163,6 +163,9 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
forEach(form.$error, function(value, name) {
|
||||
form.$setValidity(name, null, control);
|
||||
});
|
||||
forEach(form.$$success, function(value, name) {
|
||||
form.$setValidity(name, null, control);
|
||||
});
|
||||
|
||||
arrayRemove(controls, control);
|
||||
};
|
||||
@@ -180,23 +183,23 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
addSetValidityMethod({
|
||||
ctrl: this,
|
||||
$element: element,
|
||||
set: function(object, property, control) {
|
||||
set: function(object, property, controller) {
|
||||
var list = object[property];
|
||||
if (!list) {
|
||||
object[property] = [control];
|
||||
object[property] = [controller];
|
||||
} else {
|
||||
var index = list.indexOf(control);
|
||||
var index = list.indexOf(controller);
|
||||
if (index === -1) {
|
||||
list.push(control);
|
||||
list.push(controller);
|
||||
}
|
||||
}
|
||||
},
|
||||
unset: function(object, property, control) {
|
||||
unset: function(object, property, controller) {
|
||||
var list = object[property];
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
arrayRemove(list, control);
|
||||
arrayRemove(list, controller);
|
||||
if (list.length === 0) {
|
||||
delete object[property];
|
||||
}
|
||||
|
||||
+94
-1684
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngChange
|
||||
*
|
||||
* @description
|
||||
* Evaluate the given expression when the user changes the input.
|
||||
* The expression is evaluated immediately, unlike the JavaScript onchange event
|
||||
* which only triggers at the end of a change (usually, when the user leaves the
|
||||
* form element or presses the return key).
|
||||
*
|
||||
* The `ngChange` expression is only evaluated when a change in the input value causes
|
||||
* a new value to be committed to the model.
|
||||
*
|
||||
* It will not be evaluated:
|
||||
* * if the value returned from the `$parsers` transformation pipeline has not changed
|
||||
* * if the input has continued to be invalid since the model will stay `null`
|
||||
* * if the model is changed programmatically and not by a change to the input value
|
||||
*
|
||||
*
|
||||
* Note, this directive requires `ngModel` to be present.
|
||||
*
|
||||
* @element input
|
||||
* @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
|
||||
* in input value.
|
||||
*
|
||||
* @example
|
||||
* <example name="ngChange-directive" module="changeExample">
|
||||
* <file name="index.html">
|
||||
* <script>
|
||||
* angular.module('changeExample', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.counter = 0;
|
||||
* $scope.change = function() {
|
||||
* $scope.counter++;
|
||||
* };
|
||||
* }]);
|
||||
* </script>
|
||||
* <div ng-controller="ExampleController">
|
||||
* <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
|
||||
* <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
|
||||
* <label for="ng-change-example2">Confirmed</label><br />
|
||||
* <tt>debug = {{confirmed}}</tt><br/>
|
||||
* <tt>counter = {{counter}}</tt><br/>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* var counter = element(by.binding('counter'));
|
||||
* var debug = element(by.binding('confirmed'));
|
||||
*
|
||||
* it('should evaluate the expression if changing from view', function() {
|
||||
* expect(counter.getText()).toContain('0');
|
||||
*
|
||||
* element(by.id('ng-change-example1')).click();
|
||||
*
|
||||
* expect(counter.getText()).toContain('1');
|
||||
* expect(debug.getText()).toContain('true');
|
||||
* });
|
||||
*
|
||||
* it('should not evaluate the expression if changing from model', function() {
|
||||
* element(by.id('ng-change-example2')).click();
|
||||
|
||||
* expect(counter.getText()).toContain('0');
|
||||
* expect(debug.getText()).toContain('true');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var ngChangeDirective = valueFn({
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
ctrl.$viewChangeListeners.push(function() {
|
||||
scope.$eval(attr.ngChange);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -97,7 +97,7 @@ function classDirective(name, selector) {
|
||||
|
||||
function arrayClasses(classVal) {
|
||||
if (isArray(classVal)) {
|
||||
return classVal;
|
||||
return classVal.join(' ').split(' ');
|
||||
} else if (isString(classVal)) {
|
||||
return classVal.split(' ');
|
||||
} else if (isObject(classVal)) {
|
||||
@@ -141,8 +141,9 @@ function classDirective(name, selector) {
|
||||
* new classes are added.
|
||||
*
|
||||
* @animations
|
||||
* add - happens just before the class is applied to the element
|
||||
* remove - happens just before the class is removed from the element
|
||||
* **add** - happens just before the class is applied to the elements
|
||||
*
|
||||
* **remove** - happens just before the class is removed from the element
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngClass {@link guide/expression Expression} to eval. The result
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<select ng-model="template" ng-options="t.name for t in templates">
|
||||
<option value="">(blank)</option>
|
||||
</select>
|
||||
url of the template: <tt>{{template.url}}</tt>
|
||||
url of the template: <code>{{template.url}}</code>
|
||||
<hr/>
|
||||
<div class="slide-animate-container">
|
||||
<div class="slide-animate" ng-include="template.url"></div>
|
||||
@@ -173,7 +173,7 @@
|
||||
* @name ngInclude#$includeContentError
|
||||
* @eventType emit on the scope ngInclude was declared in
|
||||
* @description
|
||||
* Emitted when a template HTTP request yields an erronous response (status < 200 || status > 299)
|
||||
* Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
|
||||
*
|
||||
* @param {Object} angularEvent Synthetic event object.
|
||||
* @param {String} src URL of content to load.
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
|
||||
* sure you have parenthesis for correct precedence:
|
||||
* <pre class="prettyprint">
|
||||
* <div ng-init="test1 = (data | orderBy:'name')"></div>
|
||||
* `<div ng-init="test1 = (data | orderBy:'name')"></div>`
|
||||
* </pre>
|
||||
* </div>
|
||||
*
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngList
|
||||
*
|
||||
* @description
|
||||
* Text input that converts between a delimited string and an array of strings. The default
|
||||
* delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
|
||||
* delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
|
||||
*
|
||||
* The behaviour of the directive is affected by the use of the `ngTrim` attribute.
|
||||
* * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
|
||||
* list item is respected. This implies that the user of the directive is responsible for
|
||||
* dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
|
||||
* tab or newline character.
|
||||
* * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
|
||||
* when joining the list items back together) and whitespace around each list item is stripped
|
||||
* before it is added to the model.
|
||||
*
|
||||
* ### Example with Validation
|
||||
*
|
||||
* <example name="ngList-directive" module="listExample">
|
||||
* <file name="app.js">
|
||||
* angular.module('listExample', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.names = ['morpheus', 'neo', 'trinity'];
|
||||
* }]);
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <form name="myForm" ng-controller="ExampleController">
|
||||
* List: <input name="namesInput" ng-model="names" ng-list required>
|
||||
* <span class="error" ng-show="myForm.namesInput.$error.required">
|
||||
* Required!</span>
|
||||
* <br>
|
||||
* <tt>names = {{names}}</tt><br/>
|
||||
* <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
|
||||
* <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
|
||||
* <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
|
||||
* <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
|
||||
* </form>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* var listInput = element(by.model('names'));
|
||||
* var names = element(by.exactBinding('names'));
|
||||
* var valid = element(by.binding('myForm.namesInput.$valid'));
|
||||
* var error = element(by.css('span.error'));
|
||||
*
|
||||
* it('should initialize to model', function() {
|
||||
* expect(names.getText()).toContain('["morpheus","neo","trinity"]');
|
||||
* expect(valid.getText()).toContain('true');
|
||||
* expect(error.getCssValue('display')).toBe('none');
|
||||
* });
|
||||
*
|
||||
* it('should be invalid if empty', function() {
|
||||
* listInput.clear();
|
||||
* listInput.sendKeys('');
|
||||
*
|
||||
* expect(names.getText()).toContain('');
|
||||
* expect(valid.getText()).toContain('false');
|
||||
* expect(error.getCssValue('display')).not.toBe('none');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* ### Example - splitting on whitespace
|
||||
* <example name="ngList-directive-newlines">
|
||||
* <file name="index.html">
|
||||
* <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea>
|
||||
* <pre>{{ list | json }}</pre>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it("should split the text by newlines", function() {
|
||||
* var listInput = element(by.model('list'));
|
||||
* var output = element(by.binding('list | json'));
|
||||
* listInput.sendKeys('abc\ndef\nghi');
|
||||
* expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @element input
|
||||
* @param {string=} ngList optional delimiter that should be used to split the value.
|
||||
*/
|
||||
var ngListDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority: 100,
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
// We want to control whitespace trimming so we use this convoluted approach
|
||||
// to access the ngList attribute, which doesn't pre-trim the attribute
|
||||
var ngList = element.attr(attr.$attr.ngList) || ', ';
|
||||
var trimValues = attr.ngTrim !== 'false';
|
||||
var separator = trimValues ? trim(ngList) : ngList;
|
||||
|
||||
var parse = function(viewValue) {
|
||||
// If the viewValue is invalid (say required but empty) it will be `undefined`
|
||||
if (isUndefined(viewValue)) return;
|
||||
|
||||
var list = [];
|
||||
|
||||
if (viewValue) {
|
||||
forEach(viewValue.split(separator), function(value) {
|
||||
if (value) list.push(trimValues ? trim(value) : value);
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
ctrl.$parsers.push(parse);
|
||||
ctrl.$formatters.push(function(value) {
|
||||
if (isArray(value)) {
|
||||
return value.join(ngList);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// Override the standard $isEmpty because an empty array means the input is empty.
|
||||
ctrl.$isEmpty = function(value) {
|
||||
return !value || !value.length;
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,612 @@
|
||||
'use strict';
|
||||
|
||||
/* global jqLiteRemove */
|
||||
|
||||
var ngOptionsMinErr = minErr('ngOptions');
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngOptions
|
||||
* @restrict A
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
|
||||
* 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 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`
|
||||
* directive.
|
||||
*
|
||||
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
|
||||
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
|
||||
* option. See example below for demonstration.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** `ngModel` compares by reference, not value. This is important when binding to an
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
|
||||
* </div>
|
||||
*
|
||||
* ## `select` **`as`**
|
||||
*
|
||||
* 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
|
||||
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
|
||||
*
|
||||
*
|
||||
* ### `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.
|
||||
*
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} required The control is considered valid only if value is entered.
|
||||
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
||||
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
||||
* `required` when you want to data-bind to the `required` attribute.
|
||||
* @param {comprehension_expression=} ngOptions in one of the following forms:
|
||||
*
|
||||
* * for array data sources:
|
||||
* * `label` **`for`** `value` **`in`** `array`
|
||||
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
|
||||
* * `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`
|
||||
* * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`group by`** `group`
|
||||
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* * `array` / `object`: an expression which evaluates to an array / object to iterate over.
|
||||
* * `value`: local variable which will refer to each item in the `array` or each property value
|
||||
* of `object` during iteration.
|
||||
* * `key`: local variable which will refer to a property name in `object` during iteration.
|
||||
* * `label`: The result of this expression will be the label for `<option>` element. The
|
||||
* `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
|
||||
* * `select`: The result of this expression will be bound to the model of the parent `<select>`
|
||||
* element. If not specified, `select` expression will default to `value`.
|
||||
* * `group`: The result of this expression will be used to group options using the `<optgroup>`
|
||||
* DOM element.
|
||||
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
|
||||
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
|
||||
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
|
||||
* even when the options are recreated (e.g. reloaded from the server).
|
||||
*
|
||||
* @example
|
||||
<example module="selectExample">
|
||||
<file name="index.html">
|
||||
<script>
|
||||
angular.module('selectExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.colors = [
|
||||
{name:'black', shade:'dark'},
|
||||
{name:'white', shade:'light'},
|
||||
{name:'red', shade:'dark'},
|
||||
{name:'blue', shade:'dark'},
|
||||
{name:'yellow', shade:'light'}
|
||||
];
|
||||
$scope.myColor = $scope.colors[2]; // red
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
<ul>
|
||||
<li ng-repeat="color in colors">
|
||||
Name: <input ng-model="color.name">
|
||||
[<a href ng-click="colors.splice($index, 1)">X</a>]
|
||||
</li>
|
||||
<li>
|
||||
[<a href ng-click="colors.push({})">add</a>]
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
Color (null not allowed):
|
||||
<select ng-model="myColor" ng-options="color.name for color in colors"></select><br>
|
||||
|
||||
Color (null allowed):
|
||||
<span class="nullable">
|
||||
<select ng-model="myColor" ng-options="color.name for color in colors">
|
||||
<option value="">-- choose color --</option>
|
||||
</select>
|
||||
</span><br/>
|
||||
|
||||
Color grouped by shade:
|
||||
<select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
|
||||
</select><br/>
|
||||
|
||||
|
||||
Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br>
|
||||
<hr/>
|
||||
Currently selected: {{ {selected_color:myColor} }}
|
||||
<div style="border:solid 1px black; height:20px"
|
||||
ng-style="{'background-color':myColor.name}">
|
||||
</div>
|
||||
</div>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should check ng-options', function() {
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
|
||||
element.all(by.model('myColor')).first().click();
|
||||
element.all(by.css('select[ng-model="myColor"] option')).first().click();
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
|
||||
element(by.css('.nullable select[ng-model="myColor"]')).click();
|
||||
element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
// jshint maxlen: false
|
||||
//000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
|
||||
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
|
||||
// 1: value expression (valueFn)
|
||||
// 2: label expression (displayFn)
|
||||
// 3: group by expression (groupByFn)
|
||||
// 4: array item variable name
|
||||
// 5: object item key variable name
|
||||
// 6: object item value variable name
|
||||
// 7: collection expression
|
||||
// 8: track by expression
|
||||
// jshint maxlen: 100
|
||||
|
||||
|
||||
var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
function parseOptionsExpression(optionsExp, selectElement, scope) {
|
||||
|
||||
var match = optionsExp.match(NG_OPTIONS_REGEXP);
|
||||
if (!(match)) {
|
||||
throw ngOptionsMinErr('iexp',
|
||||
"Expected expression in form of " +
|
||||
"'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
|
||||
" but got '{0}'. Element: {1}",
|
||||
optionsExp, startingTag(selectElement));
|
||||
}
|
||||
|
||||
// Extract the parts from the ngOptions expression
|
||||
|
||||
// The variable name for the value of the item in the collection
|
||||
var valueName = match[4] || match[6];
|
||||
// The variable name for the key of the item in the collection
|
||||
var keyName = match[5];
|
||||
|
||||
// An expression that generates the viewValue for an option if there is a label expression
|
||||
var selectAs = / as /.test(match[0]) && match[1];
|
||||
// An expression that is used to track the id of each object in the options collection
|
||||
var trackBy = match[8];
|
||||
// An expression that generates the viewValue for an option if there is no label expression
|
||||
var valueFn = $parse(match[2] ? match[1] : valueName);
|
||||
var selectAsFn = selectAs && $parse(selectAs);
|
||||
var viewValueFn = selectAsFn || valueFn;
|
||||
var trackByFn = trackBy && $parse(trackBy);
|
||||
|
||||
// Get the value by which we are going to track the option
|
||||
// if we have a trackFn then use that (passing scope and locals)
|
||||
// otherwise just hash the given viewValue
|
||||
var getTrackByValue = trackBy ?
|
||||
function(viewValue, locals) { return trackByFn(scope, locals); } :
|
||||
function getHashOfValue(viewValue) { return hashKey(viewValue); };
|
||||
var displayFn = $parse(match[2] || match[1]);
|
||||
var groupByFn = $parse(match[3] || '');
|
||||
var valuesFn = $parse(match[7]);
|
||||
|
||||
var locals = {};
|
||||
var getLocals = keyName ? function(value, key) {
|
||||
locals[keyName] = key;
|
||||
locals[valueName] = value;
|
||||
return locals;
|
||||
} : function(value) {
|
||||
locals[valueName] = value;
|
||||
return locals;
|
||||
};
|
||||
|
||||
|
||||
function Option(selectValue, viewValue, label, group) {
|
||||
this.selectValue = selectValue;
|
||||
this.viewValue = viewValue;
|
||||
this.label = label;
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
return {
|
||||
getWatchables: $parse(valuesFn, function(values) {
|
||||
// Create a collection of things that we would like to watch (watchedArray)
|
||||
// so that they can all be watched using a single $watchCollection
|
||||
// that only runs the handler once if anything changes
|
||||
var watchedArray = [];
|
||||
values = values || [];
|
||||
|
||||
Object.keys(values).forEach(function getWatchable(key) {
|
||||
var locals = getLocals(values[key], key);
|
||||
var label = displayFn(scope, locals);
|
||||
var selectValue = getTrackByValue(values[key], locals);
|
||||
watchedArray.push(selectValue);
|
||||
watchedArray.push(label);
|
||||
});
|
||||
return watchedArray;
|
||||
}),
|
||||
|
||||
getOptions: function() {
|
||||
|
||||
var optionItems = [];
|
||||
var selectValueMap = {};
|
||||
|
||||
// The option values were already computed in the `getWatchables` fn,
|
||||
// which must have been called to trigger `getOptions`
|
||||
var optionValues = valuesFn(scope) || [];
|
||||
|
||||
var keys = Object.keys(optionValues);
|
||||
keys.forEach(function getOption(key) {
|
||||
|
||||
// Ignore "angular" properties that start with $ or $$
|
||||
if (key.charAt(0) === '$') return;
|
||||
|
||||
var value = optionValues[key];
|
||||
var locals = getLocals(value, key);
|
||||
var viewValue = viewValueFn(scope, locals);
|
||||
var selectValue = getTrackByValue(viewValue, locals);
|
||||
var label = displayFn(scope, locals);
|
||||
var group = groupByFn(scope, locals);
|
||||
var optionItem = new Option(selectValue, viewValue, label, group);
|
||||
|
||||
optionItems.push(optionItem);
|
||||
selectValueMap[selectValue] = optionItem;
|
||||
});
|
||||
|
||||
return {
|
||||
items: optionItems,
|
||||
selectValueMap: selectValueMap,
|
||||
getOptionFromViewValue: function(value) {
|
||||
return selectValueMap[getTrackByValue(value, getLocals(value))];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// we can't just jqLite('<option>') since jqLite is not smart enough
|
||||
// to create it in <select> and IE barfs otherwise.
|
||||
var optionTemplate = document.createElement('option'),
|
||||
optGroupTemplate = document.createElement('optgroup');
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
terminal: true,
|
||||
require: ['select', '?ngModel'],
|
||||
link: function(scope, selectElement, attr, ctrls) {
|
||||
|
||||
// if ngModel is not defined, we don't need to do anything
|
||||
var ngModelCtrl = ctrls[1];
|
||||
if (!ngModelCtrl) return;
|
||||
|
||||
var selectCtrl = ctrls[0];
|
||||
var multiple = attr.multiple;
|
||||
|
||||
var emptyOption = selectCtrl.emptyOption;
|
||||
var providedEmptyOption = !!emptyOption;
|
||||
|
||||
var unknownOption = jqLite(optionTemplate.cloneNode(false));
|
||||
unknownOption.val('?');
|
||||
|
||||
var options;
|
||||
var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
|
||||
|
||||
|
||||
var renderEmptyOption = function() {
|
||||
if (!providedEmptyOption) {
|
||||
selectElement.prepend(emptyOption);
|
||||
}
|
||||
selectElement.val('');
|
||||
emptyOption.prop('selected', true); // needed for IE
|
||||
emptyOption.attr('selected', true);
|
||||
};
|
||||
|
||||
var removeEmptyOption = function() {
|
||||
if (!providedEmptyOption) {
|
||||
emptyOption.remove();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var renderUnknownOption = function() {
|
||||
selectElement.prepend(unknownOption);
|
||||
selectElement.val('?');
|
||||
unknownOption.prop('selected', true); // needed for IE
|
||||
unknownOption.attr('selected', true);
|
||||
};
|
||||
|
||||
var removeUnknownOption = function() {
|
||||
unknownOption.remove();
|
||||
};
|
||||
|
||||
|
||||
selectCtrl.writeValue = function writeNgOptionsValue(value) {
|
||||
var option = options.getOptionFromViewValue(value);
|
||||
|
||||
if (option) {
|
||||
if (selectElement[0].value !== option.selectValue) {
|
||||
removeUnknownOption();
|
||||
removeEmptyOption();
|
||||
|
||||
selectElement[0].value = option.selectValue;
|
||||
option.element.selected = true;
|
||||
option.element.setAttribute('selected', 'selected');
|
||||
}
|
||||
} else {
|
||||
if (value === null || providedEmptyOption) {
|
||||
removeUnknownOption();
|
||||
renderEmptyOption();
|
||||
} else {
|
||||
removeEmptyOption();
|
||||
renderUnknownOption();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
selectCtrl.readValue = function readNgOptionsValue() {
|
||||
|
||||
var selectedOption = options.selectValueMap[selectElement.val()];
|
||||
|
||||
if (selectedOption) {
|
||||
removeEmptyOption();
|
||||
removeUnknownOption();
|
||||
return selectedOption.viewValue;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
// Update the controller methods for multiple selectable options
|
||||
if (multiple) {
|
||||
|
||||
ngModelCtrl.$isEmpty = function(value) {
|
||||
return !value || value.length === 0;
|
||||
};
|
||||
|
||||
|
||||
selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
|
||||
options.items.forEach(function(option) {
|
||||
option.element.selected = false;
|
||||
});
|
||||
|
||||
if (value) {
|
||||
value.forEach(function(item) {
|
||||
var option = options.getOptionFromViewValue(item);
|
||||
if (option) option.element.selected = true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
selectCtrl.readValue = function readNgOptionsMultiple() {
|
||||
var selectedValues = selectElement.val() || [];
|
||||
return selectedValues.map(function(selectedKey) {
|
||||
var option = options.selectValueMap[selectedKey];
|
||||
return option.viewValue;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (providedEmptyOption) {
|
||||
|
||||
// we need to remove it before calling selectElement.empty() because otherwise IE will
|
||||
// remove the label from the element. wtf?
|
||||
emptyOption.remove();
|
||||
|
||||
// compile the element since there might be bindings in it
|
||||
$compile(emptyOption)(scope);
|
||||
|
||||
// remove the class, which is added automatically because we recompile the element and it
|
||||
// becomes the compilation root
|
||||
emptyOption.removeClass('ng-scope');
|
||||
} else {
|
||||
emptyOption = jqLite(optionTemplate.cloneNode(false));
|
||||
}
|
||||
|
||||
// We need to do this here to ensure that the options object is defined
|
||||
// when we first hit it in writeNgOptionsValue
|
||||
updateOptions();
|
||||
|
||||
// We will re-render the option elements if the option values or labels change
|
||||
scope.$watchCollection(ngOptions.getWatchables, updateOptions);
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
|
||||
function updateOptionElement(option, element) {
|
||||
option.element = element;
|
||||
if (option.value !== element.value) element.value = option.selectValue;
|
||||
if (option.label !== element.label) {
|
||||
element.label = option.label;
|
||||
element.textContent = option.label;
|
||||
}
|
||||
}
|
||||
|
||||
function addOrReuseElement(parent, current, type, templateElement) {
|
||||
var element;
|
||||
// Check whether we can reuse the next element
|
||||
if (current && lowercase(current.nodeName) === type) {
|
||||
// The next element is the right type so reuse it
|
||||
element = current;
|
||||
} else {
|
||||
// The next element is not the right type so create a new one
|
||||
element = templateElement.cloneNode(false);
|
||||
if (!current) {
|
||||
// There are no more elements so just append it to the select
|
||||
parent.appendChild(element);
|
||||
} else {
|
||||
// The next element is not a group so insert the new one
|
||||
parent.insertBefore(element, current);
|
||||
}
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
function removeExcessElements(current) {
|
||||
var next;
|
||||
while (current) {
|
||||
next = current.nextSibling;
|
||||
jqLiteRemove(current);
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function skipEmptyAndUnknownOptions(current) {
|
||||
var emptyOption_ = emptyOption && emptyOption[0];
|
||||
var unknownOption_ = unknownOption && unknownOption[0];
|
||||
|
||||
if (emptyOption_ || unknownOption_) {
|
||||
while (current &&
|
||||
(current === emptyOption_ ||
|
||||
current === unknownOption_)) {
|
||||
current = current.nextSibling;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
|
||||
function updateOptions() {
|
||||
|
||||
var previousValue = options && selectCtrl.readValue();
|
||||
|
||||
options = ngOptions.getOptions();
|
||||
|
||||
var groupMap = {};
|
||||
var currentElement = selectElement[0].firstChild;
|
||||
|
||||
// Ensure that the empty option is always there if it was explicitly provided
|
||||
if (providedEmptyOption) {
|
||||
selectElement.prepend(emptyOption);
|
||||
}
|
||||
|
||||
currentElement = skipEmptyAndUnknownOptions(currentElement);
|
||||
|
||||
options.items.forEach(function updateOption(option) {
|
||||
var group;
|
||||
var groupElement;
|
||||
var optionElement;
|
||||
|
||||
if (option.group) {
|
||||
|
||||
// This option is to live in a group
|
||||
// See if we have already created this group
|
||||
group = groupMap[option.group];
|
||||
|
||||
if (!group) {
|
||||
|
||||
// We have not already created this group
|
||||
groupElement = addOrReuseElement(selectElement[0],
|
||||
currentElement,
|
||||
'optgroup',
|
||||
optGroupTemplate);
|
||||
// Move to the next element
|
||||
currentElement = groupElement.nextSibling;
|
||||
|
||||
// Update the label on the group element
|
||||
groupElement.label = option.group;
|
||||
|
||||
// Store it for use later
|
||||
group = groupMap[option.group] = {
|
||||
groupElement: groupElement,
|
||||
currentOptionElement: groupElement.firstChild
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// So now we have a group for this option we add the option to the group
|
||||
optionElement = addOrReuseElement(group.groupElement,
|
||||
group.currentOptionElement,
|
||||
'option',
|
||||
optionTemplate);
|
||||
updateOptionElement(option, optionElement);
|
||||
// Move to the next element
|
||||
group.currentOptionElement = optionElement.nextSibling;
|
||||
|
||||
} else {
|
||||
|
||||
// This option is not in a group
|
||||
optionElement = addOrReuseElement(selectElement[0],
|
||||
currentElement,
|
||||
'option',
|
||||
optionTemplate);
|
||||
updateOptionElement(option, optionElement);
|
||||
// Move to the next element
|
||||
currentElement = optionElement.nextSibling;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Now remove all excess options and group
|
||||
Object.keys(groupMap).forEach(function(key) {
|
||||
removeExcessElements(groupMap[key].currentOptionElement);
|
||||
});
|
||||
removeExcessElements(currentElement);
|
||||
|
||||
ngModelCtrl.$render();
|
||||
|
||||
// Check to see if the value has changed due to the update to the options
|
||||
if (!ngModelCtrl.$isEmpty(previousValue)) {
|
||||
var nextValue = selectCtrl.readValue();
|
||||
if (!equals(previousValue, nextValue)) {
|
||||
ngModelCtrl.$setViewValue(nextValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}];
|
||||
@@ -54,6 +54,9 @@
|
||||
* <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
|
||||
* for <span ng-non-bindable>{{numberExpression}}</span>.
|
||||
*
|
||||
* If no rule is defined for a category, then an empty string is displayed and a warning is generated.
|
||||
* Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
|
||||
*
|
||||
* # Configuring ngPluralize with offset
|
||||
* The `offset` attribute allows further customization of pluralized text, which can result in
|
||||
* a better user experience. For example, instead of the message "4 people are viewing this document",
|
||||
@@ -172,7 +175,7 @@
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
|
||||
var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
|
||||
var BRACE = /{}/g,
|
||||
IS_WHEN = /^when(Minus)?(.+)$/;
|
||||
|
||||
@@ -214,9 +217,18 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
|
||||
|
||||
// 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))) {
|
||||
if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
|
||||
watchRemover();
|
||||
watchRemover = scope.$watch(whensExpFns[count], updateElementText);
|
||||
var whenExpFn = whensExpFns[count];
|
||||
if (isUndefined(whenExpFn)) {
|
||||
if (newVal != null) {
|
||||
$log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
|
||||
}
|
||||
watchRemover = noop;
|
||||
updateElementText();
|
||||
} else {
|
||||
watchRemover = scope.$watch(whenExpFn, updateElementText);
|
||||
}
|
||||
lastCount = count;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,6 +23,31 @@
|
||||
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
|
||||
* This may be useful when, for instance, nesting ngRepeats.
|
||||
*
|
||||
*
|
||||
* # Iterating over object properties
|
||||
*
|
||||
* It is possible to get `ngRepeat` to iterate over the properties of an object using the following
|
||||
* syntax:
|
||||
*
|
||||
* ```js
|
||||
* <div ng-repeat="(key, value) in myObj"> ... </div>
|
||||
* ```
|
||||
*
|
||||
* You need to be aware that the JavaScript specification does not define what order
|
||||
* it will return the keys for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
|
||||
* used to sort the keys alphabetically.)
|
||||
*
|
||||
* Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
|
||||
* when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
|
||||
* keys in the order in which they were defined, although there are exceptions when keys are deleted
|
||||
* and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues
|
||||
*
|
||||
* If this is not desired, the recommended workaround is to convert your object into an array
|
||||
* that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
|
||||
* do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
|
||||
* or implement a `$watch` on the object yourself.
|
||||
*
|
||||
*
|
||||
* # Special repeat start and end points
|
||||
* To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
|
||||
* the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
|
||||
@@ -257,7 +282,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}'.",
|
||||
@@ -267,7 +292,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
var keyIdentifier = match[2];
|
||||
|
||||
if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
|
||||
/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent)$/.test(aliasAs))) {
|
||||
/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
|
||||
throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
|
||||
aliasAs);
|
||||
}
|
||||
@@ -335,14 +360,13 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
|
||||
} else {
|
||||
trackByIdFn = trackByIdExpFn || trackByIdObjFn;
|
||||
// if object, extract keys, sort them and use to determine order of iteration over obj props
|
||||
// if object, extract keys, in enumeration order, unsorted
|
||||
collectionKeys = [];
|
||||
for (var itemKey in collection) {
|
||||
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) != '$') {
|
||||
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
|
||||
collectionKeys.push(itemKey);
|
||||
}
|
||||
}
|
||||
collectionKeys.sort();
|
||||
}
|
||||
|
||||
collectionLength = collectionKeys.length;
|
||||
@@ -438,4 +462,3 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
|
||||
@@ -40,10 +40,11 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
*
|
||||
* 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:
|
||||
* class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
|
||||
* with extra animation classes that can be added.
|
||||
*
|
||||
* ```css
|
||||
* .ng-hide {
|
||||
* .ng-hide:not(.ng-hide-animate) {
|
||||
* /* this is just another form of hiding an element */
|
||||
* display: block!important;
|
||||
* position: absolute;
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
</example>
|
||||
*/
|
||||
var ngStyleDirective = ngDirective(function(scope, element, attr) {
|
||||
scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
if (oldStyles && (newStyles !== oldStyles)) {
|
||||
forEach(oldStyles, function(val, style) { element.css(style, '');});
|
||||
}
|
||||
if (newStyles) element.css(newStyles);
|
||||
}, true);
|
||||
});
|
||||
});
|
||||
|
||||
+208
-674
@@ -1,6 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
var ngOptionsMinErr = minErr('ngOptions');
|
||||
var noopNgModelController = { $setViewValue: noop, $render: noop };
|
||||
|
||||
/**
|
||||
* @ngdoc type
|
||||
* @name select.SelectController
|
||||
* @description
|
||||
* The controller for the `<select>` directive. This provides support for reading
|
||||
* and writing the selected value(s) of the control and also coordinates dynamically
|
||||
* added `<option>` elements, perhaps by an `ngRepeat` directive.
|
||||
*/
|
||||
var SelectController =
|
||||
['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
|
||||
|
||||
var self = this,
|
||||
optionsMap = new HashMap();
|
||||
|
||||
// If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
|
||||
self.ngModelCtrl = noopNgModelController;
|
||||
|
||||
// The "unknown" option is one that is prepended to the list if the viewValue
|
||||
// does not match any of the options. When it is rendered the value of the unknown
|
||||
// option is '? XXX ?' where XXX is the hashKey of the value that is not known.
|
||||
//
|
||||
// We can't just jqLite('<option>') since jqLite is not smart enough
|
||||
// to create it in <select> and IE barfs otherwise.
|
||||
self.unknownOption = jqLite(document.createElement('option'));
|
||||
self.renderUnknownOption = function(val) {
|
||||
var unknownVal = '? ' + hashKey(val) + ' ?';
|
||||
self.unknownOption.val(unknownVal);
|
||||
$element.prepend(self.unknownOption);
|
||||
$element.val(unknownVal);
|
||||
};
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
// disable unknown option so that we don't do work when the whole select is being destroyed
|
||||
self.renderUnknownOption = noop;
|
||||
});
|
||||
|
||||
self.removeUnknownOption = function() {
|
||||
if (self.unknownOption.parent()) self.unknownOption.remove();
|
||||
};
|
||||
|
||||
// Here we find the option that represents the "empty" value, i.e. the option with a value
|
||||
// of `""`. This option needs to be accessed (to select it directly) when setting the value
|
||||
// of the select to `""` because IE9 will not automatically select the option.
|
||||
//
|
||||
// Additionally, the `ngOptions` directive uses this option to allow the application developer
|
||||
// to provide their own custom "empty" option when the viewValue does not match any of the
|
||||
// option values.
|
||||
for (var i = 0, children = $element.children(), ii = children.length; i < ii; i++) {
|
||||
if (children[i].value === '') {
|
||||
self.emptyOption = children.eq(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the value of the select control, the implementation of this changes depending
|
||||
// upon whether the select can have multiple values and whether ngOptions is at work.
|
||||
self.readValue = function readSingleValue() {
|
||||
self.removeUnknownOption();
|
||||
return $element.val();
|
||||
};
|
||||
|
||||
|
||||
// Write the value to the select control, the implementation of this changes depending
|
||||
// upon whether the select can have multiple values and whether ngOptions is at work.
|
||||
self.writeValue = function writeSingleValue(value) {
|
||||
if (self.hasOption(value)) {
|
||||
self.removeUnknownOption();
|
||||
$element.val(value);
|
||||
if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
|
||||
} else {
|
||||
if (isUndefined(value) && self.emptyOption) {
|
||||
$element.val('');
|
||||
} else {
|
||||
self.renderUnknownOption(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Tell the select control that an option, with the given value, has been added
|
||||
self.addOption = function(value) {
|
||||
assertNotHasOwnProperty(value, '"option value"');
|
||||
var count = optionsMap.get(value) || 0;
|
||||
optionsMap.put(value, count + 1);
|
||||
};
|
||||
|
||||
// Tell the select control that an option, with the given value, has been removed
|
||||
self.removeOption = function(value) {
|
||||
var count = optionsMap.get(value);
|
||||
if (count) {
|
||||
if (count === 1) {
|
||||
optionsMap.remove(value);
|
||||
} else {
|
||||
optionsMap.put(value, count - 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Check whether the select control has an option matching the given value
|
||||
self.hasOption = function(value) {
|
||||
return !!optionsMap.get(value);
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name select
|
||||
@@ -9,23 +114,21 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* @description
|
||||
* HTML `SELECT` element with angular data-binding.
|
||||
*
|
||||
* # `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.
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
|
||||
* ngOptions} to achieve a 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 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`
|
||||
* directive.
|
||||
*
|
||||
* If the viewValue contains a value that doesn't match any of the options then the control
|
||||
* will automatically add an "unknown" option, which it then removes when this is resolved.
|
||||
*
|
||||
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
|
||||
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
|
||||
* option. See example below for demonstration.
|
||||
@@ -35,284 +138,61 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
|
||||
* </div>
|
||||
*
|
||||
* ## `select as`
|
||||
*
|
||||
* Using `select as` will bind the result of the `select as` 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
|
||||
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
|
||||
*
|
||||
* ### `select as` with `track by`
|
||||
*
|
||||
* Using `select as` together with `track by` is not recommended. Reasoning:
|
||||
*
|
||||
* - 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.
|
||||
* @param {string=} required The control is considered valid only if value is entered.
|
||||
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
||||
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
||||
* `required` when you want to data-bind to the `required` attribute.
|
||||
* @param {comprehension_expression=} ngOptions in one of the following forms:
|
||||
*
|
||||
* * for array data sources:
|
||||
* * `label` **`for`** `value` **`in`** `array`
|
||||
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
|
||||
* * `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`
|
||||
* * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`group by`** `group`
|
||||
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* * `array` / `object`: an expression which evaluates to an array / object to iterate over.
|
||||
* * `value`: local variable which will refer to each item in the `array` or each property value
|
||||
* of `object` during iteration.
|
||||
* * `key`: local variable which will refer to a property name in `object` during iteration.
|
||||
* * `label`: The result of this expression will be the label for `<option>` element. The
|
||||
* `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
|
||||
* * `select`: The result of this expression will be bound to the model of the parent `<select>`
|
||||
* element. If not specified, `select` expression will default to `value`.
|
||||
* * `group`: The result of this expression will be used to group options using the `<optgroup>`
|
||||
* DOM element.
|
||||
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
|
||||
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
|
||||
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
|
||||
* even when the options are recreated (e.g. reloaded from the server).
|
||||
*
|
||||
* @example
|
||||
<example module="selectExample">
|
||||
<file name="index.html">
|
||||
<script>
|
||||
angular.module('selectExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.colors = [
|
||||
{name:'black', shade:'dark'},
|
||||
{name:'white', shade:'light'},
|
||||
{name:'red', shade:'dark'},
|
||||
{name:'blue', shade:'dark'},
|
||||
{name:'yellow', shade:'light'}
|
||||
];
|
||||
$scope.myColor = $scope.colors[2]; // red
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
<ul>
|
||||
<li ng-repeat="color in colors">
|
||||
Name: <input ng-model="color.name">
|
||||
[<a href ng-click="colors.splice($index, 1)">X</a>]
|
||||
</li>
|
||||
<li>
|
||||
[<a href ng-click="colors.push({})">add</a>]
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
Color (null not allowed):
|
||||
<select ng-model="myColor" ng-options="color.name for color in colors"></select><br>
|
||||
|
||||
Color (null allowed):
|
||||
<span class="nullable">
|
||||
<select ng-model="myColor" ng-options="color.name for color in colors">
|
||||
<option value="">-- choose color --</option>
|
||||
</select>
|
||||
</span><br/>
|
||||
|
||||
Color grouped by shade:
|
||||
<select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
|
||||
</select><br/>
|
||||
|
||||
|
||||
Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br>
|
||||
<hr/>
|
||||
Currently selected: {{ {selected_color:myColor} }}
|
||||
<div style="border:solid 1px black; height:20px"
|
||||
ng-style="{'background-color':myColor.name}">
|
||||
</div>
|
||||
</div>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should check ng-options', function() {
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
|
||||
element.all(by.model('myColor')).first().click();
|
||||
element.all(by.css('select[ng-model="myColor"] option')).first().click();
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
|
||||
element(by.css('.nullable select[ng-model="myColor"]')).click();
|
||||
element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
var ngOptionsDirective = valueFn({
|
||||
restrict: 'A',
|
||||
terminal: true
|
||||
});
|
||||
|
||||
// jshint maxlen: false
|
||||
var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
//000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
|
||||
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,
|
||||
nullModelCtrl = {$setViewValue: noop};
|
||||
// jshint maxlen: 100
|
||||
var selectDirective = function() {
|
||||
var lastView;
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
require: ['select', '?ngModel'],
|
||||
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
|
||||
var self = this,
|
||||
optionsMap = {},
|
||||
ngModelCtrl = nullModelCtrl,
|
||||
nullOption,
|
||||
unknownOption;
|
||||
|
||||
|
||||
self.databound = $attrs.ngModel;
|
||||
|
||||
|
||||
self.init = function(ngModelCtrl_, nullOption_, unknownOption_) {
|
||||
ngModelCtrl = ngModelCtrl_;
|
||||
nullOption = nullOption_;
|
||||
unknownOption = unknownOption_;
|
||||
};
|
||||
|
||||
|
||||
self.addOption = function(value, element) {
|
||||
assertNotHasOwnProperty(value, '"option value"');
|
||||
optionsMap[value] = true;
|
||||
|
||||
if (ngModelCtrl.$viewValue == value) {
|
||||
$element.val(value);
|
||||
if (unknownOption.parent()) unknownOption.remove();
|
||||
}
|
||||
// Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
|
||||
// Adding an <option selected="selected"> element to a <select required="required"> should
|
||||
// automatically select the new element
|
||||
if (element && element[0].hasAttribute('selected')) {
|
||||
element[0].selected = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
self.removeOption = function(value) {
|
||||
if (this.hasOption(value)) {
|
||||
delete optionsMap[value];
|
||||
if (ngModelCtrl.$viewValue === value) {
|
||||
this.renderUnknownOption(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
self.renderUnknownOption = function(val) {
|
||||
var unknownVal = '? ' + hashKey(val) + ' ?';
|
||||
unknownOption.val(unknownVal);
|
||||
$element.prepend(unknownOption);
|
||||
$element.val(unknownVal);
|
||||
unknownOption.prop('selected', true); // needed for IE
|
||||
};
|
||||
|
||||
|
||||
self.hasOption = function(value) {
|
||||
return optionsMap.hasOwnProperty(value);
|
||||
};
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
// disable unknown option so that we don't do work when the whole select is being destroyed
|
||||
self.renderUnknownOption = noop;
|
||||
});
|
||||
}],
|
||||
|
||||
controller: SelectController,
|
||||
link: function(scope, element, attr, ctrls) {
|
||||
|
||||
// if ngModel is not defined, we don't need to do anything
|
||||
if (!ctrls[1]) return;
|
||||
var ngModelCtrl = ctrls[1];
|
||||
if (!ngModelCtrl) return;
|
||||
|
||||
var selectCtrl = ctrls[0],
|
||||
ngModelCtrl = ctrls[1],
|
||||
multiple = attr.multiple,
|
||||
optionsExp = attr.ngOptions,
|
||||
nullOption = false, // if false, user will not be able to select it (used by ngOptions)
|
||||
emptyOption,
|
||||
renderScheduled = false,
|
||||
// we can't just jqLite('<option>') since jqLite is not smart enough
|
||||
// to create it in <select> and IE barfs otherwise.
|
||||
optionTemplate = jqLite(document.createElement('option')),
|
||||
optGroupTemplate =jqLite(document.createElement('optgroup')),
|
||||
unknownOption = optionTemplate.clone();
|
||||
var selectCtrl = ctrls[0];
|
||||
|
||||
// find "null" option
|
||||
for (var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
|
||||
if (children[i].value === '') {
|
||||
emptyOption = nullOption = children.eq(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
selectCtrl.ngModelCtrl = ngModelCtrl;
|
||||
|
||||
selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
|
||||
// We delegate rendering to the `writeValue` method, which can be changed
|
||||
// if the select can have multiple selected values or if the options are being
|
||||
// generated by `ngOptions`
|
||||
ngModelCtrl.$render = function() {
|
||||
selectCtrl.writeValue(ngModelCtrl.$viewValue);
|
||||
};
|
||||
|
||||
// required validator
|
||||
if (multiple) {
|
||||
ngModelCtrl.$isEmpty = function(value) {
|
||||
return !value || value.length === 0;
|
||||
};
|
||||
}
|
||||
|
||||
if (optionsExp) setupAsOptions(scope, element, ngModelCtrl);
|
||||
else if (multiple) setupAsMultiple(scope, element, ngModelCtrl);
|
||||
else setupAsSingle(scope, element, ngModelCtrl, selectCtrl);
|
||||
|
||||
|
||||
////////////////////////////
|
||||
|
||||
|
||||
|
||||
function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) {
|
||||
ngModelCtrl.$render = function() {
|
||||
var viewValue = ngModelCtrl.$viewValue;
|
||||
|
||||
if (selectCtrl.hasOption(viewValue)) {
|
||||
if (unknownOption.parent()) unknownOption.remove();
|
||||
selectElement.val(viewValue);
|
||||
if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
|
||||
} else {
|
||||
if (isUndefined(viewValue) && emptyOption) {
|
||||
selectElement.val('');
|
||||
} else {
|
||||
selectCtrl.renderUnknownOption(viewValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
selectElement.on('change', function() {
|
||||
scope.$apply(function() {
|
||||
if (unknownOption.parent()) unknownOption.remove();
|
||||
ngModelCtrl.$setViewValue(selectElement.val());
|
||||
});
|
||||
// When the selected item(s) changes we delegate getting the value of the select control
|
||||
// to the `readValue` method, which can be changed if the select can have multiple
|
||||
// selected values or if the options are being generated by `ngOptions`
|
||||
element.on('change', function() {
|
||||
scope.$apply(function() {
|
||||
ngModelCtrl.$setViewValue(selectCtrl.readValue());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function setupAsMultiple(scope, selectElement, ctrl) {
|
||||
var lastView;
|
||||
ctrl.$render = function() {
|
||||
var items = new HashMap(ctrl.$viewValue);
|
||||
forEach(selectElement.find('option'), function(option) {
|
||||
// If the select allows multiple values then we need to modify how we read and write
|
||||
// values from and to the control; also what it means for the value to be empty and
|
||||
// we have to add an extra watch since ngModel doesn't work well with arrays - it
|
||||
// doesn't trigger rendering if only an item in the array changes.
|
||||
if (attr.multiple) {
|
||||
|
||||
// Read value now needs to check each option to see if it is selected
|
||||
selectCtrl.readValue = function readMultipleValue() {
|
||||
var array = [];
|
||||
forEach(element.find('option'), function(option) {
|
||||
if (option.selected) {
|
||||
array.push(option.value);
|
||||
}
|
||||
});
|
||||
return array;
|
||||
};
|
||||
|
||||
// Write value now needs to set the selected property of each matching option
|
||||
selectCtrl.writeValue = function writeMultipleValue(value) {
|
||||
var items = new HashMap(value);
|
||||
forEach(element.find('option'), function(option) {
|
||||
option.selected = isDefined(items.get(option.value));
|
||||
});
|
||||
};
|
||||
@@ -320,400 +200,45 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// we have to do it on each watch since ngModel watches reference, but
|
||||
// we need to work of an array, so we need to see if anything was inserted/removed
|
||||
scope.$watch(function selectMultipleWatch() {
|
||||
if (!equals(lastView, ctrl.$viewValue)) {
|
||||
lastView = shallowCopy(ctrl.$viewValue);
|
||||
ctrl.$render();
|
||||
if (!equals(lastView, ngModelCtrl.$viewValue)) {
|
||||
lastView = shallowCopy(ngModelCtrl.$viewValue);
|
||||
ngModelCtrl.$render();
|
||||
}
|
||||
});
|
||||
|
||||
selectElement.on('change', function() {
|
||||
scope.$apply(function() {
|
||||
var array = [];
|
||||
forEach(selectElement.find('option'), function(option) {
|
||||
if (option.selected) {
|
||||
array.push(option.value);
|
||||
}
|
||||
});
|
||||
ctrl.$setViewValue(array);
|
||||
});
|
||||
});
|
||||
}
|
||||
// If we are a multiple select then value is now a collection
|
||||
// so the meaning of $isEmpty changes
|
||||
ngModelCtrl.$isEmpty = function(value) {
|
||||
return !value || value.length === 0;
|
||||
};
|
||||
|
||||
function setupAsOptions(scope, selectElement, ctrl) {
|
||||
var match;
|
||||
|
||||
if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) {
|
||||
throw ngOptionsMinErr('iexp',
|
||||
"Expected expression in form of " +
|
||||
"'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
|
||||
" but got '{0}'. Element: {1}",
|
||||
optionsExp, startingTag(selectElement));
|
||||
}
|
||||
|
||||
var displayFn = $parse(match[2] || match[1]),
|
||||
valueName = match[4] || match[6],
|
||||
selectAs = / as /.test(match[0]) && match[1],
|
||||
selectAsFn = selectAs ? $parse(selectAs) : null,
|
||||
keyName = match[5],
|
||||
groupByFn = $parse(match[3] || ''),
|
||||
valueFn = $parse(match[2] ? match[1] : valueName),
|
||||
valuesFn = $parse(match[7]),
|
||||
track = match[8],
|
||||
trackFn = track ? $parse(match[8]) : null,
|
||||
trackKeysCache = {},
|
||||
// This is an array of array of existing option groups in DOM.
|
||||
// We try to reuse these if possible
|
||||
// - optionGroupsCache[0] is the options with no option group
|
||||
// - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
|
||||
optionGroupsCache = [[{element: selectElement, label:''}]],
|
||||
//re-usable object to represent option's locals
|
||||
locals = {};
|
||||
|
||||
if (nullOption) {
|
||||
// compile the element since there might be bindings in it
|
||||
$compile(nullOption)(scope);
|
||||
|
||||
// remove the class, which is added automatically because we recompile the element and it
|
||||
// becomes the compilation root
|
||||
nullOption.removeClass('ng-scope');
|
||||
|
||||
// we need to remove it before calling selectElement.empty() because otherwise IE will
|
||||
// remove the label from the element. wtf?
|
||||
nullOption.remove();
|
||||
}
|
||||
|
||||
// clear contents, we'll add what's needed based on the model
|
||||
selectElement.empty();
|
||||
|
||||
selectElement.on('change', selectionChanged);
|
||||
|
||||
ctrl.$render = render;
|
||||
|
||||
scope.$watchCollection(valuesFn, scheduleRendering);
|
||||
scope.$watchCollection(getLabels, scheduleRendering);
|
||||
|
||||
if (multiple) {
|
||||
scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
function callExpression(exprFn, key, value) {
|
||||
locals[valueName] = value;
|
||||
if (keyName) locals[keyName] = key;
|
||||
return exprFn(scope, locals);
|
||||
}
|
||||
|
||||
function selectionChanged() {
|
||||
scope.$apply(function() {
|
||||
var collection = valuesFn(scope) || [];
|
||||
var viewValue;
|
||||
if (multiple) {
|
||||
viewValue = [];
|
||||
forEach(selectElement.val(), function(selectedKey) {
|
||||
selectedKey = trackFn ? trackKeysCache[selectedKey] : selectedKey;
|
||||
viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
|
||||
});
|
||||
} else {
|
||||
var selectedKey = trackFn ? trackKeysCache[selectElement.val()] : selectElement.val();
|
||||
viewValue = getViewValue(selectedKey, collection[selectedKey]);
|
||||
}
|
||||
ctrl.$setViewValue(viewValue);
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
function getViewValue(key, value) {
|
||||
if (key === '?') {
|
||||
return undefined;
|
||||
} else if (key === '') {
|
||||
return null;
|
||||
} else {
|
||||
var viewValueFn = selectAsFn ? selectAsFn : valueFn;
|
||||
return callExpression(viewValueFn, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
function getLabels() {
|
||||
var values = valuesFn(scope);
|
||||
var toDisplay;
|
||||
if (values && isArray(values)) {
|
||||
toDisplay = new Array(values.length);
|
||||
for (var i = 0, ii = values.length; i < ii; i++) {
|
||||
toDisplay[i] = callExpression(displayFn, i, values[i]);
|
||||
}
|
||||
return toDisplay;
|
||||
} else if (values) {
|
||||
// TODO: Add a test for this case
|
||||
toDisplay = {};
|
||||
for (var prop in values) {
|
||||
if (values.hasOwnProperty(prop)) {
|
||||
toDisplay[prop] = callExpression(displayFn, prop, values[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return toDisplay;
|
||||
}
|
||||
|
||||
function createIsSelectedFn(viewValue) {
|
||||
var selectedSet;
|
||||
if (multiple) {
|
||||
if (trackFn && isArray(viewValue)) {
|
||||
|
||||
selectedSet = new HashMap([]);
|
||||
for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
|
||||
// tracking by key
|
||||
selectedSet.put(callExpression(trackFn, null, viewValue[trackIndex]), true);
|
||||
}
|
||||
} else {
|
||||
selectedSet = new HashMap(viewValue);
|
||||
}
|
||||
} else if (trackFn) {
|
||||
viewValue = callExpression(trackFn, null, viewValue);
|
||||
}
|
||||
|
||||
return function isSelected(key, value) {
|
||||
var compareValueFn;
|
||||
if (trackFn) {
|
||||
compareValueFn = trackFn;
|
||||
} else if (selectAsFn) {
|
||||
compareValueFn = selectAsFn;
|
||||
} else {
|
||||
compareValueFn = valueFn;
|
||||
}
|
||||
|
||||
if (multiple) {
|
||||
return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
|
||||
} else {
|
||||
return viewValue === callExpression(compareValueFn, key, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function scheduleRendering() {
|
||||
if (!renderScheduled) {
|
||||
scope.$$postDigest(render);
|
||||
renderScheduled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A new labelMap is created with each render.
|
||||
* This function is called for each existing option with added=false,
|
||||
* and each new option with added=true.
|
||||
* - Labels that are passed to this method twice,
|
||||
* (once with added=true and once with added=false) will end up with a value of 0, and
|
||||
* will cause no change to happen to the corresponding option.
|
||||
* - Labels that are passed to this method only once with added=false will end up with a
|
||||
* value of -1 and will eventually be passed to selectCtrl.removeOption()
|
||||
* - Labels that are passed to this method only once with added=true will end up with a
|
||||
* value of 1 and will eventually be passed to selectCtrl.addOption()
|
||||
*/
|
||||
function updateLabelMap(labelMap, label, added) {
|
||||
labelMap[label] = labelMap[label] || 0;
|
||||
labelMap[label] += (added ? 1 : -1);
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderScheduled = false;
|
||||
|
||||
// Temporary location for the option groups before we render them
|
||||
var optionGroups = {'':[]},
|
||||
optionGroupNames = [''],
|
||||
optionGroupName,
|
||||
optionGroup,
|
||||
option,
|
||||
existingParent, existingOptions, existingOption,
|
||||
viewValue = ctrl.$viewValue,
|
||||
values = valuesFn(scope) || [],
|
||||
keys = keyName ? sortedKeys(values) : values,
|
||||
key,
|
||||
value,
|
||||
groupLength, length,
|
||||
groupIndex, index,
|
||||
labelMap = {},
|
||||
selected,
|
||||
isSelected = createIsSelectedFn(viewValue),
|
||||
anySelected = false,
|
||||
lastElement,
|
||||
element,
|
||||
label,
|
||||
optionId;
|
||||
|
||||
trackKeysCache = {};
|
||||
|
||||
// We now build up the list of options we need (we merge later)
|
||||
for (index = 0; length = keys.length, index < length; index++) {
|
||||
key = index;
|
||||
if (keyName) {
|
||||
key = keys[index];
|
||||
if (key.charAt(0) === '$') continue;
|
||||
}
|
||||
value = values[key];
|
||||
|
||||
optionGroupName = callExpression(groupByFn, key, value) || '';
|
||||
if (!(optionGroup = optionGroups[optionGroupName])) {
|
||||
optionGroup = optionGroups[optionGroupName] = [];
|
||||
optionGroupNames.push(optionGroupName);
|
||||
}
|
||||
|
||||
selected = isSelected(key, value);
|
||||
anySelected = anySelected || selected;
|
||||
|
||||
label = callExpression(displayFn, key, value); // what will be seen by the user
|
||||
|
||||
// doing displayFn(scope, locals) || '' overwrites zero values
|
||||
label = isDefined(label) ? label : '';
|
||||
optionId = trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index);
|
||||
if (trackFn) {
|
||||
trackKeysCache[optionId] = key;
|
||||
}
|
||||
|
||||
optionGroup.push({
|
||||
// either the index into array or key from object
|
||||
id: optionId,
|
||||
label: label,
|
||||
selected: selected // determine if we should be selected
|
||||
});
|
||||
}
|
||||
if (!multiple) {
|
||||
if (nullOption || viewValue === null) {
|
||||
// insert null option if we have a placeholder, or the model is null
|
||||
optionGroups[''].unshift({id:'', label:'', selected:!anySelected});
|
||||
} else if (!anySelected) {
|
||||
// option could not be found, we have to insert the undefined item
|
||||
optionGroups[''].unshift({id:'?', label:'', selected:true});
|
||||
}
|
||||
}
|
||||
|
||||
// Now we need to update the list of DOM nodes to match the optionGroups we computed above
|
||||
for (groupIndex = 0, groupLength = optionGroupNames.length;
|
||||
groupIndex < groupLength;
|
||||
groupIndex++) {
|
||||
// current option group name or '' if no group
|
||||
optionGroupName = optionGroupNames[groupIndex];
|
||||
|
||||
// list of options for that group. (first item has the parent)
|
||||
optionGroup = optionGroups[optionGroupName];
|
||||
|
||||
if (optionGroupsCache.length <= groupIndex) {
|
||||
// we need to grow the optionGroups
|
||||
existingParent = {
|
||||
element: optGroupTemplate.clone().attr('label', optionGroupName),
|
||||
label: optionGroup.label
|
||||
};
|
||||
existingOptions = [existingParent];
|
||||
optionGroupsCache.push(existingOptions);
|
||||
selectElement.append(existingParent.element);
|
||||
} else {
|
||||
existingOptions = optionGroupsCache[groupIndex];
|
||||
existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
|
||||
|
||||
// update the OPTGROUP label if not the same.
|
||||
if (existingParent.label != optionGroupName) {
|
||||
existingParent.element.attr('label', existingParent.label = optionGroupName);
|
||||
}
|
||||
}
|
||||
|
||||
lastElement = null; // start at the beginning
|
||||
for (index = 0, length = optionGroup.length; index < length; index++) {
|
||||
option = optionGroup[index];
|
||||
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);
|
||||
}
|
||||
// lastElement.prop('selected') provided by jQuery has side-effects
|
||||
if (lastElement[0].selected !== option.selected) {
|
||||
lastElement.prop('selected', (existingOption.selected = option.selected));
|
||||
if (msie) {
|
||||
// See #7692
|
||||
// The selected item wouldn't visually update on IE without this.
|
||||
// Tested on Win7: IE9, IE10 and IE11. Future IEs should be tested as well
|
||||
lastElement.prop('selected', existingOption.selected);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// grow elements
|
||||
|
||||
// if it's a null option
|
||||
if (option.id === '' && nullOption) {
|
||||
// put back the pre-compiled element
|
||||
element = nullOption;
|
||||
} else {
|
||||
// jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
|
||||
// in this version of jQuery on some browser the .text() returns a string
|
||||
// rather then the element.
|
||||
(element = optionTemplate.clone())
|
||||
.val(option.id)
|
||||
.prop('selected', option.selected)
|
||||
.attr('selected', option.selected)
|
||||
.prop('label', option.label)
|
||||
.text(option.label);
|
||||
}
|
||||
|
||||
existingOptions.push(existingOption = {
|
||||
element: element,
|
||||
label: option.label,
|
||||
id: option.id,
|
||||
selected: option.selected
|
||||
});
|
||||
updateLabelMap(labelMap, option.label, true);
|
||||
if (lastElement) {
|
||||
lastElement.after(element);
|
||||
} else {
|
||||
existingParent.element.append(element);
|
||||
}
|
||||
lastElement = element;
|
||||
}
|
||||
}
|
||||
// remove any excessive OPTIONs in a group
|
||||
index++; // increment since the existingOptions[0] is parent element not OPTION
|
||||
while (existingOptions.length > index) {
|
||||
option = existingOptions.pop();
|
||||
updateLabelMap(labelMap, option.label, false);
|
||||
option.element.remove();
|
||||
}
|
||||
}
|
||||
// remove any excessive OPTGROUPs from select
|
||||
while (optionGroupsCache.length > groupIndex) {
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
};
|
||||
|
||||
|
||||
// The option directive is purely designed to communicate the existence (or lack of)
|
||||
// of dynamically created (and destroyed) option elements to their containing select
|
||||
// directive via its controller.
|
||||
var optionDirective = ['$interpolate', function($interpolate) {
|
||||
var nullSelectCtrl = {
|
||||
addOption: noop,
|
||||
removeOption: noop
|
||||
};
|
||||
|
||||
function chromeHack(optionElement) {
|
||||
// Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
|
||||
// Adding an <option selected="selected"> element to a <select required="required"> should
|
||||
// automatically select the new element
|
||||
if (optionElement[0].hasAttribute('selected')) {
|
||||
optionElement[0].selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
priority: 100,
|
||||
compile: function(element, attr) {
|
||||
|
||||
// If the value attribute is not defined then we fall back to the
|
||||
// text content of the option element, which may be interpolated
|
||||
if (isUndefined(attr.value)) {
|
||||
var interpolateFn = $interpolate(element.text(), true);
|
||||
if (!interpolateFn) {
|
||||
@@ -722,30 +247,39 @@ var optionDirective = ['$interpolate', function($interpolate) {
|
||||
}
|
||||
|
||||
return function(scope, element, attr) {
|
||||
|
||||
// This is an optimization over using ^^ since we don't want to have to search
|
||||
// all the way to the root of the DOM for every single option element
|
||||
var selectCtrlName = '$selectController',
|
||||
parent = element.parent(),
|
||||
selectCtrl = parent.data(selectCtrlName) ||
|
||||
parent.parent().data(selectCtrlName); // in case we are in optgroup
|
||||
|
||||
if (!selectCtrl || !selectCtrl.databound) {
|
||||
selectCtrl = nullSelectCtrl;
|
||||
}
|
||||
// Only update trigger option updates if this is an option within a `select`
|
||||
// that also has `ngModel` attached
|
||||
if (selectCtrl && selectCtrl.ngModelCtrl) {
|
||||
|
||||
if (interpolateFn) {
|
||||
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
|
||||
attr.$set('value', newVal);
|
||||
if (oldVal !== newVal) {
|
||||
selectCtrl.removeOption(oldVal);
|
||||
}
|
||||
selectCtrl.addOption(newVal, element);
|
||||
if (interpolateFn) {
|
||||
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
|
||||
attr.$set('value', newVal);
|
||||
if (oldVal !== newVal) {
|
||||
selectCtrl.removeOption(oldVal);
|
||||
}
|
||||
selectCtrl.addOption(newVal, element);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
});
|
||||
} else {
|
||||
selectCtrl.addOption(attr.value, element);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
}
|
||||
|
||||
element.on('$destroy', function() {
|
||||
selectCtrl.removeOption(attr.value);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
});
|
||||
} else {
|
||||
selectCtrl.addOption(attr.value, element);
|
||||
}
|
||||
|
||||
element.on('$destroy', function() {
|
||||
selectCtrl.removeOption(attr.value);
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
'use strict';
|
||||
|
||||
var requiredDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
attr.required = true; // force truthy in case we are on non input element
|
||||
|
||||
ctrl.$validators.required = function(modelValue, viewValue) {
|
||||
return !attr.required || !ctrl.$isEmpty(viewValue);
|
||||
};
|
||||
|
||||
attr.$observe('required', function() {
|
||||
ctrl.$validate();
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
var patternDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var regexp, patternExp = attr.ngPattern || attr.pattern;
|
||||
attr.$observe('pattern', function(regex) {
|
||||
if (isString(regex) && regex.length > 0) {
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
}
|
||||
|
||||
if (regex && !regex.test) {
|
||||
throw minErr('ngPattern')('noregexp',
|
||||
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
|
||||
regex, startingTag(elm));
|
||||
}
|
||||
|
||||
regexp = regex || undefined;
|
||||
ctrl.$validate();
|
||||
});
|
||||
|
||||
ctrl.$validators.pattern = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
var maxlengthDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var maxlength = -1;
|
||||
attr.$observe('maxlength', function(value) {
|
||||
var intVal = toInt(value);
|
||||
maxlength = isNaN(intVal) ? -1 : intVal;
|
||||
ctrl.$validate();
|
||||
});
|
||||
ctrl.$validators.maxlength = function(modelValue, viewValue) {
|
||||
return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var minlengthDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var minlength = 0;
|
||||
attr.$observe('minlength', function(value) {
|
||||
minlength = toInt(value) || 0;
|
||||
ctrl.$validate();
|
||||
});
|
||||
ctrl.$validators.minlength = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
+34
-17
@@ -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.
|
||||
@@ -117,7 +124,13 @@
|
||||
*/
|
||||
function filterFilter() {
|
||||
return function(array, expression, comparator) {
|
||||
if (!isArray(array)) return array;
|
||||
if (!isArray(array)) {
|
||||
if (array == null) {
|
||||
return array;
|
||||
} else {
|
||||
throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
|
||||
}
|
||||
}
|
||||
|
||||
var predicateFn;
|
||||
var matchAgainstAnyProp;
|
||||
@@ -145,6 +158,7 @@ function filterFilter() {
|
||||
|
||||
// Helper functions for `filterFilter`
|
||||
function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
|
||||
var predicateFn;
|
||||
|
||||
if (comparator === true) {
|
||||
@@ -163,19 +177,22 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
}
|
||||
|
||||
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) {
|
||||
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') {
|
||||
} else if (isArray(actual)) {
|
||||
// In case `actual` is an array, consider it a match
|
||||
// if ANY of it's items matches `expected`
|
||||
return actual.some(function(item) {
|
||||
@@ -188,11 +205,11 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
|
||||
var key;
|
||||
if (matchAgainstAnyProp) {
|
||||
for (key in actual) {
|
||||
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) {
|
||||
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
|
||||
} else if (expectedType === 'object') {
|
||||
for (key in expected) {
|
||||
var expectedVal = expected[key];
|
||||
@@ -200,9 +217,9 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var keyIsDollar = key === '$';
|
||||
var actualVal = keyIsDollar ? actual : actual[key];
|
||||
if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) {
|
||||
var matchAnyProperty = key === '$';
|
||||
var actualVal = matchAnyProperty ? actual : actual[key];
|
||||
if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+38
-23
@@ -82,6 +82,8 @@ function currencyFilter($locale) {
|
||||
*
|
||||
* If the input is not a number an empty string is returned.
|
||||
*
|
||||
* If the input is an infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
|
||||
*
|
||||
* @param {number|string} number Number to format.
|
||||
* @param {(number|string)=} fractionSize Number of decimal places to round the number to.
|
||||
* If this is not provided then the fraction size is computed from the current locale's number
|
||||
@@ -138,16 +140,22 @@ function numberFilter($locale) {
|
||||
|
||||
var DECIMAL_SEP = '.';
|
||||
function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
if (!isFinite(number) || isObject(number)) return '';
|
||||
if (isObject(number)) return '';
|
||||
|
||||
var isNegative = number < 0;
|
||||
number = Math.abs(number);
|
||||
|
||||
var isInfinity = number === Infinity;
|
||||
if (!isInfinity && !isFinite(number)) return '';
|
||||
|
||||
var numStr = number + '',
|
||||
formatedText = '',
|
||||
hasExponent = false,
|
||||
parts = [];
|
||||
|
||||
var hasExponent = false;
|
||||
if (numStr.indexOf('e') !== -1) {
|
||||
if (isInfinity) formatedText = '\u221e';
|
||||
|
||||
if (!isInfinity && numStr.indexOf('e') !== -1) {
|
||||
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
|
||||
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
|
||||
number = 0;
|
||||
@@ -157,7 +165,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasExponent) {
|
||||
if (!isInfinity && !hasExponent) {
|
||||
var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
|
||||
|
||||
// determine fractionSize if it is not specified
|
||||
@@ -226,8 +234,9 @@ function padNumber(num, digits, trim) {
|
||||
}
|
||||
num = '' + num;
|
||||
while (num.length < digits) num = '0' + num;
|
||||
if (trim)
|
||||
if (trim) {
|
||||
num = num.substr(num.length - digits);
|
||||
}
|
||||
return neg + num;
|
||||
}
|
||||
|
||||
@@ -236,8 +245,9 @@ function dateGetter(name, size, offset, trim) {
|
||||
offset = offset || 0;
|
||||
return function(date) {
|
||||
var value = date['get' + name]();
|
||||
if (offset > 0 || value > -offset)
|
||||
if (offset > 0 || value > -offset) {
|
||||
value += offset;
|
||||
}
|
||||
if (value === 0 && offset == -12) value = 12;
|
||||
return padNumber(value, size, trim);
|
||||
};
|
||||
@@ -252,8 +262,8 @@ function dateStrGetter(name, shortForm) {
|
||||
};
|
||||
}
|
||||
|
||||
function timeZoneGetter(date) {
|
||||
var zone = -1 * date.getTimezoneOffset();
|
||||
function timeZoneGetter(date, formats, offset) {
|
||||
var zone = -1 * offset;
|
||||
var paddedZone = (zone >= 0) ? "+" : "";
|
||||
|
||||
paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
|
||||
@@ -353,11 +363,11 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
|
||||
* * `'m'`: Minute in hour (0-59)
|
||||
* * `'ss'`: Second in minute, padded (00-59)
|
||||
* * `'s'`: Second in minute (0-59)
|
||||
* * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
|
||||
* * `'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}:
|
||||
@@ -429,13 +439,13 @@ function dateFilter($locale) {
|
||||
timeSetter = match[8] ? date.setUTCHours : date.setHours;
|
||||
|
||||
if (match[9]) {
|
||||
tzHour = int(match[9] + match[10]);
|
||||
tzMin = int(match[9] + match[11]);
|
||||
tzHour = toInt(match[9] + match[10]);
|
||||
tzMin = toInt(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);
|
||||
dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
|
||||
var h = toInt(match[4] || 0) - tzHour;
|
||||
var m = toInt(match[5] || 0) - tzMin;
|
||||
var s = toInt(match[6] || 0);
|
||||
var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
|
||||
timeSetter.call(date, h, m, s, ms);
|
||||
return date;
|
||||
@@ -452,14 +462,14 @@ function dateFilter($locale) {
|
||||
format = format || 'mediumDate';
|
||||
format = $locale.DATETIME_FORMATS[format] || format;
|
||||
if (isString(date)) {
|
||||
date = NUMBER_STRING.test(date) ? int(date) : jsonStringToDate(date);
|
||||
date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
|
||||
}
|
||||
|
||||
if (isNumber(date)) {
|
||||
date = new Date(date);
|
||||
}
|
||||
|
||||
if (!isDate(date)) {
|
||||
if (!isDate(date) || !isFinite(date.getTime())) {
|
||||
return date;
|
||||
}
|
||||
|
||||
@@ -474,13 +484,18 @@ function dateFilter($locale) {
|
||||
}
|
||||
}
|
||||
|
||||
if (timezone && timezone === 'UTC') {
|
||||
date = new Date(date.getTime());
|
||||
date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
|
||||
var dateTimezoneOffset = date.getTimezoneOffset();
|
||||
if (timezone) {
|
||||
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
|
||||
if (!isNaN(requestedTimezoneOffset)) {
|
||||
date = new Date(date.getTime());
|
||||
date.setMinutes(date.getMinutes() + dateTimezoneOffset - requestedTimezoneOffset);
|
||||
dateTimezoneOffset = requestedTimezoneOffset;
|
||||
}
|
||||
}
|
||||
forEach(parts, function(value) {
|
||||
fn = DATE_FORMATS[value];
|
||||
text += fn ? fn(date, $locale.DATETIME_FORMATS)
|
||||
text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
|
||||
: value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
* @param {string|number} limit The length of the returned array or string. If the `limit` number
|
||||
* is positive, `limit` number of items from the beginning of the source array/string are copied.
|
||||
* If the number is negative, `limit` number of items from the end of the source array/string
|
||||
* are copied. The `limit` will be trimmed if it exceeds `array.length`
|
||||
* are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
|
||||
* the input will be returned unchanged.
|
||||
* @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
|
||||
* had less than `limit` elements.
|
||||
*
|
||||
@@ -88,45 +89,16 @@
|
||||
*/
|
||||
function limitToFilter() {
|
||||
return function(input, limit) {
|
||||
if (isNumber(input)) input = input.toString();
|
||||
if (!isArray(input) && !isString(input)) return input;
|
||||
|
||||
if (Math.abs(Number(limit)) === Infinity) {
|
||||
limit = Number(limit);
|
||||
} else {
|
||||
limit = int(limit);
|
||||
limit = toInt(limit);
|
||||
}
|
||||
if (isNaN(limit)) return input;
|
||||
|
||||
if (isString(input)) {
|
||||
//NaN check on limit
|
||||
if (limit) {
|
||||
return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
if (isNumber(input)) input = input.toString();
|
||||
if (!isArray(input) && !isString(input)) return input;
|
||||
|
||||
var out = [],
|
||||
i, n;
|
||||
|
||||
// if abs(limit) exceeds maximum length, trim it
|
||||
if (limit > input.length)
|
||||
limit = input.length;
|
||||
else if (limit < -input.length)
|
||||
limit = -input.length;
|
||||
|
||||
if (limit > 0) {
|
||||
i = 0;
|
||||
n = limit;
|
||||
} else {
|
||||
i = input.length + limit;
|
||||
n = input.length;
|
||||
}
|
||||
|
||||
for (; i < n; i++) {
|
||||
out.push(input[i]);
|
||||
}
|
||||
|
||||
return out;
|
||||
return limit >= 0 ? input.slice(0, limit) : input.slice(limit);
|
||||
};
|
||||
}
|
||||
|
||||
+28
-22
@@ -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) {
|
||||
@@ -160,29 +158,37 @@ 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;
|
||||
// Prepare values for Abstract Relational Comparison
|
||||
// (http://www.ecma-international.org/ecma-262/5.1/#sec-11.8.5):
|
||||
// If the resulting values are identical, return 0 to prevent
|
||||
// incorrect re-ordering.
|
||||
if (t1 === t2 && t1 === "object") {
|
||||
// If types are both numbers, emulate abstract ToPrimitive() operation
|
||||
// in order to get primitive values suitable for comparison
|
||||
t1 = typeof (v1.valueOf ? v1 = v1.valueOf() : v1);
|
||||
t2 = typeof (v2.valueOf ? v2 = v2.valueOf() : v2);
|
||||
if (t1 === t2 && t1 === "object") {
|
||||
// Object.prototype.valueOf will return the original object, by
|
||||
// default. If we do not receive a primitive value, use ToString()
|
||||
// instead.
|
||||
t1 = typeof (v1.toString ? v1 = v1.toString() : v1);
|
||||
t2 = typeof (v2.toString ? v2 = v2.toString() : v2);
|
||||
|
||||
// If the end result of toString() for each item is the same, do not
|
||||
// perform relational comparison, and do not re-order objects.
|
||||
if (t1 === t2 && v1 === v2 || t1 === "object") return 0;
|
||||
}
|
||||
v1 = objectToString(v1);
|
||||
v2 = objectToString(v2);
|
||||
}
|
||||
if (t1 === t2) {
|
||||
if (t1 === "string") {
|
||||
|
||||
+63
-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 && data.trim()) ||
|
||||
(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
|
||||
*
|
||||
@@ -81,16 +92,18 @@ 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) {
|
||||
if (isFunction(fns))
|
||||
return fns(data, headers);
|
||||
function transformData(data, headers, status, fns) {
|
||||
if (isFunction(fns)) {
|
||||
return fns(data, headers, status);
|
||||
}
|
||||
|
||||
forEach(fns, function(fn) {
|
||||
data = fn(data, headers);
|
||||
data = fn(data, headers, status);
|
||||
});
|
||||
|
||||
return data;
|
||||
@@ -142,7 +155,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
|
||||
@@ -335,7 +348,7 @@ function $HttpProvider() {
|
||||
* To add or overwrite these defaults, simply add or remove a property from these configuration
|
||||
* objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
|
||||
* with the lowercased HTTP method name as the key, e.g.
|
||||
* `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }.
|
||||
* `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
|
||||
*
|
||||
* The defaults can also be set at runtime via the `$http.defaults` object in the same
|
||||
* fashion. For example:
|
||||
@@ -369,7 +382,7 @@ function $HttpProvider() {
|
||||
*
|
||||
* 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
|
||||
@@ -603,7 +616,7 @@ function $HttpProvider() {
|
||||
* - **data** – `{string|Object}` – Data to be sent as the request message data.
|
||||
* - **headers** – `{Object}` – Map of strings or functions which return strings representing
|
||||
* HTTP headers to send to the server. If the return value of a function is null, the
|
||||
* header will not be sent.
|
||||
* header will not be sent. Functions accept a config object as an argument.
|
||||
* - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
|
||||
* - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
|
||||
* - **transformRequest** –
|
||||
@@ -613,9 +626,9 @@ function $HttpProvider() {
|
||||
* 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.
|
||||
* 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
|
||||
@@ -738,24 +751,23 @@ function $HttpProvider() {
|
||||
</example>
|
||||
*/
|
||||
function $http(requestConfig) {
|
||||
var config = {
|
||||
method: 'get',
|
||||
transformRequest: defaults.transformRequest,
|
||||
transformResponse: defaults.transformResponse
|
||||
};
|
||||
var headers = mergeHeaders(requestConfig);
|
||||
|
||||
if (!angular.isObject(requestConfig)) {
|
||||
throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
|
||||
}
|
||||
|
||||
extend(config, requestConfig);
|
||||
config.headers = headers;
|
||||
var config = extend({
|
||||
method: 'get',
|
||||
transformRequest: defaults.transformRequest,
|
||||
transformResponse: defaults.transformResponse
|
||||
}, requestConfig);
|
||||
|
||||
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)) {
|
||||
@@ -771,7 +783,7 @@ function $HttpProvider() {
|
||||
}
|
||||
|
||||
// send request
|
||||
return sendReq(config, reqData, headers).then(transformResponse, transformResponse);
|
||||
return sendReq(config, reqData).then(transformResponse, transformResponse);
|
||||
};
|
||||
|
||||
var chain = [serverRequest, undefined];
|
||||
@@ -816,13 +828,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, config) {
|
||||
var headerContent, processedHeaders = {};
|
||||
|
||||
forEach(headers, function(headerFn, header) {
|
||||
if (isFunction(headerFn)) {
|
||||
headerContent = headerFn(config);
|
||||
if (headerContent != null) {
|
||||
processedHeaders[header] = headerContent;
|
||||
}
|
||||
} else {
|
||||
processedHeaders[header] = headerFn;
|
||||
}
|
||||
});
|
||||
|
||||
return processedHeaders;
|
||||
}
|
||||
|
||||
function mergeHeaders(config) {
|
||||
var defHeaders = defaults.headers,
|
||||
reqHeaders = extend({}, config.headers),
|
||||
@@ -845,23 +874,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, shallowCopy(config));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1004,11 +1017,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);
|
||||
|
||||
+22
-22
@@ -88,6 +88,28 @@ function $InterpolateProvider() {
|
||||
return '\\\\\\' + ch;
|
||||
}
|
||||
|
||||
function unescapeText(text) {
|
||||
return text.replace(escapedStartRegexp, startSymbol).
|
||||
replace(escapedEndRegexp, endSymbol);
|
||||
}
|
||||
|
||||
function stringify(value) {
|
||||
if (value == null) { // null || undefined
|
||||
return '';
|
||||
}
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
break;
|
||||
case 'number':
|
||||
value = '' + value;
|
||||
break;
|
||||
default:
|
||||
value = toJson(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $interpolate
|
||||
@@ -243,23 +265,6 @@ function $InterpolateProvider() {
|
||||
$sce.valueOf(value);
|
||||
};
|
||||
|
||||
var stringify = function(value) {
|
||||
if (value == null) { // null || undefined
|
||||
return '';
|
||||
}
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
break;
|
||||
case 'number':
|
||||
value = '' + value;
|
||||
break;
|
||||
default:
|
||||
value = toJson(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
return extend(function interpolationFn(context) {
|
||||
var i = 0;
|
||||
var ii = expressions.length;
|
||||
@@ -294,11 +299,6 @@ function $InterpolateProvider() {
|
||||
});
|
||||
}
|
||||
|
||||
function unescapeText(text) {
|
||||
return text.replace(escapedStartRegexp, startSymbol).
|
||||
replace(escapedEndRegexp, endSymbol);
|
||||
}
|
||||
|
||||
function parseStringifyInterceptor(value) {
|
||||
try {
|
||||
value = getValue(value);
|
||||
|
||||
+12
-9
@@ -27,7 +27,7 @@ function parseAbsoluteUrl(absoluteUrl, locationObj) {
|
||||
|
||||
locationObj.$$protocol = parsedUrl.protocol;
|
||||
locationObj.$$host = parsedUrl.hostname;
|
||||
locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
|
||||
locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
|
||||
}
|
||||
|
||||
|
||||
@@ -373,8 +373,9 @@ var locationPrototype = {
|
||||
* @return {string} url
|
||||
*/
|
||||
url: function(url) {
|
||||
if (isUndefined(url))
|
||||
if (isUndefined(url)) {
|
||||
return this.$$url;
|
||||
}
|
||||
|
||||
var match = PATH_MATCH.exec(url);
|
||||
if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
|
||||
@@ -613,8 +614,9 @@ forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], fun
|
||||
* @return {object} state
|
||||
*/
|
||||
Location.prototype.state = function(state) {
|
||||
if (!arguments.length)
|
||||
if (!arguments.length) {
|
||||
return this.$$state;
|
||||
}
|
||||
|
||||
if (Location !== LocationHtml5Url || !this.$$html5) {
|
||||
throw $locationMinErr('nostate', 'History API state support is available only ' +
|
||||
@@ -639,8 +641,9 @@ function locationGetter(property) {
|
||||
|
||||
function locationGetterSetter(property, preprocess) {
|
||||
return function(value) {
|
||||
if (isUndefined(value))
|
||||
if (isUndefined(value)) {
|
||||
return this[property];
|
||||
}
|
||||
|
||||
this[property] = preprocess(value);
|
||||
this.$$compose();
|
||||
@@ -788,8 +791,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 ''
|
||||
@@ -837,7 +840,7 @@ function $LocationProvider() {
|
||||
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
|
||||
// currently we open nice url link and redirect then
|
||||
|
||||
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2) return;
|
||||
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
|
||||
|
||||
var elm = jqLite(event.target);
|
||||
|
||||
@@ -871,7 +874,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -879,7 +882,7 @@ function $LocationProvider() {
|
||||
|
||||
|
||||
// rewrite hashbang url <> html5 url
|
||||
if ($location.absUrl() != initialUrl) {
|
||||
if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
|
||||
$browser.url($location.absUrl(), true);
|
||||
}
|
||||
|
||||
|
||||
+1271
-626
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user