Compare commits
192 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f276ad0d51 | |||
| 33f817b99c | |||
| 6a4124d0fb | |||
| 26d1b34321 | |||
| 7fba6b603f | |||
| c115b37c33 | |||
| d20ba95e28 | |||
| 7945e5010a | |||
| 21d148aedc | |||
| 4c8aeefb62 | |||
| d384834fde | |||
| f975d8d448 | |||
| 2602daf993 | |||
| 9f681c459a | |||
| d9448dcb9f | |||
| e9c718a465 | |||
| 4dc44f7205 | |||
| 499e1b2adf | |||
| 28c6c4dae2 | |||
| 791148a328 | |||
| 01b1845088 | |||
| 56c861c9e1 | |||
| 451e0f6175 | |||
| aee7f1c72f | |||
| 5fe28a0422 | |||
| f56586d9a9 | |||
| ec0baadcb6 | |||
| 9147d5c04a | |||
| cb801378c9 | |||
| 79604f4628 | |||
| bf6cb8ab0d | |||
| de74b3fd85 | |||
| c7a92d2a9a | |||
| da284fc354 | |||
| 90da3059ce | |||
| 651fd05eca | |||
| 85d7e09bef | |||
| 243a57648b | |||
| 158dec330c | |||
| a1e63c8d4a | |||
| 8cc9978d15 | |||
| 78a404da0e | |||
| f1bb8369b5 | |||
| 76b6941b4c | |||
| e72990dc37 | |||
| f338e96ccc | |||
| 0ad2b70862 | |||
| 5261a374a9 | |||
| 832eba5fc9 | |||
| 2b569fc0b2 | |||
| ee6aeb08bf | |||
| 8d43d8b8e7 | |||
| d08f5c6986 | |||
| 3e7fa19197 | |||
| 5d379db72b | |||
| 514639b585 | |||
| 9cd9956dcb | |||
| c7813e9ebf | |||
| ef91b04cdd | |||
| 1acd97e18f | |||
| 513199ee9f | |||
| 33f3c40e93 | |||
| 696cb95d5e | |||
| 457fd21a1a | |||
| 3c6dfbf67d | |||
| 3277b885c4 | |||
| 48a256d04b | |||
| 0579430799 | |||
| 39ac68dac1 | |||
| 87fb44a5d3 | |||
| 5c76b406f7 | |||
| f665968daf | |||
| 5d1291c29d | |||
| ce7f400011 | |||
| 0fc8516b0c | |||
| 28b2a9b583 | |||
| e8549372fc | |||
| 5a434eb74e | |||
| 318de4db66 | |||
| a6c79bf15d | |||
| c6a10a755e | |||
| 1d7bd5bf4f | |||
| 94572e89c2 | |||
| fee7bac392 | |||
| dbb3b0f561 | |||
| b8bfed6a52 | |||
| 7215a89e7d | |||
| b7cca56091 | |||
| 7338e433f8 | |||
| 119ed07d5a | |||
| 5f98ae8323 | |||
| 8a598b43bb | |||
| e5dff4cfbe | |||
| 6c7a9cdd5f | |||
| 3b34f762fe | |||
| fa167ba747 | |||
| a4e60cb697 | |||
| 81150ac77d | |||
| 7ecfa5deba | |||
| 567f9b0136 | |||
| 0c2d3988ab | |||
| efd448d7d3 | |||
| db281c133d | |||
| 6a336ba6a0 | |||
| 67a98112e4 | |||
| f1aea54a9b | |||
| 663788d8c1 | |||
| af0574ebaa | |||
| 4883e95797 | |||
| 9c7c494c3e | |||
| 321180af13 | |||
| e2898c9436 | |||
| c52d0957de | |||
| 7bdc6cb358 | |||
| 2c54a3c081 | |||
| 5078c76c5d | |||
| 30a7e3a144 | |||
| a6afa780b7 | |||
| 3faa01fb15 | |||
| 0f5bcb7356 | |||
| db1cf6d293 | |||
| 6253de3913 | |||
| 32feb2b45f | |||
| 5e37b2a7fd | |||
| 0749eb44e5 | |||
| c900b9c531 | |||
| 6a4597b47d | |||
| d7cb37032b | |||
| 76f47d5632 | |||
| 871bebf7dc | |||
| 7e112c1fc3 | |||
| 59aef48281 | |||
| 2ffda41ab0 | |||
| f70237a3e8 | |||
| 3c86212710 | |||
| 85ef70f428 | |||
| a7244fdcb0 | |||
| 565391d30a | |||
| a88473db8a | |||
| 68f528aa04 | |||
| 3671adbba6 | |||
| 1edb13f784 | |||
| 113a946a99 | |||
| 70caf84634 | |||
| 1b8590a7c5 | |||
| 9955bd05ed | |||
| 02a9543189 | |||
| eda7ef66f7 | |||
| 2d6c218327 | |||
| 7324804bf5 | |||
| 84c04b0b68 | |||
| 735be18344 | |||
| 3ea4477266 | |||
| 0b1b9112a3 | |||
| 489835dd0b | |||
| adbc2b10d2 | |||
| ea6c2473c1 | |||
| b43768a345 | |||
| e47957248f | |||
| 019900d7c2 | |||
| 88bb5518eb | |||
| 3940edced4 | |||
| 7c60e19eb8 | |||
| 632fa30fe3 | |||
| bf2a76d32f | |||
| 9421674dad | |||
| ece8266b01 | |||
| b8b5b885f7 | |||
| e4e30961ca | |||
| 1061c56fe1 | |||
| 7617c08da6 | |||
| a021a376fc | |||
| a7d69c9d42 | |||
| f0f6da304c | |||
| 9425015a69 | |||
| 7b592f9edd | |||
| c966876e57 | |||
| bfce0675e2 | |||
| 2e3c6404f2 | |||
| b04871b43f | |||
| 4ba8e3463a | |||
| 9881e77ccb | |||
| 17ba2a6e7c | |||
| 3bfeda3b2b | |||
| 4fed66da6c | |||
| 7de7059f95 | |||
| cff232a8a2 | |||
| 614ecb7aa6 | |||
| 77b1407e0d | |||
| c4e47e491f | |||
| 87ac4443b6 | |||
| 75f23f0b87 |
@@ -0,0 +1,27 @@
|
||||
***Note*: for support questions, please use one of these channels: https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#question. This repository's issues are reserved for feature requests and bug reports.**
|
||||
|
||||
**Do you want to request a *feature* or report a *bug*?**
|
||||
|
||||
|
||||
|
||||
**What is the current behavior?**
|
||||
|
||||
|
||||
|
||||
**If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://plnkr.co or similar (template: http://plnkr.co/edit/tpl:yBpEi4).**
|
||||
|
||||
|
||||
|
||||
**What is the expected behavior?**
|
||||
|
||||
|
||||
|
||||
**What is the motivation / use case for changing the behavior?**
|
||||
|
||||
|
||||
|
||||
**Which versions of Angular, and which browser / OS are affected by this issue? Did this work in previous versions of Angular? Please also test with the latest stable and snapshot (https://code.angularjs.org/snapshot/) versions.**
|
||||
|
||||
|
||||
|
||||
**Other information (e.g. stacktraces, related issues, suggestions how to fix)**
|
||||
@@ -0,0 +1,23 @@
|
||||
**What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)**
|
||||
|
||||
|
||||
|
||||
**What is the current behavior? (You can also link to an open issue here)**
|
||||
|
||||
|
||||
|
||||
**What is the new behavior (if this is a feature change)?**
|
||||
|
||||
|
||||
|
||||
**Does this PR introduce a breaking change?**
|
||||
|
||||
|
||||
|
||||
**Please check if the PR fulfills these requirements**
|
||||
- [ ] The commit message follows our guidelines: https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] Docs have been added / updated (for bug fixes / features)
|
||||
|
||||
**Other information**:
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
{
|
||||
"bitwise": true,
|
||||
"esversion": 6,
|
||||
"immed": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": true,
|
||||
"nonew": true,
|
||||
"trailing": true,
|
||||
"maxlen": 200,
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"expr": true,
|
||||
"globalstrict": true,
|
||||
"laxbreak": true,
|
||||
"loopfunc": true,
|
||||
"strict": "global",
|
||||
"sub": true,
|
||||
"undef": true,
|
||||
"indent": 2
|
||||
"indent": 2,
|
||||
|
||||
"globals": {
|
||||
"ArrayBuffer": false,
|
||||
"Uint8Array": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '4.2'
|
||||
- '4.4'
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
||||
@@ -1,3 +1,294 @@
|
||||
<a name="1.5.4"></a>
|
||||
# 1.5.4 graduated-sophistry (2016-04-14)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- do not use `noop()` as controller for multiple components
|
||||
([4c8aeefb](https://github.com/angular/angular.js/commit/4c8aeefb624de7436ad95f3cd525405e0c3f493e),
|
||||
[#14391](https://github.com/angular/angular.js/issues/14391), [#14402](https://github.com/angular/angular.js/issues/14402))
|
||||
- still trigger `$onChanges` even if the inner value already matches the new value
|
||||
([d9448dcb](https://github.com/angular/angular.js/commit/d9448dcb9f901ceb04deda1d5f3d5aac8442a718),
|
||||
[#14406](https://github.com/angular/angular.js/issues/14406))
|
||||
- handle boolean attributes in `@` bindings
|
||||
([499e1b2a](https://github.com/angular/angular.js/commit/499e1b2adf27f32d671123f8dceadb3df2ad84a9),
|
||||
[#14070](https://github.com/angular/angular.js/issues/14070))
|
||||
- don't throw if controller is named
|
||||
([e72990dc](https://github.com/angular/angular.js/commit/e72990dc3714c8b847185ddb64fd5fd00e5cceab))
|
||||
- ensure that `$onChanges` hook is called correctly
|
||||
([0ad2b708](https://github.com/angular/angular.js/commit/0ad2b70862d49ecc4355a16d767c0ca9358ecc3e),
|
||||
[#14355](https://github.com/angular/angular.js/issues/14355), [#14359](https://github.com/angular/angular.js/issues/14359))
|
||||
- **$injector:** ensure functions with overridden `toString()` are annotated properly
|
||||
([d384834f](https://github.com/angular/angular.js/commit/d384834fdee140a716298bd065f304f8fba4725e),
|
||||
[#14361](https://github.com/angular/angular.js/issues/14361))
|
||||
- **ngAnimate:**
|
||||
- remove event listeners only after all listeners have been called
|
||||
([79604f46](https://github.com/angular/angular.js/commit/79604f462899c118a99d610995083ff82d38aa35),
|
||||
[#14321](https://github.com/angular/angular.js/issues/14321))
|
||||
- fire callbacks when document is hidden
|
||||
([c7a92d2a](https://github.com/angular/angular.js/commit/c7a92d2a9a436dddd65de721c9837a93e915d939),
|
||||
[#14120](https://github.com/angular/angular.js/issues/14120))
|
||||
- fire callbacks in the correct order for certain skipped animations
|
||||
([90da3059](https://github.com/angular/angular.js/commit/90da3059cecfefaecf136b01cd87aee6775a8778))
|
||||
- **ngClass:** fix watching of an array expression containing an object
|
||||
([f975d8d4](https://github.com/angular/angular.js/commit/f975d8d4481e0b8cdba553f0e5ad9ec1688adae8),
|
||||
[#14405](https://github.com/angular/angular.js/issues/14405))
|
||||
- **ngMock:** fix collecting stack trace in `inject()` on IE10+, PhantomJS
|
||||
([e9c718a4](https://github.com/angular/angular.js/commit/e9c718a465d28b9f2691e3acab944f7c31aa9fb6),
|
||||
[#13591](https://github.com/angular/angular.js/issues/13591), [#13592](https://github.com/angular/angular.js/issues/13592), [#13593](https://github.com/angular/angular.js/issues/13593))
|
||||
- **ngOptions:** set select value when model matches disabled option
|
||||
([832eba5f](https://github.com/angular/angular.js/commit/832eba5fc952312e6b99127123e6e75bdf729006),
|
||||
[#12756](https://github.com/angular/angular.js/issues/12756))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:**
|
||||
- put custom annotations on DDO
|
||||
([f338e96c](https://github.com/angular/angular.js/commit/f338e96ccc739efc4b24022eae406c3d5451d422),
|
||||
[#14369](https://github.com/angular/angular.js/issues/14369), [#14279](https://github.com/angular/angular.js/issues/14279), [#14284](https://github.com/angular/angular.js/issues/14284))
|
||||
- add `isFirstChange()` method to onChanges object
|
||||
([8d43d8b8](https://github.com/angular/angular.js/commit/8d43d8b8e7aacf97ddb9aa48bff25db57249cdd5),
|
||||
[#14318](https://github.com/angular/angular.js/issues/14318), [#14323](https://github.com/angular/angular.js/issues/14323))
|
||||
- **$componentController:** provide isolated scope if none is passed (#14425)
|
||||
([33f817b9](https://github.com/angular/angular.js/commit/33f817b99cb20e566b381e7202235fe99b4a742a),
|
||||
[#14425](https://github.com/angular/angular.js/issues/14425))
|
||||
- **$http:**
|
||||
- support handling additional XHR events
|
||||
([01b18450](https://github.com/angular/angular.js/commit/01b18450882da9bb9c903d43c0daddbc03c2c35d) and
|
||||
[56c861c9](https://github.com/angular/angular.js/commit/56c861c9e114c45790865e5635eaae8d32eb649a),
|
||||
[#14367](https://github.com/angular/angular.js/issues/14367), [#11547](https://github.com/angular/angular.js/issues/11547), [#1934](https://github.com/angular/angular.js/issues/1934))
|
||||
- **$parse:** add the ability to define the identifier characters
|
||||
([3e7fa191](https://github.com/angular/angular.js/commit/3e7fa19197c54a764225ad27c0c0bf72263daa8d))
|
||||
- **ngAnimate:** let $animate.off() remove all listeners for an element
|
||||
([bf6cb8ab](https://github.com/angular/angular.js/commit/bf6cb8ab0d157083a1ed55743e3fffe728daa6f3))
|
||||
- **ngAria:** add support for aria-readonly based on ngReadonly
|
||||
([ec0baadc](https://github.com/angular/angular.js/commit/ec0baadcb68a4fa8da27d76b7e6a4e0840acd7fa),
|
||||
[#14140](https://github.com/angular/angular.js/issues/14140), [#14077](https://github.com/angular/angular.js/issues/14077))
|
||||
- **ngParseExt:** new ngParseExt module
|
||||
([d08f5c69](https://github.com/angular/angular.js/commit/d08f5c698624f6243685b16f2d458cb9a980ebde))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** use createMap() for directive bindings to allow fast `forEach`
|
||||
([c115b37c](https://github.com/angular/angular.js/commit/c115b37c336f3a5936187279057b29c76078caf2),
|
||||
[#12529](https://github.com/angular/angular.js/issues/12529))
|
||||
- **ngOptions:** use `documentFragment` to populate `select` options
|
||||
([6a4124d0](https://github.com/angular/angular.js/commit/6a4124d0fb17cd7fc0e8bf5a1ca4d785a1d11c1c),
|
||||
[#13607](https://github.com/angular/angular.js/issues/13607), [#13239](https://github.com/angular/angular.js/issues/13239), [#12076](https://github.com/angular/angular.js/issues/12076))
|
||||
|
||||
|
||||
|
||||
<a name="1.5.3"></a>
|
||||
# 1.5.3 diplohaplontic-meiosis (2016-03-25)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** workaround a GC bug in Chrome < 50
|
||||
([513199ee](https://github.com/angular/angular.js/commit/513199ee9f1c8eef1240983d6e52c824404adb98),
|
||||
[#14041](https://github.com/angular/angular.js/issues/14041), [#14286](https://github.com/angular/angular.js/issues/14286))
|
||||
- **$sniffer:** fix history sniffing in Chrome Packaged Apps
|
||||
([457fd21a](https://github.com/angular/angular.js/commit/457fd21a1a0c10c66245c32a73602f3a09038bda),
|
||||
[#11932](https://github.com/angular/angular.js/issues/11932), [#13945](https://github.com/angular/angular.js/issues/13945))
|
||||
- **formatNumber:** handle small numbers correctly when `gSize` !== `lgSize`
|
||||
([3277b885](https://github.com/angular/angular.js/commit/3277b885c4dec3edd51b8e8c3d1776057d6d4d1d),
|
||||
[#14289](https://github.com/angular/angular.js/issues/14289), [#14290](https://github.com/angular/angular.js/issues/14290))
|
||||
- **ngAnimate:** run structural animations with cancelled out class changes
|
||||
([c7813e9e](https://github.com/angular/angular.js/commit/c7813e9ebf793fe89380dcad54e8e002fafdd985),
|
||||
[#14249](https://github.com/angular/angular.js/issues/14249))
|
||||
- **ngMessages:** don't crash when nested messages are removed
|
||||
([ef91b04c](https://github.com/angular/angular.js/commit/ef91b04cdd794f308617bca7ebd0b1b747e4f7de),
|
||||
[#14183](https://github.com/angular/angular.js/issues/14183), [#14242](https://github.com/angular/angular.js/issues/14242))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** add more lifecycle hooks to directive controllers
|
||||
([9cd9956d](https://github.com/angular/angular.js/commit/9cd9956dcbc8382e8e8757a805398bd251bbc67e),
|
||||
[#14127](https://github.com/angular/angular.js/issues/14127), [#14030](https://github.com/angular/angular.js/issues/14030), [#14020](https://github.com/angular/angular.js/issues/14020), [#13991](https://github.com/angular/angular.js/issues/13991), [#14302](https://github.com/angular/angular.js/issues/14302))
|
||||
|
||||
|
||||
|
||||
<a name="1.5.2"></a>
|
||||
# 1.5.2 differential-recovery (2016-03-18)
|
||||
|
||||
This release reverts a breaking change that accidentally made it into the 1.5.1 release. See
|
||||
[fee7bac3](https://github.com/angular/angular.js/commit/fee7bac392db24b6006d6a57ba71526f3afa102c)
|
||||
for more info.
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **ngAnimate.$animate:** remove animation callbacks when the element is removed
|
||||
([ce7f4000](https://github.com/angular/angular.js/commit/ce7f400011e1e2e1b9316f18ce87b87b79d878b4))
|
||||
|
||||
|
||||
<a name="1.5.1"></a>
|
||||
# 1.5.1 equivocal-sophistication (2016-03-16)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **core:** only call `console.log` when `window.console` exists
|
||||
([ce138f3c](https://github.com/angular/angular.js/commit/ce138f3c552f8bf741721ab8d10994ed35a4b2f5),
|
||||
[#14006](https://github.com/angular/angular.js/issues/14006), [#14007](https://github.com/angular/angular.js/issues/14007), [#14047](https://github.com/angular/angular.js/issues/14047))
|
||||
- **$compile:** allow directives to have decorators
|
||||
([0728cc2f](https://github.com/angular/angular.js/commit/0728cc2f2bb04d5dbdfca41f3afacea16c75ee07))
|
||||
- **$resource:** fix parse errors on older Android WebViews
|
||||
([df8db7b4](https://github.com/angular/angular.js/commit/df8db7b446b5bae83afef457d706d2805e597f29),
|
||||
[#13989](https://github.com/angular/angular.js/issues/13989))
|
||||
- **$routeProvider:** properly handle optional eager path named groups
|
||||
([c0797c68](https://github.com/angular/angular.js/commit/c0797c68866c9ef8ff3c2f6985e6eb9374346151),
|
||||
[#14011](https://github.com/angular/angular.js/issues/14011))
|
||||
- **copy:** add support for copying `Blob` objects
|
||||
([e9d579b6](https://github.com/angular/angular.js/commit/e9d579b608c2be8fdcf0326d0679a76bb9ae5b6e),
|
||||
[#9669](https://github.com/angular/angular.js/issues/9669), [#14064](https://github.com/angular/angular.js/issues/14064))
|
||||
- **dateFilter:** correctly format BC years
|
||||
([e36205f5](https://github.com/angular/angular.js/commit/e36205f5af82b69362def7d2b6eeeb038f592311))
|
||||
- **formatNumber:** allow negative fraction size
|
||||
([e046c170](https://github.com/angular/angular.js/commit/e046c170bcf677f26e61af6470cb5fd2f751c969),
|
||||
[#13913](https://github.com/angular/angular.js/issues/13913))
|
||||
- **input:** re-validate when partially editing date-family inputs
|
||||
([e383804c](https://github.com/angular/angular.js/commit/e383804c4ab62278fbaf4fdfaa03caeacff77fc4),
|
||||
[#12207](https://github.com/angular/angular.js/issues/12207), [#13886](https://github.com/angular/angular.js/issues/13886))
|
||||
- **input\[date\]:** support years with more than 4 digits
|
||||
([d76951f1](https://github.com/angular/angular.js/commit/d76951f1747abd2da6e320d4ff9019f170d9793f),
|
||||
[#13735](https://github.com/angular/angular.js/issues/13735), [#13905](https://github.com/angular/angular.js/issues/13905))
|
||||
- **ngOptions:** always set the 'selected' attribute for selected options
|
||||
([9f5a1722](https://github.com/angular/angular.js/commit/9f5a172291ff6926dcd246f0972288916a4c9bf6),
|
||||
[#14115](https://github.com/angular/angular.js/issues/14115))
|
||||
- **ngRoute:** allow `ngView` to be included in an asynchronously loaded template
|
||||
([8237482d](https://github.com/angular/angular.js/commit/8237482d49e76e2c4994fe6207e3c9799ef04163),
|
||||
[#1213](https://github.com/angular/angular.js/issues/1213), [#6812](https://github.com/angular/angular.js/issues/6812), [#14088](https://github.com/angular/angular.js/issues/14088))
|
||||
- **ngMock:**
|
||||
- attach `$injector` to `$rootElement` and prevent memory leak due to attached data
|
||||
([75373dd4](https://github.com/angular/angular.js/commit/75373dd4bdae6c6035272942c69444c386f824cd),
|
||||
[#14022](https://github.com/angular/angular.js/issues/14022), [#14094](https://github.com/angular/angular.js/issues/14094), [#14098](https://github.com/angular/angular.js/issues/14098))
|
||||
- don't break if `$rootScope.$destroy()` is not a function
|
||||
([50ed8712](https://github.com/angular/angular.js/commit/50ed8712566d601c9fb76b71f7b534b5bc803a36),
|
||||
[#14106](https://github.com/angular/angular.js/issues/14106), [#14107](https://github.com/angular/angular.js/issues/14107))
|
||||
- **ngMockE2E:** pass `responseType` to `$delegate` when using `passThrough`
|
||||
([d16faf9f](https://github.com/angular/angular.js/commit/d16faf9f2b9bd2b85d95e71d902cec0269282f2c),
|
||||
[#5415](https://github.com/angular/angular.js/issues/5415), [#5783](https://github.com/angular/angular.js/issues/5783))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** add custom annotations to the controller
|
||||
([0c800930](https://github.com/angular/angular.js/commit/0c8009300b819c39c5e4892856724a731a8dcda6),
|
||||
[#14114](https://github.com/angular/angular.js/issues/14114))
|
||||
- **$controllerProvider:** add a `has()` method for checking the existence of a controller
|
||||
([bb9575db](https://github.com/angular/angular.js/commit/bb9575dbd3428176216355df7b2933d2a72783cd),
|
||||
[#13951](https://github.com/angular/angular.js/issues/13951), [#14109](https://github.com/angular/angular.js/issues/14109))
|
||||
- **dateFilter:** add support for STANDALONEMONTH in format (`LLLL`)
|
||||
([3e5b25b3](https://github.com/angular/angular.js/commit/3e5b25b33f278376def432698c704b1807fdb8c0),
|
||||
[#13999](https://github.com/angular/angular.js/issues/13999), [#14013](https://github.com/angular/angular.js/issues/14013))
|
||||
- **ngMock:** add `sharedInjector()` to `angular.mock.module`
|
||||
([a46ab60f](https://github.com/angular/angular.js/commit/a46ab60fd5bf94896f0761e858ef38b998eb0f80),
|
||||
[#14093](https://github.com/angular/angular.js/issues/14093), [#10238](https://github.com/angular/angular.js/issues/10238))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **ngRepeat:** avoid duplicate jqLite wrappers
|
||||
([632e15a3](https://github.com/angular/angular.js/commit/632e15a3afdcd30168700cec1367bd81966400d4))
|
||||
- **ngAnimate:**
|
||||
- avoid jqLite/jQuery for upward DOM traversal
|
||||
([35251bd4](https://github.com/angular/angular.js/commit/35251bd4ce23251b5e9a2860cf414726c194721e))
|
||||
- avoid `$.fn.data` overhead with jQuery
|
||||
([15915e60](https://github.com/angular/angular.js/commit/15915e606fdf5114592db1a0a5e3f12e639d7cdb))
|
||||
|
||||
|
||||
<a name="1.4.10"></a>
|
||||
# 1.4.10 benignant-oscillation (2016-03-16)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **core:** only call `console.log` when `window.console` exists
|
||||
([beb00e44](https://github.com/angular/angular.js/commit/beb00e44de947981dbe35d5cf7a116e10ea8dc67),
|
||||
[#14006](https://github.com/angular/angular.js/issues/14006), [#14007](https://github.com/angular/angular.js/issues/14007), [#14047](https://github.com/angular/angular.js/issues/14047))
|
||||
- **$animateCss:** cancel fallback timeout when animation ends normally
|
||||
([a60bbc12](https://github.com/angular/angular.js/commit/a60bbc12e8c5170e70d95f1b2c3e309b3b95cb84),
|
||||
[#13787](https://github.com/angular/angular.js/issues/13787))
|
||||
- **$compile:**
|
||||
- allow directives to have decorators
|
||||
([77cdc37c](https://github.com/angular/angular.js/commit/77cdc37c65491b551fcf01a18ab848a693c293d7))
|
||||
- properly denormalize templates when only one of the start/end symbols is different
|
||||
([2d44a681](https://github.com/angular/angular.js/commit/2d44a681eb912a81a8bc8e16a278c45dae91fa24),
|
||||
[#13848](https://github.com/angular/angular.js/issues/13848))
|
||||
- handle boolean attributes in `@` bindings
|
||||
([2ffbfb0a](https://github.com/angular/angular.js/commit/2ffbfb0ad0647d103ff339ee4b772b62d4823bf3),
|
||||
[#13767](https://github.com/angular/angular.js/issues/13767), [#13769](https://github.com/angular/angular.js/issues/13769))
|
||||
- **$parse:**
|
||||
- prevent assignment on constructor properties
|
||||
([f47e2180](https://github.com/angular/angular.js/commit/f47e218006029f39b4785d820b430de3a0eebcb0),
|
||||
[#13417](https://github.com/angular/angular.js/issues/13417))
|
||||
- preserve expensive checks when runnning `$eval` inside an expression
|
||||
([96d62cc0](https://github.com/angular/angular.js/commit/96d62cc0fc77248d7e3ec4aa458bac0d3e072629))
|
||||
- copy `inputs` for expressions with expensive checks
|
||||
([0b7fff30](https://github.com/angular/angular.js/commit/0b7fff303f46202bbae1ff3ca9d0e5fa76e0fc9a))
|
||||
- **$rootScope:** set no context when calling helper functions for `$watch`
|
||||
([ab5c7698](https://github.com/angular/angular.js/commit/ab5c7698bb106669ca31b5f79a95afa54d65c5f1))
|
||||
- **$route:** allow preventing a route reload
|
||||
([4bc30314](https://github.com/angular/angular.js/commit/4bc3031497447ad527356f12bd0ceee1d7d09db5),
|
||||
[#9824](https://github.com/angular/angular.js/issues/9824), [#13894](https://github.com/angular/angular.js/issues/13894))
|
||||
- **$routeProvider:** properly handle optional eager path named groups
|
||||
([6a4403a1](https://github.com/angular/angular.js/commit/6a4403a11845173d6a96232f77d73aa544b182af),
|
||||
[#14011](https://github.com/angular/angular.js/issues/14011))
|
||||
- **copy:** add support for copying `Blob` objects
|
||||
([863a4232](https://github.com/angular/angular.js/commit/863a4232a6faa92428df45cd54d5a519be2434de),
|
||||
[#9669](https://github.com/angular/angular.js/issues/9669), [#14064](https://github.com/angular/angular.js/issues/14064))
|
||||
- **dateFilter:** follow the CLDR on pattern escape sequences
|
||||
([f476060d](https://github.com/angular/angular.js/commit/f476060de6cc016380c0343490a184543f853652),
|
||||
[#12839](https://github.com/angular/angular.js/issues/12839))
|
||||
- **dateFilter, input:** fix Date parsing in IE/Edge when timezone offset contains `:`
|
||||
([571afd65](https://github.com/angular/angular.js/commit/571afd6558786d7b99e2aebd307b4a94c9f2bb87),
|
||||
[#13880](https://github.com/angular/angular.js/issues/13880), [#13887](https://github.com/angular/angular.js/issues/13887))
|
||||
- **input:** re-validate when partially editing date-family inputs
|
||||
([02929f82](https://github.com/angular/angular.js/commit/02929f82f30449301ff18fea84a6396a017683b1),
|
||||
[#12207](https://github.com/angular/angular.js/issues/12207), [#13886](https://github.com/angular/angular.js/issues/13886))
|
||||
- **select:** handle corner case of adding options via a custom directive
|
||||
([df6e7315](https://github.com/angular/angular.js/commit/df6e731506831a3dc7f44c9a90abe17515450b3e),
|
||||
[#13874](https://github.com/angular/angular.js/issues/13874), [#13878](https://github.com/angular/angular.js/issues/13878))
|
||||
- **ngOptions:** always set the 'selected' attribute for selected options
|
||||
([f87e8288](https://github.com/angular/angular.js/commit/f87e8288fb69526fd240a66a046f5de52ed204de),
|
||||
[#14115](https://github.com/angular/angular.js/issues/14115))
|
||||
- **ngAnimate:** properly cancel previously running class-based animations
|
||||
([3b27dd37](https://github.com/angular/angular.js/commit/3b27dd37a2cc8a52992784ece6b371023dadf792),
|
||||
[#10156](https://github.com/angular/angular.js/issues/10156), [#13822](https://github.com/angular/angular.js/issues/13822))
|
||||
- **ngAnimateChildren:** make it compatible with `ngIf`
|
||||
([dc158e7e](https://github.com/angular/angular.js/commit/dc158e7e40624ef94c66560386522ef7e991a9ce),
|
||||
[#13865](https://github.com/angular/angular.js/issues/13865), [#13876](https://github.com/angular/angular.js/issues/13876))
|
||||
- **ngMockE2E:** pass `responseType` to `$delegate` when using `passThrough`
|
||||
([947cb4d1](https://github.com/angular/angular.js/commit/947cb4d1451afa4f5090a693df5b1968dd0df70c),
|
||||
[#5415](https://github.com/angular/angular.js/issues/5415), [#5783](https://github.com/angular/angular.js/issues/5783))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$locale:** Include original locale ID in $locale
|
||||
([e69f3550](https://github.com/angular/angular.js/commit/e69f35507e10c994708ce4f1efba7573951d1acd),
|
||||
[#13390](https://github.com/angular/angular.js/issues/13390))
|
||||
- **ngAnimate:** provide ng-[event]-prepare class for structural animations
|
||||
([796f7ab4](https://github.com/angular/angular.js/commit/796f7ab41487e124b5b0c02dbf0a03bd581bf073))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** avoid needless overhead when wrapping text nodes
|
||||
([946d9ae9](https://github.com/angular/angular.js/commit/946d9ae90bb31fe911ebbe1b80cd4c8af5a665c6))
|
||||
- **ngRepeat:** avoid duplicate jqLite wrappers
|
||||
([d04c38c4](https://github.com/angular/angular.js/commit/d04c38c48968db777c3ea6a177ce2ff0116df7b4))
|
||||
- **ngAnimate:**
|
||||
- avoid jqLite/jQuery for upward DOM traversal
|
||||
([ab95ba65](https://github.com/angular/angular.js/commit/ab95ba65c08b38cace83de6717b7681079182b45))
|
||||
- avoid `$.fn.data` overhead with jQuery
|
||||
([86416bcb](https://github.com/angular/angular.js/commit/86416bcbee2192fa31c017163c5d856763182ade))
|
||||
|
||||
|
||||
<a name="1.5.0"></a>
|
||||
# 1.5.0 ennoblement-facilitation (2016-02-05)
|
||||
|
||||
@@ -33,6 +324,9 @@
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### Upgrade to 1.5.1
|
||||
This version of AngularJS is problematic due to a issue during its release. Please upgrade to version [1.5.2](#1.5.2).
|
||||
|
||||
- **ngAria:** due to [d06431e5](https://github.com/angular/angular.js/commit/d06431e5309bb0125588877451dc79b935808134),
|
||||
Where appropriate, ngAria now applies ARIA to custom controls only, not native inputs. Because of this, support for `aria-multiline` on textareas has been removed.
|
||||
|
||||
@@ -1519,6 +1813,43 @@ describe('$q.when', function() {
|
||||
});
|
||||
```
|
||||
|
||||
- **form:** Due to [94533e57](https://github.com/angular/angular.js/commit/94533e570673e6b2eb92073955541fa289aabe02),
|
||||
the `name` attribute of `form` elements can now only contain characters that can be evaluated as part
|
||||
of an Angular expression. This is because Angular uses the value of `name` as an assignable expression
|
||||
to set the form on the `$scope`. For example, `name="myForm"` assigns the form to `$scope.myForm` and
|
||||
`name="myObj.myForm"` assigns it to `$scope.myObj.myForm`.
|
||||
|
||||
Previously, it was possible to also use names such `name="my:name"`, because Angular used a special setter
|
||||
function for the form name. Now the general, more robust `$parse` setter is used.
|
||||
|
||||
The easiest way to migrate your code is therefore to remove all special characters from the `name` attribute.
|
||||
|
||||
If you need to keep the special characters, you can use the following directive, which will replace
|
||||
the `name` with a value that can be evaluated as an expression in the compile function, and then
|
||||
re-set the original name in the postLink function. This ensures that (1), the form is published on
|
||||
the scope, and (2), the form has the original name, which might be important if you are doing server-side
|
||||
form submission.
|
||||
|
||||
```js
|
||||
angular.module('myApp').directive('form', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
priority: 1000,
|
||||
compile: function(element, attrs) {
|
||||
var unsupportedCharacter = ':'; // change accordingly
|
||||
var originalName = attrs.name;
|
||||
if (attrs.name && attrs.name.indexOf(unsupportedCharacter) > 0) {
|
||||
attrs.$set('name', 'this["' + originalName + '"]');
|
||||
}
|
||||
|
||||
return postLinkFunction(scope, element) {
|
||||
// Don't trigger $observers
|
||||
element.setAttribute('name', originalName);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
<a name="1.4.3"></a>
|
||||
# 1.4.3 foam-acceleration (2015-07-15)
|
||||
|
||||
@@ -135,6 +135,9 @@ module.exports = function(grunt) {
|
||||
ngMock: {
|
||||
files: { src: 'src/ngMock/**/*.js' },
|
||||
},
|
||||
ngParseExt: {
|
||||
files: { src: 'src/ngParseExt/**/*.js' },
|
||||
},
|
||||
ngResource: {
|
||||
files: { src: 'src/ngResource/**/*.js' },
|
||||
},
|
||||
@@ -231,7 +234,11 @@ module.exports = function(grunt) {
|
||||
dest: 'build/angular-aria.js',
|
||||
src: util.wrap(files['angularModules']['ngAria'], 'module')
|
||||
},
|
||||
'promises-aplus-adapter': {
|
||||
parseext: {
|
||||
dest: 'build/angular-parse-ext.js',
|
||||
src: util.wrap(files['angularModules']['ngParseExt'], 'module')
|
||||
},
|
||||
"promises-aplus-adapter": {
|
||||
dest:'tmp/promises-aplus-adapter++.js',
|
||||
src:['src/ng/q.js', 'lib/promises-aplus/promises-aplus-test-adapter.js']
|
||||
}
|
||||
@@ -249,7 +256,8 @@ module.exports = function(grunt) {
|
||||
resource: 'build/angular-resource.js',
|
||||
route: 'build/angular-route.js',
|
||||
sanitize: 'build/angular-sanitize.js',
|
||||
aria: 'build/angular-aria.js'
|
||||
aria: 'build/angular-aria.js',
|
||||
parseext: 'build/angular-parse-ext.js'
|
||||
},
|
||||
|
||||
|
||||
@@ -264,12 +272,17 @@ module.exports = function(grunt) {
|
||||
],
|
||||
options: {
|
||||
disallowed: [
|
||||
'fit',
|
||||
'iit',
|
||||
'xit',
|
||||
'fthey',
|
||||
'tthey',
|
||||
'xthey',
|
||||
'fdescribe',
|
||||
'ddescribe',
|
||||
'xdescribe'
|
||||
'xdescribe',
|
||||
'it.only',
|
||||
'describe.only'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,25 +8,26 @@ synchronizes data from your UI (view) with your JavaScript objects (model) throu
|
||||
binding. To help you structure your application better and make it easy to test, AngularJS teaches
|
||||
the browser how to do dependency injection and inversion of control.
|
||||
|
||||
Oh yeah and it helps with server-side communication, taming async callbacks with promises and
|
||||
deferreds. It also makes client-side navigation and deeplinking with hashbang urls or HTML5 pushState a
|
||||
piece of cake. Best of all?? It makes development fun!
|
||||
It also helps with server-side communication, taming async callbacks with promises and deferreds,
|
||||
and it makes client-side navigation and deeplinking with hashbang urls or HTML5 pushState a
|
||||
piece of cake. Best of all? It makes development fun!
|
||||
|
||||
* Web site: http://angularjs.org
|
||||
* Tutorial: http://docs.angularjs.org/tutorial
|
||||
* API Docs: http://docs.angularjs.org/api
|
||||
* Developer Guide: http://docs.angularjs.org/guide
|
||||
* Web site: https://angularjs.org
|
||||
* Tutorial: https://docs.angularjs.org/tutorial
|
||||
* API Docs: https://docs.angularjs.org/api
|
||||
* Developer Guide: https://docs.angularjs.org/guide
|
||||
* Contribution guidelines: [CONTRIBUTING.md](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md)
|
||||
* Dashboard: http://dashboard.angularjs.org
|
||||
* Dashboard: https://dashboard.angularjs.org
|
||||
|
||||
|
||||
Building AngularJS
|
||||
---------
|
||||
[Once you have your environment set up](http://docs.angularjs.org/misc/contribute) just run:
|
||||
[Once you have set up your environment](https://docs.angularjs.org/misc/contribute), just run:
|
||||
|
||||
grunt package
|
||||
|
||||
|
||||
Running Tests
|
||||
Running tests
|
||||
-------------
|
||||
To execute all unit tests, use:
|
||||
|
||||
@@ -37,9 +38,36 @@ To execute end-to-end (e2e) tests, use:
|
||||
grunt package
|
||||
grunt test:e2e
|
||||
|
||||
To learn more about the grunt tasks, run `grunt --help` and also read our
|
||||
[contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).
|
||||
To learn more about the grunt tasks, run `grunt --help`
|
||||
|
||||
Contribute & Develop
|
||||
--------------------
|
||||
|
||||
We've set up a separate document for our [contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).
|
||||
|
||||
|
||||
[](https://github.com/igrigorik/ga-beacon)
|
||||
|
||||
What to use AngularJS for and when to use it
|
||||
---------
|
||||
AngularJS is the next generation framework where each component is designed to work with every other component in an interconnected way like a well-oiled machine. AngularJS is JavaScript MVC made easy and done right. (Well it is not really MVC, read on, to understand what this means.)
|
||||
|
||||
#### MVC, no, MV* done the right way!
|
||||
MVC, short for Model-View-Controller, is a design pattern, i.e. how the code should be organized and how the different parts of an application separated for proper readability and debugging. Model is the data and the database. View is the user interface and what the user sees. Controller is the main link between Model and View. These are the three pillars of major programming frameworks present on the market today. On the other hand AngularJS works on MV*, short for Model-View-_Whatever_. The _Whatever_ is AngularJS's way of telling that you may create any kind of linking between the Model and the View here.
|
||||
|
||||
Unlike other frameworks in any programming language, where MVC, the three separate components, each one has to be written and then connected by the programmer, AngularJS helps the programmer by asking him/her to just create these and everything else will be taken care of by AngularJS.
|
||||
|
||||
#### Interconnection with HTML at the root level
|
||||
AngularJS uses HTML to define the user's interface. AngularJS also enables the programmer to write new HTML tags (AngularJS Directives) and increase the readability and understandability of the HTML code. Directives are AngularJS’s way of bringing additional functionality to HTML. Directives achieve this by enabling us to invent our own HTML elements. This also helps in making the code DRY (Don't Repeat Yourself), which means once created, a new directive can be used anywhere within the application.
|
||||
|
||||
#### Data Handling made simple
|
||||
Data and Data Models in AngularJS are plain JavaScript objects and one can add and change properties directly on it and loop over objects and arrays at will.
|
||||
|
||||
#### Two-way Data Binding
|
||||
One of AngularJS's strongest features. Two-way Data Binding means that if something changes in the Model, the change gets reflected in the View instantaneously, and the same happens the other way around. This is also referred to as Reactive Programming, i.e. suppose `a = b + c` is being programmed and after this, if the value of `b` and/or `c` is changed then the value of `a` will be automatically updated to reflect the change. AngularJS uses its "scopes" as a glue between the Model and View and makes these updates in one available for the other.
|
||||
|
||||
#### Less Written Code and Easily Maintainable Code
|
||||
Everything in AngularJS is created to enable the programmer to end up writing less code that is easily maintainable and readable by any other new person on the team. Believe it or not, one can write a complete working two-way data binded application in less than 10 lines of code. Try and see for yourself!
|
||||
|
||||
#### Testing Ready
|
||||
AngularJS has Dependency Injection, i.e. it takes care of providing all the necessary dependencies to its controllers whenever required. This helps in making the AngularJS code ready for unit testing by making use of mock dependencies created and injected. This makes AngularJS more modular and easily testable thus in turn helping a team create more robust applications.
|
||||
|
||||
@@ -120,6 +120,10 @@ var angularFiles = {
|
||||
'ngMessages': [
|
||||
'src/ngMessages/messages.js'
|
||||
],
|
||||
'ngParseExt': [
|
||||
'src/ngParseExt/ucd.js',
|
||||
'src/ngParseExt/module.js'
|
||||
],
|
||||
'ngResource': [
|
||||
'src/ngResource/resource.js'
|
||||
],
|
||||
@@ -205,6 +209,7 @@ var angularFiles = {
|
||||
"karmaModules": [
|
||||
'build/angular.js',
|
||||
'@angularSrcModules',
|
||||
'test/modules/no_bootstrap.js',
|
||||
'src/ngScenario/browserTrigger.js',
|
||||
'test/helpers/*.js',
|
||||
'test/ngMessageFormat/*.js',
|
||||
|
||||
@@ -1,442 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var directive = {};
|
||||
|
||||
directive.runnableExample = ['$templateCache', '$document', function($templateCache, $document) {
|
||||
var exampleClassNameSelector = '.runnable-example-file';
|
||||
var doc = $document[0];
|
||||
var tpl =
|
||||
'<nav class="runnable-example-tabs" ng-if="tabs">' +
|
||||
' <a ng-class="{active:$index==activeTabIndex}"' +
|
||||
'ng-repeat="tab in tabs track by $index" ' +
|
||||
'href="" ' +
|
||||
'class="btn"' +
|
||||
'ng-click="setTab($index)">' +
|
||||
' {{ tab }}' +
|
||||
' </a>' +
|
||||
'</nav>';
|
||||
|
||||
return {
|
||||
restrict: 'C',
|
||||
scope : true,
|
||||
controller : ['$scope', function($scope) {
|
||||
$scope.setTab = function(index) {
|
||||
var tab = $scope.tabs[index];
|
||||
$scope.activeTabIndex = index;
|
||||
$scope.$broadcast('tabChange', index, tab);
|
||||
};
|
||||
}],
|
||||
compile : function(element) {
|
||||
element.html(tpl + element.html());
|
||||
return function(scope, element) {
|
||||
var node = element[0];
|
||||
var examples = node.querySelectorAll(exampleClassNameSelector);
|
||||
var tabs = [], now = Date.now();
|
||||
angular.forEach(examples, function(child, index) {
|
||||
tabs.push(child.getAttribute('name'));
|
||||
});
|
||||
|
||||
if(tabs.length > 0) {
|
||||
scope.tabs = tabs;
|
||||
scope.$on('tabChange', function(e, index, title) {
|
||||
angular.forEach(examples, function(child) {
|
||||
child.style.display = 'none';
|
||||
});
|
||||
var selected = examples[index];
|
||||
selected.style.display = 'block';
|
||||
});
|
||||
scope.setTab(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
directive.dropdownToggle =
|
||||
['$document', '$location', '$window',
|
||||
function ($document, $location, $window) {
|
||||
var openElement = null, close;
|
||||
return {
|
||||
restrict: 'C',
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() {
|
||||
close && close();
|
||||
});
|
||||
|
||||
element.parent().on('click', function(event) {
|
||||
close && close();
|
||||
});
|
||||
|
||||
element.on('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
var iWasOpen = false;
|
||||
|
||||
if (openElement) {
|
||||
iWasOpen = openElement === element;
|
||||
close();
|
||||
}
|
||||
|
||||
if (!iWasOpen){
|
||||
element.parent().addClass('open');
|
||||
openElement = element;
|
||||
|
||||
close = function (event) {
|
||||
event && event.preventDefault();
|
||||
event && event.stopPropagation();
|
||||
$document.off('click', close);
|
||||
element.parent().removeClass('open');
|
||||
close = null;
|
||||
openElement = null;
|
||||
}
|
||||
|
||||
$document.on('click', close);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
directive.syntax = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
function makeLink(type, text, link, icon) {
|
||||
return '<a href="' + link + '" class="btn syntax-' + type + '" target="_blank" rel="nofollow">' +
|
||||
'<span class="' + icon + '"></span> ' + text +
|
||||
'</a>';
|
||||
};
|
||||
|
||||
var html = '';
|
||||
var types = {
|
||||
'github' : {
|
||||
text : 'View on Github',
|
||||
key : 'syntaxGithub',
|
||||
icon : 'icon-github'
|
||||
},
|
||||
'plunkr' : {
|
||||
text : 'View on Plunkr',
|
||||
key : 'syntaxPlunkr',
|
||||
icon : 'icon-arrow-down'
|
||||
},
|
||||
'jsfiddle' : {
|
||||
text : 'View on JSFiddle',
|
||||
key : 'syntaxFiddle',
|
||||
icon : 'icon-cloud'
|
||||
}
|
||||
};
|
||||
for(var type in types) {
|
||||
var data = types[type];
|
||||
var link = attrs[data.key];
|
||||
if(link) {
|
||||
html += makeLink(type, data.text, link, data.icon);
|
||||
}
|
||||
};
|
||||
|
||||
var nav = document.createElement('nav');
|
||||
nav.className = 'syntax-links';
|
||||
nav.innerHTML = html;
|
||||
|
||||
var node = element[0];
|
||||
var par = node.parentNode;
|
||||
par.insertBefore(nav, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
directive.tabbable = function() {
|
||||
return {
|
||||
restrict: 'C',
|
||||
compile: function(element) {
|
||||
var navTabs = angular.element('<ul class="nav nav-tabs"></ul>'),
|
||||
tabContent = angular.element('<div class="tab-content"></div>');
|
||||
|
||||
tabContent.append(element.contents());
|
||||
element.append(navTabs).append(tabContent);
|
||||
},
|
||||
controller: ['$scope', '$element', function($scope, $element) {
|
||||
var navTabs = $element.contents().eq(0),
|
||||
ngModel = $element.controller('ngModel') || {},
|
||||
tabs = [],
|
||||
selectedTab;
|
||||
|
||||
ngModel.$render = function() {
|
||||
var $viewValue = this.$viewValue;
|
||||
|
||||
if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
|
||||
if(selectedTab) {
|
||||
selectedTab.paneElement.removeClass('active');
|
||||
selectedTab.tabElement.removeClass('active');
|
||||
selectedTab = null;
|
||||
}
|
||||
if($viewValue) {
|
||||
for(var i = 0, ii = tabs.length; i < ii; i++) {
|
||||
if ($viewValue == tabs[i].value) {
|
||||
selectedTab = tabs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (selectedTab) {
|
||||
selectedTab.paneElement.addClass('active');
|
||||
selectedTab.tabElement.addClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
this.addPane = function(element, attr) {
|
||||
var li = angular.element('<li><a href></a></li>'),
|
||||
a = li.find('a'),
|
||||
tab = {
|
||||
paneElement: element,
|
||||
paneAttrs: attr,
|
||||
tabElement: li
|
||||
};
|
||||
|
||||
tabs.push(tab);
|
||||
|
||||
attr.$observe('value', update)();
|
||||
attr.$observe('title', function(){ update(); a.text(tab.title); })();
|
||||
|
||||
function update() {
|
||||
tab.title = attr.title;
|
||||
tab.value = attr.value || attr.title;
|
||||
if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
|
||||
// we are not part of angular
|
||||
ngModel.$viewValue = tab.value;
|
||||
}
|
||||
ngModel.$render();
|
||||
}
|
||||
|
||||
navTabs.append(li);
|
||||
li.on('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (ngModel.$setViewValue) {
|
||||
$scope.$apply(function() {
|
||||
ngModel.$setViewValue(tab.value);
|
||||
ngModel.$render();
|
||||
});
|
||||
} else {
|
||||
// we are not part of angular
|
||||
ngModel.$viewValue = tab.value;
|
||||
ngModel.$render();
|
||||
}
|
||||
});
|
||||
|
||||
return function() {
|
||||
tab.tabElement.remove();
|
||||
for(var i = 0, ii = tabs.length; i < ii; i++ ) {
|
||||
if (tab == tabs[i]) {
|
||||
tabs.splice(i, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}]
|
||||
};
|
||||
};
|
||||
|
||||
directive.table = function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, element, attrs) {
|
||||
if (!attrs['class']) {
|
||||
element.addClass('table table-bordered table-striped code-table');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var popoverElement = function() {
|
||||
var object = {
|
||||
init : function() {
|
||||
this.element = angular.element(
|
||||
'<div class="popover popover-incode top">' +
|
||||
'<div class="arrow"></div>' +
|
||||
'<div class="popover-inner">' +
|
||||
'<div class="popover-title"><code></code></div>' +
|
||||
'<div class="popover-content"></div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
this.node = this.element[0];
|
||||
this.element.css({
|
||||
'display':'block',
|
||||
'position':'absolute'
|
||||
});
|
||||
angular.element(document.body).append(this.element);
|
||||
|
||||
var inner = this.element.children()[1];
|
||||
this.titleElement = angular.element(inner.childNodes[0].firstChild);
|
||||
this.contentElement = angular.element(inner.childNodes[1]);
|
||||
|
||||
//stop the click on the tooltip
|
||||
this.element.on('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
var self = this;
|
||||
angular.element(document.body).on('click',function(event) {
|
||||
if(self.visible()) self.hide();
|
||||
});
|
||||
},
|
||||
|
||||
show : function(x,y) {
|
||||
this.element.addClass('visible');
|
||||
this.position(x || 0, y || 0);
|
||||
},
|
||||
|
||||
hide : function() {
|
||||
this.element.removeClass('visible');
|
||||
this.position(-9999,-9999);
|
||||
},
|
||||
|
||||
visible : function() {
|
||||
return this.position().y >= 0;
|
||||
},
|
||||
|
||||
isSituatedAt : function(element) {
|
||||
return this.besideElement ? element[0] == this.besideElement[0] : false;
|
||||
},
|
||||
|
||||
title : function(value) {
|
||||
return this.titleElement.html(value);
|
||||
},
|
||||
|
||||
content : function(value) {
|
||||
if(value && value.length > 0) {
|
||||
value = marked(value);
|
||||
}
|
||||
return this.contentElement.html(value);
|
||||
},
|
||||
|
||||
positionArrow : function(position) {
|
||||
this.node.className = 'popover ' + position;
|
||||
},
|
||||
|
||||
positionAway : function() {
|
||||
this.besideElement = null;
|
||||
this.hide();
|
||||
},
|
||||
|
||||
positionBeside : function(element) {
|
||||
this.besideElement = element;
|
||||
|
||||
var elm = element[0];
|
||||
var x = elm.offsetLeft;
|
||||
var y = elm.offsetTop;
|
||||
x -= 30;
|
||||
y -= this.node.offsetHeight + 10;
|
||||
this.show(x,y);
|
||||
},
|
||||
|
||||
position : function(x,y) {
|
||||
if(x != null && y != null) {
|
||||
this.element.css('left',x + 'px');
|
||||
this.element.css('top', y + 'px');
|
||||
}
|
||||
else {
|
||||
return {
|
||||
x : this.node.offsetLeft,
|
||||
y : this.node.offsetTop
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
object.init();
|
||||
object.hide();
|
||||
|
||||
return object;
|
||||
};
|
||||
|
||||
directive.popover = ['popoverElement', function(popover) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority : 500,
|
||||
link: function(scope, element, attrs) {
|
||||
element.on('click',function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if(popover.isSituatedAt(element) && popover.visible()) {
|
||||
popover.title('');
|
||||
popover.content('');
|
||||
popover.positionAway();
|
||||
}
|
||||
else {
|
||||
popover.title(attrs.title);
|
||||
popover.content(attrs.content);
|
||||
popover.positionBeside(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
directive.tabPane = function() {
|
||||
return {
|
||||
require: '^tabbable',
|
||||
restrict: 'C',
|
||||
link: function(scope, element, attrs, tabsCtrl) {
|
||||
element.on('$remove', tabsCtrl.addPane(element, attrs));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
directive.foldout = ['$http', '$animate','$window', function($http, $animate, $window) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority : 500,
|
||||
link: function(scope, element, attrs) {
|
||||
var container, loading, url = attrs.url;
|
||||
if(/\/build\//.test($window.location.href)) {
|
||||
url = '/build/docs' + url;
|
||||
}
|
||||
element.on('click',function() {
|
||||
scope.$apply(function() {
|
||||
if(!container) {
|
||||
if(loading) return;
|
||||
|
||||
loading = true;
|
||||
var par = element.parent();
|
||||
container = angular.element('<div class="foldout">loading...</div>');
|
||||
$animate.enter(container, null, par);
|
||||
|
||||
$http.get(url, { cache : true }).success(function(html) {
|
||||
loading = false;
|
||||
|
||||
html = '<div class="foldout-inner">' +
|
||||
'<div calss="foldout-arrow"></div>' +
|
||||
html +
|
||||
'</div>';
|
||||
container.html(html);
|
||||
|
||||
//avoid showing the element if the user has already closed it
|
||||
if(container.css('display') == 'block') {
|
||||
container.css('display','none');
|
||||
$animate.addClass(container, 'ng-hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
container.hasClass('ng-hide') ? $animate.removeClass(container, 'ng-hide') : $animate.addClass(container, 'ng-hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
angular.module('bootstrap', [])
|
||||
.directive(directive)
|
||||
.factory('popoverElement', popoverElement)
|
||||
.run(function() {
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
tables: true
|
||||
});
|
||||
});
|
||||
@@ -54,7 +54,9 @@ angular.module('ui.bootstrap.dropdown', [])
|
||||
}
|
||||
};
|
||||
|
||||
var closeDropdown = function() {
|
||||
var closeDropdown = function(evt) {
|
||||
if (evt && evt.which === 3) return;
|
||||
|
||||
openScope.$apply(function() {
|
||||
openScope.isOpen = false;
|
||||
});
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
"dump": false,
|
||||
|
||||
/* e2e */
|
||||
"protractor": false,
|
||||
"browser": false,
|
||||
"element": false,
|
||||
"by": false,
|
||||
"$": false,
|
||||
"$$": false,
|
||||
|
||||
/* testabilityPatch / matchers */
|
||||
"inject": false,
|
||||
@@ -39,4 +42,4 @@
|
||||
"browserTrigger": false,
|
||||
"jqLiteCacheSize": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ angular.module('docsApp', [
|
||||
'search',
|
||||
'tutorials',
|
||||
'versions',
|
||||
'bootstrap',
|
||||
'ui.bootstrap.dropdown'
|
||||
])
|
||||
|
||||
|
||||
@@ -34,4 +34,15 @@ angular.module('directives', [])
|
||||
return function(scope, element) {
|
||||
$anchorScroll.yOffset = element;
|
||||
};
|
||||
}]);
|
||||
}])
|
||||
|
||||
.directive('table', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, element, attrs) {
|
||||
if (!attrs['class']) {
|
||||
element.addClass('table table-bordered table-striped code-table');
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
angular.module('DocsController', [])
|
||||
|
||||
.controller('DocsController', [
|
||||
'$scope', '$rootScope', '$location', '$window', '$cookies', 'openPlunkr',
|
||||
'$scope', '$rootScope', '$location', '$window', '$cookies',
|
||||
'NG_PAGES', 'NG_NAVIGATION', 'NG_VERSION',
|
||||
function($scope, $rootScope, $location, $window, $cookies, openPlunkr,
|
||||
function($scope, $rootScope, $location, $window, $cookies,
|
||||
NG_PAGES, NG_NAVIGATION, NG_VERSION) {
|
||||
|
||||
$scope.openPlunkr = openPlunkr;
|
||||
|
||||
$scope.docsVersion = NG_VERSION.isSnapshot ? 'snapshot' : NG_VERSION.version;
|
||||
|
||||
$scope.navClass = function(navItem) {
|
||||
|
||||
@@ -13,10 +13,10 @@ angular.module('errors', ['ngSanitize'])
|
||||
};
|
||||
|
||||
return function (text, target) {
|
||||
var targetHtml = target ? ' target="' + target + '"' : '';
|
||||
|
||||
if (!text) return text;
|
||||
|
||||
var targetHtml = target ? ' target="' + target + '"' : '';
|
||||
|
||||
return $sanitize(text.replace(LINKY_URL_REGEXP, function (url) {
|
||||
if (STACK_TRACE_REGEXP.test(url)) {
|
||||
return url;
|
||||
@@ -34,6 +34,10 @@ angular.module('errors', ['ngSanitize'])
|
||||
|
||||
|
||||
.directive('errorDisplay', ['$location', 'errorLinkFilter', function ($location, errorLinkFilter) {
|
||||
var encodeAngleBrackets = function (text) {
|
||||
return text.replace(/</g, '<').replace(/>/g, '>');
|
||||
};
|
||||
|
||||
var interpolate = function (formatString) {
|
||||
var formatArgs = arguments;
|
||||
return formatString.replace(/\{\d+\}/g, function (match) {
|
||||
@@ -51,12 +55,15 @@ angular.module('errors', ['ngSanitize'])
|
||||
link: function (scope, element, attrs) {
|
||||
var search = $location.search(),
|
||||
formatArgs = [attrs.errorDisplay],
|
||||
formattedText,
|
||||
i;
|
||||
|
||||
for (i = 0; angular.isDefined(search['p'+i]); i++) {
|
||||
formatArgs.push(search['p'+i]);
|
||||
}
|
||||
element.html(errorLinkFilter(interpolate.apply(null, formatArgs), '_blank'));
|
||||
|
||||
formattedText = encodeAngleBrackets(interpolate.apply(null, formatArgs));
|
||||
element.html(errorLinkFilter(formattedText, '_blank'));
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -1,5 +1,55 @@
|
||||
angular.module('examples', [])
|
||||
|
||||
.directive('runnableExample', ['$templateCache', '$document', function($templateCache, $document) {
|
||||
var exampleClassNameSelector = '.runnable-example-file';
|
||||
var doc = $document[0];
|
||||
var tpl =
|
||||
'<nav class="runnable-example-tabs" ng-if="tabs">' +
|
||||
' <a ng-class="{active:$index==activeTabIndex}"' +
|
||||
'ng-repeat="tab in tabs track by $index" ' +
|
||||
'href="" ' +
|
||||
'class="btn"' +
|
||||
'ng-click="setTab($index)">' +
|
||||
' {{ tab }}' +
|
||||
' </a>' +
|
||||
'</nav>';
|
||||
|
||||
return {
|
||||
restrict: 'C',
|
||||
scope : true,
|
||||
controller : ['$scope', function($scope) {
|
||||
$scope.setTab = function(index) {
|
||||
var tab = $scope.tabs[index];
|
||||
$scope.activeTabIndex = index;
|
||||
$scope.$broadcast('tabChange', index, tab);
|
||||
};
|
||||
}],
|
||||
compile : function(element) {
|
||||
element.html(tpl + element.html());
|
||||
return function(scope, element) {
|
||||
var node = element[0];
|
||||
var examples = node.querySelectorAll(exampleClassNameSelector);
|
||||
var tabs = [], now = Date.now();
|
||||
angular.forEach(examples, function(child, index) {
|
||||
tabs.push(child.getAttribute('name'));
|
||||
});
|
||||
|
||||
if(tabs.length > 0) {
|
||||
scope.tabs = tabs;
|
||||
scope.$on('tabChange', function(e, index, title) {
|
||||
angular.forEach(examples, function(child) {
|
||||
child.style.display = 'none';
|
||||
});
|
||||
var selected = examples[index];
|
||||
selected.style.display = 'block';
|
||||
});
|
||||
scope.setTab(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('formPostData', ['$document', function($document) {
|
||||
return function(url, newWindow, fields) {
|
||||
/**
|
||||
@@ -22,15 +72,14 @@ angular.module('examples', [])
|
||||
};
|
||||
}])
|
||||
|
||||
|
||||
.factory('openPlunkr', ['formPostData', '$http', '$q', function(formPostData, $http, $q) {
|
||||
|
||||
.factory('createCopyrightNotice', function() {
|
||||
var COPYRIGHT = 'Copyright ' + (new Date()).getFullYear() + ' Google Inc. All Rights Reserved.\n'
|
||||
+ 'Use of this source code is governed by an MIT-style license that\n'
|
||||
+ 'can be found in the LICENSE file at http://angular.io/license';
|
||||
var COPYRIGHT_JS_CSS = '\n\n/*\n' + COPYRIGHT + '\n*/';
|
||||
var COPYRIGHT_HTML = '\n\n<!-- \n' + COPYRIGHT + '\n-->';
|
||||
function getCopyright(filename) {
|
||||
|
||||
return function getCopyright(filename) {
|
||||
switch (filename.substr(filename.lastIndexOf('.'))) {
|
||||
case '.html':
|
||||
return COPYRIGHT_HTML;
|
||||
@@ -41,29 +90,92 @@ angular.module('examples', [])
|
||||
return COPYRIGHT;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
return function(exampleFolder, clickEvent) {
|
||||
.directive('plnkrOpener', ['$q', 'getExampleData', 'formPostData', 'createCopyrightNotice', function($q, getExampleData, formPostData, createCopyrightNotice) {
|
||||
return {
|
||||
scope: {},
|
||||
bindToController: {
|
||||
'examplePath': '@'
|
||||
},
|
||||
controllerAs: 'plnkr',
|
||||
template: '<button ng-click="plnkr.open($event)" class="btn pull-right"> <i class="glyphicon glyphicon-edit"> </i> Edit in Plunker</button> ',
|
||||
controller: [function() {
|
||||
var ctrl = this;
|
||||
|
||||
var exampleName = 'AngularJS Example';
|
||||
var newWindow = clickEvent.ctrlKey || clickEvent.metaKey;
|
||||
ctrl.example = {
|
||||
path: ctrl.examplePath,
|
||||
manifest: undefined,
|
||||
files: undefined,
|
||||
name: 'AngularJS Example'
|
||||
};
|
||||
|
||||
ctrl.prepareExampleData = function() {
|
||||
if (ctrl.example.manifest) {
|
||||
return $q.when(ctrl.example);
|
||||
}
|
||||
|
||||
return getExampleData(ctrl.examplePath).then(function(data) {
|
||||
ctrl.example.files = data.files;
|
||||
ctrl.example.manifest = data.manifest;
|
||||
|
||||
// Build a pretty title for the Plunkr
|
||||
var exampleNameParts = data.manifest.name.split('-');
|
||||
exampleNameParts.unshift('AngularJS');
|
||||
angular.forEach(exampleNameParts, function(part, index) {
|
||||
exampleNameParts[index] = part.charAt(0).toUpperCase() + part.substr(1);
|
||||
});
|
||||
ctrl.example.name = exampleNameParts.join(' - ');
|
||||
|
||||
return ctrl.example;
|
||||
});
|
||||
};
|
||||
|
||||
ctrl.open = function(clickEvent) {
|
||||
|
||||
var newWindow = clickEvent.ctrlKey || clickEvent.metaKey;
|
||||
|
||||
var postData = {
|
||||
'tags[0]': "angularjs",
|
||||
'tags[1]': "example",
|
||||
'private': true
|
||||
};
|
||||
|
||||
// Make sure the example data is available.
|
||||
// If an XHR must be made, this might break some pop-up blockers when
|
||||
// new window is requested
|
||||
ctrl.prepareExampleData()
|
||||
.then(function() {
|
||||
angular.forEach(ctrl.example.files, function(file) {
|
||||
postData['files[' + file.name + ']'] = file.content + createCopyrightNotice(file.name);
|
||||
});
|
||||
|
||||
postData.description = ctrl.example.name;
|
||||
|
||||
formPostData('http://plnkr.co/edit/?p=preview', newWindow, postData);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// Initialize the example data, so it's ready when clicking the open button.
|
||||
// Otherwise pop-up blockers will prevent a new window from opening
|
||||
ctrl.prepareExampleData(ctrl.example.path);
|
||||
|
||||
}]
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('getExampleData', ['$http', '$q', function($http, $q) {
|
||||
return function(exampleFolder){
|
||||
// Load the manifest for the example
|
||||
$http.get(exampleFolder + '/manifest.json')
|
||||
return $http.get(exampleFolder + '/manifest.json')
|
||||
.then(function(response) {
|
||||
return response.data;
|
||||
})
|
||||
.then(function(manifest) {
|
||||
var filePromises = [];
|
||||
|
||||
// Build a pretty title for the Plunkr
|
||||
var exampleNameParts = manifest.name.split('-');
|
||||
exampleNameParts.unshift('AngularJS');
|
||||
angular.forEach(exampleNameParts, function(part, index) {
|
||||
exampleNameParts[index] = part.charAt(0).toUpperCase() + part.substr(1);
|
||||
});
|
||||
exampleName = exampleNameParts.join(' - ');
|
||||
|
||||
angular.forEach(manifest.files, function(filename) {
|
||||
filePromises.push($http.get(exampleFolder + '/' + filename, { transformResponse: [] })
|
||||
.then(function(response) {
|
||||
@@ -71,7 +183,7 @@ angular.module('examples', [])
|
||||
// The manifests provide the production index file but Plunkr wants
|
||||
// a straight index.html
|
||||
if (filename === "index-production.html") {
|
||||
filename = "index.html"
|
||||
filename = "index.html";
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -80,21 +192,11 @@ angular.module('examples', [])
|
||||
};
|
||||
}));
|
||||
});
|
||||
return $q.all(filePromises);
|
||||
})
|
||||
.then(function(files) {
|
||||
var postData = {};
|
||||
|
||||
angular.forEach(files, function(file) {
|
||||
postData['files[' + file.name + ']'] = file.content + getCopyright(file.name);
|
||||
return $q.all({
|
||||
manifest: manifest,
|
||||
files: $q.all(filePromises)
|
||||
});
|
||||
|
||||
postData['tags[0]'] = "angularjs";
|
||||
postData['tags[1]'] = "example";
|
||||
postData.private = true;
|
||||
postData.description = exampleName;
|
||||
|
||||
formPostData('http://plnkr.co/edit/?p=preview', newWindow, postData);
|
||||
});
|
||||
};
|
||||
}]);
|
||||
}]);
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"extends": "../../../.jshintrc-base",
|
||||
"browser": true,
|
||||
"globals": {
|
||||
// AngularJS
|
||||
"angular": false,
|
||||
|
||||
// ngMocks
|
||||
"module": false,
|
||||
"inject": true,
|
||||
|
||||
// Jasmine
|
||||
"jasmine": false,
|
||||
"describe": false,
|
||||
"ddescribe": false,
|
||||
"xdescribe": false,
|
||||
"it": false,
|
||||
"iit": false,
|
||||
"xit": false,
|
||||
"beforeEach": false,
|
||||
"afterEach": false,
|
||||
"spyOn": false,
|
||||
"expect": false
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ describe("DocsController", function() {
|
||||
|
||||
angular.module('fake', [])
|
||||
.value('$cookies', {})
|
||||
.value('openPlunkr', function() {})
|
||||
.value('NG_PAGES', {})
|
||||
.value('NG_NAVIGATION', {})
|
||||
.value('NG_VERSION', {});
|
||||
@@ -26,7 +25,7 @@ describe("DocsController", function() {
|
||||
|
||||
it("should update the Google Analytics with $location.path if currentPage is missing", inject(function($window, $location) {
|
||||
$window._gaq = [];
|
||||
spyOn($location, 'path').andReturn('x/y/z');
|
||||
spyOn($location, 'path').and.returnValue('x/y/z');
|
||||
$scope.$broadcast('$includeContentLoaded');
|
||||
expect($window._gaq.pop()).toEqual(['_trackPageview', 'x/y/z']);
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
'use strict';
|
||||
|
||||
describe('errors', function() {
|
||||
// Mock `ngSanitize` module
|
||||
angular.
|
||||
module('ngSanitize', []).
|
||||
value('$sanitize', jasmine.createSpy('$sanitize').and.callFake(angular.identity));
|
||||
|
||||
beforeEach(module('errors'));
|
||||
|
||||
|
||||
describe('errorDisplay', function() {
|
||||
var $sanitize;
|
||||
var errorLinkFilter;
|
||||
|
||||
beforeEach(inject(function(_$sanitize_, _errorLinkFilter_) {
|
||||
$sanitize = _$sanitize_;
|
||||
errorLinkFilter = _errorLinkFilter_;
|
||||
}));
|
||||
|
||||
|
||||
it('should return empty input unchanged', function() {
|
||||
var inputs = [undefined, null, false, 0, ''];
|
||||
var remaining = inputs.length;
|
||||
|
||||
inputs.forEach(function(falsyValue) {
|
||||
expect(errorLinkFilter(falsyValue)).toBe(falsyValue);
|
||||
remaining--;
|
||||
});
|
||||
|
||||
expect(remaining).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('should recognize URLs and convert them to `<a>`', function() {
|
||||
var urls = [
|
||||
['ftp://foo/bar?baz#qux'],
|
||||
['http://foo/bar?baz#qux'],
|
||||
['https://foo/bar?baz#qux'],
|
||||
['mailto:foo_bar@baz.qux', null, 'foo_bar@baz.qux'],
|
||||
['foo_bar@baz.qux', 'mailto:foo_bar@baz.qux', 'foo_bar@baz.qux']
|
||||
];
|
||||
var remaining = urls.length;
|
||||
|
||||
urls.forEach(function(values) {
|
||||
var actualUrl = values[0];
|
||||
var expectedUrl = values[1] || actualUrl;
|
||||
var expectedText = values[2] || expectedUrl;
|
||||
var anchor = '<a href="' + expectedUrl + '">' + expectedText + '</a>';
|
||||
|
||||
var input = 'start ' + actualUrl + ' end';
|
||||
var output = 'start ' + anchor + ' end';
|
||||
|
||||
expect(errorLinkFilter(input)).toBe(output);
|
||||
remaining--;
|
||||
});
|
||||
|
||||
expect(remaining).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('should not recognize stack-traces as URLs', function() {
|
||||
var urls = [
|
||||
'ftp://foo/bar?baz#qux:4:2',
|
||||
'http://foo/bar?baz#qux:4:2',
|
||||
'https://foo/bar?baz#qux:4:2',
|
||||
'mailto:foo_bar@baz.qux:4:2',
|
||||
'foo_bar@baz.qux:4:2'
|
||||
];
|
||||
var remaining = urls.length;
|
||||
|
||||
urls.forEach(function(url) {
|
||||
var input = 'start ' + url + ' end';
|
||||
|
||||
expect(errorLinkFilter(input)).toBe(input);
|
||||
remaining--;
|
||||
});
|
||||
|
||||
expect(remaining).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('should should set `[target]` if specified', function() {
|
||||
var url = 'https://foo/bar?baz#qux';
|
||||
var target = '_blank';
|
||||
var outputWithoutTarget = '<a href="' + url + '">' + url + '</a>';
|
||||
var outputWithTarget = '<a target="' + target + '" href="' + url + '">' + url + '</a>';
|
||||
|
||||
expect(errorLinkFilter(url)).toBe(outputWithoutTarget);
|
||||
expect(errorLinkFilter(url, target)).toBe(outputWithTarget);
|
||||
});
|
||||
|
||||
|
||||
it('should truncate the contents of the generated `<a>` to 60 characters', function() {
|
||||
var looongUrl = 'https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo';
|
||||
var truncatedUrl = 'https://foooooooooooooooooooooooooooooooooooooooooooooooo...';
|
||||
var output = '<a href="' + looongUrl + '">' + truncatedUrl + '</a>';
|
||||
|
||||
expect(looongUrl.length).toBeGreaterThan(60);
|
||||
expect(truncatedUrl.length).toBe(60);
|
||||
expect(errorLinkFilter(looongUrl)).toBe(output);
|
||||
});
|
||||
|
||||
|
||||
it('should pass the final string through `$sanitize`', function() {
|
||||
$sanitize.calls.reset();
|
||||
|
||||
var input = 'start https://foo/bar?baz#qux end';
|
||||
var output = errorLinkFilter(input);
|
||||
|
||||
expect($sanitize).toHaveBeenCalledTimes(1);
|
||||
expect($sanitize).toHaveBeenCalledWith(output);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('errorDisplay', function() {
|
||||
var $compile;
|
||||
var $location;
|
||||
var $rootScope;
|
||||
var errorLinkFilter;
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.decorator('errorLinkFilter', function() {
|
||||
errorLinkFilter = jasmine.createSpy('errorLinkFilter');
|
||||
errorLinkFilter.and.callFake(angular.identity);
|
||||
|
||||
return errorLinkFilter;
|
||||
});
|
||||
}));
|
||||
beforeEach(inject(function(_$compile_, _$location_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
$location = _$location_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
|
||||
it('should set the element\s HTML', function() {
|
||||
var elem = $compile('<span error-display="bar">foo</span>')($rootScope);
|
||||
expect(elem.html()).toBe('bar');
|
||||
});
|
||||
|
||||
|
||||
it('should interpolate the contents against `$location.search()`', function() {
|
||||
spyOn($location, 'search').and.returnValue({p0: 'foo', p1: 'bar'});
|
||||
|
||||
var elem = $compile('<span error-display="foo = {0}, bar = {1}"></span>')($rootScope);
|
||||
expect(elem.html()).toBe('foo = foo, bar = bar');
|
||||
});
|
||||
|
||||
|
||||
it('should pass the interpolated text through `errorLinkFilter`', function() {
|
||||
$location.search = jasmine.createSpy('search').and.returnValue({p0: 'foo'});
|
||||
|
||||
var elem = $compile('<span error-display="foo = {0}"></span>')($rootScope);
|
||||
expect(errorLinkFilter).toHaveBeenCalledTimes(1);
|
||||
expect(errorLinkFilter).toHaveBeenCalledWith('foo = foo', '_blank');
|
||||
});
|
||||
|
||||
|
||||
it('should encode `<` and `>`', function() {
|
||||
var elem = $compile('<span error-display="<xyz>"></span>')($rootScope);
|
||||
expect(elem.text()).toBe('<xyz>');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -54,6 +54,7 @@ module.exports = new Package('angularjs', [
|
||||
.config(function(parseTagsProcessor) {
|
||||
parseTagsProcessor.tagDefinitions.push(require('./tag-defs/tutorial-step'));
|
||||
parseTagsProcessor.tagDefinitions.push(require('./tag-defs/sortOrder'));
|
||||
parseTagsProcessor.tagDefinitions.push(require('./tag-defs/installation'));
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ module.exports = function debugDeployment(getVersion) {
|
||||
'../angular-touch.js',
|
||||
'../angular-animate.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
|
||||
@@ -18,7 +18,6 @@ module.exports = function defaultDeployment(getVersion) {
|
||||
'../angular-touch.min.js',
|
||||
'../angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.min.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
|
||||
@@ -22,7 +22,6 @@ module.exports = function jqueryDeployment(getVersion) {
|
||||
'../angular-touch.min.js',
|
||||
'../angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.min.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
|
||||
@@ -21,7 +21,6 @@ module.exports = function productionDeployment(getVersion) {
|
||||
cdnUrl + '/angular-touch.min.js',
|
||||
cdnUrl + '/angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.min.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
name: 'installation'
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
{% extends "base.template.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% if doc.title %}{$ doc.title | marked $}{% else %}{$ doc.name | code $}{% endif %}
|
||||
</h1>
|
||||
|
||||
{% if doc.installation or doc.installation == '' %}
|
||||
{$ doc.installation | marked $}
|
||||
{% else %}
|
||||
<h2>Installation</h2>
|
||||
|
||||
<p>First include {$ doc.packageFile | code $} in your HTML:</p>
|
||||
|
||||
{% code %}
|
||||
<script src="angular.js">
|
||||
<script src="{$ doc.packageFile $}">
|
||||
{% endcode %}
|
||||
|
||||
<p>You can download this file from the following places:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://developers.google.com/speed/libraries/devguide#angularjs">Google CDN</a><br>
|
||||
e.g. {$ ("//ajax.googleapis.com/ajax/libs/angularjs/X.Y.Z/" + doc.packageFile) | code $}
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://bower.io">Bower</a><br>
|
||||
e.g. {% code %}bower install {$ doc.packageName $}@X.Y.Z{% endcode %}
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://code.angularjs.org/">code.angularjs.org</a><br>
|
||||
e.g. {% code %}"//code.angularjs.org/X.Y.Z/{$ doc.packageFile $}"{% endcode %}
|
||||
</li>
|
||||
</ul>
|
||||
<p>where X.Y.Z is the AngularJS version you are running.</p>
|
||||
<p>Then load the module in your application by adding it as a dependent module:</p>
|
||||
{% code %}
|
||||
angular.module('app', ['{$ doc.name $}']);
|
||||
{% endcode %}
|
||||
|
||||
<p>With that you're ready to get started!</p>
|
||||
{% endif %}
|
||||
|
||||
{$ doc.description | marked $}
|
||||
|
||||
{% if doc.knownIssueDocs %}
|
||||
<div class="known-issues">
|
||||
<h2 id="known-issues">Known Issues</h2>
|
||||
<table class="definition-table">
|
||||
<tr><th>Name</th><th>Description</th></tr>
|
||||
{% for issueDoc in doc.knownIssueDocs -%}
|
||||
<tr>
|
||||
<td>{$ issueDoc.id | link(issueDoc.name, issueDoc) $}</td>
|
||||
<td>
|
||||
{% for issue in issueDoc.knownIssues -%}
|
||||
{$ issue | marked $}
|
||||
{% endfor -%}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor -%}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="component-breakdown">
|
||||
<h2>Module Components</h2>
|
||||
{% for componentGroup in doc.componentGroups %}
|
||||
<div>
|
||||
<h3 class="component-heading" id="{$ componentGroup.groupType | dashCase $}">{$ componentGroup.groupType | title $}</h3>
|
||||
<table class="definition-table">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
{% for component in componentGroup.components %}
|
||||
<tr>
|
||||
<td>{$ component.id | link(component.name, component) $}</td>
|
||||
<td>{$ component.description | firstParagraph | marked $}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if doc.usage %}
|
||||
<h2>Usage</h2>
|
||||
{$ doc.usage | marked $}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -2,9 +2,7 @@
|
||||
is HTML and wrap each line in a <p> - thus breaking the HTML #}
|
||||
|
||||
<div>
|
||||
<a ng-click="openPlunkr('{$ doc.path $}', $event)" class="btn pull-right">
|
||||
<i class="glyphicon glyphicon-edit"> </i>
|
||||
Edit in Plunker</a>
|
||||
<plnkr-opener example-path="{$ doc.path $}"></plnkr-opener>
|
||||
|
||||
<div class="runnable-example"
|
||||
path="{$ doc.example.deployments.default.path $}"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@ngdoc error
|
||||
@name $compile:baddir
|
||||
@fullName Invalid Directive Name
|
||||
@fullName Invalid Directive/Component Name
|
||||
@description
|
||||
|
||||
This error occurs when the name of a directive is not valid.
|
||||
This error occurs when the name of a directive or component is not valid.
|
||||
|
||||
Directives must start with a lowercase character and must not contain leading or trailing whitespaces.
|
||||
Directives and Components must start with a lowercase character and must not contain leading or trailing whitespaces.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
@ngdoc error
|
||||
@name $compile:infchng
|
||||
@fullName Unstable `$onChanges` hooks
|
||||
@description
|
||||
|
||||
This error occurs when the application's model becomes unstable because some `$onChanges` hooks are causing updates which then trigger
|
||||
further calls to `$onChanges` that can never complete.
|
||||
Angular detects this situation and prevents an infinite loop from causing the browser to become unresponsive.
|
||||
|
||||
For example, the situation can occur by setting up a `$onChanges()` hook which triggers an event on the component, which subsequently
|
||||
triggers the component's bound inputs to be updated:
|
||||
|
||||
```html
|
||||
<c1 prop="a" on-change="a = -a"></c1>
|
||||
```
|
||||
|
||||
```js
|
||||
function Controller1() {}
|
||||
Controller1.$onChanges = function() {
|
||||
this.onChange();
|
||||
};
|
||||
|
||||
mod.component('c1', {
|
||||
controller: Controller1,
|
||||
bindings: {'prop': '<', onChange: '&'}
|
||||
}
|
||||
```
|
||||
|
||||
The maximum number of allowed iterations of the `$onChanges` hooks is controlled via TTL setting which can be configured via
|
||||
{@link ng.$compileProvider#onChangesTtl `$compileProvider.onChangesTtl`}.
|
||||
@@ -3,7 +3,7 @@
|
||||
@fullName Invalid Isolate Scope Definition
|
||||
@description
|
||||
|
||||
When declaring isolate scope the scope definition object must be in specific format which starts with mode character (`@&=`) with an optional local name.
|
||||
When declaring isolate scope the scope definition object must be in specific format which starts with mode character (`@&=<`), after which comes an optional `?`, and it ends with an optional local name.
|
||||
|
||||
```
|
||||
myModule.directive('directiveName', function factory() {
|
||||
@@ -12,9 +12,11 @@ myModule.directive('directiveName', function factory() {
|
||||
scope: {
|
||||
'attrName': '@', // OK
|
||||
'attrName2': '=localName', // OK
|
||||
'attrName3': 'name', // ERROR: missing mode @&=
|
||||
'attrName4': ' = name', // ERROR: extra spaces
|
||||
'attrName5': 'name=', // ERROR: must be prefixed with @&=
|
||||
'attrName3': '<?localName', // OK
|
||||
'attrName4': ' = name', // OK
|
||||
'attrName5': 'name', // ERROR: missing mode @&=
|
||||
'attrName6': 'name=', // ERROR: must be prefixed with @&=
|
||||
'attrName7': '=name?', // ERROR: ? must come directly after the mode
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ single root element, like the `div` element in this template:
|
||||
<div><b>Hello</b> World!</div>
|
||||
```
|
||||
|
||||
An an invalid template to be used with this directive is one that defines multiple root nodes or
|
||||
An invalid template to be used with this directive is one that defines multiple root nodes or
|
||||
elements. For example:
|
||||
|
||||
```
|
||||
@@ -43,7 +43,7 @@ well. Consider the following template:
|
||||
|
||||
```
|
||||
<div class='container'>
|
||||
<div class='wrapper>
|
||||
<div class='wrapper'>
|
||||
...
|
||||
</div> <!-- wrapper -->
|
||||
</div> <!-- container -->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@ngdoc error
|
||||
@name $location:nobase
|
||||
@fullName $location in HTML5 mode requires a `<base>` tag to be present!
|
||||
@fullName $location in HTML5 mode requires a <base> tag to be present!
|
||||
@description
|
||||
|
||||
If you configure {@link ng.$location `$location`} to use
|
||||
|
||||
@@ -330,8 +330,8 @@ reload to the original link.
|
||||
### Relative links
|
||||
|
||||
Be sure to check all relative links, images, scripts etc. Angular requires you to specify the url
|
||||
base in the head of your main html file (`<base href="/my-base">`) unless `html5Mode.requireBase` is
|
||||
set to `false` in the html5Mode definition object passed to `$locationProvider.html5Mode()`. With
|
||||
base in the head of your main html file (`<base href="/my-base/index.html">`) unless `html5Mode.requireBase`
|
||||
is set to `false` in the html5Mode definition object passed to `$locationProvider.html5Mode()`. With
|
||||
that, relative urls will always be resolved to this base url, even if the initial url of the
|
||||
document was different.
|
||||
|
||||
@@ -339,6 +339,7 @@ There is one exception: Links that only contain a hash fragment (e.g. `<a href="
|
||||
will only change `$location.hash()` and not modify the url otherwise. This is useful for scrolling
|
||||
to anchors on the same page without needing to know on which page the user currently is.
|
||||
|
||||
|
||||
### Server side
|
||||
|
||||
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
|
||||
@@ -346,6 +347,20 @@ to entry point of your application (e.g. index.html). Requiring a `<base>` tag i
|
||||
this case, as it allows Angular to differentiate between the part of the url that is the application
|
||||
base and the path that should be handled by the application.
|
||||
|
||||
### Base href constraints
|
||||
|
||||
The `$location` service is not able to function properly if the current URL is outside the URL given
|
||||
as the base href. This can have subtle confusing consequencies...
|
||||
|
||||
Consider a base href set as follows: `<base href="/base/">` (i.e. the application exists in the "folder"
|
||||
called `/base`). The URL `/base` is actually outside the application (it refers to the `base` file found
|
||||
in the root `/` folder).
|
||||
|
||||
If you wish to be able to navigate to the application via a URL such as `/base` then you should ensure that
|
||||
you server is setup to redirect such requests to `/base/`.
|
||||
|
||||
See https://github.com/angular/angular.js/issues/14018 for more information.
|
||||
|
||||
### Sending links among different browsers
|
||||
|
||||
Because of rewriting capability in HTML5 mode, your users will be able to open regular url links in
|
||||
|
||||
@@ -10,7 +10,7 @@ The goal of ngAria is to improve Angular's default accessibility by enabling com
|
||||
[ARIA](http://www.w3.org/TR/wai-aria/) attributes that convey state or semantic information for
|
||||
assistive technologies used by persons with disabilities.
|
||||
|
||||
##Including ngAria
|
||||
## Including ngAria
|
||||
|
||||
Using {@link ngAria ngAria} is as simple as requiring the ngAria module in your application. ngAria hooks into
|
||||
standard AngularJS directives and quietly injects accessibility support into your application
|
||||
@@ -20,7 +20,7 @@ at runtime.
|
||||
angular.module('myApp', ['ngAria'])...
|
||||
```
|
||||
|
||||
###Using ngAria
|
||||
### Using ngAria
|
||||
Most of what ngAria does is only visible "under the hood". To see the module in action, once you've
|
||||
added it as a dependency, you can test a few things:
|
||||
* Using your favorite element inspector, look for attributes added by ngAria in your own code.
|
||||
@@ -28,12 +28,13 @@ added it as a dependency, you can test a few things:
|
||||
* Fire up a screen reader such as VoiceOver or NVDA to check for ARIA support.
|
||||
[Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/)
|
||||
|
||||
##Supported directives
|
||||
## Supported directives
|
||||
Currently, ngAria interfaces with the following directives:
|
||||
|
||||
* {@link guide/accessibility#ngmodel ngModel}
|
||||
* {@link guide/accessibility#ngdisabled ngDisabled}
|
||||
* {@link guide/accessibility#ngrequired ngRequired}
|
||||
* {@link guide/accessibility#ngreadonly ngReadonly}
|
||||
* {@link guide/accessibility#ngvaluechecked ngChecked}
|
||||
* {@link guide/accessibility#ngvaluechecked ngValue}
|
||||
* {@link guide/accessibility#ngshow ngShow}
|
||||
@@ -57,12 +58,62 @@ attributes (if they have not been explicitly specified by the developer):
|
||||
* aria-valuenow
|
||||
* aria-invalid
|
||||
* aria-required
|
||||
* aria-readonly
|
||||
|
||||
###Example
|
||||
### Example
|
||||
|
||||
<example module="ngAria_ngModelExample" deps="angular-aria.js">
|
||||
<file name="index.html">
|
||||
<style>
|
||||
<file name="index.html">
|
||||
<form ng-controller="formsController">
|
||||
<some-checkbox role="checkbox" ng-model="checked" ng-class="{active: checked}"
|
||||
ng-disabled="isDisabled" ng-click="toggleCheckbox()"
|
||||
aria-label="Custom Checkbox" show-attrs>
|
||||
<span class="icon" aria-hidden="true"></span>
|
||||
Custom Checkbox
|
||||
</some-checkbox>
|
||||
</form>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
var app = angular.module('ngAria_ngModelExample', ['ngAria'])
|
||||
.controller('formsController', function($scope){
|
||||
$scope.checked = false;
|
||||
$scope.toggleCheckbox = function(){
|
||||
$scope.checked = !$scope.checked;
|
||||
};
|
||||
})
|
||||
.directive('someCheckbox', function(){
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, $el, $attrs) {
|
||||
$el.on('keypress', function(event){
|
||||
event.preventDefault();
|
||||
if(event.keyCode === 32 || event.keyCode === 13){
|
||||
$scope.toggleCheckbox();
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
.directive('showAttrs', function() {
|
||||
return function($scope, $el, $attrs) {
|
||||
var pre = document.createElement('pre');
|
||||
$el.after(pre);
|
||||
$scope.$watch(function() {
|
||||
var $attrs = {};
|
||||
Array.prototype.slice.call($el[0].attributes, 0).forEach(function(item) {
|
||||
if (item.name !== 'show-$attrs') {
|
||||
$attrs[item.name] = item.value;
|
||||
}
|
||||
});
|
||||
return $attrs;
|
||||
}, function(newAttrs, oldAttrs) {
|
||||
pre.textContent = JSON.stringify(newAttrs, null, 2);
|
||||
}, true);
|
||||
};
|
||||
});
|
||||
</file>
|
||||
<file name="style.css">
|
||||
[role=checkbox] {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
@@ -81,58 +132,7 @@ attributes (if they have not been explicitly specified by the developer):
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<form ng-controller="formsController">
|
||||
<some-checkbox role="checkbox" ng-model="checked" ng-class="{active: checked}"
|
||||
ng-disabled="isDisabled" ng-click="toggleCheckbox()"
|
||||
aria-label="Custom Checkbox" show-attrs>
|
||||
<span class="icon" aria-hidden="true"></span>
|
||||
Custom Checkbox
|
||||
</some-checkbox>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
var app = angular.module('ngAria_ngModelExample', ['ngAria'])
|
||||
.controller('formsController', function($scope){
|
||||
$scope.checked = false;
|
||||
$scope.toggleCheckbox = function(){
|
||||
$scope.checked = !$scope.checked;
|
||||
}
|
||||
})
|
||||
.directive('someCheckbox', function(){
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, $el, $attrs) {
|
||||
$el.on('keypress', function(event){
|
||||
event.preventDefault();
|
||||
if(event.keyCode === 32 || event.keyCode === 13){
|
||||
$scope.toggleCheckbox();
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.directive('showAttrs', function() {
|
||||
return function($scope, $el, $attrs) {
|
||||
var pre = document.createElement('pre');
|
||||
$el.after(pre);
|
||||
$scope.$watch(function() {
|
||||
var $attrs = {};
|
||||
Array.prototype.slice.call($el[0].attributes, 0).forEach(function(item) {
|
||||
if (item.name !== 'show-$attrs') {
|
||||
$attrs[item.name] = item.value;
|
||||
}
|
||||
});
|
||||
return $attrs;
|
||||
}, function(newAttrs, oldAttrs) {
|
||||
pre.textContent = JSON.stringify(newAttrs, null, 2);
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</file>
|
||||
</file>
|
||||
</example>
|
||||
|
||||
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from
|
||||
@@ -147,7 +147,7 @@ To ease the transition between native inputs and custom controls, ngAria now sup
|
||||
The original directives were created for native inputs only, so ngAria extends
|
||||
support to custom elements by managing `aria-checked` for accessibility.
|
||||
|
||||
###Example
|
||||
### Example
|
||||
|
||||
```html
|
||||
<custom-checkbox ng-checked="val"></custom-checkbox>
|
||||
@@ -169,7 +169,7 @@ using ngAria with {@link ng.ngDisabled ngDisabled} will also
|
||||
add `aria-disabled`. This tells assistive technologies when a non-native input is disabled, helping
|
||||
custom controls to be more accessible.
|
||||
|
||||
###Example
|
||||
### Example
|
||||
|
||||
```html
|
||||
<md-checkbox ng-disabled="disabled"></md-checkbox>
|
||||
@@ -181,8 +181,10 @@ Becomes:
|
||||
<md-checkbox disabled aria-disabled="true"></md-checkbox>
|
||||
```
|
||||
|
||||
>You can check whether a control is legitimately disabled for a screen reader by visiting
|
||||
<div class="alert alert-info">
|
||||
You can check whether a control is legitimately disabled for a screen reader by visiting
|
||||
[chrome://accessibility](chrome://accessibility) and inspecting [the accessibility tree](http://www.paciellogroup.com/blog/2015/01/the-browser-accessibility-tree/).
|
||||
</div>
|
||||
|
||||
<h2 id="ngrequired">ngRequired</h2>
|
||||
|
||||
@@ -191,7 +193,7 @@ The boolean `required` attribute is only valid for native form controls such as
|
||||
as required, using ngAria with {@link ng.ngRequired ngRequired} will also add
|
||||
`aria-required`. This tells accessibility APIs when a custom control is required.
|
||||
|
||||
###Example
|
||||
### Example
|
||||
|
||||
```html
|
||||
<md-checkbox ng-required="val"></md-checkbox>
|
||||
@@ -203,9 +205,28 @@ Becomes:
|
||||
<md-checkbox ng-required="val" aria-required="true"></md-checkbox>
|
||||
```
|
||||
|
||||
<h2 id="ngreadonly">ngReadonly</h2>
|
||||
|
||||
The boolean `readonly` attribute is only valid for native form controls such as `input` and
|
||||
`textarea`. To properly indicate custom element directives such as `<md-checkbox>` or `<custom-input>`
|
||||
as required, using ngAria with {@link ng.ngReadonly ngReadonly} will also add
|
||||
`aria-readonly`. This tells accessibility APIs when a custom control is read-only.
|
||||
|
||||
### Example
|
||||
|
||||
```html
|
||||
<md-checkbox ng-readonly="val"></md-checkbox>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<md-checkbox ng-readonly="val" aria-readonly="true"></md-checkbox>
|
||||
```
|
||||
|
||||
<h2 id="ngshow">ngShow</h2>
|
||||
|
||||
>The {@link ng.ngShow ngShow} directive shows or hides the
|
||||
The {@link ng.ngShow ngShow} directive shows or hides the
|
||||
given HTML element based on the expression provided to the `ngShow` attribute. The element is
|
||||
shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
|
||||
|
||||
@@ -222,7 +243,7 @@ screen reader users won't accidentally focus on "mystery elements". Managing tab
|
||||
child control can be complex and affect performance, so it’s best to just stick with the default
|
||||
`display: none` CSS. See the [fourth rule of ARIA use](http://www.w3.org/TR/aria-in-html/#fourth-rule-of-aria-use).
|
||||
|
||||
###Example
|
||||
### Example
|
||||
```css
|
||||
.ng-hide {
|
||||
display: block;
|
||||
@@ -242,7 +263,7 @@ Becomes:
|
||||
|
||||
<h2 id="nghide">ngHide</h2>
|
||||
|
||||
>The {@link ng.ngHide ngHide} directive shows or hides the
|
||||
The {@link ng.ngHide ngHide} directive shows or hides the
|
||||
given HTML element based on the expression provided to the `ngHide` attribute. The element is
|
||||
shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
|
||||
|
||||
@@ -283,11 +304,11 @@ Becomes:
|
||||
|
||||
<h2 id="ngmessages">ngMessages</h2>
|
||||
|
||||
The new ngMessages module makes it easy to display form validation or other messages with priority
|
||||
The ngMessages module makes it easy to display form validation or other messages with priority
|
||||
sequencing and animation. To expose these visual messages to screen readers,
|
||||
ngAria injects `aria-live="assertive"`, causing them to be read aloud any time a message is shown,
|
||||
regardless of the user's focus location.
|
||||
###Example
|
||||
### Example
|
||||
|
||||
```html
|
||||
<div ng-messages="myForm.myName.$error">
|
||||
@@ -305,7 +326,7 @@ Becomes:
|
||||
</div>
|
||||
```
|
||||
|
||||
##Disabling attributes
|
||||
## Disabling attributes
|
||||
The attribute magic of ngAria may not work for every scenario. To disable individual attributes,
|
||||
you can use the {@link ngAria.$ariaProvider#config config} method. Just keep in mind this will
|
||||
tell ngAria to ignore the attribute globally.
|
||||
@@ -343,7 +364,7 @@ tell ngAria to ignore the attribute globally.
|
||||
</file>
|
||||
</example>
|
||||
|
||||
##Common Accessibility Patterns
|
||||
## Common Accessibility Patterns
|
||||
|
||||
Accessibility best practices that apply to web apps in general also apply to Angular.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Animations
|
||||
|
||||
AngularJS 1.3 provides animation hooks for common directives such as `ngRepeat`, `ngSwitch`, and `ngView`, as well as custom directives
|
||||
AngularJS provides animation hooks for common directives such as `ngRepeat`, `ngSwitch`, and `ngView`, as well as custom directives
|
||||
via the `$animate` service. These animation hooks are set in place to trigger animations during the life cycle of various directives and when
|
||||
triggered, will attempt to perform a CSS Transition, CSS Keyframe Animation or a JavaScript callback Animation (depending on if an animation is
|
||||
placed on the given directive). Animations can be placed using vanilla CSS by following the naming conventions set in place by AngularJS
|
||||
@@ -274,6 +274,100 @@ myModule.directive('my-directive', ['$animate', function($animate) {
|
||||
}]);
|
||||
```
|
||||
|
||||
## Animations on app bootstrap / page load
|
||||
|
||||
By default, animations are disabled when the Angular app {@link guide/bootstrap bootstraps}. If you are using the {@link ngApp} directive,
|
||||
this happens in the `DOMContentLoaded` event, so immediately after the page has been loaded.
|
||||
Animations are disabled, so that UI and content are instantly visible. Otherwise, with many animations on
|
||||
the page, the loading process may become too visually overwhelming, and the performance may suffer.
|
||||
|
||||
Internally, `ngAnimate` waits until all template downloads that are started right after bootstrap have finished.
|
||||
Then, it waits for the currently running {@link ng.$rootScope.Scope#$digest} and the one after that to finish.
|
||||
This ensures that the whole app has been compiled fully before animations are attempted.
|
||||
|
||||
If you do want your animations to play when the app bootstraps, you can enable animations globally in
|
||||
your main module's {@link angular.Module#run run} function:
|
||||
|
||||
```js
|
||||
myModule.run(function($animate) {
|
||||
$animate.enabled(true);
|
||||
});
|
||||
```
|
||||
|
||||
## How to (selectively) enable, disable and skip animations
|
||||
|
||||
There are three different ways to disable animations, both globally and for specific animations.
|
||||
Disabling specific animations can help to speed up the render performance, for example for large `ngRepeat`
|
||||
lists that don't actually have animations. Because ngAnimate checks at runtime if animations are present,
|
||||
performance will take a hit even if an element has no animation.
|
||||
|
||||
### In the config: {@link $animateProvider#classNameFilter $animateProvider.classNameFilter()}
|
||||
|
||||
This function can be called in the {@link angular.Module#config config} phase of an app. It takes a regex as the only argument,
|
||||
which will then be matched against the classes of any element that is about to be animated. The regex
|
||||
allows a lot of flexibility - you can either allow animations only for specific classes (useful when
|
||||
you are working with 3rd party animations), or exclude specific classes from getting animated.
|
||||
|
||||
```js
|
||||
app.config(function($animateProvider) {
|
||||
$animateProvider.classNameFilter(/animate-/);
|
||||
});
|
||||
```
|
||||
|
||||
```css
|
||||
/* prefixed with animate- */
|
||||
.animate-fade-add.animate-fade-add-active {
|
||||
transition:1s linear all;
|
||||
opacity:0;
|
||||
}
|
||||
```
|
||||
|
||||
The classNameFilter approach generally applies the biggest speed boost, because the matching is
|
||||
done before any other animation disabling strategies are checked. However, that also means it is not
|
||||
possible to override class name matching with the two following strategies. It's of course still possible
|
||||
to enable / disable animations by changing an element's class name at runtime.
|
||||
|
||||
### At runtime: {@link ng.$animate#enabled $animate.enabled()}
|
||||
|
||||
This function can be used to enable / disable animations in two different ways:
|
||||
|
||||
With a single `boolean` argument, it enables / disables animations globally: `$animate.enabled(false)`
|
||||
disables all animations in your app.
|
||||
|
||||
When the second argument is a native DOM or jQuery element, the function enables / disables
|
||||
animations on this element *and all its children*: `$animate.enabled(false, myElement)`. This is the
|
||||
most flexible way to change the animation state. For example, even if you have used it to disable
|
||||
animations on a parent element, you can still re-enable it for a child element. And compared to the
|
||||
`classNameFilter`, you can change the animation status at runtime instead of during the config phase.
|
||||
|
||||
Note however that the `$animate.enabled()` state for individual elements does not overwrite disabling
|
||||
rules that have been set in the {@link $animateProvider#classNameFilter classNameFilter}.
|
||||
|
||||
### Via CSS styles: overwriting styles in the `ng-animate` CSS class
|
||||
Whenever an animation is started, ngAnimate applies the `ng-animate` class to the element for the
|
||||
whole duration of the animation. By applying CSS transition / animation styling to the class,
|
||||
you can skip an animation:
|
||||
|
||||
```css
|
||||
|
||||
.my-class{
|
||||
transition: transform 2s;
|
||||
}
|
||||
|
||||
.my-class:hover {
|
||||
transform: translateX(50px);
|
||||
}
|
||||
|
||||
my-class.ng-animate {
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
By setting `transition: 0s`, ngAnimate will ignore the existing transition styles, and not try to animate them (Javascript
|
||||
animations will still execute, though). This can be used to prevent {@link guide/animations#preventing-collisions-with-existing-animations-and-third-party-libraries
|
||||
issues with existing animations interfering with ngAnimate}.
|
||||
|
||||
## Preventing flicker before an animation starts
|
||||
|
||||
When nesting elements with structural animations such as `ngIf` into elements that have class-based
|
||||
@@ -305,6 +399,49 @@ In that case, you can add styles to the CSS that make sure the element stays hid
|
||||
/* Other animation styles ... */
|
||||
```
|
||||
|
||||
## Preventing Collisions with Existing Animations and Third Party Libraries
|
||||
By default, any `ngAnimate` enabled directives will assume any transition / animation styles on the
|
||||
element are part of an `ngAnimate` animation. This can lead to problems when the styles are actually
|
||||
for animations that are independent of `ngAnimate`.
|
||||
|
||||
For example, an element acts as a loading spinner. It has an inifinite css animation on it, and also an
|
||||
{@link ngIf `ngIf`} directive, for which no animations are defined:
|
||||
|
||||
```css
|
||||
@keyframes rotating {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.spinner {
|
||||
animation: rotating 2s linear infinite;
|
||||
}
|
||||
```
|
||||
|
||||
Now, when the `ngIf` changes, `ngAnimate` will see the spinner animation and use
|
||||
it to animate the `enter`/`leave` event, which doesn't work because
|
||||
the animation is infinite. The element will still be added / removed after a timeout, but there will be a
|
||||
noticable delay.
|
||||
|
||||
This might also happen because some third-party frameworks place animation duration defaults
|
||||
across many element or className selectors in order to make their code small and reuseable.
|
||||
|
||||
You can prevent this unwanted behavior by adding CSS to the `.ng-animate` class that is added
|
||||
for the whole duration of an animation. Simply overwrite the transition / animation duration. In the
|
||||
case of the spinner, this would be:
|
||||
|
||||
```css
|
||||
.spinner.ng-animate {
|
||||
transition: 0s none;
|
||||
animation: 0s none;
|
||||
}
|
||||
```
|
||||
|
||||
If you do have CSS transitions / animations defined for the animation events, make sure they have higher priority
|
||||
than any styles that are independent from ngAnimate.
|
||||
|
||||
You can also use one of the two other {@link guide/animations#how-to-selectively-enable-disable-and-skip-animations strategies to disable animations}.
|
||||
|
||||
## More about animations
|
||||
|
||||
For a full breakdown of each method available on `$animate`, see the {@link ng.$animate API documentation}.
|
||||
|
||||
@@ -53,13 +53,13 @@ initialization.
|
||||
|
||||
Angular initializes automatically upon `DOMContentLoaded` event or when the `angular.js` script is
|
||||
evaluated if at that time `document.readyState` is set to `'complete'`. At this point Angular looks
|
||||
for the {@link ng.directive:ngApp `ng-app`} directive which designates your application root.
|
||||
If the {@link ng.directive:ngApp `ng-app`} directive is found then Angular will:
|
||||
for the {@link ng.directive:ngApp `ngApp`} directive which designates your application root.
|
||||
If the {@link ng.directive:ngApp `ngApp`} directive is found then Angular will:
|
||||
|
||||
* load the {@link guide/module module} associated with the directive.
|
||||
* create the application {@link auto.$injector injector}
|
||||
* compile the DOM treating the {@link ng.directive:ngApp
|
||||
`ng-app`} directive as the root of the compilation. This allows you to tell it to treat only a
|
||||
`ngApp`} directive as the root of the compilation. This allows you to tell it to treat only a
|
||||
portion of the DOM as an Angular application.
|
||||
|
||||
|
||||
@@ -142,6 +142,17 @@ This is the sequence that your code should follow:
|
||||
2. Call {@link angular.bootstrap} to {@link compiler compile} the element into an
|
||||
executable, bi-directionally bound application.
|
||||
|
||||
## Things to keep in mind
|
||||
|
||||
There a few things to keep in mind regardless of automatic or manual bootstrapping:
|
||||
|
||||
- While it's possible to bootstrap more than one AngularJS application per page, we don't actively
|
||||
test against this scenario. It's possible that you'll run into problems, especially with complex apps, so
|
||||
caution is advised.
|
||||
- Do not bootstrap your app on an element with a directive that uses {@link ng.$compile#transclusion transclusion}, such as
|
||||
{@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}.
|
||||
Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
|
||||
causing animations to stop working and making the injector inaccessible from outside the app.
|
||||
|
||||
## Deferred Bootstrap
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ and link functions are unavailable
|
||||
Components can be registered using the `.component()` method of an Angular module (returned by {@link module `angular.module()`}). The method takes two arguments:
|
||||
|
||||
* The name of the Component (as string).
|
||||
* The Component config object (note that, unlike the `.directive()` method, this method does **not** take a factory function.
|
||||
* The Component config object. (Note that, unlike the `.directive()` method, this method does **not** take a factory function.)
|
||||
|
||||
<example name="heroComponentSimple" module="heroApp">
|
||||
<file name="index.js">
|
||||
@@ -109,13 +109,20 @@ change will be reflected in the parent component. For components however, only t
|
||||
the data should modify it, to make it easy to reason about what data is changed, and when. For that reason,
|
||||
components should follow a few simple conventions:
|
||||
|
||||
- Inputs are realized with `@` and `=` bindings
|
||||
- Inputs should be using `<` and `@` bindings. The `<` symbol denotes {@link $compile#-scope- one-way bindings} which are
|
||||
available since 1.5. The difference to `=` is that the bound properties in the component scope are not watched, which means
|
||||
if you assign a new value to the property in the component scope, it will not update the parent scope. Note however, that both parent
|
||||
and component scope reference the same object, so if you are changing object properties or array elements in the
|
||||
component, the parent will still reflect that change.
|
||||
The general rule should therefore be to never change an object or array property in the component scope.
|
||||
`@` bindings can be used when the input is a string, especially when the value of the binding doesn't change.
|
||||
```js
|
||||
bindings: {
|
||||
hero: `=`,
|
||||
hero: '<',
|
||||
comment: '@'
|
||||
}
|
||||
```
|
||||
- Outputs are realized with `&` bindings, which function as callbacks to component events
|
||||
- Outputs are realized with `&` bindings, which function as callbacks to component events.
|
||||
```js
|
||||
bindings: {
|
||||
onDelete: '&',
|
||||
@@ -126,7 +133,9 @@ components should follow a few simple conventions:
|
||||
For a deletion, that means the component doesn't delete the `hero` itself, but sends it back to
|
||||
the owner component via the correct event.
|
||||
```html
|
||||
<button ng-click="$ctrl.onDelete({hero: hero})">Delete</button>
|
||||
<!-- note that we use snake case for bindings in the template as usual -->
|
||||
<editable-field on-update="$ctrl.update('location', value)"></editable-field><br>
|
||||
<button ng-click="$ctrl.onDelete({hero: $ctrl.hero})">Delete</button>
|
||||
```
|
||||
- That way, the parent component can decide what to do with the event (e.g. delete an item or update the properties)
|
||||
```js
|
||||
@@ -140,6 +149,30 @@ components should follow a few simple conventions:
|
||||
}
|
||||
```
|
||||
|
||||
- **Components have a well-defined lifecycle**
|
||||
Each component can implement "lifecycle hooks". These are methods that will be called at certain points in the life
|
||||
of the component. The following hook methods can be implemented:
|
||||
|
||||
* `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
|
||||
had their bindings initialized (and before the pre & post linking functions for the directives on
|
||||
this element). This is a good place to put initialization code for your controller.
|
||||
* `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys
|
||||
are the names of the bound properties that have changed, and the values are an object of the form
|
||||
`{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a component such as
|
||||
cloning the bound value to prevent accidental mutation of the outer value.
|
||||
* `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
|
||||
external resources, watches and event handlers.
|
||||
* `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
|
||||
function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
|
||||
Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
|
||||
they are waiting for their template to load asynchronously and their own compilation and linking has been
|
||||
suspended until that occurs.
|
||||
This hook can be considered analogous to the `ngAfterViewInit` and `ngAfterContentInit` hooks in Angular 2.
|
||||
Since the compilation process is rather different in Angular 1 there is no direct mapping and care should
|
||||
be taken when upgrading.
|
||||
|
||||
By implementing these methods, your component can hook into its lifecycle.
|
||||
|
||||
- **An application is a tree of components:**
|
||||
Ideally, the whole application should be a tree of components that implement clearly defined inputs
|
||||
and outputs, and minimize two-way data binding. That way, it's easier to predict when data changes and what the state
|
||||
@@ -153,11 +186,11 @@ above:
|
||||
Instead of an ngController, we now have a heroList component that holds the data of
|
||||
different heroes, and creates a heroDetail for each of them.
|
||||
|
||||
The heroDetail component now contains the following new functionality:
|
||||
- a delete button that calls the bound onDelete function of the heroList component
|
||||
The heroDetail component now contains new functionality:
|
||||
- a delete button that calls the bound `onDelete` function of the heroList component
|
||||
- an input to change the hero location, in the form of a reusable editableField component. Instead
|
||||
of manipulating the hero object itself, it sends the changes upwards to the heroDetail, which sends
|
||||
it upwards to the heroList component, which updates it.
|
||||
of manipulating the hero object itself, it sends a changeset upwards to the heroDetail, which sends
|
||||
it upwards to the heroList component, which updates the original data.
|
||||
|
||||
<example name="heroComponentTree" module="heroApp">
|
||||
<file name="index.js">
|
||||
@@ -212,7 +245,7 @@ it upwards to the heroList component, which updates it.
|
||||
templateUrl: 'heroDetail.html',
|
||||
controller: HeroDetailController,
|
||||
bindings: {
|
||||
hero: '=',
|
||||
hero: '<',
|
||||
onDelete: '&',
|
||||
onUpdate: '&'
|
||||
}
|
||||
@@ -223,9 +256,9 @@ it upwards to the heroList component, which updates it.
|
||||
|
||||
function EditableFieldController($scope, $element, $attrs) {
|
||||
var ctrl = this;
|
||||
this.editMode = false;
|
||||
ctrl.editMode = false;
|
||||
|
||||
this.handleModeChange = function() {
|
||||
ctrl.handleModeChange = function() {
|
||||
if (ctrl.editMode) {
|
||||
ctrl.onUpdate({value: ctrl.fieldValue});
|
||||
ctrl.fieldValueCopy = ctrl.fieldValue;
|
||||
@@ -233,17 +266,17 @@ it upwards to the heroList component, which updates it.
|
||||
ctrl.editMode = !ctrl.editMode;
|
||||
};
|
||||
|
||||
this.reset = function() {
|
||||
ctrl.reset = function() {
|
||||
ctrl.fieldValue = ctrl.fieldValueCopy;
|
||||
};
|
||||
|
||||
this.$onInit = function() {
|
||||
ctrl.$onInit = function() {
|
||||
// Make a copy of the initial value to be able to reset it later
|
||||
this.fieldValueCopy = this.fieldValue;
|
||||
ctrl.fieldValueCopy = ctrl.fieldValue;
|
||||
|
||||
// Set a default fieldType
|
||||
if (!this.fieldType) {
|
||||
this.fieldType = 'text';
|
||||
if (!ctrl.fieldType) {
|
||||
ctrl.fieldType = 'text';
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -252,8 +285,8 @@ it upwards to the heroList component, which updates it.
|
||||
templateUrl: 'editableField.html',
|
||||
controller: EditableFieldController,
|
||||
bindings: {
|
||||
fieldValue: '@',
|
||||
fieldType: '@',
|
||||
fieldValue: '<',
|
||||
fieldType: '@?',
|
||||
onUpdate: '&'
|
||||
}
|
||||
});
|
||||
@@ -269,13 +302,13 @@ it upwards to the heroList component, which updates it.
|
||||
<hr>
|
||||
<div>
|
||||
Name: {{$ctrl.hero.name}}<br>
|
||||
Location: <editable-field field-value="{{$ctrl.hero.location}}" field-type="text" on-update="$ctrl.update('location', value)"></editable-field><br>
|
||||
Location: <editable-field field-value="$ctrl.hero.location" field-type="text" on-update="$ctrl.update('location', value)"></editable-field><br>
|
||||
<button ng-click="$ctrl.onDelete({hero: $ctrl.hero})">Delete</button>
|
||||
</div>
|
||||
</file>
|
||||
<file name="editableField.html">
|
||||
<span ng-switch="$ctrl.editMode">
|
||||
<input ng-switch-when="true" type="text" ng-model="$ctrl.fieldValue">
|
||||
<input ng-switch-when="true" type="{{$ctrl.fieldType}}" ng-model="$ctrl.fieldValue">
|
||||
<span ng-switch-default>{{$ctrl.fieldValue}}</span>
|
||||
</span>
|
||||
<button ng-click="$ctrl.handleModeChange()">{{$ctrl.editMode ? 'Save' : 'Edit'}}</button>
|
||||
@@ -303,17 +336,25 @@ application, every view is a component:
|
||||
```
|
||||
<br />
|
||||
When using {@link ngRoute.$routeProvider $routeProvider}, you can often avoid some
|
||||
boilerplate, by assigning the resolved dependencies directly to the route scope:
|
||||
boilerplate, by passing the resolved route dependencies directly to the component. Since 1.5,
|
||||
ngRoute automatically assigns the resolves to the route scope property `$resolve` (you can also
|
||||
configure the property name via `resolveAs`). When using components, you can take advantage of this and pass resolves
|
||||
directly into your component without creating an extra route controller:
|
||||
|
||||
```js
|
||||
var myMod = angular.module('myMod', ['ngRoute']);
|
||||
myMod.component('home', {
|
||||
template: '<h1>Home</h1><p>Hello, {{ $ctrl.user.name }} !</p>',
|
||||
bindings: {user: '='}
|
||||
bindings: {
|
||||
user: '<'
|
||||
}
|
||||
});
|
||||
myMod.config(function($routeProvider) {
|
||||
$routeProvider.when('/', {
|
||||
template: '<home user="$resolve.user"></home>',
|
||||
resolve: {user: function($http) { return $http.get('...'); }}
|
||||
resolve: {
|
||||
user: function($http) { return $http.get('...'); }
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
@@ -322,8 +363,15 @@ boilerplate, by assigning the resolved dependencies directly to the route scope:
|
||||
|
||||
Directives can require the controllers of other directives to enable communication
|
||||
between each other. This can be achieved in a component by providing an
|
||||
object mapping for the `require` property. Here is a tab pane example built
|
||||
from components:
|
||||
object mapping for the `require` property. The object keys specify the property names under which
|
||||
the required controllers (object values) will be bound to the requiring component's controller.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
Note that the required controllers will not be available during the instantiation of the controller,
|
||||
but they are guaranteed to be available just before the `$onInit` method is executed!
|
||||
</div>
|
||||
|
||||
Here is a tab pane example built from components:
|
||||
|
||||
<example module="docsTabsExample">
|
||||
<file name="script.js">
|
||||
@@ -349,7 +397,9 @@ angular.module('docsTabsExample', [])
|
||||
})
|
||||
.component('myPane', {
|
||||
transclude: true,
|
||||
require: {tabsCtrl: '^myTabs'},
|
||||
require: {
|
||||
tabsCtrl: '^myTabs'
|
||||
},
|
||||
bindings: {
|
||||
title: '@'
|
||||
},
|
||||
@@ -403,12 +453,13 @@ The examples use the [Jasmine](http://jasmine.github.io/) testing framework.
|
||||
**Controller Test:**
|
||||
```js
|
||||
describe('component: heroDetail', function() {
|
||||
var component, scope, hero;
|
||||
var component, scope, hero, $componentController;
|
||||
|
||||
beforeEach(module('simpleComponent'));
|
||||
|
||||
beforeEach(inject(function($rootScope, $componentController) {
|
||||
beforeEach(inject(function($rootScope, _$componentController_) {
|
||||
scope = $rootScope.$new();
|
||||
$componentController = _$componentController_;
|
||||
hero = {name: 'Wolverine'};
|
||||
}));
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
@sortOrder 280
|
||||
@description
|
||||
|
||||
# Filters
|
||||
|
||||
A filter formats the value of an expression for display to the user. They can be used in view templates,
|
||||
controllers or services and it is easy to define your own filter.
|
||||
|
||||
|
||||
@@ -440,8 +440,7 @@ Note that you can alternatively use `ng-pattern` to further restrict the validat
|
||||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@example\.com$/i;
|
||||
|
||||
return {
|
||||
require: 'ngModel',
|
||||
restrict: '',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attrs, ctrl) {
|
||||
// only apply the validator if ngModel is present and Angular has added the email validator
|
||||
if (ctrl && ctrl.$validators.email) {
|
||||
|
||||
@@ -28,4 +28,7 @@ browsers, but it is up to you to test and decide whether it works for your parti
|
||||
To ensure your Angular application works on IE please consider:
|
||||
|
||||
1. Use `ng-style` tags instead of `style="{{ someCss }}"`. The latter works in Chrome and Firefox
|
||||
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
|
||||
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
|
||||
2. For the `type` attribute of buttons, use `ng-attr-type` tags instead of
|
||||
`type="{{ someExpression }}"`. If using the latter, Internet Explorer overwrites the expression
|
||||
with `type="submit"` before Angular has a chance to interpolate it.
|
||||
|
||||
@@ -91,7 +91,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
### Server-Specific
|
||||
|
||||
* **Django:** [Tutorial](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework), [Integrating AngularJS with Django](http://django-angular.readthedocs.org/en/latest/integration.html), [Getting Started with Django Rest Framework and AngularJS](http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html)
|
||||
* **FireBase:** [AngularFire](http://angularfire.com/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
|
||||
* **FireBase:** [AngularFire](http://angularfire.com/), [Firebase Foundations for AngularJS](http://blog.watchandcode.com/firebase-foundations/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
|
||||
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/developers/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications/), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
|
||||
* **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/)
|
||||
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
|
||||
@@ -111,10 +111,12 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* [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
|
||||
* [Professional AngularJS](http://www.amazon.com/Professional-AngularJS-Valeri-Karpov/dp/1118832078/)
|
||||
|
||||
###Videos:
|
||||
* [egghead.io](http://egghead.io/)
|
||||
* [Angular on YouTube](http://youtube.com/angularjs)
|
||||
* [Firebase Foundations for AngularJS](http://blog.watchandcode.com/firebase-foundations/)
|
||||
|
||||
### Courses
|
||||
* **Free online:**
|
||||
@@ -122,6 +124,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
[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:**
|
||||
[The Angular Course (115 videos that show you how to build a full app)](http://watchandcode.com/courses/angular-course/),
|
||||
[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),
|
||||
|
||||
@@ -98,16 +98,20 @@ For example, to bind to `viewBox`, we can write:
|
||||
</svg>
|
||||
```
|
||||
|
||||
The following attributes are also known to cause problems when used with normal bindings:
|
||||
Other attributes may also not work as expected when they contain interpolation markup, and
|
||||
can be used with `ngAttr` instead. The following is a list of known problematic attributes:
|
||||
|
||||
- **size** in `<select>` elements (see [Github issue 1619](https://github.com/angular/angular.js/issues/1619))
|
||||
- **placeholder** in `<textarea>` in Internet Explorer 10/11 (see [Github issue 5025](https://github.com/angular/angular.js/issues/5025))
|
||||
- **size** in `<select>` elements (see [issue 1619](https://github.com/angular/angular.js/issues/1619))
|
||||
- **placeholder** in `<textarea>` in Internet Explorer 10/11 (see [issue 5025](https://github.com/angular/angular.js/issues/5025))
|
||||
- **type** in `<button>` in Internet Explorer 11 (see [issue 14117](https://github.com/angular/angular.js/issues/5025))
|
||||
|
||||
|
||||
### Embedding interpolation markup inside expressions
|
||||
|
||||
Angular directives take either expressions or interpolation markup with embedded expressions. So the
|
||||
following example which embeds interpolation inside an expression is a bad practice:
|
||||
<div class="alert alert-danger">
|
||||
**Note:** Angular directive attributes take either expressions *or* interpolation markup with embedded expressions.
|
||||
It is considered **bad practice** to embed interpolation markup inside an expression:
|
||||
</div>
|
||||
|
||||
```html
|
||||
<div ng-show="form{{$index}}.$invalid"></div>
|
||||
@@ -120,8 +124,8 @@ You should instead delegate the computation of complex expressions to the scope,
|
||||
```
|
||||
|
||||
```js
|
||||
function getForm() {
|
||||
return $scope['form' + $index];
|
||||
function getForm(index) {
|
||||
return $scope['form' + index];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ which drives many of these changes.
|
||||
* Several new features, especially animations, would not be possible without a few changes.
|
||||
* Finally, some outstanding bugs were best fixed by changing an existing API.
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
## Contents
|
||||
|
||||
@@ -114,6 +114,27 @@ Objects considered array-like include: arrays, array subclasses, strings, NodeLi
|
||||
jqLite/jQuery collections
|
||||
|
||||
|
||||
### ngAria
|
||||
|
||||
Due to [d06431e](https://github.com/angular/angular.js/commit/d06431e5309bb0125588877451dc79b935808134),
|
||||
the `ngAria`-enhanced directives (e.g. `ngModel`, `ngDisabled` etc) will not apply ARIA attributes
|
||||
to native inputs, unless necessary. Previously, ARIA attributes were always applied to native
|
||||
inputs, despite this being unnecessary in most cases.
|
||||
In the context of `ngAria`, elements considered "native inputs" include:
|
||||
`<a>`, `<button>`, `<details>`, `<input>`, `<select>`, `<summary>`, `<textarea>`
|
||||
|
||||
This change will not affect the accessibility of your applications (since native inputs are
|
||||
accessible by default), but if you relied on ARIA attributes being present on native inputs (for
|
||||
whatever reason), you'll have to add and update them manually.
|
||||
|
||||
Additionally, the `aria-multiline` attribute, which was previously added to elements with a `type`
|
||||
or `role` of `textbox`, will not be added anymore, since there is no way `ngAria` can tell if the
|
||||
textbox element is multiline or not.
|
||||
If you relied on `aria-multiline="true"` being automatically added by `ngAria`, you need to apply it
|
||||
yourself. E.g. change your code from `<div role="textbox" ng-model="..." ...>` to
|
||||
`<div role="textbox" ng-model="..." ... aria-multiline="true">`.
|
||||
|
||||
|
||||
### ngMessages (`ngMessage`)
|
||||
|
||||
Due to [4971ef12](https://github.com/angular/angular.js/commit/4971ef12d4c2c268cb8d26f90385dc96eba19db8),
|
||||
@@ -452,6 +473,47 @@ ngModelCtrl.$formatters.push(function(value) {
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
### form
|
||||
|
||||
Due to [94533e57](https://github.com/angular/angular.js/commit/94533e570673e6b2eb92073955541fa289aabe02),
|
||||
the `name` attribute of `form` elements can now only contain characters that can be evaluated as part
|
||||
of an Angular expression. This is because Angular uses the value of `name` as an assignable expression
|
||||
to set the form on the `$scope`. For example, `name="myForm"` assigns the form to `$scope.myForm` and
|
||||
`name="myObj.myForm"` assigns it to `$scope.myObj.myForm`.
|
||||
|
||||
Previously, it was possible to also use names such `name="my:name"`, because Angular used a special setter
|
||||
function for the form name. Now the general, more robust `$parse` setter is used.
|
||||
|
||||
The easiest way to migrate your code is therefore to remove all special characters from the `name` attribute.
|
||||
|
||||
If you need to keep the special characters, you can use the following directive, which will replace
|
||||
the `name` with a value that can be evaluated as an expression in the compile function, and then
|
||||
re-set the original name in the postLink function. This ensures that (1), the form is published on
|
||||
the scope, and (2), the form has the original name, which might be important if you are doing server-side
|
||||
form submission.
|
||||
|
||||
```js
|
||||
angular.module('myApp').directive('form', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
priority: 1000,
|
||||
compile: function(element, attrs) {
|
||||
var unsupportedCharacter = ':'; // change accordingly
|
||||
var originalName = attrs.name;
|
||||
if (attrs.name && attrs.name.indexOf(unsupportedCharacter) > 0) {
|
||||
attrs.$set('name', 'this["' + originalName + '"]');
|
||||
}
|
||||
|
||||
return postLinkFunction(scope, element) {
|
||||
// Don't trigger $observers
|
||||
element.setAttribute('name', originalName);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
### Templating (`ngRepeat`, `$compile`)
|
||||
|
||||
#### ngRepeat
|
||||
@@ -1055,7 +1117,7 @@ to:
|
||||
|
||||
Any class-based animation code that makes use of transitions
|
||||
and uses the setup CSS classes (such as class-add and class-remove) must now
|
||||
provide a empty transition value to ensure that its styling is applied right
|
||||
provide an empty transition value to ensure that its styling is applied right
|
||||
away. In other words if your animation code is expecting any styling to be
|
||||
applied that is defined in the setup class then it will not be applied
|
||||
"instantly" unless a `transition:0s none` value is present in the styling
|
||||
|
||||
@@ -257,7 +257,7 @@ the `$digest` phase. This delay is desirable, since it coalesces multiple model
|
||||
|
||||
2. **Watcher registration**
|
||||
|
||||
During template linking directives register {@link
|
||||
During template linking, directives register {@link
|
||||
ng.$rootScope.Scope#$watch watches} on the scope. These watches will be
|
||||
used to propagate model values to the DOM.
|
||||
|
||||
|
||||
@@ -9,6 +9,27 @@ This document explains some of AngularJS's security features and best practices
|
||||
keep in mind as you build your application.
|
||||
|
||||
|
||||
## Reporting a security issue
|
||||
|
||||
Email us at [security@angularjs.org](mailto:security@angularjs.org) to report any potential
|
||||
security issues in AngularJS.
|
||||
|
||||
Please keep in mind the points below about Angular's expression language.
|
||||
|
||||
|
||||
## Use the latest AngularJS possible
|
||||
|
||||
Like any software library, it is critical to keep AngularJS up to date. Please track the
|
||||
[CHANGELOG](https://github.com/angular/angular.js/blob/master/CHANGELOG.md) and make sure you are aware
|
||||
of upcoming security patches and other updates.
|
||||
|
||||
Be ready to update rapidly when new security-centric patches are available.
|
||||
|
||||
Those that stray from Angular standards (such as modifying Angular's core) may have difficulty updating,
|
||||
so keeping to AngularJS standards is not just a functionality issue, it's also critical in order to
|
||||
facilitate rapid security updates.
|
||||
|
||||
|
||||
## Expression Sandboxing
|
||||
|
||||
AngularJS's expressions are sandboxed not for security reasons, but instead to maintain a proper
|
||||
@@ -25,7 +46,8 @@ But if an attacker can change arbitrary HTML templates, there's nothing stopping
|
||||
<script>somethingEvil();</script>
|
||||
```
|
||||
|
||||
It's better to design your application in such a way that users cannot change client-side templates.
|
||||
**It's better to design your application in such a way that users cannot change client-side templates.**
|
||||
|
||||
For instance:
|
||||
|
||||
* Do not mix client and server templates
|
||||
@@ -33,7 +55,8 @@ For instance:
|
||||
* Do not run user input through `$scope.$eval`
|
||||
* Consider using {@link ng.directive:ngCsp CSP} (but don't rely only on CSP)
|
||||
|
||||
## Mixing client-side and server-side templates
|
||||
|
||||
### Mixing client-side and server-side templates
|
||||
|
||||
In general, we recommend against this because it can create unintended XSS vectors.
|
||||
|
||||
@@ -41,20 +64,62 @@ However, it's ok to mix server-side templating in the bootstrap template (`index
|
||||
as user input cannot be used on the server to output html that would then be processed by Angular
|
||||
in a way that would allow for arbitrary code execution.
|
||||
|
||||
For instance, you can use server-side templating to dynamically generate CSS, URLs, etc, but not
|
||||
for generating templates that are bootstrapped/compiled by Angular.
|
||||
**For instance, you can use server-side templating to dynamically generate CSS, URLs, etc, but not
|
||||
for generating templates that are bootstrapped/compiled by Angular.**
|
||||
|
||||
|
||||
## Reporting a security issue
|
||||
## HTTP Requests
|
||||
|
||||
Email us at [security@angularjs.org](mailto:security@angularjs.org) to report any potential
|
||||
security issues in AngularJS.
|
||||
Whenever your application makes requests to a server there are potential security issues that need
|
||||
to be blocked. Both server and the client must cooperate in order to eliminate these threats.
|
||||
Angular comes pre-configured with strategies that address these issues, but for this to work backend
|
||||
server cooperation is required.
|
||||
|
||||
Please keep in mind the above points about Angular's expression language.
|
||||
|
||||
### Cross Site Request Forgery (XSRF/CSRF)
|
||||
|
||||
Protection from XSRF is provided by using the double-submit cookie defense pattern.
|
||||
For more information please visit {@link $http#cross-site-request-forgery-xsrf-protection XSRF protection}.
|
||||
|
||||
### JSON Hijacking Protection
|
||||
|
||||
Protection from JSON Hijacking is provided if the server prefixes all JSON requests with following string `")]}',\n"`.
|
||||
Angular will automatically strip the prefix before processing it as JSON.
|
||||
For more information please visit {@link $http#json-vulnerability-protection JSON Hijacking Protection}.
|
||||
|
||||
|
||||
## Strict Contextual Escaping
|
||||
|
||||
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain contexts to require
|
||||
a value that is marked as safe to use for that context.
|
||||
|
||||
This mode is implemented by the {@link $sce} service and various core directives.
|
||||
|
||||
One example of such a context is rendering arbitrary content via the {@link ngBindHtml} directive. If the content is
|
||||
provided by a user there is a chance of Cross Site Scripting (XSS) attacks. The {@link ngBindHtml} directive will not
|
||||
render content that is not marked as safe by {@link $sce}. The {@link ngSanitize} module can be used to clean such
|
||||
user provided content and mark the content as safe.
|
||||
|
||||
**Be aware that marking untrusted data as safe via calls to {@link $sce#trustAsHtml `$sce.trustAsHtml`}, etc is
|
||||
dangerous and will lead to Cross Site Scripting exploits.**
|
||||
|
||||
For more information please visit {@link $sce} and {@link ngSanitize.$sanitize}.
|
||||
|
||||
## Using Local Caches
|
||||
|
||||
There are various places that the browser can store (or cache) data. Within Angular there are objects created by
|
||||
the {@link $cacheFactory}. These objects, such as {@link $templateCache} are used to store and retrieve data,
|
||||
primarily used by {@link $http} and the {@link script} directive to cache templates and other data.
|
||||
|
||||
Similarly the browser itself offers `localStorage` and `sessionStorage` objects for caching data.
|
||||
|
||||
**Attackers with local access can retrieve sensitive data from this cache even when users are not authenticated.**
|
||||
|
||||
For instance in a long running Single Page Application (SPA), one user may "log out", but then another user
|
||||
may access the application without refreshing, in which case all the cached data is still available.
|
||||
|
||||
For more information please visit [Web Storage Security](https://www.whitehatsec.com/blog/web-storage-security/).
|
||||
|
||||
## See also
|
||||
|
||||
* {@link ng.directive:ngCsp Content Security Policy}
|
||||
* {@link ng.$sce Strict Contextual Escaping}
|
||||
* {@link ngSanitize.$sanitize $sanitize}
|
||||
|
||||
@@ -43,7 +43,7 @@ subsystem takes care of the rest.
|
||||
<file name="script.js">
|
||||
angular.
|
||||
module('myServiceModule', []).
|
||||
controller('MyController', ['$scope','notify', function ($scope, notify) {
|
||||
controller('MyController', ['$scope', 'notify', function ($scope, notify) {
|
||||
$scope.callNotify = function(msg) {
|
||||
notify(msg);
|
||||
};
|
||||
@@ -138,9 +138,13 @@ batchModule.factory('batchLog', ['$interval', '$log', function($interval, $log)
|
||||
*/
|
||||
batchModule.factory('routeTemplateMonitor', ['$route', 'batchLog', '$rootScope',
|
||||
function($route, batchLog, $rootScope) {
|
||||
$rootScope.$on('$routeChangeSuccess', function() {
|
||||
batchLog($route.current ? $route.current.template : null);
|
||||
});
|
||||
return {
|
||||
startMonitoring: function() {
|
||||
$rootScope.$on('$routeChangeSuccess', function() {
|
||||
batchLog($route.current ? $route.current.template : null);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
```
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
@sortOrder 260
|
||||
@description
|
||||
|
||||
# Templates
|
||||
|
||||
In Angular, templates are written with HTML that contains Angular-specific elements and attributes.
|
||||
Angular combines the template with information from the model and controller to render the dynamic
|
||||
view that a user sees in the browser.
|
||||
|
||||
@@ -430,5 +430,50 @@ If your directive uses `templateUrl`, consider using
|
||||
to pre-compile HTML templates and thus avoid having to load them over HTTP during test execution.
|
||||
Otherwise you may run into issues if the test directory hierarchy differs from the application's.
|
||||
|
||||
## Testing Promises
|
||||
|
||||
When testing promises, it's important to know that the resolution of promises is tied to the {@link ng.$rootScope.Scope#$digest digest cycle}.
|
||||
That means a promise's `then`, `catch` and `finally` callback functions are only called after a digest has run.
|
||||
In tests, you can trigger a digest by calling a scope's {@link ng.$rootScope.Scope#$apply `$apply` function}.
|
||||
If you don't have a scope in your test, you can inject the {@link ng.$rootScope $rootScope} and call `$apply` on it.
|
||||
There is also an example of testing promises in the {@link ng.$q#testing `$q` service documentation}.
|
||||
|
||||
## Using `beforeAll()`
|
||||
|
||||
Jasmine's `beforeAll()` and mocha's `before()` hooks are often useful for sharing test setup - either to reduce test run-time or simply to make for more focussed test cases.
|
||||
|
||||
By default, ngMock will create an injector per test case to ensure your tests do not affect each other. However, if we want to use `beforeAll()`, ngMock will have to create the injector before any test cases are run, and share that injector through all the cases for that `describe`. That is where {@link angular.mock.module.sharedInjector module.sharedInjector()} comes in. When it's called within a `describe` block, a single injector is shared between all hooks and test cases run in that block.
|
||||
|
||||
In the example below we are testing a service that takes a long time to generate its answer. To avoid having all of the assertions we want to write in a single test case, {@link angular.mock.module.sharedInjector module.sharedInjector()} and Jasmine's `beforeAll()` are used to run the service only once. The test cases then all make assertions about the properties added to the service instance.
|
||||
|
||||
```javascript
|
||||
describe("Deep Thought", function() {
|
||||
|
||||
module.sharedInjector();
|
||||
|
||||
beforeAll(module("UltimateQuestion"));
|
||||
|
||||
beforeAll(inject(function(DeepThought) {
|
||||
expect(DeepThought.answer).toBeUndefined();
|
||||
DeepThought.generateAnswer();
|
||||
}));
|
||||
|
||||
it("has calculated the answer correctly", inject(function(DeepThought) {
|
||||
// Because of sharedInjector, we have access to the instance of the DeepThought service
|
||||
// that was provided to the beforeAll() hook. Therefore we can test the generated answer
|
||||
expect(DeepThought.answer).toBe(42);
|
||||
}));
|
||||
|
||||
it("has calculated the answer within the expected time", inject(function(DeepThought) {
|
||||
expect(DeepThought.runTimeMillennia).toBeLessThan(8000);
|
||||
}));
|
||||
|
||||
it("has double checked the answer", inject(function(DeepThought) {
|
||||
expect(DeepThought.absolutelySureItIsTheRightAnswer).toBe(true);
|
||||
}));
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
## Sample project
|
||||
See the [angular-seed](https://github.com/angular/angular-seed) project for an example.
|
||||
|
||||
@@ -16,9 +16,9 @@ becoming an Angular expert.
|
||||
starter app with a directory layout, test harness, and scripts to begin building your application.
|
||||
|
||||
|
||||
#Further Steps
|
||||
# Further Steps
|
||||
|
||||
##Watch Videos
|
||||
## Watch Videos
|
||||
|
||||
If you haven’t had a chance to watch the videos from the homepage, please check out:
|
||||
|
||||
@@ -29,13 +29,13 @@ If you haven’t had a chance to watch the videos from the homepage, please chec
|
||||
And visit our [YouTube channel](http://www.youtube.com/user/angularjs) for more AngularJS video presentations and
|
||||
tutorials.
|
||||
|
||||
##Subscribe
|
||||
## Subscribe
|
||||
|
||||
* Subscribe to the [mailing list](http://groups.google.com/forum/?fromgroups#!forum/angular). Ask questions here!
|
||||
* Follow us on [Twitter](https://twitter.com/intent/follow?original_referer=http%3A%2F%2Fangularjs.org%2F®ion=follow_link&screen_name=angularjs&source=followbutton&variant=2.0)
|
||||
* Add us to your circles on [Google+](https://plus.google.com/110323587230527980117/posts)
|
||||
|
||||
##Read more
|
||||
## Read more
|
||||
|
||||
The AngularJS documentation includes the {@link guide/index Developer Guide} covering concepts and the
|
||||
{@link ./api API Reference} for syntax and usage.
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
var fs = require('fs');
|
||||
var _ = require('lodash');
|
||||
var stripJsonComments = require('strip-json-comments');
|
||||
|
||||
var gulp = require('gulp');
|
||||
var log = require('gulp-util').log;
|
||||
var concat = require('gulp-concat');
|
||||
@@ -25,6 +29,23 @@ var ignoredFiles = '!src/angular.bind.js';
|
||||
var assets = 'app/assets/**/*';
|
||||
|
||||
|
||||
var getJshintConfig = function(filepath) {
|
||||
return JSON.parse(stripJsonComments(fs.readFileSync(filepath, {encoding: 'utf-8'})));
|
||||
};
|
||||
|
||||
var getMergedJshintConfig = function(filepath) {
|
||||
// "extends" doesn't work in configuration passed by an object, we need to do the extending ourselves.
|
||||
var config = getJshintConfig(filepath);
|
||||
var baseConfig = getJshintConfig('../.jshintrc-base');
|
||||
_.merge(config, baseConfig);
|
||||
delete config.extends;
|
||||
|
||||
// Examples don't run in strict mode; accept that for now.
|
||||
config.strict = false;
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
var copyComponent = function(component, pattern, sourceFolder, packageFile) {
|
||||
pattern = pattern || '/**/*';
|
||||
sourceFolder = sourceFolder || bowerFolder;
|
||||
@@ -90,17 +111,35 @@ gulp.task('assets', ['bower'], function() {
|
||||
|
||||
gulp.task('doc-gen', ['bower'], function() {
|
||||
var dgeni = new Dgeni([require('./config')]);
|
||||
return dgeni.generate().catch(function(error) {
|
||||
return dgeni.generate().catch(function() {
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
// JSHint the example and protractor test files
|
||||
gulp.task('jshint', ['doc-gen'], function() {
|
||||
gulp.src([outputFolder + '/ptore2e/**/*.js', outputFolder + '/examples/**/*.js'])
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter('jshint-stylish'))
|
||||
.pipe(jshint.reporter('fail'));
|
||||
var examplesConfig = getMergedJshintConfig('../docs/app/test/.jshintrc');
|
||||
// Some tests use `alert` which is not assumed to be available even with `"browser": true`.
|
||||
examplesConfig.globals.alert = false;
|
||||
|
||||
var protractorConfig = getMergedJshintConfig('../docs/app/e2e/.jshintrc');
|
||||
|
||||
return merge(
|
||||
gulp.src([
|
||||
outputFolder + '/examples/**/*.js',
|
||||
'!' + outputFolder + '/examples/**/protractor.js',
|
||||
])
|
||||
.pipe(jshint(examplesConfig))
|
||||
.pipe(jshint.reporter('jshint-stylish'))
|
||||
.pipe(jshint.reporter('fail')),
|
||||
gulp.src([
|
||||
outputFolder + '/ptore2e/**/*.js',
|
||||
outputFolder + '/examples/**/protractor.js',
|
||||
])
|
||||
.pipe(jshint(protractorConfig))
|
||||
.pipe(jshint.reporter('jshint-stylish'))
|
||||
.pipe(jshint.reporter('fail'))
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 30 KiB |
@@ -9,4 +9,8 @@ npm run test-i18n
|
||||
|
||||
node src/closureSlurper.js
|
||||
|
||||
npm run test-i18n-ucd
|
||||
|
||||
echo "Generating ngParseExt"
|
||||
node ucd/src/extract.js
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ describe("serializeContent", function() {
|
||||
it("should not transform arrays into objects", function() {
|
||||
var serializedContent = closureI18nExtractor.serializeContent(newTestLocaleInfo().fr_CA);
|
||||
var deserializedLocale = eval("(" + serializedContent + ")");
|
||||
expect(deserializedLocale.DATETIME_FORMATS.MONTH.length).not.toBe(undefined);
|
||||
expect(deserializedLocale.DATETIME_FORMATS.MONTH.length).not.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
var extractValues = require('../src/extractValues.js').extractValues;
|
||||
var stream = require('stream');
|
||||
|
||||
function StringStream(str) {
|
||||
return new stream.Readable({
|
||||
read: function(n) {
|
||||
this.push(str);
|
||||
str = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('extractValues', function() {
|
||||
it('should extract the values from the xml', function(done) {
|
||||
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><char cp="0002" IDS="Y"></char><char cp="0003" IDS="N"></char></repertoire></ucd>';
|
||||
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
|
||||
expect(values).toEqual({ IDS_Y : [ [ '0001', '0002' ] ] });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should extract the values from the xml if the last element matches', function(done) {
|
||||
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><char cp="0002" IDS="Y"></char><char cp="0003" IDS="Y"></char></repertoire></ucd>';
|
||||
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
|
||||
expect(values).toEqual({ IDS_Y : [ [ '0001', '0003' ] ] });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should support `reserved`', function(done) {
|
||||
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><reserved first-cp="0002" last-cp="0005" IDS="N"></reserved><char cp="0006" IDS="Y"></char></repertoire></ucd>';
|
||||
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
|
||||
expect(values).toEqual({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ] });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should support `surrogate`', function(done) {
|
||||
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><surrogate first-cp="0002" last-cp="0005" IDS="N"></surrogate><char cp="0006" IDS="Y"></char></repertoire></ucd>';
|
||||
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
|
||||
expect(values).toEqual({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ] });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should support `noncharactere`', function(done) {
|
||||
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><noncharacter first-cp="0002" last-cp="0005" IDS="N"></noncharacter><char cp="0006" IDS="Y"></char></repertoire></ucd>';
|
||||
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
|
||||
expect(values).toEqual({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ] });
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
var generateCodeModule = require('../src/generateCode.js');
|
||||
var generateCode = generateCodeModule.generateCode;
|
||||
var generateFunction = generateCodeModule.generateFunction;
|
||||
|
||||
describe('generateFunction', function() {
|
||||
it('should generate function with ranges', function() {
|
||||
expect(generateFunction([ [ '0001', '0003' ] ], 'IDS_Y')).toEqual('\
|
||||
function IDS_Y(cp) {\n\
|
||||
if (0x0001 <= cp && cp <= 0x0003) return true;\n\
|
||||
return false;\n\
|
||||
}\n');
|
||||
});
|
||||
|
||||
it('should generate function with multiple ranges', function() {
|
||||
expect(generateFunction([ [ '0001', '0003' ], [ '0005', '0009'] ], 'IDS_Y')).toEqual('\
|
||||
function IDS_Y(cp) {\n\
|
||||
if (0x0001 <= cp && cp <= 0x0003) return true;\n\
|
||||
if (0x0005 <= cp && cp <= 0x0009) return true;\n\
|
||||
return false;\n\
|
||||
}\n');
|
||||
});
|
||||
|
||||
it('should generate function with unique values', function() {
|
||||
expect(generateFunction([ [ '0001', '0001' ], [ '0005', '0009'] ], 'IDS_Y')).toEqual('\
|
||||
function IDS_Y(cp) {\n\
|
||||
if (cp === 0x0001) return true;\n\
|
||||
if (0x0005 <= cp && cp <= 0x0009) return true;\n\
|
||||
return false;\n\
|
||||
}\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateCode', function() {
|
||||
it('should generate the function for all the values', function() {
|
||||
expect(generateCode({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ], IDC_Y : [ [ '0002', '0002' ], [ '0007', '0007' ] ] })).toEqual('\
|
||||
/******************************************************\n\
|
||||
* Generated file, do not modify *\n\
|
||||
* *\n\
|
||||
*****************************************************/\n\
|
||||
"use strict";\n\
|
||||
function IDS_Y(cp) {\n\
|
||||
if (cp === 0x0001) return true;\n\
|
||||
if (cp === 0x0006) return true;\n\
|
||||
return false;\n\
|
||||
}\n\
|
||||
function IDC_Y(cp) {\n\
|
||||
if (cp === 0x0002) return true;\n\
|
||||
if (cp === 0x0007) return true;\n\
|
||||
return false;\n\
|
||||
}\n\
|
||||
');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
|
||||
var fs = require('fs');
|
||||
var zlib = require('zlib');
|
||||
var extractValues = require('./extractValues').extractValues;
|
||||
var generateCode = require('./generateCode').generateCode;
|
||||
var generateextractValues = require('./extractValues').extractValues;
|
||||
// ID_Start and ID_Continue
|
||||
var propertiesToExtract = {'IDS': 'Y', 'IDC': 'Y'};
|
||||
|
||||
function main() {
|
||||
extractValues(
|
||||
fs.createReadStream('./ucd/src/ucd.all.flat.xml.gz').pipe(zlib.createGunzip()),
|
||||
propertiesToExtract,
|
||||
writeFile);
|
||||
|
||||
function writeFile(validRanges) {
|
||||
var code = generateCode(validRanges);
|
||||
try {
|
||||
var stats = fs.lstatSync('../src/ngParseExt');
|
||||
} catch (e) {
|
||||
fs.mkdirSync('../src/ngParseExt');
|
||||
}
|
||||
fs.writeFile('../src/ngParseExt/ucd.js', code);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Extract values from a stream.
|
||||
*/
|
||||
|
||||
exports.extractValues = extractValues;
|
||||
|
||||
var sax = require('sax/lib/sax');
|
||||
var saxStrict = true;
|
||||
var saxOptions = {};
|
||||
var validXMLTagNames = { char: 'Y', reserved: 'Y', surrogate: 'Y', noncharacter: 'Y'};
|
||||
|
||||
function extractValues(stream, propertiesToExtract, callback) {
|
||||
var saxStream = sax.createStream(saxStrict, saxOptions);
|
||||
var firstValid = {};
|
||||
var lastValid = {};
|
||||
var keys = Object.keys(propertiesToExtract);
|
||||
var keyValues = keys.map(function(k) { return propertiesToExtract[k]; });
|
||||
var validRanges = {};
|
||||
|
||||
for (var i in keys) {
|
||||
validRanges[keys[i] + '_' + keyValues[i]] = [];
|
||||
}
|
||||
saxStream.onopentag = onOpenTag;
|
||||
stream
|
||||
.pipe(saxStream)
|
||||
.on('end', doCallback);
|
||||
|
||||
function onOpenTag(node) {
|
||||
var property;
|
||||
if (validXMLTagNames[node.name]) {
|
||||
for (var i in keys) {
|
||||
property = keyValues[i];
|
||||
if (node.attributes[keys[i]] === property) validProperty(keys[i] + '_' + property, node);
|
||||
else invalidProperty(keys[i] + '_' + property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validProperty(property, node) {
|
||||
if (!firstValid[property]) firstValid[property] =
|
||||
node.attributes.cp || node.attributes['first-cp'];
|
||||
lastValid[property] = node.attributes.cp || node.attributes['last-cp'];
|
||||
}
|
||||
|
||||
function invalidProperty(property) {
|
||||
if (!firstValid[property]) return;
|
||||
validRanges[property].push([firstValid[property], lastValid[property]]);
|
||||
firstValid[property] = null;
|
||||
}
|
||||
|
||||
function doCallback() {
|
||||
for (var i in keys) {
|
||||
property = keys[i] + '_' + keyValues[i];
|
||||
invalidProperty(property);
|
||||
}
|
||||
callback(validRanges);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
exports.generateCode = generateCode;
|
||||
exports.generateFunction = generateFunction;
|
||||
|
||||
function generateCode(validRanges) {
|
||||
var code = '/******************************************************\n' +
|
||||
' * Generated file, do not modify *\n' +
|
||||
' * *\n' +
|
||||
' *****************************************************/\n' +
|
||||
'"use strict";\n';
|
||||
var keys = Object.keys(validRanges);
|
||||
for (var i in keys) {
|
||||
code += generateFunction(validRanges[keys[i]], keys[i]);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
|
||||
function generateFunction(positiveElements, functionName) {
|
||||
var result = [];
|
||||
result.push('function ', functionName, '(cp) {\n');
|
||||
positiveElements.forEach(function(range) {
|
||||
if (range[0] === range[1]) {
|
||||
result.push(' if (cp === 0x', range[0], ')');
|
||||
} else {
|
||||
result.push(' if (0x', range[0], ' <= cp && cp <= 0x', range[1], ')');
|
||||
}
|
||||
result.push(' return true;\n');
|
||||
});
|
||||
result.push(' return false;\n}\n');
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
@@ -37,19 +37,25 @@ module.exports = function(config, specificOptions) {
|
||||
'SL_Chrome': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'chrome',
|
||||
version: '45'
|
||||
version: '47'
|
||||
},
|
||||
'SL_Firefox': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'firefox',
|
||||
version: '39'
|
||||
version: '43'
|
||||
},
|
||||
'SL_Safari': {
|
||||
'SL_Safari_8': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.10',
|
||||
version: '8'
|
||||
},
|
||||
'SL_Safari_9': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.11',
|
||||
version: '9'
|
||||
},
|
||||
'SL_IE_9': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
|
||||
@@ -287,7 +287,7 @@ module.exports = {
|
||||
rewrite: function(){
|
||||
return function(req, res, next){
|
||||
var REWRITE = /\/(guide|api|cookbook|misc|tutorial|error).*$/,
|
||||
IGNORED = /(\.(css|js|png|jpg|gif)$|partials\/.*\.html$)/,
|
||||
IGNORED = /(\.(css|js|png|jpg|gif|svg)$|partials\/.*\.html$)/,
|
||||
match;
|
||||
|
||||
if (!IGNORED.test(req.url) && (match = req.url.match(REWRITE))) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"license": "MIT",
|
||||
"branchVersion": "^1.5.0-beta.2",
|
||||
"branchVersion": "1.5.x",
|
||||
"branchPattern": "1.5.*",
|
||||
"distTag": "beta",
|
||||
"distTag": "latest",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
@@ -17,7 +17,8 @@
|
||||
"preinstall": "node scripts/npm/check-node-modules.js --purge",
|
||||
"postinstall": "node scripts/npm/copy-npm-shrinkwrap.js",
|
||||
"commit": "git-cz",
|
||||
"test-i18n": "jasmine-node i18n/spec"
|
||||
"test-i18n": "jasmine-node i18n/spec",
|
||||
"test-i18n-ucd": "jasmine-node i18n/ucd/spec"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-benchpress": "0.x.x",
|
||||
@@ -29,7 +30,7 @@
|
||||
"commitizen": "^2.3.0",
|
||||
"cz-conventional-changelog": "1.1.4",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.11.0",
|
||||
"dgeni-packages": "^0.12.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"glob": "^6.0.1",
|
||||
"grunt": "~0.4.2",
|
||||
@@ -38,7 +39,7 @@
|
||||
"grunt-contrib-compress": "~0.12.0",
|
||||
"grunt-contrib-connect": "~0.8.0",
|
||||
"grunt-contrib-copy": "~0.6.0",
|
||||
"grunt-contrib-jshint": "~0.10.0",
|
||||
"grunt-contrib-jshint": "^1.0.0",
|
||||
"grunt-ddescribe-iit": "~0.0.1",
|
||||
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"grunt-jscs": "^2.1.0",
|
||||
@@ -47,19 +48,21 @@
|
||||
"gulp": "~3.8.0",
|
||||
"gulp-concat": "^2.4.1",
|
||||
"gulp-foreach": "0.0.1",
|
||||
"gulp-jshint": "~1.4.2",
|
||||
"gulp-jshint": "^2.0.0",
|
||||
"gulp-rename": "^1.2.0",
|
||||
"gulp-sourcemaps": "^1.2.2",
|
||||
"gulp-uglify": "^1.0.1",
|
||||
"gulp-util": "^3.0.1",
|
||||
"jasmine-core": "^2.4.0",
|
||||
"jasmine-node": "^2.0.0",
|
||||
"jasmine-reporters": "~1.0.1",
|
||||
"jshint-stylish": "~1.0.0",
|
||||
"jshint": "^2.9.1",
|
||||
"jshint-stylish": "^2.1.0",
|
||||
"karma": "^0.13.19",
|
||||
"karma-browserstack-launcher": "^0.1.8",
|
||||
"karma-chrome-launcher": "^0.2.2",
|
||||
"karma-firefox-launcher": "^0.1.7",
|
||||
"karma-jasmine": "^0.1.6",
|
||||
"karma-jasmine": "^0.3.7",
|
||||
"karma-junit-reporter": "^0.3.8",
|
||||
"karma-ng-scenario": "^0.1.0",
|
||||
"karma-sauce-launcher": "^0.3.0",
|
||||
@@ -74,10 +77,12 @@
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
"rewire": "~2.1.0",
|
||||
"sax": "^1.1.1",
|
||||
"semver": "~4.0.3",
|
||||
"shelljs": "~0.3.0",
|
||||
"sorted-object": "^1.0.0",
|
||||
"stringmap": "^0.2.2"
|
||||
"stringmap": "^0.2.2",
|
||||
"strip-json-comments": "^2.0.1"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'chrome',
|
||||
platform: 'MAC',
|
||||
version: '34'
|
||||
version: '49'
|
||||
}),
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'firefox',
|
||||
@@ -18,7 +18,7 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'safari',
|
||||
platform: 'MAC',
|
||||
version: '7'
|
||||
version: '9'
|
||||
})
|
||||
];
|
||||
} else {
|
||||
@@ -28,8 +28,8 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
config.multiCapabilities = [
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'chrome',
|
||||
platform: 'OS X 10.9',
|
||||
version: '34'
|
||||
platform: 'OS X 10.11',
|
||||
version: '48'
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'firefox',
|
||||
@@ -37,8 +37,8 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.9',
|
||||
version: '7'
|
||||
platform: 'OS X 10.11',
|
||||
version: '9'
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ REPOS=(
|
||||
angular-message-format
|
||||
angular-messages
|
||||
angular-mocks
|
||||
angular-parse-ext
|
||||
angular-resource
|
||||
angular-route
|
||||
angular-sanitize
|
||||
|
||||
@@ -4,11 +4,11 @@ echo "#################################"
|
||||
echo "#### Jenkins Build ############"
|
||||
echo "#################################"
|
||||
|
||||
source scripts/jenkins/set-node-version.sh
|
||||
|
||||
# Enable tracing and exit on first failure
|
||||
set -xe
|
||||
|
||||
scripts/jenkins/set-node-version.sh
|
||||
|
||||
# This is the default set of browsers to use on the CI server unless overridden via env variable
|
||||
if [[ -z "$BROWSERS" ]]
|
||||
then
|
||||
|
||||
@@ -35,7 +35,7 @@ function init {
|
||||
}
|
||||
|
||||
function build {
|
||||
./set-node-version.sh
|
||||
source ./set-node-version.sh
|
||||
cd ../..
|
||||
|
||||
npm install -g grunt-cli
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
source ~/.nvm/nvm.sh
|
||||
|
||||
# Use node.js at 4.2.x
|
||||
nvm install 4.2
|
||||
nvm install 4.4
|
||||
|
||||
@@ -11,7 +11,7 @@ elif [ $JOB = "unit" ]; then
|
||||
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
|
||||
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_iOS"
|
||||
else
|
||||
BROWSERS="SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11,SL_iOS"
|
||||
BROWSERS="SL_Chrome,SL_Firefox,SL_Safari_8,SL_Safari_9,SL_IE_9,SL_IE_10,SL_IE_11,SL_iOS"
|
||||
fi
|
||||
|
||||
grunt test:promises-aplus
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"extends": "../.jshintrc-base",
|
||||
"browser": true,
|
||||
"globals": {
|
||||
"window": false,
|
||||
|
||||
/* auto/injector.js */
|
||||
"createInjector": false,
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
* @ngdoc module
|
||||
* @name ng
|
||||
* @module ng
|
||||
* @installation
|
||||
* @description
|
||||
*
|
||||
* # ng (core module)
|
||||
@@ -168,7 +169,7 @@ var
|
||||
* documentMode is an IE-only property
|
||||
* http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
|
||||
*/
|
||||
msie = document.documentMode;
|
||||
msie = window.document.documentMode;
|
||||
|
||||
|
||||
/**
|
||||
@@ -216,7 +217,7 @@ function isArrayLike(obj) {
|
||||
*
|
||||
* Unlike ES262's
|
||||
* [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
|
||||
* Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
|
||||
* providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
|
||||
* return the value provided.
|
||||
*
|
||||
```js
|
||||
@@ -457,7 +458,7 @@ function identity($) {return $;}
|
||||
identity.$inject = [];
|
||||
|
||||
|
||||
function valueFn(value) {return function() {return value;};}
|
||||
function valueFn(value) {return function valueRef() {return value;};}
|
||||
|
||||
function hasCustomToString(obj) {
|
||||
return isFunction(obj.toString) && obj.toString !== toString;
|
||||
@@ -819,7 +820,7 @@ function copy(source, destination) {
|
||||
|
||||
function copyRecurse(source, destination) {
|
||||
var h = destination.$$hashKey;
|
||||
var result, key;
|
||||
var key;
|
||||
if (isArray(source)) {
|
||||
for (var i = 0, ii = source.length; i < ii; i++) {
|
||||
destination.push(copyElement(source[i]));
|
||||
@@ -913,6 +914,9 @@ function copy(source, destination) {
|
||||
var re = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
|
||||
re.lastIndex = source.lastIndex;
|
||||
return re;
|
||||
|
||||
case '[object Blob]':
|
||||
return new source.constructor([source], {type: source.type});
|
||||
}
|
||||
|
||||
if (isFunction(source.cloneNode)) {
|
||||
@@ -975,6 +979,41 @@ function shallowCopy(src, dst) {
|
||||
* @param {*} o1 Object or value to compare.
|
||||
* @param {*} o2 Object or value to compare.
|
||||
* @returns {boolean} True if arguments are equal.
|
||||
*
|
||||
* @example
|
||||
<example module="equalsExample" name="equalsExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ExampleController">
|
||||
<form novalidate>
|
||||
<h3>User 1</h3>
|
||||
Name: <input type="text" ng-model="user1.name">
|
||||
Age: <input type="number" ng-model="user1.age">
|
||||
|
||||
<h3>User 2</h3>
|
||||
Name: <input type="text" ng-model="user2.name">
|
||||
Age: <input type="number" ng-model="user2.age">
|
||||
|
||||
<div>
|
||||
<br/>
|
||||
<input type="button" value="Compare" ng-click="compare()">
|
||||
</div>
|
||||
User 1: <pre>{{user1 | json}}</pre>
|
||||
User 2: <pre>{{user2 | json}}</pre>
|
||||
Equal: <pre>{{result}}</pre>
|
||||
</form>
|
||||
</div>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.user1 = {};
|
||||
$scope.user2 = {};
|
||||
$scope.result;
|
||||
$scope.compare = function() {
|
||||
$scope.result = angular.equals($scope.user1, $scope.user2);
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
function equals(o1, o2) {
|
||||
if (o1 === o2) return true;
|
||||
@@ -1021,8 +1060,8 @@ var csp = function() {
|
||||
if (!isDefined(csp.rules)) {
|
||||
|
||||
|
||||
var ngCspElement = (document.querySelector('[ng-csp]') ||
|
||||
document.querySelector('[data-ng-csp]'));
|
||||
var ngCspElement = (window.document.querySelector('[ng-csp]') ||
|
||||
window.document.querySelector('[data-ng-csp]'));
|
||||
|
||||
if (ngCspElement) {
|
||||
var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
|
||||
@@ -1097,7 +1136,7 @@ var jq = function() {
|
||||
var i, ii = ngAttrPrefixes.length, prefix, name;
|
||||
for (i = 0; i < ii; ++i) {
|
||||
prefix = ngAttrPrefixes[i];
|
||||
if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
|
||||
if (el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
|
||||
name = el.getAttribute(prefix + 'jq');
|
||||
break;
|
||||
}
|
||||
@@ -1162,7 +1201,7 @@ function toJsonReplacer(key, value) {
|
||||
val = undefined;
|
||||
} else if (isWindow(value)) {
|
||||
val = '$WINDOW';
|
||||
} else if (value && document === value) {
|
||||
} else if (value && window.document === value) {
|
||||
val = '$DOCUMENT';
|
||||
} else if (isScope(value)) {
|
||||
val = '$SCOPE';
|
||||
@@ -1402,10 +1441,17 @@ function getNgAttribute(element, ngAttr) {
|
||||
* designates the **root element** of the application and is typically placed near the root element
|
||||
* of the page - e.g. on the `<body>` or `<html>` tags.
|
||||
*
|
||||
* Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
|
||||
* found in the document will be used to define the root element to auto-bootstrap as an
|
||||
* application. To run multiple applications in an HTML document you must manually bootstrap them using
|
||||
* {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
|
||||
* There are a few things to keep in mind when using `ngApp`:
|
||||
* - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
|
||||
* found in the document will be used to define the root element to auto-bootstrap as an
|
||||
* application. To run multiple applications in an HTML document you must manually bootstrap them using
|
||||
* {@link angular.bootstrap} instead.
|
||||
* - AngularJS applications cannot be nested within each other.
|
||||
* - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`.
|
||||
* This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and
|
||||
* {@link ngRoute.ngView `ngView`}.
|
||||
* Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
|
||||
* causing animations to stop working and making the injector inaccessible from outside the app.
|
||||
*
|
||||
* 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. It
|
||||
@@ -1545,16 +1591,25 @@ function angularInit(element, bootstrap) {
|
||||
* @description
|
||||
* Use this function to manually start up angular application.
|
||||
*
|
||||
* See: {@link guide/bootstrap Bootstrap}
|
||||
*
|
||||
* Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
|
||||
* They must use {@link ng.directive:ngApp ngApp}.
|
||||
* For more information, see the {@link guide/bootstrap Bootstrap guide}.
|
||||
*
|
||||
* Angular will detect if it has been loaded into the browser more than once and only allow the
|
||||
* first loaded script to be bootstrapped and will report a warning to the browser console for
|
||||
* each of the subsequent scripts. This prevents strange results in applications, where otherwise
|
||||
* multiple instances of Angular try to work on the DOM.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually.
|
||||
* They must use {@link ng.directive:ngApp ngApp}.
|
||||
* </div>
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion},
|
||||
* such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}.
|
||||
* Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
|
||||
* causing animations to stop working and making the injector inaccessible from outside the app.
|
||||
* </div>
|
||||
*
|
||||
* ```html
|
||||
* <!doctype html>
|
||||
* <html>
|
||||
@@ -1598,11 +1653,11 @@ function bootstrap(element, modules, config) {
|
||||
element = jqLite(element);
|
||||
|
||||
if (element.injector()) {
|
||||
var tag = (element[0] === document) ? 'document' : startingTag(element);
|
||||
var tag = (element[0] === window.document) ? 'document' : startingTag(element);
|
||||
//Encode angle brackets to prevent input from being sanitized to empty string #8683
|
||||
throw ngMinErr(
|
||||
'btstrpd',
|
||||
"App Already Bootstrapped with this Element '{0}'",
|
||||
"App already bootstrapped with this element '{0}'",
|
||||
tag.replace(/</,'<').replace(/>/,'>'));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
if (window.angular.bootstrap) {
|
||||
//AngularJS is already loaded, so we can return here...
|
||||
console.log('WARNING: Tried to load angular more than once.');
|
||||
if (window.console) {
|
||||
console.log('WARNING: Tried to load angular more than once.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, document, undefined) {
|
||||
(function(window) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
jqLite(document).ready(function() {
|
||||
angularInit(document, bootstrap);
|
||||
jqLite(window.document).ready(function() {
|
||||
angularInit(window.document, bootstrap);
|
||||
});
|
||||
|
||||
})(window, document);
|
||||
})(window);
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
/**
|
||||
* @ngdoc module
|
||||
* @name auto
|
||||
* @installation
|
||||
* @description
|
||||
*
|
||||
* Implicit module which gets automatically added to each {@link auto.$injector $injector}.
|
||||
@@ -70,7 +71,7 @@ var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
||||
var $injectorMinErr = minErr('$injector');
|
||||
|
||||
function extractArgs(fn) {
|
||||
var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
|
||||
var fnText = Function.prototype.toString.call(fn).replace(STRIP_COMMENTS, ''),
|
||||
args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
|
||||
return args;
|
||||
}
|
||||
@@ -544,14 +545,13 @@ function annotate(fn, strictDi, name) {
|
||||
* @description
|
||||
*
|
||||
* Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
|
||||
* number, an array, an object or a function. This is short for registering a service where its
|
||||
* number, an array, an object or a function. This is short for registering a service where its
|
||||
* provider's `$get` property is a factory function that takes no arguments and returns the **value
|
||||
* service**.
|
||||
* service**. That also means it is not possible to inject other services into a value service.
|
||||
*
|
||||
* Value services are similar to constant services, except that they cannot be injected into a
|
||||
* module configuration function (see {@link angular.Module#config}) but they can be overridden by
|
||||
* an Angular
|
||||
* {@link auto.$provide#decorator decorator}.
|
||||
* an Angular {@link auto.$provide#decorator decorator}.
|
||||
*
|
||||
* @param {string} name The name of the instance.
|
||||
* @param {*} value The value.
|
||||
@@ -576,8 +576,11 @@ function annotate(fn, strictDi, name) {
|
||||
* @name $provide#constant
|
||||
* @description
|
||||
*
|
||||
* Register a **constant service**, such as a string, a number, an array, an object or a function,
|
||||
* with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
|
||||
* Register a **constant service** with the {@link auto.$injector $injector}, such as a string,
|
||||
* a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not
|
||||
* possible to inject other services into a constant.
|
||||
*
|
||||
* But unlike {@link auto.$provide#value value}, a constant can be
|
||||
* injected into a module configuration function (see {@link angular.Module#config}) and it cannot
|
||||
* be overridden by an Angular {@link auto.$provide#decorator decorator}.
|
||||
*
|
||||
|
||||
@@ -114,6 +114,9 @@
|
||||
* - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
|
||||
* parent element is reached.
|
||||
*
|
||||
* @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See
|
||||
* https://github.com/angular/angular.js/issues/14251 for more information.
|
||||
*
|
||||
* @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
|
||||
* @returns {Object} jQuery object.
|
||||
*/
|
||||
@@ -240,7 +243,7 @@ function jqLiteBuildFragment(html, context) {
|
||||
}
|
||||
|
||||
function jqLiteParseHTML(html, context) {
|
||||
context = context || document;
|
||||
context = context || window.document;
|
||||
var parsed;
|
||||
|
||||
if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
|
||||
@@ -266,7 +269,7 @@ function jqLiteWrapNode(node, wrapper) {
|
||||
|
||||
|
||||
// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
|
||||
var jqLiteContains = Node.prototype.contains || function(arg) {
|
||||
var jqLiteContains = window.Node.prototype.contains || function(arg) {
|
||||
// jshint bitwise: false
|
||||
return !!(this.compareDocumentPosition(arg) & 16);
|
||||
// jshint bitwise: true
|
||||
@@ -538,8 +541,8 @@ var JQLitePrototype = JQLite.prototype = {
|
||||
}
|
||||
|
||||
// check if document is already loaded
|
||||
if (document.readyState === 'complete') {
|
||||
setTimeout(trigger);
|
||||
if (window.document.readyState === 'complete') {
|
||||
window.setTimeout(trigger);
|
||||
} else {
|
||||
this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
|
||||
// we can not use jqLite since we are not done loading and jQuery could be loaded later.
|
||||
|
||||
@@ -197,9 +197,9 @@ function setupModuleLoader(window) {
|
||||
* @ngdoc method
|
||||
* @name angular.Module#decorator
|
||||
* @module ng
|
||||
* @param {string} The name of the service to decorate.
|
||||
* @param {Function} This function will be invoked when the service needs to be
|
||||
* instantiated and should return the decorated service instance.
|
||||
* @param {string} name The name of the service to decorate.
|
||||
* @param {Function} decorFn This function will be invoked when the service needs to be
|
||||
* instantiated and should return the decorated service instance.
|
||||
* @description
|
||||
* See {@link auto.$provide#decorator $provide.decorator()}.
|
||||
*/
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
(function(window, angular) {
|
||||
|
||||
@@ -54,7 +54,7 @@ function prepareAnimateOptions(options) {
|
||||
}
|
||||
|
||||
var $$CoreAnimateJsProvider = function() {
|
||||
this.$get = function() {};
|
||||
this.$get = noop;
|
||||
};
|
||||
|
||||
// this is prefixed with Core since it conflicts with
|
||||
@@ -326,6 +326,9 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* // remove all the animation event listeners listening for `enter`
|
||||
* $animate.off('enter');
|
||||
*
|
||||
* // remove listeners for all animation events from the container element
|
||||
* $animate.off(container);
|
||||
*
|
||||
* // remove all the animation event listeners listening for `enter` on the given element and its children
|
||||
* $animate.off('enter', container);
|
||||
*
|
||||
@@ -334,7 +337,9 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* $animate.off('enter', container, callback);
|
||||
* ```
|
||||
*
|
||||
* @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
|
||||
* @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move,
|
||||
* addClass, removeClass, etc...), or the container element. If it is the element, all other
|
||||
* arguments are ignored.
|
||||
* @param {DOMElement=} container the container element the event listener was placed on
|
||||
* @param {Function=} callback the callback function that was registered as the listener
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
*/
|
||||
function Browser(window, document, $log, $sniffer) {
|
||||
var self = this,
|
||||
rawDocument = document[0],
|
||||
location = window.location,
|
||||
history = window.history,
|
||||
setTimeout = window.setTimeout,
|
||||
@@ -87,7 +86,14 @@ function Browser(window, document, $log, $sniffer) {
|
||||
var cachedState, lastHistoryState,
|
||||
lastBrowserUrl = location.href,
|
||||
baseElement = document.find('base'),
|
||||
pendingLocation = null;
|
||||
pendingLocation = null,
|
||||
getCurrentState = !$sniffer.history ? noop : function getCurrentState() {
|
||||
try {
|
||||
return history.state;
|
||||
} catch (e) {
|
||||
// MSIE can reportedly throw when there is no state (UNCONFIRMED).
|
||||
}
|
||||
};
|
||||
|
||||
cacheState();
|
||||
lastHistoryState = cachedState;
|
||||
@@ -195,14 +201,6 @@ function Browser(window, document, $log, $sniffer) {
|
||||
fireUrlChange();
|
||||
}
|
||||
|
||||
function getCurrentState() {
|
||||
try {
|
||||
return history.state;
|
||||
} catch (e) {
|
||||
// MSIE can reportedly throw when there is no state (UNCONFIRMED).
|
||||
}
|
||||
}
|
||||
|
||||
// This variable should be used *only* inside the cacheState function.
|
||||
var lastCachedState = null;
|
||||
function cacheState() {
|
||||
|
||||
@@ -293,9 +293,23 @@
|
||||
* `true` if the specified slot contains content (i.e. one or more DOM nodes).
|
||||
*
|
||||
* The controller can provide the following methods that act as life-cycle hooks:
|
||||
* * `$onInit` - Called on each controller after all the controllers on an element have been constructed and
|
||||
* * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
|
||||
* had their bindings initialized (and before the pre & post linking functions for the directives on
|
||||
* this element). This is a good place to put initialization code for your controller.
|
||||
* * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
|
||||
* `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
|
||||
* object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
|
||||
* component such as cloning the bound value to prevent accidental mutation of the outer value.
|
||||
* * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
|
||||
* external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
|
||||
* the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
|
||||
* components will have their `$onDestroy()` hook called before child components.
|
||||
* * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
|
||||
* function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
|
||||
* Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
|
||||
* they are waiting for their template to load asynchronously and their own compilation and linking has been
|
||||
* suspended until that occurs.
|
||||
*
|
||||
*
|
||||
* #### `require`
|
||||
* Require another directive and inject its controller as the fourth argument to the linking function. The
|
||||
@@ -832,6 +846,9 @@
|
||||
|
||||
var $compileMinErr = minErr('$compile');
|
||||
|
||||
function UNINITIALIZED_VALUE() {}
|
||||
var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE();
|
||||
|
||||
/**
|
||||
* @ngdoc provider
|
||||
* @name $compileProvider
|
||||
@@ -851,13 +868,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// The assumption is that future DOM event attribute names will begin with
|
||||
// 'on' and be composed of only English letters.
|
||||
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
|
||||
var bindingCache = createMap();
|
||||
|
||||
function parseIsolateBindings(scope, directiveName, isController) {
|
||||
var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/;
|
||||
|
||||
var bindings = {};
|
||||
var bindings = createMap();
|
||||
|
||||
forEach(scope, function(definition, scopeName) {
|
||||
if (definition in bindingCache) {
|
||||
bindings[scopeName] = bindingCache[definition];
|
||||
return;
|
||||
}
|
||||
var match = definition.match(LOCAL_REGEXP);
|
||||
|
||||
if (!match) {
|
||||
@@ -875,6 +897,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
optional: match[3] === '?',
|
||||
attrName: match[4] || scopeName
|
||||
};
|
||||
if (match[4]) {
|
||||
bindingCache[definition] = bindings[scopeName];
|
||||
}
|
||||
});
|
||||
|
||||
return bindings;
|
||||
@@ -920,11 +945,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
function assertValidDirectiveName(name) {
|
||||
var letter = name.charAt(0);
|
||||
if (!letter || letter !== lowercase(letter)) {
|
||||
throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
|
||||
throw $compileMinErr('baddir', "Directive/Component name '{0}' is invalid. The first character must be a lowercase letter", name);
|
||||
}
|
||||
if (name !== name.trim()) {
|
||||
throw $compileMinErr('baddir',
|
||||
"Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
|
||||
"Directive/Component name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
|
||||
name);
|
||||
}
|
||||
}
|
||||
@@ -944,7 +969,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
* {@link guide/directive directive guide} and the {@link $compile compile API} for more info.
|
||||
* @returns {ng.$compileProvider} Self for chaining.
|
||||
*/
|
||||
this.directive = function registerDirective(name, directiveFactory) {
|
||||
this.directive = function registerDirective(name, directiveFactory) {
|
||||
assertNotHasOwnProperty(name, 'directive');
|
||||
if (isString(name)) {
|
||||
assertValidDirectiveName(name);
|
||||
@@ -967,11 +992,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
directive.name = directive.name || name;
|
||||
directive.require = directive.require || (directive.controller && directive.name);
|
||||
directive.restrict = directive.restrict || 'EA';
|
||||
var bindings = directive.$$bindings =
|
||||
parseDirectiveBindings(directive, directive.name);
|
||||
if (isObject(bindings.isolateScope)) {
|
||||
directive.$$isolateBindings = bindings.isolateScope;
|
||||
}
|
||||
directive.$$moduleName = directiveFactory.$$moduleName;
|
||||
directives.push(directive);
|
||||
} catch (e) {
|
||||
@@ -1027,7 +1047,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
* See {@link ng.$compile#-bindtocontroller- `bindToController`}.
|
||||
* - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled.
|
||||
* Disabled by default.
|
||||
* - `$...` – `{function()=}` – additional annotations to provide to the directive factory function.
|
||||
* - `$...` – additional properties to attach to the directive factory function and the controller
|
||||
* constructor function. (This is used by the component router to annotate)
|
||||
*
|
||||
* @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls.
|
||||
* @description
|
||||
@@ -1059,7 +1080,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
*
|
||||
* myMod.component('myComp', {
|
||||
* templateUrl: 'views/my-comp.html',
|
||||
* controller: 'MyCtrl as ctrl',
|
||||
* controller: 'MyCtrl',
|
||||
* controllerAs: 'ctrl',
|
||||
* bindings: {name: '@'}
|
||||
* });
|
||||
*
|
||||
@@ -1084,7 +1106,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
var template = (!options.template && !options.templateUrl ? '' : options.template);
|
||||
return {
|
||||
var ddo = {
|
||||
controller: controller,
|
||||
controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
|
||||
template: makeInjectable(template),
|
||||
@@ -1095,13 +1117,27 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
restrict: 'E',
|
||||
require: options.require
|
||||
};
|
||||
|
||||
// Copy annotations (starting with $) over to the DDO
|
||||
forEach(options, function(val, key) {
|
||||
if (key.charAt(0) === '$') ddo[key] = val;
|
||||
});
|
||||
|
||||
return ddo;
|
||||
}
|
||||
|
||||
// Copy any annotation properties (starting with $) over to the factory function
|
||||
// TODO(pete) remove the following `forEach` before we release 1.6.0
|
||||
// The component-router@0.2.0 looks for the annotations on the controller constructor
|
||||
// Nothing in Angular looks for annotations on the factory function but we can't remove
|
||||
// it from 1.5.x yet.
|
||||
|
||||
// Copy any annotation properties (starting with $) over to the factory and controller constructor functions
|
||||
// These could be used by libraries such as the new component router
|
||||
forEach(options, function(val, key) {
|
||||
if (key.charAt(0) === '$') {
|
||||
factory[key] = val;
|
||||
// Don't try to copy over annotations to named controller
|
||||
if (isFunction(controller)) controller[key] = val;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1201,6 +1237,36 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return debugInfoEnabled;
|
||||
};
|
||||
|
||||
|
||||
var TTL = 10;
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compileProvider#onChangesTtl
|
||||
* @description
|
||||
*
|
||||
* Sets the number of times `$onChanges` hooks can trigger new changes before giving up and
|
||||
* assuming that the model is unstable.
|
||||
*
|
||||
* The current default is 10 iterations.
|
||||
*
|
||||
* In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result
|
||||
* in several iterations of calls to these hooks. However if an application needs more than the default 10
|
||||
* iterations to stabilize then you should investigate what is causing the model to continuously change during
|
||||
* the `$onChanges` hook execution.
|
||||
*
|
||||
* Increasing the TTL could have performance implications, so you should not change it without proper justification.
|
||||
*
|
||||
* @param {number} limit The number of `$onChanges` hook iterations.
|
||||
* @returns {number|object} the current limit (or `this` if called as a setter for chaining)
|
||||
*/
|
||||
this.onChangesTtl = function(value) {
|
||||
if (arguments.length) {
|
||||
TTL = value;
|
||||
return this;
|
||||
}
|
||||
return TTL;
|
||||
};
|
||||
|
||||
this.$get = [
|
||||
'$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
|
||||
'$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri',
|
||||
@@ -1208,8 +1274,38 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
$controller, $rootScope, $sce, $animate, $$sanitizeUri) {
|
||||
|
||||
var SIMPLE_ATTR_NAME = /^\w/;
|
||||
var specialAttrHolder = document.createElement('div');
|
||||
var Attributes = function(element, attributesToCopy) {
|
||||
var specialAttrHolder = window.document.createElement('div');
|
||||
|
||||
|
||||
|
||||
var onChangesTtl = TTL;
|
||||
// The onChanges hooks should all be run together in a single digest
|
||||
// When changes occur, the call to trigger their hooks will be added to this queue
|
||||
var onChangesQueue;
|
||||
|
||||
// This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest
|
||||
function flushOnChangesQueue() {
|
||||
try {
|
||||
if (!(--onChangesTtl)) {
|
||||
// We have hit the TTL limit so reset everything
|
||||
onChangesQueue = undefined;
|
||||
throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL);
|
||||
}
|
||||
// We must run this hook in an apply since the $$postDigest runs outside apply
|
||||
$rootScope.$apply(function() {
|
||||
for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) {
|
||||
onChangesQueue[i]();
|
||||
}
|
||||
// Reset the queue to trigger a new schedule next time there is a change
|
||||
onChangesQueue = undefined;
|
||||
});
|
||||
} finally {
|
||||
onChangesTtl++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Attributes(element, attributesToCopy) {
|
||||
if (attributesToCopy) {
|
||||
var keys = Object.keys(attributesToCopy);
|
||||
var i, l, key;
|
||||
@@ -1223,7 +1319,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
this.$$element = element;
|
||||
};
|
||||
}
|
||||
|
||||
Attributes.prototype = {
|
||||
/**
|
||||
@@ -1504,6 +1600,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
|
||||
} : noop;
|
||||
|
||||
compile.$$createComment = function(directiveName, comment) {
|
||||
var content = '';
|
||||
if (debugInfoEnabled) {
|
||||
content = ' ' + (directiveName || '') + ': ' + (comment || '') + ' ';
|
||||
}
|
||||
return window.document.createComment(content);
|
||||
};
|
||||
|
||||
return compile;
|
||||
|
||||
//================================
|
||||
@@ -1524,7 +1628,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var domNode = $compileNodes[i];
|
||||
|
||||
if (domNode.nodeType === NODE_TYPE_TEXT && domNode.nodeValue.match(NOT_EMPTY) /* non-empty */) {
|
||||
jqLiteWrapNode(domNode, $compileNodes[i] = document.createElement('span'));
|
||||
jqLiteWrapNode(domNode, $compileNodes[i] = window.document.createElement('span'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1717,8 +1821,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
|
||||
|
||||
var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
|
||||
function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
|
||||
|
||||
if (!transcludedScope) {
|
||||
transcludedScope = scope.$new(false, containingScope);
|
||||
@@ -1730,7 +1833,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
transcludeControllers: controllers,
|
||||
futureParentElement: futureParentElement
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// We need to attach the transclusion slots onto the `boundTranscludeFn`
|
||||
// so that they are available inside the `controllersBoundTransclude` function
|
||||
@@ -1895,7 +1998,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
|
||||
return function(scope, element, attrs, controllers, transcludeFn) {
|
||||
return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) {
|
||||
element = groupScan(element[0], attrStart, attrEnd);
|
||||
return linkFn(scope, element, attrs, controllers, transcludeFn);
|
||||
};
|
||||
@@ -1913,23 +2016,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
|
||||
if (eager) {
|
||||
return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
|
||||
var compiled;
|
||||
|
||||
if (eager) {
|
||||
return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
|
||||
}
|
||||
return function lazyCompilation() {
|
||||
if (!compiled) {
|
||||
compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
|
||||
|
||||
// Null out all of these references in order to make them eligible for garbage collection
|
||||
// since this is a potentially long lived closure
|
||||
$compileNodes = transcludeFn = previousCompileContext = null;
|
||||
}
|
||||
|
||||
var compiled;
|
||||
|
||||
return function() {
|
||||
if (!compiled) {
|
||||
compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
|
||||
|
||||
// Null out all of these references in order to make them eligible for garbage collection
|
||||
// since this is a potentially long lived closure
|
||||
$compileNodes = transcludeFn = previousCompileContext = null;
|
||||
}
|
||||
|
||||
return compiled.apply(this, arguments);
|
||||
};
|
||||
return compiled.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2065,11 +2166,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
terminalPriority = directive.priority;
|
||||
$template = $compileNode;
|
||||
$compileNode = templateAttrs.$$element =
|
||||
jqLite(document.createComment(' ' + directiveName + ': ' +
|
||||
templateAttrs[directiveName] + ' '));
|
||||
jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName]));
|
||||
compileNode = $compileNode[0];
|
||||
replaceWith(jqCollection, sliceArgs($template), compileNode);
|
||||
|
||||
// Support: Chrome < 50
|
||||
// https://github.com/angular/angular.js/issues/14041
|
||||
|
||||
// In the versions of V8 prior to Chrome 50, the document fragment that is created
|
||||
// in the `replaceWith` function is improperly garbage collected despite still
|
||||
// being referenced by the `parentNode` property of all of the child nodes. By adding
|
||||
// a reference to the fragment via a different property, we can avoid that incorrect
|
||||
// behavior.
|
||||
// TODO: remove this line after Chrome 50 has been released
|
||||
$template[0].$$parentNode = $template[0].parentNode;
|
||||
|
||||
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
|
||||
replaceDirective && replaceDirective.name, {
|
||||
// Don't pass in:
|
||||
@@ -2210,7 +2321,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
replaceDirective = directive;
|
||||
}
|
||||
|
||||
/* jshint -W021 */
|
||||
nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
|
||||
/* jshint +W021 */
|
||||
templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
|
||||
controllerDirectives: controllerDirectives,
|
||||
newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
|
||||
@@ -2272,85 +2385,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getControllers(directiveName, require, $element, elementControllers) {
|
||||
var value;
|
||||
|
||||
if (isString(require)) {
|
||||
var match = require.match(REQUIRE_PREFIX_REGEXP);
|
||||
var name = require.substring(match[0].length);
|
||||
var inheritType = match[1] || match[3];
|
||||
var optional = match[2] === '?';
|
||||
|
||||
//If only parents then start at the parent element
|
||||
if (inheritType === '^^') {
|
||||
$element = $element.parent();
|
||||
//Otherwise attempt getting the controller from elementControllers in case
|
||||
//the element is transcluded (and has no data) and to avoid .data if possible
|
||||
} else {
|
||||
value = elementControllers && elementControllers[name];
|
||||
value = value && value.instance;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
var dataName = '$' + name + 'Controller';
|
||||
value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
|
||||
}
|
||||
|
||||
if (!value && !optional) {
|
||||
throw $compileMinErr('ctreq',
|
||||
"Controller '{0}', required by directive '{1}', can't be found!",
|
||||
name, directiveName);
|
||||
}
|
||||
} else if (isArray(require)) {
|
||||
value = [];
|
||||
for (var i = 0, ii = require.length; i < ii; i++) {
|
||||
value[i] = getControllers(directiveName, require[i], $element, elementControllers);
|
||||
}
|
||||
} else if (isObject(require)) {
|
||||
value = {};
|
||||
forEach(require, function(controller, property) {
|
||||
value[property] = getControllers(directiveName, controller, $element, elementControllers);
|
||||
});
|
||||
}
|
||||
|
||||
return value || null;
|
||||
}
|
||||
|
||||
function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
|
||||
var elementControllers = createMap();
|
||||
for (var controllerKey in controllerDirectives) {
|
||||
var directive = controllerDirectives[controllerKey];
|
||||
var locals = {
|
||||
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
|
||||
$element: $element,
|
||||
$attrs: attrs,
|
||||
$transclude: transcludeFn
|
||||
};
|
||||
|
||||
var controller = directive.controller;
|
||||
if (controller == '@') {
|
||||
controller = attrs[directive.name];
|
||||
}
|
||||
|
||||
var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
|
||||
|
||||
// For directives with element transclusion the element is a comment,
|
||||
// but jQuery .data doesn't support attaching data to comment nodes as it's hard to
|
||||
// clean up (http://bugs.jquery.com/ticket/8335).
|
||||
// Instead, we save the controllers for the element in a local hash and attach to .data
|
||||
// later, once we have the actual element.
|
||||
elementControllers[directive.name] = controllerInstance;
|
||||
if (!hasElementTranscludeDirective) {
|
||||
$element.data('$' + directive.name + 'Controller', controllerInstance.instance);
|
||||
}
|
||||
}
|
||||
return elementControllers;
|
||||
}
|
||||
|
||||
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
|
||||
var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
|
||||
attrs, removeScopeBindingWatches, removeControllerBindingWatches;
|
||||
attrs, scopeBindingInfo;
|
||||
|
||||
if (compileNode === linkNode) {
|
||||
attrs = templateAttrs;
|
||||
@@ -2379,7 +2416,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
if (controllerDirectives) {
|
||||
elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
|
||||
elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective);
|
||||
}
|
||||
|
||||
if (newIsolateScopeDirective) {
|
||||
@@ -2389,23 +2426,26 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
compile.$$addScopeClass($element, true);
|
||||
isolateScope.$$isolateBindings =
|
||||
newIsolateScopeDirective.$$isolateBindings;
|
||||
removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
|
||||
scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope,
|
||||
isolateScope.$$isolateBindings,
|
||||
newIsolateScopeDirective);
|
||||
if (removeScopeBindingWatches) {
|
||||
isolateScope.$on('$destroy', removeScopeBindingWatches);
|
||||
if (scopeBindingInfo.removeWatches) {
|
||||
isolateScope.$on('$destroy', scopeBindingInfo.removeWatches);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize bindToController bindings
|
||||
// Initialize controllers
|
||||
for (var name in elementControllers) {
|
||||
var controllerDirective = controllerDirectives[name];
|
||||
var controller = elementControllers[name];
|
||||
var bindings = controllerDirective.$$bindings.bindToController;
|
||||
|
||||
// Initialize bindToController bindings
|
||||
if (controller.identifier && bindings) {
|
||||
removeControllerBindingWatches =
|
||||
controller.bindingInfo =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
} else {
|
||||
controller.bindingInfo = {};
|
||||
}
|
||||
|
||||
var controllerResult = controller();
|
||||
@@ -2413,11 +2453,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// If the controller constructor has a return value, overwrite the instance
|
||||
// from setupControllers
|
||||
controller.instance = controllerResult;
|
||||
$element.data('$' + controllerDirective.name + 'Controller', controllerResult);
|
||||
removeControllerBindingWatches && removeControllerBindingWatches();
|
||||
removeControllerBindingWatches =
|
||||
controller.bindingInfo.removeWatches && controller.bindingInfo.removeWatches();
|
||||
controller.bindingInfo =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
}
|
||||
|
||||
// Store controllers on the $element data
|
||||
// For transclude comment nodes this will be a noop and will be done at transclusion time
|
||||
$element.data('$' + controllerDirective.name + 'Controller', controllerResult);
|
||||
}
|
||||
|
||||
// Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
|
||||
@@ -2428,10 +2471,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger the `$onInit` method on all controllers that have one
|
||||
// Handle the init and destroy lifecycle hooks on all controllers that have them
|
||||
forEach(elementControllers, function(controller) {
|
||||
if (isFunction(controller.instance.$onInit)) {
|
||||
controller.instance.$onInit();
|
||||
var controllerInstance = controller.instance;
|
||||
if (isFunction(controllerInstance.$onChanges)) {
|
||||
controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
|
||||
}
|
||||
if (isFunction(controllerInstance.$onInit)) {
|
||||
controllerInstance.$onInit();
|
||||
}
|
||||
if (isFunction(controllerInstance.$onDestroy)) {
|
||||
controllerScope.$on('$destroy', function callOnDestroyHook() {
|
||||
controllerInstance.$onDestroy();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2468,6 +2520,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
);
|
||||
}
|
||||
|
||||
// Trigger $postLink lifecycle hooks
|
||||
forEach(elementControllers, function(controller) {
|
||||
var controllerInstance = controller.instance;
|
||||
if (isFunction(controllerInstance.$postLink)) {
|
||||
controllerInstance.$postLink();
|
||||
}
|
||||
});
|
||||
|
||||
// This is the function that is injected as `$transclude`.
|
||||
// Note: all arguments are optional!
|
||||
function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) {
|
||||
@@ -2507,6 +2567,71 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
function getControllers(directiveName, require, $element, elementControllers) {
|
||||
var value;
|
||||
|
||||
if (isString(require)) {
|
||||
var match = require.match(REQUIRE_PREFIX_REGEXP);
|
||||
var name = require.substring(match[0].length);
|
||||
var inheritType = match[1] || match[3];
|
||||
var optional = match[2] === '?';
|
||||
|
||||
//If only parents then start at the parent element
|
||||
if (inheritType === '^^') {
|
||||
$element = $element.parent();
|
||||
//Otherwise attempt getting the controller from elementControllers in case
|
||||
//the element is transcluded (and has no data) and to avoid .data if possible
|
||||
} else {
|
||||
value = elementControllers && elementControllers[name];
|
||||
value = value && value.instance;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
var dataName = '$' + name + 'Controller';
|
||||
value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
|
||||
}
|
||||
|
||||
if (!value && !optional) {
|
||||
throw $compileMinErr('ctreq',
|
||||
"Controller '{0}', required by directive '{1}', can't be found!",
|
||||
name, directiveName);
|
||||
}
|
||||
} else if (isArray(require)) {
|
||||
value = [];
|
||||
for (var i = 0, ii = require.length; i < ii; i++) {
|
||||
value[i] = getControllers(directiveName, require[i], $element, elementControllers);
|
||||
}
|
||||
} else if (isObject(require)) {
|
||||
value = {};
|
||||
forEach(require, function(controller, property) {
|
||||
value[property] = getControllers(directiveName, controller, $element, elementControllers);
|
||||
});
|
||||
}
|
||||
|
||||
return value || null;
|
||||
}
|
||||
|
||||
function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) {
|
||||
var elementControllers = createMap();
|
||||
for (var controllerKey in controllerDirectives) {
|
||||
var directive = controllerDirectives[controllerKey];
|
||||
var locals = {
|
||||
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
|
||||
$element: $element,
|
||||
$attrs: attrs,
|
||||
$transclude: transcludeFn
|
||||
};
|
||||
|
||||
var controller = directive.controller;
|
||||
if (controller == '@') {
|
||||
controller = attrs[directive.name];
|
||||
}
|
||||
|
||||
elementControllers[directive.name] = $controller(controller, locals, true, directive.controllerAs);
|
||||
}
|
||||
return elementControllers;
|
||||
}
|
||||
|
||||
// Depending upon the context in which a directive finds itself it might need to have a new isolated
|
||||
// or child scope created. For instance:
|
||||
// * if the directive has been pulled into a template because another directive with a higher priority
|
||||
@@ -2547,6 +2672,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (startAttrName) {
|
||||
directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
|
||||
}
|
||||
if (!directive.$$bindings) {
|
||||
var bindings = directive.$$bindings =
|
||||
parseDirectiveBindings(directive, directive.name);
|
||||
if (isObject(bindings.isolateScope)) {
|
||||
directive.$$isolateBindings = bindings.isolateScope;
|
||||
}
|
||||
}
|
||||
tDirectives.push(directive);
|
||||
match = directive;
|
||||
}
|
||||
@@ -2794,7 +2926,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
switch (type) {
|
||||
case 'svg':
|
||||
case 'math':
|
||||
var wrapper = document.createElement('div');
|
||||
var wrapper = window.document.createElement('div');
|
||||
wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
|
||||
return wrapper.childNodes[0].childNodes;
|
||||
default:
|
||||
@@ -2938,7 +3070,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// - remove them from the DOM
|
||||
// - allow them to still be traversed with .nextSibling
|
||||
// - allow a single fragment.qSA to fetch all elements being removed
|
||||
var fragment = document.createDocumentFragment();
|
||||
var fragment = window.document.createDocumentFragment();
|
||||
for (i = 0; i < removeCount; i++) {
|
||||
fragment.appendChild(elementsToRemove[i]);
|
||||
}
|
||||
@@ -2984,7 +3116,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// only occurs for isolate scopes and new scopes with controllerAs.
|
||||
function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
|
||||
var removeWatchCollection = [];
|
||||
forEach(bindings, function(definition, scopeName) {
|
||||
var initialChanges = {};
|
||||
var changes;
|
||||
forEach(bindings, function initializeBinding(definition, scopeName) {
|
||||
var attrName = definition.attrName,
|
||||
optional = definition.optional,
|
||||
mode = definition.mode, // @, =, or &
|
||||
@@ -2998,7 +3132,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
destination[scopeName] = attrs[attrName] = void 0;
|
||||
}
|
||||
attrs.$observe(attrName, function(value) {
|
||||
if (isString(value)) {
|
||||
if (isString(value) || isBoolean(value)) {
|
||||
var oldValue = destination[scopeName];
|
||||
recordChanges(scopeName, value, oldValue);
|
||||
destination[scopeName] = value;
|
||||
}
|
||||
});
|
||||
@@ -3013,6 +3149,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// the value to boolean rather than a string, so we special case this situation
|
||||
destination[scopeName] = lastValue;
|
||||
}
|
||||
initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
|
||||
break;
|
||||
|
||||
case '=':
|
||||
@@ -3026,7 +3163,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (parentGet.literal) {
|
||||
compare = equals;
|
||||
} else {
|
||||
compare = function(a, b) { return a === b || (a !== a && b !== b); };
|
||||
compare = function simpleCompare(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
|
||||
@@ -3068,9 +3205,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
parentGet = $parse(attrs[attrName]);
|
||||
|
||||
destination[scopeName] = parentGet(scope);
|
||||
initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
|
||||
|
||||
removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newParentValue) {
|
||||
destination[scopeName] = newParentValue;
|
||||
removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) {
|
||||
if (newValue === oldValue) {
|
||||
// If the new and old values are identical then this is the first time the watch has been triggered
|
||||
// So instead we use the current value on the destination as the old value
|
||||
oldValue = destination[scopeName];
|
||||
}
|
||||
recordChanges(scopeName, newValue, oldValue);
|
||||
destination[scopeName] = newValue;
|
||||
}, parentGet.literal);
|
||||
|
||||
removeWatchCollection.push(removeWatch);
|
||||
@@ -3090,15 +3234,52 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
});
|
||||
|
||||
return removeWatchCollection.length && function removeWatches() {
|
||||
for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
|
||||
removeWatchCollection[i]();
|
||||
function recordChanges(key, currentValue, previousValue) {
|
||||
if (isFunction(destination.$onChanges) && currentValue !== previousValue) {
|
||||
// If we have not already scheduled the top level onChangesQueue handler then do so now
|
||||
if (!onChangesQueue) {
|
||||
scope.$$postDigest(flushOnChangesQueue);
|
||||
onChangesQueue = [];
|
||||
}
|
||||
// If we have not already queued a trigger of onChanges for this controller then do so now
|
||||
if (!changes) {
|
||||
changes = {};
|
||||
onChangesQueue.push(triggerOnChangesHook);
|
||||
}
|
||||
// If the has been a change on this property already then we need to reuse the previous value
|
||||
if (changes[key]) {
|
||||
previousValue = changes[key].previousValue;
|
||||
}
|
||||
// Store this change
|
||||
changes[key] = new SimpleChange(previousValue, currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
function triggerOnChangesHook() {
|
||||
destination.$onChanges(changes);
|
||||
// Now clear the changes so that we schedule onChanges when more changes arrive
|
||||
changes = undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
initialChanges: initialChanges,
|
||||
removeWatches: removeWatchCollection.length && function removeWatches() {
|
||||
for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
|
||||
removeWatchCollection[i]();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
function SimpleChange(previous, current) {
|
||||
this.previousValue = previous;
|
||||
this.currentValue = current;
|
||||
}
|
||||
SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; };
|
||||
|
||||
|
||||
var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
|
||||
/**
|
||||
* Converts all accepted directives format into proper directive name.
|
||||
|
||||
@@ -27,6 +27,15 @@ function $ControllerProvider() {
|
||||
var controllers = {},
|
||||
globals = false;
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $controllerProvider#has
|
||||
* @param {string} name Controller name to check.
|
||||
*/
|
||||
this.has = function(name) {
|
||||
return controllers.hasOwnProperty(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $controllerProvider#register
|
||||
@@ -83,7 +92,7 @@ function $ControllerProvider() {
|
||||
* It's just a simple call to {@link auto.$injector $injector}, but extracted into
|
||||
* a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
|
||||
*/
|
||||
return function(expression, locals, later, ident) {
|
||||
return function $controller(expression, locals, later, ident) {
|
||||
// PRIVATE API:
|
||||
// param `later` --- indicates that the controller's constructor is invoked at a later time.
|
||||
// If true, $controller will allocate the object with the correct
|
||||
@@ -134,7 +143,7 @@ function $ControllerProvider() {
|
||||
}
|
||||
|
||||
var instantiate;
|
||||
return instantiate = extend(function() {
|
||||
return instantiate = extend(function $controllerInit() {
|
||||
var result = $injector.invoke(expression, instance, locals, constructor);
|
||||
if (result !== instance && (isObject(result) || isFunction(result))) {
|
||||
instance = result;
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
ngModelMinErr: false,
|
||||
*/
|
||||
|
||||
// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
|
||||
var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/;
|
||||
// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
|
||||
var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/;
|
||||
// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
|
||||
// Note: We are being more lenient, because browsers are too.
|
||||
// 1. Scheme
|
||||
@@ -26,12 +26,18 @@ var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-
|
||||
var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i;
|
||||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
||||
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
|
||||
var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
|
||||
var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
|
||||
var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
|
||||
var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
|
||||
var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/;
|
||||
var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
|
||||
var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/;
|
||||
var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/;
|
||||
var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
|
||||
|
||||
var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown';
|
||||
var PARTIAL_VALIDATION_TYPES = createMap();
|
||||
forEach('date,datetime-local,month,time,week'.split(','), function(type) {
|
||||
PARTIAL_VALIDATION_TYPES[type] = true;
|
||||
});
|
||||
|
||||
var inputType = {
|
||||
|
||||
/**
|
||||
@@ -387,7 +393,7 @@ var inputType = {
|
||||
}]);
|
||||
</script>
|
||||
<form name="myForm" ng-controller="DateController as dateCtrl">
|
||||
<label for="exampleInput">Pick a between 8am and 5pm:</label>
|
||||
<label for="exampleInput">Pick a time between 8am and 5pm:</label>
|
||||
<input type="time" id="exampleInput" name="input" ng-model="example.value"
|
||||
placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
|
||||
<div role="alert">
|
||||
@@ -1108,7 +1114,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
if (!$sniffer.android) {
|
||||
var composing = false;
|
||||
|
||||
element.on('compositionstart', function(data) {
|
||||
element.on('compositionstart', function() {
|
||||
composing = true;
|
||||
});
|
||||
|
||||
@@ -1118,6 +1124,8 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
});
|
||||
}
|
||||
|
||||
var timeout;
|
||||
|
||||
var listener = function(ev) {
|
||||
if (timeout) {
|
||||
$browser.defer.cancel(timeout);
|
||||
@@ -1147,8 +1155,6 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
if ($sniffer.hasEvent('input')) {
|
||||
element.on('input', listener);
|
||||
} else {
|
||||
var timeout;
|
||||
|
||||
var deferListener = function(ev, input, origValue) {
|
||||
if (!timeout) {
|
||||
timeout = $browser.defer(function() {
|
||||
@@ -1180,6 +1186,26 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
// or form autocomplete on newer browser, we need "change" event to catch it
|
||||
element.on('change', listener);
|
||||
|
||||
// Some native input types (date-family) have the ability to change validity without
|
||||
// firing any input/change events.
|
||||
// For these event types, when native validators are present and the browser supports the type,
|
||||
// check for validity changes on various DOM events.
|
||||
if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) {
|
||||
element.on(PARTIAL_VALIDATION_EVENTS, function(ev) {
|
||||
if (!timeout) {
|
||||
var validity = this[VALIDITY_STATE_PROPERTY];
|
||||
var origBadInput = validity.badInput;
|
||||
var origTypeMismatch = validity.typeMismatch;
|
||||
timeout = $browser.defer(function() {
|
||||
timeout = null;
|
||||
if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) {
|
||||
listener(ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ctrl.$render = function() {
|
||||
// Workaround for Firefox validation #12102.
|
||||
var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
|
||||
|
||||
@@ -78,7 +78,11 @@ function classDirective(name, selector) {
|
||||
updateClasses(oldClasses, newClasses);
|
||||
}
|
||||
}
|
||||
oldVal = shallowCopy(newVal);
|
||||
if (isArray(newVal)) {
|
||||
oldVal = newVal.map(function(v) { return shallowCopy(v); });
|
||||
} else {
|
||||
oldVal = shallowCopy(newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -148,9 +152,10 @@ function classDirective(name, selector) {
|
||||
* new classes added.
|
||||
*
|
||||
* @animations
|
||||
* **add** - happens just before the class is applied to the elements
|
||||
*
|
||||
* **remove** - happens just before the class is removed from the element
|
||||
* | Animation | Occurs |
|
||||
* |----------------------------------|-------------------------------------|
|
||||
* | {@link ng.$animate#addClass addClass} | just before the class is applied to the element |
|
||||
* | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngClass {@link guide/expression Expression} to eval. The result
|
||||
|
||||
@@ -34,8 +34,10 @@
|
||||
* and `leave` effects.
|
||||
*
|
||||
* @animations
|
||||
* enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
|
||||
* leave - happens just before the `ngIf` contents are removed from the DOM
|
||||
* | Animation | Occurs |
|
||||
* |----------------------------------|-------------------------------------|
|
||||
* | {@link ng.$animate#enter enter} | just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container |
|
||||
* | {@link ng.$animate#leave leave} | just before the `ngIf` contents are removed from the DOM |
|
||||
*
|
||||
* @element ANY
|
||||
* @scope
|
||||
@@ -76,7 +78,7 @@
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
var ngIfDirective = ['$animate', function($animate) {
|
||||
var ngIfDirective = ['$animate', '$compile', function($animate, $compile) {
|
||||
return {
|
||||
multiElement: true,
|
||||
transclude: 'element',
|
||||
@@ -92,7 +94,7 @@ var ngIfDirective = ['$animate', function($animate) {
|
||||
if (!childScope) {
|
||||
$transclude(function(clone, newScope) {
|
||||
childScope = newScope;
|
||||
clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
|
||||
clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf);
|
||||
// Note: We only need the first/last node of the cloned nodes.
|
||||
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
|
||||
// by a directive with templateUrl when its template arrives.
|
||||
|
||||
@@ -23,8 +23,10 @@
|
||||
* access on some browsers.
|
||||
*
|
||||
* @animations
|
||||
* enter - animation is used to bring new content into the browser.
|
||||
* leave - animation is used to animate existing content away.
|
||||
* | Animation | Occurs |
|
||||
* |----------------------------------|-------------------------------------|
|
||||
* | {@link ng.$animate#enter enter} | when the expression changes, on the new include |
|
||||
* | {@link ng.$animate#leave leave} | when the expression changes, on the old include |
|
||||
*
|
||||
* The enter and leave animation occur concurrently.
|
||||
*
|
||||
@@ -290,7 +292,7 @@ var ngIncludeFillContentDirective = ['$compile',
|
||||
// support innerHTML, so detect this here and try to generate the contents
|
||||
// specially.
|
||||
$element.empty();
|
||||
$compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
|
||||
$compile(jqLiteBuildFragment(ctrl.template, window.document).childNodes)(scope,
|
||||
function namespaceAdaptedClone(clone) {
|
||||
$element.append(clone);
|
||||
}, {futureParentElement: $element});
|
||||
|
||||
@@ -265,9 +265,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
};
|
||||
ngModelSet = function($scope, newValue) {
|
||||
if (isFunction(parsedNgModel($scope))) {
|
||||
invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
|
||||
invokeModelSetter($scope, {$$$p: newValue});
|
||||
} else {
|
||||
parsedNgModelAssign($scope, ctrl.$modelValue);
|
||||
parsedNgModelAssign($scope, newValue);
|
||||
}
|
||||
};
|
||||
} else if (!parsedNgModel.assign) {
|
||||
@@ -292,7 +292,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* the `$viewValue` are different from last time.
|
||||
*
|
||||
* Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
|
||||
* `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
|
||||
* `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue`
|
||||
* or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
|
||||
* invoked if you only change a property on the objects.
|
||||
*/
|
||||
@@ -644,7 +644,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
setValidity(name, undefined);
|
||||
validatorPromises.push(promise.then(function() {
|
||||
setValidity(name, true);
|
||||
}, function(error) {
|
||||
}, function() {
|
||||
allValid = false;
|
||||
setValidity(name, false);
|
||||
}));
|
||||
@@ -1118,7 +1118,7 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
|
||||
});
|
||||
}
|
||||
|
||||
element.on('blur', function(ev) {
|
||||
element.on('blur', function() {
|
||||
if (modelCtrl.$touched) return;
|
||||
|
||||
if ($rootScope.$$phase) {
|
||||
|
||||
@@ -245,7 +245,7 @@ var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s
|
||||
// jshint maxlen: 100
|
||||
|
||||
|
||||
var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, $document, $parse) {
|
||||
|
||||
function parseOptionsExpression(optionsExp, selectElement, scope) {
|
||||
|
||||
@@ -342,8 +342,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
|
||||
var value = optionValues[key];
|
||||
|
||||
var locals = getLocals(optionValues[key], key);
|
||||
var selectValue = getTrackByValueFn(optionValues[key], locals);
|
||||
var locals = getLocals(value, key);
|
||||
var selectValue = getTrackByValueFn(value, locals);
|
||||
watchedArray.push(selectValue);
|
||||
|
||||
// Only need to watch the displayFn if there is a specific label expression
|
||||
@@ -406,8 +406,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
// 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');
|
||||
var optionTemplate = window.document.createElement('option'),
|
||||
optGroupTemplate = window.document.createElement('optgroup');
|
||||
|
||||
function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
|
||||
|
||||
@@ -432,7 +432,10 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
var options;
|
||||
var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
|
||||
|
||||
// This stores the newly created options before they are appended to the select.
|
||||
// Since the contents are removed from the fragment when it is appended,
|
||||
// we only need to create it once.
|
||||
var listFragment = $document[0].createDocumentFragment();
|
||||
|
||||
var renderEmptyOption = function() {
|
||||
if (!providedEmptyOption) {
|
||||
@@ -467,15 +470,21 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
selectCtrl.writeValue = function writeNgOptionsValue(value) {
|
||||
var option = options.getOptionFromViewValue(value);
|
||||
|
||||
if (option && !option.disabled) {
|
||||
if (option) {
|
||||
// Don't update the option when it is already selected.
|
||||
// For example, the browser will select the first option by default. In that case,
|
||||
// most properties are set automatically - except the `selected` attribute, which we
|
||||
// set always
|
||||
|
||||
if (selectElement[0].value !== option.selectValue) {
|
||||
removeUnknownOption();
|
||||
removeEmptyOption();
|
||||
|
||||
selectElement[0].value = option.selectValue;
|
||||
option.element.selected = true;
|
||||
option.element.setAttribute('selected', 'selected');
|
||||
}
|
||||
|
||||
option.element.setAttribute('selected', 'selected');
|
||||
} else {
|
||||
if (value === null || providedEmptyOption) {
|
||||
removeUnknownOption();
|
||||
@@ -523,7 +532,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
if (value) {
|
||||
value.forEach(function(item) {
|
||||
var option = options.getOptionFromViewValue(item);
|
||||
if (option && !option.disabled) option.element.selected = true;
|
||||
if (option) option.element.selected = true;
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -575,6 +584,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
emptyOption = jqLite(optionTemplate.cloneNode(false));
|
||||
}
|
||||
|
||||
selectElement.empty();
|
||||
|
||||
// We need to do this here to ensure that the options object is defined
|
||||
// when we first hit it in writeNgOptionsValue
|
||||
updateOptions();
|
||||
@@ -584,6 +595,12 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
function addOptionElement(option, parent) {
|
||||
var optionElement = optionTemplate.cloneNode(false);
|
||||
parent.appendChild(optionElement);
|
||||
updateOptionElement(option, optionElement);
|
||||
}
|
||||
|
||||
|
||||
function updateOptionElement(option, element) {
|
||||
option.element = element;
|
||||
@@ -600,133 +617,66 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
if (option.value !== element.value) element.value = option.selectValue;
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
// We cannot rely on the extracted empty option being the same as the compiled empty option,
|
||||
// because the compiled empty option might have been replaced by a comment because
|
||||
// it had an "element" transclusion directive on it (such as ngIf)
|
||||
if (emptyOption_ || unknownOption_) {
|
||||
while (current &&
|
||||
(current === emptyOption_ ||
|
||||
current === unknownOption_ ||
|
||||
current.nodeType === NODE_TYPE_COMMENT ||
|
||||
(nodeName_(current) === 'option' && current.value === ''))) {
|
||||
current = current.nextSibling;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
|
||||
function updateOptions() {
|
||||
|
||||
var previousValue = options && selectCtrl.readValue();
|
||||
|
||||
// We must remove all current options, but cannot simply set innerHTML = null
|
||||
// since the providedEmptyOption might have an ngIf on it that inserts comments which we
|
||||
// must preserve.
|
||||
// Instead, iterate over the current option elements and remove them or their optgroup
|
||||
// parents
|
||||
if (options) {
|
||||
|
||||
for (var i = options.items.length - 1; i >= 0; i--) {
|
||||
var option = options.items[i];
|
||||
if (option.group) {
|
||||
jqLiteRemove(option.element.parentNode);
|
||||
} else {
|
||||
jqLiteRemove(option.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options = ngOptions.getOptions();
|
||||
|
||||
var groupMap = {};
|
||||
var currentElement = selectElement[0].firstChild;
|
||||
var groupElementMap = {};
|
||||
|
||||
// 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;
|
||||
options.items.forEach(function addOption(option) {
|
||||
var groupElement;
|
||||
var optionElement;
|
||||
|
||||
if (isDefined(option.group)) {
|
||||
|
||||
// This option is to live in a group
|
||||
// See if we have already created this group
|
||||
group = groupMap[option.group];
|
||||
groupElement = groupElementMap[option.group];
|
||||
|
||||
if (!group) {
|
||||
if (!groupElement) {
|
||||
|
||||
// We have not already created this group
|
||||
groupElement = addOrReuseElement(selectElement[0],
|
||||
currentElement,
|
||||
'optgroup',
|
||||
optGroupTemplate);
|
||||
// Move to the next element
|
||||
currentElement = groupElement.nextSibling;
|
||||
groupElement = optGroupTemplate.cloneNode(false);
|
||||
listFragment.appendChild(groupElement);
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
groupElementMap[option.group] = groupElement;
|
||||
}
|
||||
|
||||
// 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;
|
||||
addOptionElement(option, groupElement);
|
||||
|
||||
} 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;
|
||||
addOptionElement(option, listFragment);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Now remove all excess options and group
|
||||
Object.keys(groupMap).forEach(function(key) {
|
||||
removeExcessElements(groupMap[key].currentOptionElement);
|
||||
});
|
||||
removeExcessElements(currentElement);
|
||||
selectElement[0].appendChild(listFragment);
|
||||
|
||||
ngModelCtrl.$render();
|
||||
|
||||
|
||||
@@ -36,17 +36,23 @@
|
||||
* <div ng-repeat="(key, value) in myObj"> ... </div>
|
||||
* ```
|
||||
*
|
||||
* You need to be aware that the JavaScript specification does not define the order of keys
|
||||
* returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
|
||||
* used to sort the keys alphabetically.)
|
||||
* However, there are a limitations compared to array iteration:
|
||||
*
|
||||
* 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 the [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes).
|
||||
* - The JavaScript specification does not define the order of keys
|
||||
* returned for an object, so Angular relies on the order returned by the browser
|
||||
* when running `for key in myObj`. 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 the
|
||||
* [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes).
|
||||
*
|
||||
* 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
|
||||
* - `ngRepeat` will silently *ignore* object keys starting with `$`, because
|
||||
* it's a prefix used by Angular for public (`$`) and private (`$$`) properties.
|
||||
*
|
||||
* - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with
|
||||
* objects, and will throw if used with one.
|
||||
*
|
||||
* If you are hitting any of these limitations, 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.
|
||||
*
|
||||
@@ -164,11 +170,11 @@
|
||||
* as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
|
||||
*
|
||||
* @animations
|
||||
* **.enter** - when a new item is added to the list or when an item is revealed after a filter
|
||||
*
|
||||
* **.leave** - when an item is removed from the list or when an item is filtered out
|
||||
*
|
||||
* **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
|
||||
* | Animation | Occurs |
|
||||
* |----------------------------------|-------------------------------------|
|
||||
* | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter |
|
||||
* | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out |
|
||||
* | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered |
|
||||
*
|
||||
* See the example below for defining CSS animations with ngRepeat.
|
||||
*
|
||||
@@ -316,7 +322,7 @@
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) {
|
||||
var NG_REMOVED = '$$NG_REMOVED';
|
||||
var ngRepeatMinErr = minErr('ngRepeat');
|
||||
|
||||
@@ -351,7 +357,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
$$tlb: true,
|
||||
compile: function ngRepeatCompile($element, $attr) {
|
||||
var expression = $attr.ngRepeat;
|
||||
var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' ');
|
||||
var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression);
|
||||
|
||||
var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
|
||||
|
||||
@@ -515,7 +521,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
|
||||
if (getBlockStart(block) != nextNode) {
|
||||
// existing item which got moved
|
||||
$animate.move(getBlockNodes(block.clone), null, jqLite(previousNode));
|
||||
$animate.move(getBlockNodes(block.clone), null, previousNode);
|
||||
}
|
||||
previousNode = getBlockEnd(block);
|
||||
updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
|
||||
@@ -527,8 +533,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
var endNode = ngRepeatEndComment.cloneNode(false);
|
||||
clone[clone.length++] = endNode;
|
||||
|
||||
// TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper?
|
||||
$animate.enter(clone, null, jqLite(previousNode));
|
||||
$animate.enter(clone, null, previousNode);
|
||||
previousNode = endNode;
|
||||
// Note: We only need the first/last node of the cloned nodes.
|
||||
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
|
||||
|
||||
@@ -85,12 +85,14 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
* .my-element.ng-hide-remove.ng-hide-remove-active { ... }
|
||||
* ```
|
||||
*
|
||||
* Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
|
||||
* Keep in mind that, as of AngularJS version 1.3, there is no need to change the display
|
||||
* property to block during animation states--ngAnimate will handle the style toggling automatically for you.
|
||||
*
|
||||
* @animations
|
||||
* addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible
|
||||
* removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden
|
||||
* | Animation | Occurs |
|
||||
* |----------------------------------|-------------------------------------|
|
||||
* | {@link $animate#addClass addClass} `.ng-hide` | after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden |
|
||||
* | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngShow` expression evaluates to a truthy value and just before contents are set to visible |
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngShow If the {@link guide/expression expression} is truthy
|
||||
@@ -249,12 +251,15 @@ var ngShowDirective = ['$animate', function($animate) {
|
||||
* .my-element.ng-hide-remove.ng-hide-remove-active { ... }
|
||||
* ```
|
||||
*
|
||||
* Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
|
||||
* Keep in mind that, as of AngularJS version 1.3, there is no need to change the display
|
||||
* property to block during animation states--ngAnimate will handle the style toggling automatically for you.
|
||||
*
|
||||
* @animations
|
||||
* removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden
|
||||
* addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible
|
||||
* | Animation | Occurs |
|
||||
* |----------------------------------|-------------------------------------|
|
||||
* | {@link $animate#addClass addClass} `.ng-hide` | after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden |
|
||||
* | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible |
|
||||
*
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngHide If the {@link guide/expression expression} is truthy then
|
||||
|
||||
@@ -27,8 +27,10 @@
|
||||
* </div>
|
||||
|
||||
* @animations
|
||||
* enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
|
||||
* leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
|
||||
* | Animation | Occurs |
|
||||
* |----------------------------------|-------------------------------------|
|
||||
* | {@link ng.$animate#enter enter} | after the ngSwitch contents change and the matched child element is placed inside the container |
|
||||
* | {@link ng.$animate#leave leave} | after the ngSwitch contents change and just before the former contents are removed from the DOM |
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
@@ -127,7 +129,7 @@
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
var ngSwitchDirective = ['$animate', function($animate) {
|
||||
var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) {
|
||||
return {
|
||||
require: 'ngSwitch',
|
||||
|
||||
@@ -168,7 +170,7 @@ var ngSwitchDirective = ['$animate', function($animate) {
|
||||
selectedTransclude.transclude(function(caseElement, selectedScope) {
|
||||
selectedScopes.push(selectedScope);
|
||||
var anchor = selectedTransclude.element;
|
||||
caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: ');
|
||||
caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen');
|
||||
var block = { clone: caseElement };
|
||||
|
||||
selectedElements.push(block);
|
||||
|
||||