Compare commits
151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37310e024d | |||
| 306e626196 | |||
| f31c7492ec | |||
| a1e7eb6360 | |||
| 169e5326d1 | |||
| bb3b65374d | |||
| ac9336b35a | |||
| 75787446ee | |||
| 14409d7a7f | |||
| 370676d4d0 | |||
| 4c218de4d3 | |||
| 929dd15b9b | |||
| 1b9e408ddb | |||
| 7505d126fa | |||
| 7044e55feb | |||
| bd9e894fb7 | |||
| ba7e24ec6c | |||
| e1f98773c7 | |||
| 17d8a520ca | |||
| 84bf883f6d | |||
| 38ff199a3c | |||
| 7ff5ec254e | |||
| a2f2032a20 | |||
| 16833d0fb6 | |||
| 2a8a4e7fad | |||
| beeb64a6f6 | |||
| e49e7d50c5 | |||
| 3b3de3f876 | |||
| eec78e78d6 | |||
| b1f2917696 | |||
| 409bcb3810 | |||
| e49a1433fd | |||
| c26b5e3c88 | |||
| 4d0614fd0d | |||
| 756640f5aa | |||
| d87b7912df | |||
| bff8041bd0 | |||
| e0ee491633 | |||
| 7dfe82e135 | |||
| 9f2a53b33e | |||
| 9128eac501 | |||
| 85e8d5ea67 | |||
| a36863eea3 | |||
| 98da7ade7a | |||
| 846fe1b3ef | |||
| f4648abe03 | |||
| 5f9a1122e2 | |||
| 1afeb37756 | |||
| 7809e75a56 | |||
| 1472c31431 | |||
| d3b839d986 | |||
| c8c2386296 | |||
| d0b5bfa454 | |||
| 1fa2d56ba3 | |||
| efedc643d1 | |||
| 34230b30b7 | |||
| 8354d02805 | |||
| 1426b02980 | |||
| 29aeee2250 | |||
| 6a8348f715 | |||
| 38e2856889 | |||
| a56435f3ae | |||
| 75082c975c | |||
| 31b6bfaaf4 | |||
| 2d74323e3e | |||
| edc2613ed5 | |||
| 6eab8ab187 | |||
| 80ce046fd1 | |||
| 315f320e2e | |||
| ec4fe1bcab | |||
| 3303fe41e4 | |||
| f22e5fd980 | |||
| b11d6c792f | |||
| e713f36d7c | |||
| 8162a1d731 | |||
| 1e6f8543e5 | |||
| 0e293d2a97 | |||
| 434d7a0903 | |||
| a6e6438dae | |||
| 3691d2c15f | |||
| 4cee5fde1b | |||
| 5d042592fc | |||
| f619d032c9 | |||
| 95f5b86240 | |||
| 483d91a624 | |||
| e9339935d4 | |||
| 3e468523b7 | |||
| 99f3931e3d | |||
| 80b78f7461 | |||
| 386c179a94 | |||
| 054893b555 | |||
| e865726e00 | |||
| f9370755d4 | |||
| f906603dd6 | |||
| d7c084f9cf | |||
| 5847fc48e7 | |||
| 1ee9b4ef5e | |||
| 430082e6bd | |||
| fe7d9dedaa | |||
| 029ac8cb80 | |||
| 45b896a16a | |||
| f807d7ab4e | |||
| 07d62426f6 | |||
| 3a142e79bd | |||
| b40130abcf | |||
| ee988d40d0 | |||
| ad4d26c525 | |||
| f58681564d | |||
| d23f585f4a | |||
| 1850f59765 | |||
| 1f182853d4 | |||
| 5036ac905b | |||
| 11d2242df6 | |||
| cea23db3de | |||
| 5c9c197305 | |||
| ecb2222ea1 | |||
| 276310e5b5 | |||
| 6afe0dba39 | |||
| 038ca9b9bf | |||
| 44c451e9f5 | |||
| 1b8b41ad23 | |||
| 37f2265b88 | |||
| 05a2a1d395 | |||
| fc913eea89 | |||
| f839e4944a | |||
| 2aead39486 | |||
| 54af406c2e | |||
| 930bd40bac | |||
| 4aa4005f5f | |||
| d5c04121cb | |||
| e6996cc457 | |||
| 33793ec3e1 | |||
| c9f8fb2559 | |||
| a0229803de | |||
| 38cb2b348e | |||
| c54287bf8d | |||
| a36515289a | |||
| c59da09d3e | |||
| 98ceda2194 | |||
| fbd620e8ae | |||
| 4cfc279d39 | |||
| 6a07009c12 | |||
| f3f65caac2 | |||
| 130fe92cda | |||
| 3d83973fa4 | |||
| 3b171a851d | |||
| 9782bc2aa2 | |||
| 77be7cc7d9 | |||
| 3da5847500 | |||
| 53e6590e88 | |||
| d1c05ad832 |
@@ -12,6 +12,7 @@ angular.js.tmproj
|
||||
bower_components/
|
||||
angular.xcodeproj
|
||||
.idea
|
||||
*.iml
|
||||
.agignore
|
||||
libpeerconnection.log
|
||||
npm-debug.log
|
||||
|
||||
@@ -11,7 +11,6 @@ env:
|
||||
- JOB=unit
|
||||
- JOB=e2e TEST_TARGET=jqlite
|
||||
- JOB=e2e TEST_TARGET=jquery
|
||||
- JOB=e2e TEST_TARGET=doce2e
|
||||
global:
|
||||
- SAUCE_USERNAME=angular-ci
|
||||
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
|
||||
|
||||
+610
@@ -1,3 +1,611 @@
|
||||
<a name="1.2.27"></a>
|
||||
# 1.2.27 prime-factorization (2014-11-20)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** clear the GCS cache even when no animation is detected
|
||||
([f619d032](https://github.com/angular/angular.js/commit/f619d032c932752313c646b5295bad8a68ef3871),
|
||||
[#8813](https://github.com/angular/angular.js/issues/8813))
|
||||
- **$browser:**
|
||||
- Cache `location.href` only during page reload phase
|
||||
([434d7a09](https://github.com/angular/angular.js/commit/434d7a09039151c1e627ac156213905d06b7df10),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235), [#9470](https://github.com/angular/angular.js/issues/9470))
|
||||
- don’t use history api when only the hash changes
|
||||
([a6e6438d](https://github.com/angular/angular.js/commit/a6e6438dae1ed92b29608d0b8830b0a7fbb624ef),
|
||||
[#9423](https://github.com/angular/angular.js/issues/9423), [#9424](https://github.com/angular/angular.js/issues/9424))
|
||||
- handle async href on url change in <=IE9
|
||||
([fe7d9ded](https://github.com/angular/angular.js/commit/fe7d9dedaa5ec3b3f56d9eb9c513cf99e40121ce),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235))
|
||||
- **$http:** add missing shortcut methods and missing docs
|
||||
([ec4fe1bc](https://github.com/angular/angular.js/commit/ec4fe1bcab6f981103a10f860a3a00122aa78607),
|
||||
[#9180](https://github.com/angular/angular.js/issues/9180), [#9321](https://github.com/angular/angular.js/issues/9321))
|
||||
- **$location:**
|
||||
- revert erroneous logic and backport refactorings from master
|
||||
([1ee9b4ef](https://github.com/angular/angular.js/commit/1ee9b4ef5e4a795061d3aa19adefdeb7e0209eeb),
|
||||
[#8492](https://github.com/angular/angular.js/issues/8492))
|
||||
- allow 0 in path() and hash()
|
||||
([f807d7ab](https://github.com/angular/angular.js/commit/f807d7ab4ebd18899154528ea9ed50d5bc25c57a))
|
||||
- **$parse:** add quick check for Function constructor in fast path
|
||||
([756640f5](https://github.com/angular/angular.js/commit/756640f5aa8f3fd0084bff50534e23976a6fff00))
|
||||
- **$parse, events:** prevent accidental misuse of properties on $event
|
||||
([4d0614fd](https://github.com/angular/angular.js/commit/4d0614fd0da12c5783dfb4956c330edac87e62fe),
|
||||
[#9969](https://github.com/angular/angular.js/issues/9969))
|
||||
- **ngMock:** $httpBackend should match data containing Date objects correctly
|
||||
([1426b029](https://github.com/angular/angular.js/commit/1426b02980badfd322eb960d71bfb1a14d657847),
|
||||
[#5127](https://github.com/angular/angular.js/issues/5127))
|
||||
- **orderBy:** sort by identity if no predicate is given
|
||||
([45b896a1](https://github.com/angular/angular.js/commit/45b896a16abbcbfcdfb9a95c2d10c76a805b57cc),
|
||||
[#5847](https://github.com/angular/angular.js/issues/5847), [#4579](https://github.com/angular/angular.js/issues/4579), [#9403](https://github.com/angular/angular.js/issues/9403))
|
||||
- **select:** ensure the label attribute is updated in Internet Explorer
|
||||
([16833d0f](https://github.com/angular/angular.js/commit/16833d0fb6585117e9978d1accc3ade83e22e797),
|
||||
[#9621](https://github.com/angular/angular.js/issues/9621), [#10042](https://github.com/angular/angular.js/issues/10042))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **orderBy:** copy array with slice instead of for loop
|
||||
([409bcb38](https://github.com/angular/angular.js/commit/409bcb3810a1622178268f7ff7f4130887a1a3dc),
|
||||
[#9942](https://github.com/angular/angular.js/issues/9942))
|
||||
|
||||
|
||||
<a name="1.3.3"></a>
|
||||
# 1.3.3 undersea-arithmetic (2014-11-17)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$http:** don't parse single space responses as JSON
|
||||
([6f19a6fd](https://github.com/angular/angular.js/commit/6f19a6fd33ab72d3908e3418fba47ee8e1598fa6),
|
||||
[#9907](https://github.com/angular/angular.js/issues/9907))
|
||||
- **minErr:** stringify non-JSON compatible objects in error messages
|
||||
([cf43ccdf](https://github.com/angular/angular.js/commit/cf43ccdf9b8665a2fd5d6aa52f80cb2d7c9bb7e2),
|
||||
[#10085](https://github.com/angular/angular.js/issues/10085))
|
||||
- **$rootScope:** handle cyclic references in scopes when creating error messages
|
||||
([e80053d9](https://github.com/angular/angular.js/commit/e80053d91fd7c722e092a23d326384de2e552eb6),
|
||||
[#10085](https://github.com/angular/angular.js/issues/10085))
|
||||
- **ngRepeat:** support cyclic object references in error messages
|
||||
([fa12c3c8](https://github.com/angular/angular.js/commit/fa12c3c86af7965d1b9d9a5dd3434755e9e04635),
|
||||
[#9838](https://github.com/angular/angular.js/issues/9838), [#10065](https://github.com/angular/angular.js/issues/10065), [#10085](https://github.com/angular/angular.js/issues/10085))
|
||||
- **ngMock:** call $interval callbacks even when invokeApply is false
|
||||
([d81ff888](https://github.com/angular/angular.js/commit/d81ff8885b77f70c6417d7be3124d86d07447375),
|
||||
[#10032](https://github.com/angular/angular.js/issues/10032))
|
||||
- **ngPattern:** match behaviour of native HTML pattern attribute
|
||||
([85eb9660](https://github.com/angular/angular.js/commit/85eb9660ef67c24d5104a6a1921bedad0bd1b57e),
|
||||
[#9881](https://github.com/angular/angular.js/issues/9881), [#9888](https://github.com/angular/angular.js/issues/9888))
|
||||
- **select:** ensure the label attribute is updated in Internet Explorer
|
||||
([6604c236](https://github.com/angular/angular.js/commit/6604c2361427fba8c43a39dc2e92197390dfbdbe),
|
||||
[#9621](https://github.com/angular/angular.js/issues/9621), [#10042](https://github.com/angular/angular.js/issues/10042))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$location:** allow to location to be changed during $locationChangeStart
|
||||
([a9352c19](https://github.com/angular/angular.js/commit/a9352c19ce33f0393d6581547c7ea8dfc2a8b78f),
|
||||
[#9607](https://github.com/angular/angular.js/issues/9607), [#9678](https://github.com/angular/angular.js/issues/9678))
|
||||
- **$routeProvider:** allow setting caseInsensitiveMatch on the provider
|
||||
([0db573b7](https://github.com/angular/angular.js/commit/0db573b7493f76abd94ff65ce660017d617e865b),
|
||||
[#6477](https://github.com/angular/angular.js/issues/6477), [#9873](https://github.com/angular/angular.js/issues/9873))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **orderBy:** copy array with slice instead of for loop
|
||||
([8eabc546](https://github.com/angular/angular.js/commit/8eabc5463c795d87f37e5a9eacbbb14435024061),
|
||||
[#9942](https://github.com/angular/angular.js/issues/9942))
|
||||
|
||||
|
||||
|
||||
<a name="1.3.2"></a>
|
||||
# 1.3.2 cardiovasculatory-magnification (2014-11-07)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** do not rebind parent bound transclude functions
|
||||
([841c0907](https://github.com/angular/angular.js/commit/841c0907556f525dbc4223609d808319fe0dd7e2),
|
||||
[#9413](https://github.com/angular/angular.js/issues/9413))
|
||||
- **$parse:**
|
||||
- stateful interceptors override an `undefined` expression
|
||||
([ed99821e](https://github.com/angular/angular.js/commit/ed99821e4dc621864f7e2d9a6b5305fca27fb7fa),
|
||||
[#9821](https://github.com/angular/angular.js/issues/9821), [#9825](https://github.com/angular/angular.js/issues/9825))
|
||||
- add quick check for Function constructor in fast path
|
||||
([e676d642](https://github.com/angular/angular.js/commit/e676d642f5feb8d3ba88944634afb479ba525c36))
|
||||
- **$parse, events:** prevent accidental misuse of properties on $event
|
||||
([e057a9aa](https://github.com/angular/angular.js/commit/e057a9aa398ead209bd6bbf76e22d2d5562904fb))
|
||||
- **ngRoute:** allow proto inherited properties in route params object
|
||||
([b4770582](https://github.com/angular/angular.js/commit/b4770582f84f26c8ff7f2320a36a6b0ceff6e6cc),
|
||||
[#8181](https://github.com/angular/angular.js/issues/8181), [#9731](https://github.com/angular/angular.js/issues/9731))
|
||||
- **select:** use strict comparison for isSelected with selectAs
|
||||
([9e305948](https://github.com/angular/angular.js/commit/9e305948e4965fb86b0c79985dc6e8c59a9c66af),
|
||||
[#9639](https://github.com/angular/angular.js/issues/9639), [#9949](https://github.com/angular/angular.js/issues/9949))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngAria:** announce ngMessages with aria-live
|
||||
([187e4318](https://github.com/angular/angular.js/commit/187e43185dfb1bce6a318d95958c73cfb789d33c),
|
||||
[#9834](https://github.com/angular/angular.js/issues/9834))
|
||||
- **ngMock:** decorator that adds Scope#$countChildScopes and Scope#$countWatchers
|
||||
([74981c9f](https://github.com/angular/angular.js/commit/74981c9f208b3617cbf00beafd61138d25c5d546),
|
||||
[#9926](https://github.com/angular/angular.js/issues/9926), [#9871](https://github.com/angular/angular.js/issues/9871))
|
||||
|
||||
|
||||
## Security Note
|
||||
|
||||
This release also contains security fixes for expression sandbox bypasses.
|
||||
|
||||
These issues affect only applications with known server-side XSS holes that are also using [CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP) to secure their client-side code. If your application falls into this rare category, we recommend updating your version of Angular.
|
||||
|
||||
We'd like to thank security researches [Sebastian Lekies](https://twitter.com/sebastianlekies), [Jann Horn](http://thejh.net/), and [Gábor Molnár](https://twitter.com/molnar_g) for reporting these issues to us.
|
||||
|
||||
We also added a documentation page focused on security, which contains some of the best practices, DOs and DON'Ts. Please check out [https://docs.angularjs.org/guide/security](https://docs.angularjs.org/guide/security).
|
||||
|
||||
|
||||
|
||||
<a name="1.3.1"></a>
|
||||
# 1.3.1 spectral-lobster (2014-10-31)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** returning null when an optional controller is not found
|
||||
([2cd5b4ec](https://github.com/angular/angular.js/commit/2cd5b4ec4409a818ccd33a6fbdeb99a3443a1809),
|
||||
[#9404](https://github.com/angular/angular.js/issues/9404), [#9392](https://github.com/angular/angular.js/issues/9392))
|
||||
- **$observe:** check if the attribute is undefined
|
||||
([531a8de7](https://github.com/angular/angular.js/commit/531a8de72c439d8ddd064874bf364c00cedabb11),
|
||||
[#9707](https://github.com/angular/angular.js/issues/9707), [#9720](https://github.com/angular/angular.js/issues/9720))
|
||||
- **$parse:** support dirty-checking objects with null prototype
|
||||
([28661d1a](https://github.com/angular/angular.js/commit/28661d1a8cc3a8454bad7ae531e027b1256476c9),
|
||||
[#9568](https://github.com/angular/angular.js/issues/9568))
|
||||
- **$sce:** use msie instead of $document[0].documentMode
|
||||
([45252c3a](https://github.com/angular/angular.js/commit/45252c3a545336a0bac93be6ee28cde6afaa3cb4),
|
||||
[#9661](https://github.com/angular/angular.js/issues/9661))
|
||||
- **$templateRequest:** ignore JSON Content-Type header and content
|
||||
([1bd473eb](https://github.com/angular/angular.js/commit/1bd473eb4587900086e0b6b308dcf1dcfe9760d9),
|
||||
[#5756](https://github.com/angular/angular.js/issues/5756), [#9619](https://github.com/angular/angular.js/issues/9619))
|
||||
- **i18n:** rename datetimeSymbols to be camelCase
|
||||
([94f5a285](https://github.com/angular/angular.js/commit/94f5a285bfcf04d800afc462a7a37a3469d77f1a))
|
||||
- **loader:** fix double spaces
|
||||
([8b2f1a47](https://github.com/angular/angular.js/commit/8b2f1a47b584ceb98689f48538a2af73cd65dfd8),
|
||||
[#9630](https://github.com/angular/angular.js/issues/9630))
|
||||
- **ngMock:** $httpBackend should match data containing Date objects correctly
|
||||
([1025f6eb](https://github.com/angular/angular.js/commit/1025f6ebf4e5933a12920889be00cd8ac8a106fa),
|
||||
[#5127](https://github.com/angular/angular.js/issues/5127))
|
||||
- **ngSanitize:** attribute name: xmlns:href -> xlink:href
|
||||
([4cccf0f2](https://github.com/angular/angular.js/commit/4cccf0f2a89b002d63cb443e1e7b15f76dcef425),
|
||||
[#9769](https://github.com/angular/angular.js/issues/9769))
|
||||
- **select:** assign result of track exp to element value
|
||||
([4b4098bf](https://github.com/angular/angular.js/commit/4b4098bfcae64f69c70a22393de1f3d9a0d3dc46),
|
||||
[#9718](https://github.com/angular/angular.js/issues/9718), [#9592](https://github.com/angular/angular.js/issues/9592))
|
||||
- **templateRequest:** allow empty html template
|
||||
([52ceec22](https://github.com/angular/angular.js/commit/52ceec2229dc132b76da4e022c91474344f2d906),
|
||||
[#9581](https://github.com/angular/angular.js/issues/9581))
|
||||
- **testability:** escape regex chars in `findBindings` if using `exactMatch`
|
||||
([02aa4f4b](https://github.com/angular/angular.js/commit/02aa4f4b85ee15922a1f2de8ba78f562c18518d0),
|
||||
[#9595](https://github.com/angular/angular.js/issues/9595), [#9600](https://github.com/angular/angular.js/issues/9600))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** allow $watchCollection to be used in bi-directional bindings
|
||||
([40bbc981](https://github.com/angular/angular.js/commit/40bbc9817845bf75581daee5d0ec30980affb0f5),
|
||||
[#9725](https://github.com/angular/angular.js/issues/9725))
|
||||
- **ngSanitize:** accept SVG elements and attributes
|
||||
([a54b25d7](https://github.com/angular/angular.js/commit/a54b25d77999a85701dfc5396fef78e586a99667),
|
||||
[#9578](https://github.com/angular/angular.js/issues/9578), [#9751](https://github.com/angular/angular.js/issues/9751))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.3.0"></a>
|
||||
# 1.3.0 superluminal-nudge (2014-10-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$browser:**
|
||||
- account for IE deserializing history.state on each read
|
||||
([1efaf3dc](https://github.com/angular/angular.js/commit/1efaf3dc136f822703a9cda55afac7895a923ccb),
|
||||
[#9587](https://github.com/angular/angular.js/issues/9587), [#9545](https://github.com/angular/angular.js/issues/9545))
|
||||
- do not decode cookies that do not appear encoded
|
||||
([9c995905](https://github.com/angular/angular.js/commit/9c9959059eb84f0f1d748b70b50ec47b7d23d065),
|
||||
[#9211](https://github.com/angular/angular.js/issues/9211), [#9225](https://github.com/angular/angular.js/issues/9225))
|
||||
- **$http:**
|
||||
- allow empty json response
|
||||
([9ba24c54](https://github.com/angular/angular.js/commit/9ba24c54d60e643b1450cc5cfa8f990bd524c130),
|
||||
[#9532](https://github.com/angular/angular.js/issues/9532), [#9562](https://github.com/angular/angular.js/issues/9562))
|
||||
- don't run transformData on HEAD methods
|
||||
([6e4955a3](https://github.com/angular/angular.js/commit/6e4955a3086555d8ca30c29955faa213b39c6f27),
|
||||
[#9528](https://github.com/angular/angular.js/issues/9528), [#9529](https://github.com/angular/angular.js/issues/9529))
|
||||
- **$injector:** ensure $get method invoked with provider context
|
||||
([372fa699](https://github.com/angular/angular.js/commit/372fa6993b2b1b4848aa4be3c3e11f69244fca6f),
|
||||
[#9511](https://github.com/angular/angular.js/issues/9511), [#9512](https://github.com/angular/angular.js/issues/9512))
|
||||
- **$location:** use clone of passed search() object
|
||||
([c7a9009e](https://github.com/angular/angular.js/commit/c7a9009e143299f0e45a85d715ff22fc676d3f93),
|
||||
[#9445](https://github.com/angular/angular.js/issues/9445))
|
||||
- **$parse:** stabilize one-time literal expressions correctly
|
||||
([874cac82](https://github.com/angular/angular.js/commit/874cac825bf29a936cb1b35f9af239687bc5e036))
|
||||
- **formController:** remove scope reference when form is destroyed
|
||||
([01f50e1a](https://github.com/angular/angular.js/commit/01f50e1a7b2bff7070616494774ec493f8133204),
|
||||
[#9315](https://github.com/angular/angular.js/issues/9315))
|
||||
- **jqLite:** remove native listener when all jqLite listeners were deregistered
|
||||
([d71fb6f2](https://github.com/angular/angular.js/commit/d71fb6f2713f1a636f6e9c25479870ee9941ad18),
|
||||
[#9509](https://github.com/angular/angular.js/issues/9509))
|
||||
- **select:**
|
||||
- add basic track by and select as support
|
||||
([addfff3c](https://github.com/angular/angular.js/commit/addfff3c46311f59bdcd100351260006d457316f),
|
||||
[#6564](https://github.com/angular/angular.js/issues/6564))
|
||||
- manage select controller options correctly
|
||||
([2435e2b8](https://github.com/angular/angular.js/commit/2435e2b8f84fde9495b8e9440a2b4f865b1ff541),
|
||||
[#9418](https://github.com/angular/angular.js/issues/9418))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$anchorScroll:** support a configurable vertical scroll offset
|
||||
([09c39d2c](https://github.com/angular/angular.js/commit/09c39d2ce687cdf0ac35dbb34a91f0d198c9d83a),
|
||||
[#9368](https://github.com/angular/angular.js/issues/9368), [#2070](https://github.com/angular/angular.js/issues/2070), [#9360](https://github.com/angular/angular.js/issues/9360))
|
||||
- **$animate:**
|
||||
- introduce the $animate.animate() method
|
||||
([02be700b](https://github.com/angular/angular.js/commit/02be700bda191b454de393f2805916f374a1d764))
|
||||
- allow $animate to pass custom styles into animations
|
||||
([e5f4d7b1](https://github.com/angular/angular.js/commit/e5f4d7b10ae5e6a17ab349995451c33b7d294245))
|
||||
- **currencyFilter:** add fractionSize as optional parameter
|
||||
([20685ffe](https://github.com/angular/angular.js/commit/20685ffe11036d4d604d13f0d792ca46497af4a1),
|
||||
[#3642](https://github.com/angular/angular.js/issues/3642), [#3461](https://github.com/angular/angular.js/issues/3461), [#3642](https://github.com/angular/angular.js/issues/3642), [#7922](https://github.com/angular/angular.js/issues/7922))
|
||||
- **jqLite:** add private jqLiteDocumentLoaded function
|
||||
([0dd316ef](https://github.com/angular/angular.js/commit/0dd316efea209e5e5de3e456b4e6562f011a1294))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$animate:** due to [e5f4d7b1](https://github.com/angular/angular.js/commit/e5f4d7b10ae5e6a17ab349995451c33b7d294245),
|
||||
staggering animations that use transitions will now
|
||||
always block the transition from starting (via `transition: 0s none`)
|
||||
up until the stagger step kicks in. The former behaviour was that the
|
||||
block was removed as soon as the pending class was added. This fix
|
||||
allows for styles to be applied in the pending class without causing
|
||||
an animation to trigger prematurely.
|
||||
|
||||
|
||||
<a name="1.3.0-rc.5"></a>
|
||||
# 1.3.0-rc.5 impossible-choreography (2014-10-08)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$anchorScroll:** don't scroll to top when initializing and location hash is empty
|
||||
([d5445c60](https://github.com/angular/angular.js/commit/d5445c601fafd6ecd38befeaa4c9ec7bb044127c),
|
||||
[#8848](https://github.com/angular/angular.js/issues/8848), [#9393](https://github.com/angular/angular.js/issues/9393))
|
||||
- **$animate:**
|
||||
- ensure hidden elements with ngShow/ngHide stay hidden during animations
|
||||
([39d0b368](https://github.com/angular/angular.js/commit/39d0b36826a077f7549a70d0cf3edebe90a10aaa),
|
||||
[#9103](https://github.com/angular/angular.js/issues/9103), [#9493](https://github.com/angular/angular.js/issues/9493))
|
||||
- permit class-based animations for leave operations if ngAnimateChildren is enabled
|
||||
([df1a00b1](https://github.com/angular/angular.js/commit/df1a00b11ac2722f4da441837795985f12682030),
|
||||
[#8092](https://github.com/angular/angular.js/issues/8092), [#9491](https://github.com/angular/angular.js/issues/9491))
|
||||
- ensure that class-based animations only consider the most recent DOM operations
|
||||
([c93924ed](https://github.com/angular/angular.js/commit/c93924ed275a62683b85c82f1c6c2e19d5662c9a),
|
||||
[#8946](https://github.com/angular/angular.js/issues/8946), [#9458](https://github.com/angular/angular.js/issues/9458))
|
||||
- abort class-based animations if the element is removed during digest
|
||||
([613d0a32](https://github.com/angular/angular.js/commit/613d0a3212de8dc01c817ca8526e09c57978a621),
|
||||
[#8796](https://github.com/angular/angular.js/issues/8796))
|
||||
- clear the GCS cache even when no animation is detected
|
||||
([cb85cbce](https://github.com/angular/angular.js/commit/cb85cbcec1c876db6062a0dc0bad80f842782194),
|
||||
[#8813](https://github.com/angular/angular.js/issues/8813))
|
||||
- **$browser:**
|
||||
- Cache `location.href` only during page reload phase
|
||||
([8ee1ba4b](https://github.com/angular/angular.js/commit/8ee1ba4b94d6fccff06d8781f7ed256c6ce664ff),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235), [#9455](https://github.com/angular/angular.js/issues/9455))
|
||||
- don’t use the history API when only the hash changes
|
||||
([7cb01a80](https://github.com/angular/angular.js/commit/7cb01a80beec669d8f6aae1dc211d2f0b7d4eac4),
|
||||
[#9423](https://github.com/angular/angular.js/issues/9423), [#9424](https://github.com/angular/angular.js/issues/9424),
|
||||
[858360b6](https://github.com/angular/angular.js/commit/858360b680a2bb5c19429c1be1c9506700cda476),
|
||||
[0656484d](https://github.com/angular/angular.js/commit/0656484d3e709c5162570b0dd6473b0b6140e5b2),
|
||||
[#9143](https://github.com/angular/angular.js/issues/9143), [#9406](https://github.com/angular/angular.js/issues/9406))
|
||||
- handle async href on url change in <=IE9
|
||||
([404b95fe](https://github.com/angular/angular.js/commit/404b95fe30a1bcd1313adafbd0018578d5b21d3d),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235))
|
||||
- **$compile:**
|
||||
- handle the removal of an interpolated attribute
|
||||
([a75546af](https://github.com/angular/angular.js/commit/a75546afdf41adab786eda30c258190cd4c5f1ae),
|
||||
[#9236](https://github.com/angular/angular.js/issues/9236), [#9240](https://github.com/angular/angular.js/issues/9240))
|
||||
- remove comment nodes from templates before asserting single root node
|
||||
([feba0174](https://github.com/angular/angular.js/commit/feba0174db0f8f929273beb8b90691734a9292e2),
|
||||
[#9212](https://github.com/angular/angular.js/issues/9212), [#9215](https://github.com/angular/angular.js/issues/9215))
|
||||
- use the correct namespace for transcluded svg elements
|
||||
([f3539f3c](https://github.com/angular/angular.js/commit/f3539f3cb5d9477f50f065c6a0ac7d6ca0a31092),
|
||||
[#9344](https://github.com/angular/angular.js/issues/9344), [#9415](https://github.com/angular/angular.js/issues/9415))
|
||||
- **$http:** honor application/json response header and parse json primitives
|
||||
([7b6c1d08](https://github.com/angular/angular.js/commit/7b6c1d08aceba6704a40302f373400aed9ed0e0b),
|
||||
[#2973](https://github.com/angular/angular.js/issues/2973))
|
||||
- **$injector:** throw when factory $get method does not return a value
|
||||
([0d3b69a5](https://github.com/angular/angular.js/commit/0d3b69a5f27b41745b504c7ffd8d72653bac1f85),
|
||||
[#4575](https://github.com/angular/angular.js/issues/4575), [#9210](https://github.com/angular/angular.js/issues/9210))
|
||||
- **$location:** allow `0` in `path()` and `hash()`
|
||||
([b8c5b871](https://github.com/angular/angular.js/commit/b8c5b87119a06edb8e8d1cefad81ee8d1f64f070))
|
||||
- **form:** fix submit prevention
|
||||
([86c7d122](https://github.com/angular/angular.js/commit/86c7d1221c706993044583d51a0c61423fee5bcf),
|
||||
[#3370](https://github.com/angular/angular.js/issues/3370), [#3776](https://github.com/angular/angular.js/issues/3776))
|
||||
- **ngAnimate:** defer DOM operations for changing classes to postDigest
|
||||
([667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
|
||||
[#8234](https://github.com/angular/angular.js/issues/8234), [#9263](https://github.com/angular/angular.js/issues/9263))
|
||||
- **orderBy:** sort by identity if no predicate is given
|
||||
([607f016a](https://github.com/angular/angular.js/commit/607f016a0ba705ce40df0164360fb96a9d7f5912),
|
||||
[#5847](https://github.com/angular/angular.js/issues/5847), [#4579](https://github.com/angular/angular.js/issues/4579), [#9403](https://github.com/angular/angular.js/issues/9403))
|
||||
- **select:**
|
||||
- throw for `selectAs` and `trackBy`
|
||||
([30996f82](https://github.com/angular/angular.js/commit/30996f82afa03cd11771b3267e9367ecf9af6e6d))
|
||||
- use `$viewValue` instead of `$modelValue`
|
||||
([f7174169](https://github.com/angular/angular.js/commit/f7174169f4f710d605f6a67f39f90a67a07d4cab),
|
||||
[#8929](https://github.com/angular/angular.js/issues/8929))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$location:**
|
||||
- add support for History API state handling ([6fd36dee](https://github.com/angular/angular.js/commit/6fd36deed954b338e48390862971d465148dc1f2),
|
||||
[#9027](https://github.com/angular/angular.js/issues/9027))
|
||||
- allow automatic rewriting of links to be disabled
|
||||
([b3e09be5](https://github.com/angular/angular.js/commit/b3e09be58960b913fee3869bf36e7de3305bbe00),
|
||||
[#5487](https://github.com/angular/angular.js/issues/5487))
|
||||
- **$route:** ability to cancel $routeChangeStart event
|
||||
([f4ff11b0](https://github.com/angular/angular.js/commit/f4ff11b01e6a5f9a9eb25a38d327dfaadbd7c80c),
|
||||
[#5581](https://github.com/angular/angular.js/issues/5581), [#5714](https://github.com/angular/angular.js/issues/5714), [#9502](https://github.com/angular/angular.js/issues/9502))
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$animate:**
|
||||
- access DOM less in resolveElementClasses
|
||||
([22358cf9](https://github.com/angular/angular.js/commit/22358cf9c703d67f3cf9eb4899404b09578a5fad))
|
||||
- don't join classes before it's necessary in resolveElementClasses
|
||||
([003c44ec](https://github.com/angular/angular.js/commit/003c44eceee54c3398b0d2971fd97a512d7f7cec))
|
||||
- **ngBind:** set textContent rather than using element.text()
|
||||
([074a146d](https://github.com/angular/angular.js/commit/074a146d8b1ee7c93bf6d5892448a5c2a0143a28),
|
||||
[#9369](https://github.com/angular/angular.js/issues/9369), [#9396](https://github.com/angular/angular.js/issues/9396))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [feba0174](https://github.com/angular/angular.js/commit/feba0174db0f8f929273beb8b90691734a9292e2),
|
||||
|
||||
|
||||
If a template contains directives within comment nodes, and there is more than a single node in the
|
||||
template, those comment nodes are removed. The impact of this breaking change is expected to be
|
||||
quite low.
|
||||
|
||||
Closes #9212
|
||||
Closes #9215
|
||||
|
||||
- **ngAnimate:** due to [667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
|
||||
|
||||
|
||||
The `$animate` CSS class API will always defer changes until the end of the next digest. This allows ngAnimate
|
||||
to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than
|
||||
many. This prevents jank in browsers such as IE, and is generally a good thing.
|
||||
|
||||
If you find that your classes are not being immediately applied, be sure to invoke `$digest()`.
|
||||
|
||||
Closes #8234
|
||||
Closes #9263
|
||||
|
||||
- **$select:** due to [30996f8](https://github.com/angular/angular.js/commit/30996f82afa03cd11771b3267e9367ecf9af6e6d)
|
||||
|
||||
`ngOptions` will now throw an error when the comprehension expressions contains both a `select as`
|
||||
and `track by` expression.
|
||||
|
||||
These expressions are fundamentally incompatible because it is not possible to reliably and
|
||||
consistently determine the parent object of a model, since `select as` can assign any child of a
|
||||
`value` as the model value.
|
||||
|
||||
Prior to refactorings in this release, neither of these expressions worked correctly independently,
|
||||
and did not work at all when combined.
|
||||
|
||||
See #6564
|
||||
|
||||
- **$route:** due to [f4ff11b0](https://github.com/angular/angular.js/commit/f4ff11b01e6a5f9a9eb25a38d327dfaadbd7c80c),
|
||||
|
||||
Order of events has changed.
|
||||
Previously: `$locationChangeStart` -> `$locationChangeSuccess`
|
||||
-> `$routeChangeStart` -> `$routeChangeSuccess`
|
||||
|
||||
Now: `$locationChangeStart` -> `$routeChangeStart`
|
||||
-> `$locationChangeSuccess` -> -> `$routeChangeSuccess`
|
||||
|
||||
Fixes #5581
|
||||
Closes #5714
|
||||
Closes #9502- **ngAnimate:** due to [667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
|
||||
|
||||
|
||||
The $animate class API will always defer changes until the end of the next digest. This allows ngAnimate
|
||||
to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than
|
||||
many. This prevents jank in browsers such as IE, and is generally a good thing.
|
||||
|
||||
If you're finding that your classes are not being immediately applied, be sure to invoke $digest().
|
||||
|
||||
Closes #8234
|
||||
Closes #9263
|
||||
|
||||
|
||||
<a name="1.3.0-rc.4"></a>
|
||||
# 1.3.0-rc.4 unicorn-hydrafication (2014-10-01)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- get $$observe listeners array as own property
|
||||
([a27d827c](https://github.com/angular/angular.js/commit/a27d827c22b0b6b3ba6b7495cf4fc338c6934b37),
|
||||
[#9343](https://github.com/angular/angular.js/issues/9343), [#9345](https://github.com/angular/angular.js/issues/9345))
|
||||
- Resolve leak with asynchronous compilation
|
||||
([6303c3dc](https://github.com/angular/angular.js/commit/6303c3dcf64685458fc84aa12289f5c9d57f4e47),
|
||||
[#9199](https://github.com/angular/angular.js/issues/9199), [#9079](https://github.com/angular/angular.js/issues/9079), [#8504](https://github.com/angular/angular.js/issues/8504), [#9197](https://github.com/angular/angular.js/issues/9197))
|
||||
- connect transclude scopes to their containing scope to prevent memory leaks
|
||||
([fb0c77f0](https://github.com/angular/angular.js/commit/fb0c77f0b66ed757a56af13f81b943419fdcbd7f),
|
||||
[#9095](https://github.com/angular/angular.js/issues/9095), [#9281](https://github.com/angular/angular.js/issues/9281))
|
||||
- sanitize srcset attribute
|
||||
([ab80cd90](https://github.com/angular/angular.js/commit/ab80cd90661396dbb1c94c5f4dd2d11ee8f6b6af))
|
||||
- **input:**
|
||||
- register builtin parsers/formatters before anyone else
|
||||
([10644432](https://github.com/angular/angular.js/commit/10644432ca9d5da69ce790a8d9e691640f333711),
|
||||
[#9218](https://github.com/angular/angular.js/issues/9218), [#9358](https://github.com/angular/angular.js/issues/9358))
|
||||
- correctly handle invalid model values for `input[date/time/…]`
|
||||
([a0bfdd0d](https://github.com/angular/angular.js/commit/a0bfdd0d60882125f614a91c321f12f730735e7b),
|
||||
[#8949](https://github.com/angular/angular.js/issues/8949), [#9375](https://github.com/angular/angular.js/issues/9375))
|
||||
- **ngModel:** do not parse undefined viewValue when validating
|
||||
([92f05e5a](https://github.com/angular/angular.js/commit/92f05e5a5900713301e64373d7b7daa45a88278b),
|
||||
[#9106](https://github.com/angular/angular.js/issues/9106), [#9260](https://github.com/angular/angular.js/issues/9260))
|
||||
- **ngView:** use animation promises ensure that only one leave animation occurs at a time
|
||||
([3624e380](https://github.com/angular/angular.js/commit/3624e3800fb3ccd2e9ea361a763e20131fd42c29),
|
||||
[#9355](https://github.com/angular/angular.js/issues/9355), [#7606](https://github.com/angular/angular.js/issues/7606), [#9374](https://github.com/angular/angular.js/issues/9374))
|
||||
- **select:** make ctrl.hasOption method consistent
|
||||
([2bcd02dc](https://github.com/angular/angular.js/commit/2bcd02dc1a6b28b357d47c83be3bed5c9a38417c),
|
||||
[#8761](https://github.com/angular/angular.js/issues/8761))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** optionally get controllers from ancestors only
|
||||
([07e3abc7](https://github.com/angular/angular.js/commit/07e3abc7dda872adc3fb25cb3e133f86f494b35d),
|
||||
[#4518](https://github.com/angular/angular.js/issues/4518), [#4540](https://github.com/angular/angular.js/issues/4540), [#8240](https://github.com/angular/angular.js/issues/8240), [#8511](https://github.com/angular/angular.js/issues/8511))
|
||||
- **Scope:** allow the parent of a new scope to be specified on creation
|
||||
([6417a3e9](https://github.com/angular/angular.js/commit/6417a3e9eb7ab0011cefada8db855aa929a64ff8))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$rootScope:** moving internal queues out of the Scope instances
|
||||
([b1192518](https://github.com/angular/angular.js/commit/b119251827cea670051198e1b48af7ee0c9f2a1b),
|
||||
[#9071](https://github.com/angular/angular.js/issues/9071))
|
||||
- **benchmark:** add ngBindOnce benchmarks to largetable-bp
|
||||
([2c8b4648](https://github.com/angular/angular.js/commit/2c8b4648526acf5c2645de8408a6d9ace2144b5f))
|
||||
- **ngForm,ngModel:** move initial addClass to the compile phase
|
||||
([b1ee5386](https://github.com/angular/angular.js/commit/b1ee5386d584f208bce6d3b613afdb3bae9df76a),
|
||||
[#8268](https://github.com/angular/angular.js/issues/8268))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [fb0c77f0](https://github.com/angular/angular.js/commit/fb0c77f0b66ed757a56af13f81b943419fdcbd7f),
|
||||
|
||||
|
||||
`$transclude` functions no longer attach `$destroy` event handlers to the
|
||||
transcluded content, and so the associated transclude scope will not automatically
|
||||
be destroyed if you remove a transcluded element from the DOM using direct DOM
|
||||
manipulation such as the jquery `remove()` method.
|
||||
|
||||
If you want to explicitly remove DOM elements inside your directive that have
|
||||
been compiled, and so potentially contain child (and transcluded) scopes, then
|
||||
it is your responsibility to get hold of the scope and destroy it at the same time.
|
||||
|
||||
The suggested approach is to create a new child scope of your own around any DOM
|
||||
elements that you wish to manipulate in this way and destroy those scopes if you
|
||||
remove their contents - any child scopes will then be destroyed and cleaned up
|
||||
automatically.
|
||||
|
||||
Note that all the built-in directives that manipulate the DOM (ngIf, ngRepeat,
|
||||
ngSwitch, etc) already follow this best practice, so if you only use these for
|
||||
manipulating the DOM then you do not have to worry about this change.
|
||||
|
||||
Closes #9095
|
||||
Closes #9281
|
||||
|
||||
- **$parse:** due to [5572b40b](https://github.com/angular/angular.js/commit/5572b40b15ed06969c8e0e92866c5afd088484b4),
|
||||
|
||||
- $scope['this'] no longer exits on the $scope object
|
||||
- $parse-ed expressions no longer allow chaining 'this' such as this['this'] or $parent['this']
|
||||
- 'this' in $parse-ed expressions can no longer be overriden, if a variable named 'this' is put on the scope it must be accessed using this['this']
|
||||
|
||||
Closes #9105
|
||||
|
||||
- **input:** due to [1eda1836](https://github.com/angular/angular.js/commit/1eda18365a348c9597aafba9d195d345e4f13d1e),
|
||||
|
||||
(Note: this change landed in 1.3.0-rc.3, but was not considered a breaking change at the time).
|
||||
|
||||
For text based inputs (text, email, url), the `$viewValue` will now always be converted to a string,
|
||||
regardless of what type the value is on the model.
|
||||
|
||||
To migrate, any code or expressions that expect the `$viewValue` to be anything other than string
|
||||
should be updated to expect a string.
|
||||
|
||||
|
||||
- **input:** due to a0bfdd0d60882125f614a91c321f12f730735e7b (see #8949),
|
||||
|
||||
Similar to `input[number]` Angular will now throw if the model value
|
||||
for a `input[date]` is not a `Date` object. Previously, Angular only
|
||||
showed an empty string instead.
|
||||
Angular does not set validation errors on the `<input>` in this case
|
||||
as those errors are shown to the user, but the erroneous state was
|
||||
caused by incorrect application logic and not by the user.
|
||||
|
||||
<a name="1.2.26"></a>
|
||||
# 1.2.26 captivating-disinterest (2014-10-01)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
|
||||
- **$compile:** Resolve leak with asynchronous compilation
|
||||
([5c9c1973](https://github.com/angular/angular.js/commit/5c9c19730526d5df6f16c523e578e5305f3796d0),
|
||||
[#9199](https://github.com/angular/angular.js/issues/9199), [#9079](https://github.com/angular/angular.js/issues/9079), [#8504](https://github.com/angular/angular.js/issues/8504), [#9197](https://github.com/angular/angular.js/issues/9197))
|
||||
- **select:** make ctrl.hasOption method consistent
|
||||
([11d2242d](https://github.com/angular/angular.js/commit/11d2242df65b2ade0dabe366a0c42963b6d37df5),
|
||||
[#8761](https://github.com/angular/angular.js/issues/8761))
|
||||
|
||||
|
||||
<a name="1.3.0-rc.3"></a>
|
||||
# 1.3.0-rc.3 aggressive-pacification (2014-09-23)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **ngModel:** support milliseconds in time and datetime
|
||||
([4b83f6ca](https://github.com/angular/angular.js/commit/4b83f6ca2c15bd65fe2b3894a02c04f9967fbff4),
|
||||
[#8874](https://github.com/angular/angular.js/issues/8874))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$location:** add ability to opt-out of `<base>` tag requirement in html5Mode
|
||||
([dc3de7fb](https://github.com/angular/angular.js/commit/dc3de7fb7a14c38b5c3dc7decfafb0b51d422dd1),
|
||||
[#8934](https://github.com/angular/angular.js/issues/8934))
|
||||
- **formController:** add $setUntouched to propagate untouched state
|
||||
([fd899755](https://github.com/angular/angular.js/commit/fd8997551f9ed4431f5e99d61f637139485076b9),
|
||||
[#9050](https://github.com/angular/angular.js/issues/9050))
|
||||
- **input:** support dynamic element validation
|
||||
([729c238e](https://github.com/angular/angular.js/commit/729c238e19ab27deff01448d79342ea53721bfed),
|
||||
[#4791](https://github.com/angular/angular.js/issues/4791), [#1404](https://github.com/angular/angular.js/issues/1404))
|
||||
- **ngAria:** add an ngAria module to make a11y easier
|
||||
([d1434c99](https://github.com/angular/angular.js/commit/d1434c999a66c6bb915ee1a8b091e497d288d940),
|
||||
[#5486](https://github.com/angular/angular.js/issues/5486))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **map:** use Array.prototype.map
|
||||
([a591e8b8](https://github.com/angular/angular.js/commit/a591e8b8d302efefd67bf0d5c4bad300a5f3aded))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$location:** due to [dc3de7fb](https://github.com/angular/angular.js/commit/dc3de7fb7a14c38b5c3dc7decfafb0b51d422dd1),
|
||||
The $location.html5Mode API has changed to allow enabling html5Mode by
|
||||
passing an object (as well as still supporting passing a boolean). Symmetrically, the
|
||||
method now returns an object instead of a boolean value.
|
||||
|
||||
To migrate, follow the code example below:
|
||||
|
||||
Before:
|
||||
|
||||
var mode = $locationProvider.html5Mode();
|
||||
|
||||
After:
|
||||
|
||||
var mode = $locationProvider.html5Mode().enabled;
|
||||
|
||||
Fixes #8934
|
||||
|
||||
|
||||
<a name="1.2.25"></a>
|
||||
# 1.2.25 hypnotic-gesticulation (2014-09-16)
|
||||
|
||||
@@ -1012,6 +1620,8 @@ Closes #8230
|
||||
|
||||
- **jQuery:** due to [9e7cb3c3](https://github.com/angular/angular.js/commit/9e7cb3c37543008e6236bb5a2c4536df2e1e43a9),
|
||||
Angular no longer supports jQuery versions below 2.1.1.
|
||||
- **$q:** due to [23bc92b1](https://github.com/angular/angular.js/commit/23bc92b17df882a907fb326320f0622717fefe7b),
|
||||
Promises methods are no longer enumerated when using for-loops with `hasOwnProperty` check. E.g. `angular.extends`
|
||||
|
||||
|
||||
<a name="1.2.22"></a>
|
||||
|
||||
+4
-4
@@ -65,13 +65,13 @@ Help us to maximize the effort we can spend fixing issues and adding new
|
||||
features, by not reporting duplicate issues. Providing the following information will increase the
|
||||
chances of your issue being dealt with quickly:
|
||||
|
||||
* **Overview of the issue** - if an error is being thrown a non-minified stack trace helps
|
||||
* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
|
||||
* **Motivation for or Use Case** - explain why this is a bug for you
|
||||
* **Angular Version(s)** - is it a regression?
|
||||
* **Browsers and Operating System** - is this a problem with all browsers or only IE8?
|
||||
* **Reproduce the error** - provide a live example (using [Plunker][plunker] or
|
||||
* **Reproduce the Error** - provide a live example (using [Plunker][plunker] or
|
||||
[JSFiddle][jsfiddle]) or a unambiguous set of steps.
|
||||
* **Related issues** - has a similar issue been reported before?
|
||||
* **Related Issues** - has a similar issue been reported before?
|
||||
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
|
||||
causing the problem (line of code or commit)
|
||||
|
||||
@@ -172,7 +172,7 @@ To ensure consistency throughout the source code, keep these rules in mind as yo
|
||||
* **Do not use namespaces**: Instead, wrap the entire angular code base in an anonymous closure and
|
||||
export our API explicitly rather than implicitly.
|
||||
* Wrap all code at **100 characters**.
|
||||
* Instead of complex inheritance hierarchies, we **prefer simple objects**. We use prototypical
|
||||
* Instead of complex inheritance hierarchies, we **prefer simple objects**. We use prototypal
|
||||
inheritance only when absolutely necessary.
|
||||
* We **love functions and closures** and, whenever possible, prefer them over objects.
|
||||
* To write concise code that can be better minified, we **use aliases internally** that map to the
|
||||
|
||||
@@ -6,10 +6,11 @@ use good old HTML (or HAML, Jade and friends!) as your template language and let
|
||||
syntax to express your application’s components clearly and succinctly. It automatically
|
||||
synchronizes data from your UI (view) with your JavaScript objects (model) through 2-way data
|
||||
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 also helps with
|
||||
server-side communication, taming async callbacks with promises and deferreds; and makes client-side
|
||||
navigation and deeplinking with hashbang urls or HTML5 pushState a piece of cake. The best of all:
|
||||
it makes development fun!
|
||||
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. The best of all: it makes development fun!
|
||||
|
||||
* Web site: http://angularjs.org
|
||||
* Tutorial: http://docs.angularjs.org/tutorial
|
||||
|
||||
@@ -26,7 +26,6 @@ This process based on the idea of minimizing user pain
|
||||
* You can triage older issues as well
|
||||
* Triage to your heart's content
|
||||
1. Assign yourself: Pick an issue that is not assigned to anyone and assign it to you
|
||||
|
||||
1. Understandable? - verify if the description of the request is clear.
|
||||
* If not, [close it][] according to the instructions below and go to the last step.
|
||||
1. Duplicate?
|
||||
@@ -36,7 +35,6 @@ This process based on the idea of minimizing user pain
|
||||
* Label `Type: Bug`
|
||||
* Reproducible? - Steps to reproduce the bug are clear. If they are not, ask for a clarification. If there's no reply after a week, [close it][].
|
||||
* Reproducible on master? - <http://code.angularjs.org/snapshot/>
|
||||
|
||||
1. Non bugs:
|
||||
* Label `Type: Feature`, `Type: Chore`, or `Type: Perf`
|
||||
* Belongs in core? – Often new features should be implemented as a third-party module rather than an addition to the core.
|
||||
@@ -59,7 +57,6 @@ This process based on the idea of minimizing user pain
|
||||
* In rare cases, it's ok to have multiple components.
|
||||
1. Label `PRs plz!` - These issues are good targets for PRs from the open source community. Apply to issues where the problem and solution are well defined in the comments, and it's not too complex.
|
||||
1. Label `origin: google` for issues from Google
|
||||
|
||||
1. Assign a milestone:
|
||||
* Backlog - triaged fixes and features, should be the default choice
|
||||
* Current 1.x.y milestone (e.g. 1.3.0-beta-2) - regressions and urgent bugs only
|
||||
|
||||
+3
-1
@@ -112,7 +112,7 @@ var printSection = function(stream, title, section, printCommitLinks) {
|
||||
}
|
||||
stream.write(')\n');
|
||||
} else {
|
||||
stream.write(util.format('%s %s', prefix, commit.subject));
|
||||
stream.write(util.format('%s %s\n', prefix, commit.subject));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -188,6 +188,7 @@ var getPreviousTag = function() {
|
||||
|
||||
|
||||
var generate = function(version, file) {
|
||||
|
||||
getPreviousTag().then(function(tag) {
|
||||
console.log('Reading git log since', tag);
|
||||
readGitLog('^fix|^feat|^perf|BREAKING', tag).then(function(commits) {
|
||||
@@ -201,6 +202,7 @@ var generate = function(version, file) {
|
||||
|
||||
// publish for testing
|
||||
exports.parseRawCommit = parseRawCommit;
|
||||
exports.printSection = printSection;
|
||||
|
||||
// hacky start if not run by jasmine :-D
|
||||
if (process.argv.join('').indexOf('jasmine-node') === -1) {
|
||||
|
||||
+62
-1
@@ -1,4 +1,4 @@
|
||||
/* global describe: false, it: false, expect: false */
|
||||
/* global describe: false, beforeEach: false, afterEach: false, it: false, expect: false */
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -44,4 +44,65 @@ describe('changelog.js', function() {
|
||||
expect(msg.breaking).toEqual(' first breaking change\nsomething else\nanother line with more info\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('printSection', function() {
|
||||
var output;
|
||||
var streamMock = {
|
||||
write: function(str) {
|
||||
output += str;
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
output = '';
|
||||
});
|
||||
|
||||
it('should add a new line at the end of each breaking change list item ' +
|
||||
'when there is 1 item per component', function() {
|
||||
var title = 'test';
|
||||
var printCommitLinks = false;
|
||||
|
||||
var section = {
|
||||
module1: [{subject: 'breaking change 1'}],
|
||||
module2: [{subject: 'breaking change 2'}]
|
||||
};
|
||||
var expectedOutput =
|
||||
'\n' + '## test\n\n' +
|
||||
'- **module1:** breaking change 1\n' +
|
||||
'- **module2:** breaking change 2\n' +
|
||||
'\n';
|
||||
|
||||
ch.printSection(streamMock, title, section, printCommitLinks);
|
||||
expect(output).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should add a new line at the end of each breaking change list item ' +
|
||||
'when there are multiple items per component', function() {
|
||||
var title = 'test';
|
||||
var printCommitLinks = false;
|
||||
|
||||
var section = {
|
||||
module1: [
|
||||
{subject: 'breaking change 1.1'},
|
||||
{subject: 'breaking change 1.2'}
|
||||
],
|
||||
module2: [
|
||||
{subject: 'breaking change 2.1'},
|
||||
{subject: 'breaking change 2.2'}
|
||||
]
|
||||
};
|
||||
var expectedOutput =
|
||||
'\n' + '## test\n\n' +
|
||||
'- **module1:**\n' +
|
||||
' - breaking change 1.1\n' +
|
||||
' - breaking change 1.2\n' +
|
||||
'- **module2:**\n' +
|
||||
' - breaking change 2.1\n' +
|
||||
' - breaking change 2.2\n' +
|
||||
'\n';
|
||||
|
||||
ch.printSection(streamMock, title, section, printCommitLinks);
|
||||
expect(output).toBe(expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -316,10 +316,10 @@ iframe.example {
|
||||
}
|
||||
|
||||
.search-results-group.col-group-api { width:30%; }
|
||||
.search-results-group.col-group-guide { width:30%; }
|
||||
.search-results-group.col-group-tutorial { width:25%; }
|
||||
.search-results-group.col-group-guide,
|
||||
.search-results-group.col-group-tutorial { width:20%; }
|
||||
.search-results-group.col-group-misc,
|
||||
.search-results-group.col-group-error { float:right; clear:both; width:15% }
|
||||
.search-results-group.col-group-error { width:15%; float: right; }
|
||||
|
||||
|
||||
.search-results-group.col-group-api .search-result {
|
||||
@@ -391,7 +391,6 @@ iframe.example {
|
||||
position:fixed;
|
||||
top:120px;
|
||||
bottom:0;
|
||||
padding-bottom:120px;
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
@@ -412,6 +411,7 @@ iframe.example {
|
||||
|
||||
.main-body-grid .side-navigation {
|
||||
position:relative;
|
||||
padding-bottom:120px;
|
||||
}
|
||||
|
||||
.main-body-grid .side-navigation.ng-hide {
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var directive = {};
|
||||
var service = { value: {} };
|
||||
|
||||
var DEPENDENCIES = {
|
||||
'angular.js': 'http://code.angularjs.org/' + angular.version.full + '/angular.min.js',
|
||||
'angular-resource.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-resource.min.js',
|
||||
'angular-route.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-route.min.js',
|
||||
'angular-animate.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-animate.min.js',
|
||||
'angular-sanitize.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-sanitize.min.js',
|
||||
'angular-cookies.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-cookies.min.js'
|
||||
};
|
||||
|
||||
|
||||
function escape(text) {
|
||||
return text.
|
||||
replace(/\&/g, '&').
|
||||
replace(/\</g, '<').
|
||||
replace(/\>/g, '>').
|
||||
replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie
|
||||
* http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript
|
||||
*/
|
||||
function setHtmlIe8SafeWay(element, html) {
|
||||
var newElement = angular.element('<pre>' + html + '</pre>');
|
||||
|
||||
element.empty();
|
||||
element.append(newElement.contents());
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
directive.jsFiddle = function(getEmbeddedTemplate, escape, script) {
|
||||
return {
|
||||
terminal: true,
|
||||
link: function(scope, element, attr) {
|
||||
var name = '',
|
||||
stylesheet = '<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">\n',
|
||||
fields = {
|
||||
html: '',
|
||||
css: '',
|
||||
js: ''
|
||||
};
|
||||
|
||||
angular.forEach(attr.jsFiddle.split(' '), function(file, index) {
|
||||
var fileType = file.split('.')[1];
|
||||
|
||||
if (fileType == 'html') {
|
||||
if (index == 0) {
|
||||
fields[fileType] +=
|
||||
'<div ng-app' + (attr.module ? '="' + attr.module + '"' : '') + '>\n' +
|
||||
getEmbeddedTemplate(file, 2);
|
||||
} else {
|
||||
fields[fileType] += '\n\n\n <!-- CACHE FILE: ' + file + ' -->\n' +
|
||||
' <script type="text/ng-template" id="' + file + '">\n' +
|
||||
getEmbeddedTemplate(file, 4) +
|
||||
' </script>\n';
|
||||
}
|
||||
} else {
|
||||
fields[fileType] += getEmbeddedTemplate(file) + '\n';
|
||||
}
|
||||
});
|
||||
|
||||
fields.html += '</div>\n';
|
||||
|
||||
setHtmlIe8SafeWay(element,
|
||||
'<form class="jsfiddle" method="post" action="http://jsfiddle.net/api/post/library/pure/" target="_blank">' +
|
||||
hiddenField('title', 'AngularJS Example: ' + name) +
|
||||
hiddenField('css', '</style> <!-- Ugly Hack due to jsFiddle issue: http://goo.gl/BUfGZ --> \n' +
|
||||
stylesheet +
|
||||
script.angular +
|
||||
(attr.resource ? script.resource : '') +
|
||||
'<style>\n' +
|
||||
fields.css) +
|
||||
hiddenField('html', fields.html) +
|
||||
hiddenField('js', fields.js) +
|
||||
'<button class="btn btn-primary"><i class="icon-white icon-pencil"></i> Edit Me</button>' +
|
||||
'</form>');
|
||||
|
||||
function hiddenField(name, value) {
|
||||
return '<input type="hidden" name="' + name + '" value="' + escape(value) + '">';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
directive.ngSetText = ['getEmbeddedTemplate', function(getEmbeddedTemplate) {
|
||||
return {
|
||||
restrict: 'CA',
|
||||
priority: 10,
|
||||
compile: function(element, attr) {
|
||||
setHtmlIe8SafeWay(element, escape(getEmbeddedTemplate(attr.ngSetText)));
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
|
||||
directive.ngHtmlWrap = ['reindentCode', 'templateMerge', function(reindentCode, templateMerge) {
|
||||
return {
|
||||
compile: function(element, attr) {
|
||||
var properties = {
|
||||
head: '',
|
||||
module: '',
|
||||
body: element.text()
|
||||
},
|
||||
html = "<!doctype html>\n<html ng-app{{module}}>\n <head>\n{{head:4}} </head>\n <body>\n{{body:4}} </body>\n</html>";
|
||||
|
||||
angular.forEach((attr.ngHtmlWrap || '').split(' '), function(dep) {
|
||||
if (!dep) return;
|
||||
dep = DEPENDENCIES[dep] || dep;
|
||||
|
||||
var ext = dep.split(/\./).pop();
|
||||
|
||||
if (ext == 'css') {
|
||||
properties.head += '<link rel="stylesheet" href="' + dep + '" type="text/css">\n';
|
||||
} else if(ext == 'js') {
|
||||
properties.head += '<script src="' + dep + '"></script>\n';
|
||||
} else {
|
||||
properties.module = '="' + dep + '"';
|
||||
}
|
||||
});
|
||||
|
||||
setHtmlIe8SafeWay(element, escape(templateMerge(html, properties)));
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
directive.ngSetHtml = ['getEmbeddedTemplate', function(getEmbeddedTemplate) {
|
||||
return {
|
||||
restrict: 'CA',
|
||||
priority: 10,
|
||||
compile: function(element, attr) {
|
||||
setHtmlIe8SafeWay(element, getEmbeddedTemplate(attr.ngSetHtml));
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
directive.ngEvalJavascript = ['getEmbeddedTemplate', function(getEmbeddedTemplate) {
|
||||
return {
|
||||
compile: function (element, attr) {
|
||||
var fileNames = attr.ngEvalJavascript.split(' ');
|
||||
angular.forEach(fileNames, function(fileName) {
|
||||
var script = getEmbeddedTemplate(fileName);
|
||||
try {
|
||||
if (window.execScript) { // IE
|
||||
window.execScript(script || '""'); // IE complains when evaling empty string
|
||||
} else {
|
||||
window.eval(script + '//@ sourceURL=' + fileName);
|
||||
}
|
||||
} catch (e) {
|
||||
if (window.console) {
|
||||
window.console.log(script, '\n', e);
|
||||
} else {
|
||||
window.alert(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
|
||||
directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location', '$sniffer', '$animate',
|
||||
function($templateCache, $browser, docsRootScope, $location, $sniffer, $animate) {
|
||||
return {
|
||||
terminal: true,
|
||||
link: function(scope, element, attrs) {
|
||||
var modules = ['ngAnimate'],
|
||||
embedRootScope,
|
||||
deregisterEmbedRootScope;
|
||||
|
||||
modules.push(['$provide', function($provide) {
|
||||
$provide.value('$templateCache', $templateCache);
|
||||
$provide.value('$anchorScroll', angular.noop);
|
||||
$provide.value('$browser', $browser);
|
||||
$provide.value('$sniffer', $sniffer);
|
||||
$provide.value('$animate', $animate);
|
||||
$provide.provider('$location', function() {
|
||||
this.$get = ['$rootScope', function($rootScope) {
|
||||
docsRootScope.$on('$locationChangeSuccess', function(event, oldUrl, newUrl) {
|
||||
$rootScope.$broadcast('$locationChangeSuccess', oldUrl, newUrl);
|
||||
});
|
||||
return $location;
|
||||
}];
|
||||
this.html5Mode = angular.noop;
|
||||
});
|
||||
|
||||
$provide.decorator('$rootScope', ['$delegate', function($delegate) {
|
||||
embedRootScope = $delegate;
|
||||
|
||||
// Since we are teleporting the $animate service, which relies on the $$postDigestQueue
|
||||
// we need the embedded scope to use the same $$postDigestQueue as the outer scope
|
||||
embedRootScope.$$postDigestQueue = docsRootScope.$$postDigestQueue;
|
||||
|
||||
deregisterEmbedRootScope = docsRootScope.$watch(function embedRootScopeDigestWatch() {
|
||||
embedRootScope.$digest();
|
||||
});
|
||||
|
||||
return embedRootScope;
|
||||
}]);
|
||||
}]);
|
||||
if (attrs.ngEmbedApp) modules.push(attrs.ngEmbedApp);
|
||||
|
||||
element.on('click', function(event) {
|
||||
if (event.target.attributes.getNamedItem('ng-click')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
element.bind('$destroy', function() {
|
||||
deregisterEmbedRootScope();
|
||||
embedRootScope.$destroy();
|
||||
});
|
||||
|
||||
element.data('$injector', null);
|
||||
angular.bootstrap(element, modules);
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
service.reindentCode = function() {
|
||||
return function (text, spaces) {
|
||||
if (!text) return text;
|
||||
var lines = text.split(/\r?\n/);
|
||||
var prefix = ' '.substr(0, spaces || 0);
|
||||
var i;
|
||||
|
||||
// remove any leading blank lines
|
||||
while (lines.length && lines[0].match(/^\s*$/)) lines.shift();
|
||||
// remove any trailing blank lines
|
||||
while (lines.length && lines[lines.length - 1].match(/^\s*$/)) lines.pop();
|
||||
var minIndent = 999;
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
var line = lines[0];
|
||||
var reindentCode = line.match(/^\s*/)[0];
|
||||
if (reindentCode !== line && reindentCode.length < minIndent) {
|
||||
minIndent = reindentCode.length;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
lines[i] = prefix + lines[i].substring(minIndent);
|
||||
}
|
||||
lines.push('');
|
||||
return lines.join('\n');
|
||||
}
|
||||
};
|
||||
|
||||
service.templateMerge = ['reindentCode', function(indentCode) {
|
||||
return function(template, properties) {
|
||||
return template.replace(/\{\{(\w+)(?:\:(\d+))?\}\}/g, function(_, key, indent) {
|
||||
var value = properties[key];
|
||||
|
||||
if (indent) {
|
||||
value = indentCode(value, indent);
|
||||
}
|
||||
|
||||
return value == undefined ? '' : value;
|
||||
});
|
||||
};
|
||||
}];
|
||||
|
||||
service.getEmbeddedTemplate = ['reindentCode', function(reindentCode) {
|
||||
return function (id) {
|
||||
var element = document.getElementById(id);
|
||||
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return reindentCode(angular.element(element).html(), 0);
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
angular.module('bootstrapPrettify', []).directive(directive).factory(service);
|
||||
@@ -0,0 +1,44 @@
|
||||
"use strict";
|
||||
/* jshint browser: true */
|
||||
/* global importScripts, onmessage: true, postMessage, lunr */
|
||||
|
||||
// Load up the lunr library
|
||||
importScripts('../components/lunr.js-0.4.2/lunr.min.js');
|
||||
|
||||
// Create the lunr index - the docs should be an array of object, each object containing
|
||||
// the path and search terms for a page
|
||||
var index = lunr(function() {
|
||||
this.ref('path');
|
||||
this.field('titleWords', {boost: 50});
|
||||
this.field('members', { boost: 40});
|
||||
this.field('keywords', { boost : 20 });
|
||||
});
|
||||
|
||||
// Retrieve the searchData which contains the information about each page to be indexed
|
||||
var searchData = {};
|
||||
var searchDataRequest = new XMLHttpRequest();
|
||||
searchDataRequest.onload = function() {
|
||||
|
||||
// Store the pages data to be used in mapping query results back to pages
|
||||
searchData = JSON.parse(this.responseText);
|
||||
// Add search terms from each page to the search index
|
||||
searchData.forEach(function(page) {
|
||||
index.add(page);
|
||||
});
|
||||
postMessage({ e: 'index-ready' });
|
||||
};
|
||||
searchDataRequest.open('GET', 'search-data.json');
|
||||
searchDataRequest.send();
|
||||
|
||||
// The worker receives a message everytime the web app wants to query the index
|
||||
onmessage = function(oEvent) {
|
||||
var q = oEvent.data.q;
|
||||
var hits = index.search(q);
|
||||
var results = [];
|
||||
// Only return the array of paths to pages
|
||||
hits.forEach(function(hit) {
|
||||
results.push(hit.ref);
|
||||
});
|
||||
// The results of the query are sent back to the web app via a new message
|
||||
postMessage({ e: 'query-ready', q: q, d: results });
|
||||
};
|
||||
@@ -54,10 +54,13 @@ describe('docs.angularjs.org', function () {
|
||||
});
|
||||
|
||||
|
||||
it("should display an error if the page does not exist", function() {
|
||||
browser.get('index-debug.html#!/api/does/not/exist');
|
||||
expect(element(by.css('h1')).getText()).toBe('Oops!');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Error Handling', function() {
|
||||
it("should display an error if the page does not exist", function() {
|
||||
browser.get('index-debug.html#!/api/does/not/exist');
|
||||
expect(element(by.css('h1')).getText()).toBe('Oops!');
|
||||
});
|
||||
});
|
||||
|
||||
+3
-3
@@ -6,6 +6,7 @@ angular.module('docsApp', [
|
||||
'DocsController',
|
||||
'versionsData',
|
||||
'pagesData',
|
||||
'navData',
|
||||
'directives',
|
||||
'errors',
|
||||
'examples',
|
||||
@@ -13,11 +14,10 @@ angular.module('docsApp', [
|
||||
'tutorials',
|
||||
'versions',
|
||||
'bootstrap',
|
||||
'bootstrapPrettify',
|
||||
'ui.bootstrap.dropdown'
|
||||
])
|
||||
|
||||
|
||||
.config(function($locationProvider) {
|
||||
.config(['$locationProvider', function($locationProvider) {
|
||||
$locationProvider.html5Mode(true).hashPrefix('!');
|
||||
});
|
||||
}]);
|
||||
|
||||
+9
-74
@@ -6,31 +6,10 @@ angular.module('DocsController', [])
|
||||
function($scope, $rootScope, $location, $window, $cookies, openPlunkr,
|
||||
NG_PAGES, NG_NAVIGATION, NG_VERSION) {
|
||||
|
||||
|
||||
$scope.openPlunkr = openPlunkr;
|
||||
|
||||
$scope.docsVersion = NG_VERSION.isSnapshot ? 'snapshot' : NG_VERSION.version;
|
||||
|
||||
$scope.fold = function(url) {
|
||||
if(url) {
|
||||
$scope.docs_fold = '/notes/' + url;
|
||||
if(/\/build/.test($window.location.href)) {
|
||||
$scope.docs_fold = '/build/docs' + $scope.docs_fold;
|
||||
}
|
||||
window.scrollTo(0,0);
|
||||
}
|
||||
else {
|
||||
$scope.docs_fold = null;
|
||||
}
|
||||
};
|
||||
var OFFLINE_COOKIE_NAME = 'ng-offline',
|
||||
INDEX_PATH = /^(\/|\/index[^\.]*.html)$/;
|
||||
|
||||
|
||||
/**********************************
|
||||
Publish methods
|
||||
***********************************/
|
||||
|
||||
$scope.navClass = function(navItem) {
|
||||
return {
|
||||
active: navItem.href && this.currentPage.path,
|
||||
@@ -38,55 +17,22 @@ angular.module('DocsController', [])
|
||||
};
|
||||
};
|
||||
|
||||
$scope.afterPartialLoaded = function() {
|
||||
|
||||
|
||||
$scope.$on('$includeContentLoaded', function() {
|
||||
var pagePath = $scope.currentPage ? $scope.currentPage.path : $location.path();
|
||||
$window._gaq.push(['_trackPageview', pagePath]);
|
||||
};
|
||||
|
||||
/** stores a cookie that is used by apache to decide which manifest ot send */
|
||||
$scope.enableOffline = function() {
|
||||
//The cookie will be good for one year!
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime()+(365*24*60*60*1000));
|
||||
var expires = "; expires="+date.toGMTString();
|
||||
var value = angular.version.full;
|
||||
document.cookie = OFFLINE_COOKIE_NAME + "="+value+expires+"; path=" + $location.path;
|
||||
|
||||
//force the page to reload so server can serve new manifest file
|
||||
window.location.reload(true);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**********************************
|
||||
Watches
|
||||
***********************************/
|
||||
|
||||
});
|
||||
|
||||
$scope.$watch(function docsPathWatch() {return $location.path(); }, function docsPathWatchAction(path) {
|
||||
|
||||
var currentPage = $scope.currentPage = NG_PAGES[path];
|
||||
if ( !currentPage && path.charAt(0)==='/' ) {
|
||||
// Strip off leading slash
|
||||
path = path.substr(1);
|
||||
}
|
||||
|
||||
currentPage = $scope.currentPage = NG_PAGES[path];
|
||||
if ( !currentPage && path.charAt(path.length-1) === '/' && path.length > 1 ) {
|
||||
// Strip off trailing slash
|
||||
path = path.substr(0, path.length-1);
|
||||
}
|
||||
|
||||
currentPage = $scope.currentPage = NG_PAGES[path];
|
||||
if ( !currentPage && /\/index$/.test(path) ) {
|
||||
// Strip off index from the end
|
||||
path = path.substr(0, path.length - 6);
|
||||
}
|
||||
path = path.replace(/^\/?(.+?)(\/index)?\/?$/, '$1');
|
||||
|
||||
currentPage = $scope.currentPage = NG_PAGES[path];
|
||||
|
||||
if ( currentPage ) {
|
||||
$scope.currentArea = currentPage && NG_NAVIGATION[currentPage.area];
|
||||
$scope.partialPath = 'partials/' + path + '.html';
|
||||
$scope.currentArea = NG_NAVIGATION[currentPage.area];
|
||||
var pathParts = currentPage.path.split('/');
|
||||
var breadcrumb = $scope.breadcrumb = [];
|
||||
var breadcrumbPath = '';
|
||||
@@ -98,6 +44,7 @@ angular.module('DocsController', [])
|
||||
} else {
|
||||
$scope.currentArea = NG_NAVIGATION['api'];
|
||||
$scope.breadcrumb = [];
|
||||
$scope.partialPath = 'Error404.html';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -107,24 +54,12 @@ angular.module('DocsController', [])
|
||||
|
||||
$scope.versionNumber = angular.version.full;
|
||||
$scope.version = angular.version.full + " " + angular.version.codeName;
|
||||
$scope.subpage = false;
|
||||
$scope.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
|
||||
$scope.futurePartialTitle = null;
|
||||
$scope.loading = 0;
|
||||
$scope.$cookies = $cookies;
|
||||
|
||||
$cookies.platformPreference = $cookies.platformPreference || 'gitUnix';
|
||||
|
||||
var INDEX_PATH = /^(\/|\/index[^\.]*.html)$/;
|
||||
if (!$location.path() || INDEX_PATH.test($location.path())) {
|
||||
$location.path('/api').replace();
|
||||
}
|
||||
|
||||
// bind escape to hash reset callback
|
||||
angular.element(window).on('keydown', function(e) {
|
||||
if (e.keyCode === 27) {
|
||||
$scope.$apply(function() {
|
||||
$scope.subpage = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
angular.module('docsApp.navigationService', [])
|
||||
|
||||
.factory('navigationService', function($window) {
|
||||
var service = {
|
||||
currentPage: null,
|
||||
currentVersion: null,
|
||||
changePage: function(newPage) {
|
||||
|
||||
},
|
||||
changeVersion: function(newVersion) {
|
||||
|
||||
//TODO =========
|
||||
// var currentPagePath = '';
|
||||
|
||||
// // preserve URL path when switching between doc versions
|
||||
// if (angular.isObject($rootScope.currentPage) && $rootScope.currentPage.section && $rootScope.currentPage.id) {
|
||||
// currentPagePath = '/' + $rootScope.currentPage.section + '/' + $rootScope.currentPage.id;
|
||||
// }
|
||||
|
||||
// $window.location = version.url + currentPagePath;
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
+123
-64
@@ -10,22 +10,35 @@ angular.module('search', [])
|
||||
$scope.search = function(q) {
|
||||
var MIN_SEARCH_LENGTH = 2;
|
||||
if(q.length >= MIN_SEARCH_LENGTH) {
|
||||
var results = docsSearch(q);
|
||||
var totalAreas = 0;
|
||||
for(var i in results) {
|
||||
++totalAreas;
|
||||
}
|
||||
if(totalAreas > 0) {
|
||||
$scope.colClassName = 'cols-' + totalAreas;
|
||||
}
|
||||
$scope.hasResults = totalAreas > 0;
|
||||
$scope.results = results;
|
||||
docsSearch(q).then(function(hits) {
|
||||
var results = {};
|
||||
angular.forEach(hits, function(hit) {
|
||||
var area = hit.area;
|
||||
|
||||
var limit = (area == 'api') ? 40 : 14;
|
||||
results[area] = results[area] || [];
|
||||
if(results[area].length < limit) {
|
||||
results[area].push(hit);
|
||||
}
|
||||
});
|
||||
|
||||
var totalAreas = 0;
|
||||
for(var i in results) {
|
||||
++totalAreas;
|
||||
}
|
||||
if(totalAreas > 0) {
|
||||
$scope.colClassName = 'cols-' + totalAreas;
|
||||
}
|
||||
$scope.hasResults = totalAreas > 0;
|
||||
$scope.results = results;
|
||||
});
|
||||
}
|
||||
else {
|
||||
clearResults();
|
||||
}
|
||||
if(!$scope.$$phase) $scope.$apply();
|
||||
};
|
||||
|
||||
$scope.submit = function() {
|
||||
var result;
|
||||
for(var i in $scope.results) {
|
||||
@@ -39,78 +52,124 @@ angular.module('search', [])
|
||||
$scope.hideResults();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.hideResults = function() {
|
||||
clearResults();
|
||||
$scope.q = '';
|
||||
};
|
||||
}])
|
||||
|
||||
.controller('Error404SearchCtrl', ['$scope', '$location', 'docsSearch', function($scope, $location, docsSearch) {
|
||||
$scope.results = docsSearch($location.path().split(/[\/\.:]/).pop());
|
||||
|
||||
.controller('Error404SearchCtrl', ['$scope', '$location', 'docsSearch',
|
||||
function($scope, $location, docsSearch) {
|
||||
docsSearch($location.path().split(/[\/\.:]/).pop()).then(function(results) {
|
||||
$scope.results = {};
|
||||
angular.forEach(results, function(result) {
|
||||
var area = $scope.results[result.area] || [];
|
||||
area.push(result);
|
||||
$scope.results[result.area] = area;
|
||||
});
|
||||
});
|
||||
}])
|
||||
|
||||
.factory('lunrSearch', function() {
|
||||
return function(properties) {
|
||||
if (window.RUNNING_IN_NG_TEST_RUNNER) return null;
|
||||
|
||||
var engine = lunr(properties);
|
||||
return {
|
||||
store : function(values) {
|
||||
engine.add(values);
|
||||
},
|
||||
search : function(q) {
|
||||
return engine.search(q);
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.provider('docsSearch', function() {
|
||||
|
||||
.factory('docsSearch', ['$rootScope','lunrSearch', 'NG_PAGES',
|
||||
function($rootScope, lunrSearch, NG_PAGES) {
|
||||
if (window.RUNNING_IN_NG_TEST_RUNNER) {
|
||||
return null;
|
||||
}
|
||||
// This version of the service builds the index in the current thread,
|
||||
// which blocks rendering and other browser activities.
|
||||
// It should only be used where the browser does not support WebWorkers
|
||||
function localSearchFactory($http, $timeout, NG_PAGES) {
|
||||
|
||||
var index = lunrSearch(function() {
|
||||
this.ref('id');
|
||||
this.field('title', {boost: 50});
|
||||
this.field('members', { boost: 40});
|
||||
this.field('keywords', { boost : 20 });
|
||||
});
|
||||
console.log('Using Local Search Index');
|
||||
|
||||
angular.forEach(NG_PAGES, function(page, key) {
|
||||
if(page.searchTerms) {
|
||||
index.store({
|
||||
id : key,
|
||||
title : page.searchTerms.titleWords,
|
||||
keywords : page.searchTerms.keywords,
|
||||
members : page.searchTerms.members
|
||||
// Create the lunr index
|
||||
var index = lunr(function() {
|
||||
this.ref('path');
|
||||
this.field('titleWords', {boost: 50});
|
||||
this.field('members', { boost: 40});
|
||||
this.field('keywords', { boost : 20 });
|
||||
});
|
||||
|
||||
// Delay building the index by loading the data asynchronously
|
||||
var indexReadyPromise = $http.get('js/search-data.json').then(function(response) {
|
||||
var searchData = response.data;
|
||||
// Delay building the index for 500ms to allow the page to render
|
||||
return $timeout(function() {
|
||||
// load the page data into the index
|
||||
angular.forEach(searchData, function(page) {
|
||||
index.add(page);
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// The actual service is a function that takes a query string and
|
||||
// returns a promise to the search results
|
||||
// (In this case we just resolve the promise immediately as it is not
|
||||
// inherently an async process)
|
||||
return function(q) {
|
||||
return indexReadyPromise.then(function() {
|
||||
var hits = index.search(q);
|
||||
var results = [];
|
||||
angular.forEach(hits, function(hit) {
|
||||
results.push(NG_PAGES[hit.ref]);
|
||||
});
|
||||
return results;
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
localSearchFactory.$inject = ['$http', '$timeout', 'NG_PAGES'];
|
||||
|
||||
return function(q) {
|
||||
var results = {
|
||||
api : [],
|
||||
tutorial : [],
|
||||
guide : [],
|
||||
error : [],
|
||||
misc : []
|
||||
// This version of the service builds the index in a WebWorker,
|
||||
// which does not block rendering and other browser activities.
|
||||
// It should only be used where the browser does support WebWorkers
|
||||
function webWorkerSearchFactory($q, $rootScope, NG_PAGES) {
|
||||
|
||||
console.log('Using WebWorker Search Index')
|
||||
|
||||
var searchIndex = $q.defer();
|
||||
var results;
|
||||
|
||||
var worker = new Worker('js/search-worker.js');
|
||||
|
||||
// The worker will send us a message in two situations:
|
||||
// - when the index has been built, ready to run a query
|
||||
// - when it has completed a search query and the results are available
|
||||
worker.onmessage = function(oEvent) {
|
||||
$rootScope.$apply(function() {
|
||||
|
||||
switch(oEvent.data.e) {
|
||||
case 'index-ready':
|
||||
searchIndex.resolve();
|
||||
break;
|
||||
case 'query-ready':
|
||||
var pages = oEvent.data.d.map(function(path) {
|
||||
return NG_PAGES[path];
|
||||
});
|
||||
results.resolve(pages);
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
angular.forEach(index.search(q), function(result) {
|
||||
var key = result.ref;
|
||||
var item = NG_PAGES[key];
|
||||
var area = item.area;
|
||||
item.path = key;
|
||||
|
||||
var limit = area == 'api' ? 40 : 14;
|
||||
if(results[area].length < limit) {
|
||||
results[area].push(item);
|
||||
}
|
||||
});
|
||||
return results;
|
||||
// The actual service is a function that takes a query string and
|
||||
// returns a promise to the search results
|
||||
return function(q) {
|
||||
|
||||
// We only run the query once the index is ready
|
||||
return searchIndex.promise.then(function() {
|
||||
|
||||
results = $q.defer();
|
||||
worker.postMessage({ q: q });
|
||||
return results.promise;
|
||||
});
|
||||
};
|
||||
}
|
||||
webWorkerSearchFactory.$inject = ['$q', '$rootScope', 'NG_PAGES'];
|
||||
|
||||
return {
|
||||
$get: window.Worker ? webWorkerSearchFactory : localSearchFactory
|
||||
};
|
||||
}])
|
||||
})
|
||||
|
||||
.directive('focused', function($timeout) {
|
||||
return function(scope, element, attrs) {
|
||||
|
||||
+15
-16
@@ -1,6 +1,6 @@
|
||||
angular.module('tutorials', [])
|
||||
|
||||
.directive('docTutorialNav', function(templateMerge) {
|
||||
.directive('docTutorialNav', function() {
|
||||
var pages = [
|
||||
'',
|
||||
'step_00', 'step_01', 'step_02', 'step_03', 'step_04',
|
||||
@@ -8,23 +8,22 @@ angular.module('tutorials', [])
|
||||
'step_10', 'step_11', 'step_12', 'the_end'
|
||||
];
|
||||
return {
|
||||
compile: function(element, attrs) {
|
||||
var seq = 1 * attrs.docTutorialNav,
|
||||
props = {
|
||||
seq: seq,
|
||||
prev: pages[seq],
|
||||
next: pages[2 + seq],
|
||||
diffLo: seq ? (seq - 1): '0~1',
|
||||
diffHi: seq
|
||||
};
|
||||
scope: {},
|
||||
template:
|
||||
'<a ng-href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-step-backward"></i> Previous</li></a>\n' +
|
||||
'<a ng-href="http://angular.github.io/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
|
||||
'<a ng-href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="glyphicon glyphicon-step-forward"></i></li></a>',
|
||||
link: function(scope, element, attrs) {
|
||||
var seq = 1 * attrs.docTutorialNav;
|
||||
scope.seq = seq;
|
||||
scope.prev = pages[seq];
|
||||
scope.next = pages[2 + seq];
|
||||
scope.diffLo = seq ? (seq - 1): '0~1';
|
||||
scope.diffHi = seq;
|
||||
|
||||
element.addClass('btn-group');
|
||||
element.addClass('tutorial-nav');
|
||||
element.append(templateMerge(
|
||||
'<a href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-step-backward"></i> Previous</li></a>\n' +
|
||||
'<a href="http://angular.github.io/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
|
||||
'<a href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
|
||||
'<a href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="glyphicon glyphicon-step-forward"></i></li></a>', props));
|
||||
}
|
||||
};
|
||||
})
|
||||
@@ -47,4 +46,4 @@ angular.module('tutorials', [])
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{step ? (step - 1): \'0~1\'}}...step-{{step}}">GitHub</a>\n' +
|
||||
'</p>'
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ angular.module('versions', [])
|
||||
|
||||
.controller('DocsVersionsCtrl', ['$scope', '$location', '$window', 'NG_VERSIONS', function($scope, $location, $window, NG_VERSIONS) {
|
||||
$scope.docs_version = NG_VERSIONS[0];
|
||||
$scope.docs_versions = NG_VERSIONS;
|
||||
|
||||
for(var i=0, minor = NaN; i < NG_VERSIONS.length; i++) {
|
||||
var version = NG_VERSIONS[i];
|
||||
@@ -15,13 +16,12 @@ angular.module('versions', [])
|
||||
minor = version.minor;
|
||||
}
|
||||
|
||||
$scope.docs_versions = NG_VERSIONS;
|
||||
$scope.getGroupName = function(v) {
|
||||
return v.isLatest ? 'Latest' : (v.isStable ? 'Stable' : 'Unstable');
|
||||
return v.isLatest ? 'Latest' : ('v' + v.major + '.' + v.minor + '.x');
|
||||
};
|
||||
|
||||
$scope.jumpToDocsVersion = function(version) {
|
||||
var currentPagePath = $location.path();
|
||||
var currentPagePath = $location.path().replace(/\/$/, '');
|
||||
|
||||
// TODO: We need to do some munging of the path for different versions of the API...
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ describe("DocsController", function() {
|
||||
it("should update the Google Analytics with currentPage path if currentPage exists", inject(function($window) {
|
||||
$window._gaq = [];
|
||||
$scope.currentPage = { path: 'a/b/c' };
|
||||
$scope.afterPartialLoaded();
|
||||
$scope.$broadcast('$includeContentLoaded');
|
||||
expect($window._gaq.pop()).toEqual(['_trackPageview', 'a/b/c']);
|
||||
}));
|
||||
|
||||
@@ -27,7 +27,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');
|
||||
$scope.afterPartialLoaded();
|
||||
$scope.$broadcast('$includeContentLoaded');
|
||||
expect($window._gaq.pop()).toEqual(['_trackPageview', 'x/y/z']);
|
||||
}));
|
||||
});
|
||||
|
||||
+20
-7
@@ -5,8 +5,8 @@ var packagePath = __dirname;
|
||||
|
||||
var Package = require('dgeni').Package;
|
||||
|
||||
// Create and export a new Dgeni package called dgeni-example. This package depends upon
|
||||
// the jsdoc and nunjucks packages defined in the dgeni-packages npm module.
|
||||
// Create and export a new Dgeni package called angularjs. This package depends upon
|
||||
// the ngdoc,nunjucks and examples packages defined in the dgeni-packages npm module.
|
||||
module.exports = new Package('angularjs', [
|
||||
require('dgeni-packages/ngdoc'),
|
||||
require('dgeni-packages/nunjucks'),
|
||||
@@ -92,10 +92,7 @@ module.exports = new Package('angularjs', [
|
||||
}
|
||||
return docPath;
|
||||
},
|
||||
getOutputPath: function(doc) {
|
||||
return 'partials/' + doc.path +
|
||||
( doc.fileInfo.baseName === 'index' ? '/index.html' : '.html');
|
||||
}
|
||||
outputPathTemplate: 'partials/${path}.html'
|
||||
});
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
@@ -106,10 +103,20 @@ module.exports = new Package('angularjs', [
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
docTypes: ['indexPage'],
|
||||
getPath: function() {},
|
||||
pathTemplate: '.',
|
||||
outputPathTemplate: '${id}.html'
|
||||
});
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
docTypes: ['module' ],
|
||||
pathTemplate: '${area}/${name}',
|
||||
outputPathTemplate: 'partials/${area}/${name}.html'
|
||||
});
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
docTypes: ['componentGroup' ],
|
||||
pathTemplate: '${area}/${moduleName}/${groupType}',
|
||||
outputPathTemplate: 'partials/${area}/${moduleName}/${groupType}.html'
|
||||
});
|
||||
|
||||
computeIdsProcessor.idTemplates.push({
|
||||
docTypes: ['overview', 'tutorial', 'e2e-test', 'indexPage'],
|
||||
@@ -124,6 +131,12 @@ module.exports = new Package('angularjs', [
|
||||
});
|
||||
})
|
||||
|
||||
.config(function(checkAnchorLinksProcessor) {
|
||||
checkAnchorLinksProcessor.base = '/';
|
||||
// We are only interested in docs that have an area (i.e. they are pages)
|
||||
checkAnchorLinksProcessor.checkDoc = function(doc) { return doc.area; };
|
||||
})
|
||||
|
||||
|
||||
.config(function(
|
||||
generateIndexPagesProcessor,
|
||||
|
||||
@@ -147,24 +147,18 @@ module.exports = function generatePagesDataProcessor(log) {
|
||||
};
|
||||
|
||||
return {
|
||||
$runAfter: ['paths-computed'],
|
||||
$runAfter: ['paths-computed', 'generateKeywordsProcessor'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
$process: function(docs) {
|
||||
|
||||
_(docs)
|
||||
.filter(function(doc) { return doc.area === 'api' && doc.docType === 'module'; })
|
||||
.forEach(function(doc) { if ( !doc.path ) {
|
||||
log.warn('Missing path property for ', doc.id);
|
||||
}})
|
||||
.map(function(doc) { return _.pick(doc, ['id', 'module', 'docType', 'area']); })
|
||||
.tap(function(docs) {
|
||||
log.debug(docs);
|
||||
// We are only interested in docs that are in an area
|
||||
var pages = _.filter(docs, function(doc) {
|
||||
return doc.area;
|
||||
});
|
||||
|
||||
|
||||
// We are only interested in docs that are in an area and are not landing pages
|
||||
var navPages = _.filter(docs, function(page) {
|
||||
return page.area && page.docType != 'componentGroup';
|
||||
// We are only interested in pages that are not landing pages
|
||||
var navPages = _.filter(pages, function(page) {
|
||||
return page.docType != 'componentGroup';
|
||||
});
|
||||
|
||||
// Generate an object collection of pages that is grouped by area e.g.
|
||||
@@ -198,28 +192,48 @@ module.exports = function generatePagesDataProcessor(log) {
|
||||
area.navGroups = navGroupMapper(pages, area);
|
||||
});
|
||||
|
||||
docs.push({
|
||||
docType: 'nav-data',
|
||||
id: 'nav-data',
|
||||
template: 'nav-data.template.js',
|
||||
outputPath: 'js/nav-data.js',
|
||||
areas: areas
|
||||
});
|
||||
|
||||
|
||||
|
||||
var searchData = _(pages)
|
||||
.filter(function(page) {
|
||||
return page.searchTerms;
|
||||
})
|
||||
.map(function(page) {
|
||||
return _.extend({ path: page.path }, page.searchTerms);
|
||||
})
|
||||
.value();
|
||||
|
||||
docs.push({
|
||||
docType: 'json-doc',
|
||||
id: 'search-data-json',
|
||||
template: 'json-doc.template.json',
|
||||
outputPath: 'js/search-data.json',
|
||||
data: searchData
|
||||
});
|
||||
|
||||
// Extract a list of basic page information for mapping paths to partials and for client side searching
|
||||
var pages = _(docs)
|
||||
var pageData = _(docs)
|
||||
.map(function(doc) {
|
||||
var page = _.pick(doc, [
|
||||
'docType', 'id', 'name', 'area', 'outputPath', 'path', 'searchTerms'
|
||||
]);
|
||||
return page;
|
||||
return _.pick(doc, ['name', 'area', 'path']);
|
||||
})
|
||||
.indexBy('path')
|
||||
.value();
|
||||
|
||||
var docData = {
|
||||
docs.push({
|
||||
docType: 'pages-data',
|
||||
id: 'pages-data',
|
||||
template: 'pages-data.template.js',
|
||||
outputPath: 'js/pages-data.js',
|
||||
|
||||
areas: areas,
|
||||
pages: pages
|
||||
};
|
||||
|
||||
docs.push(docData);
|
||||
pages: pageData
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -19,13 +19,13 @@ module.exports = function debugDeployment(getVersion) {
|
||||
'../angular-animate.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.js',
|
||||
'js/angular-bootstrap/bootstrap-prettify.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',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/versions-data.js',
|
||||
'js/pages-data.js',
|
||||
'js/nav-data.js',
|
||||
'js/docs.js'
|
||||
],
|
||||
stylesheets: [
|
||||
|
||||
@@ -18,15 +18,15 @@ 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.js',
|
||||
'js/angular-bootstrap/bootstrap-prettify.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.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',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/versions-data.js',
|
||||
'js/pages-data.js',
|
||||
'js/docs.js'
|
||||
'js/nav-data.js',
|
||||
'js/docs.min.js'
|
||||
],
|
||||
stylesheets: [
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
|
||||
|
||||
+4
-4
@@ -22,15 +22,15 @@ 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.js',
|
||||
'js/angular-bootstrap/bootstrap-prettify.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.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',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/versions-data.js',
|
||||
'js/pages-data.js',
|
||||
'js/docs.js'
|
||||
'js/nav-data.js',
|
||||
'js/docs.min.js'
|
||||
],
|
||||
stylesheets: [
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
|
||||
|
||||
@@ -21,15 +21,15 @@ 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.js',
|
||||
'js/angular-bootstrap/bootstrap-prettify.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.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',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/versions-data.js',
|
||||
'js/pages-data.js',
|
||||
'js/docs.js'
|
||||
'js/nav-data.js',
|
||||
'js/docs.min.js'
|
||||
],
|
||||
stylesheets: [
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var gruntUtils = require('../../../lib/grunt/utils');
|
||||
var versionInfo = require('../../../lib/versions/version-info');
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,15 +56,6 @@
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
// force page reload when new update is available
|
||||
window.applicationCache && window.applicationCache.addEventListener('updateready', function(e) {
|
||||
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
|
||||
window.applicationCache.swapCache();
|
||||
window.location.reload();
|
||||
}
|
||||
}, false);
|
||||
|
||||
// GA asynchronous tracker
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-8594346-3']);
|
||||
@@ -85,7 +76,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-9 header-branding">
|
||||
<a class="brand navbar-brand" href="http://angularjs.org">
|
||||
<img class="logo" src="img/angularjs-for-header-only.svg">
|
||||
<img width="117" height="30" class="logo" ng-src="img/angularjs-for-header-only.svg">
|
||||
</a>
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="divider-vertical"></li>
|
||||
@@ -219,7 +210,7 @@
|
||||
</div>
|
||||
<div class="grid-right">
|
||||
<div id="loading" ng-show="loading">Loading...</div>
|
||||
<div ng-hide="loading" ng-include="currentPage.outputPath || 'Error404.html'" onload="afterPartialLoaded()" autoscroll></div>
|
||||
<div ng-hide="loading" ng-include="partialPath" autoscroll></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{$ doc.data | json $}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Meta data used by the AngularJS docs app
|
||||
angular.module('navData', [])
|
||||
.value('NG_NAVIGATION', {$ doc.areas | json $});
|
||||
@@ -1,4 +1,3 @@
|
||||
// Meta data used by the AngularJS docs app
|
||||
angular.module('pagesData', [])
|
||||
.value('NG_PAGES', {$ doc.pages | json $})
|
||||
.value('NG_NAVIGATION', {$ doc.areas | json $});
|
||||
.value('NG_PAGES', {$ doc.pages | json $});
|
||||
|
||||
@@ -148,7 +148,7 @@ or JavaScript callbacks.
|
||||
{@link ngAnimate CSS-based animations}
|
||||
</td>
|
||||
<td>
|
||||
Follow ngAnimate’s CSS naming structure to reference CSS transitions / keyframe animations in AngularJS. Once defined the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
Follow ngAnimate’s CSS naming structure to reference CSS transitions / keyframe animations in AngularJS. Once defined, the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -156,7 +156,7 @@ or JavaScript callbacks.
|
||||
{@link ngAnimate JS-based animations}
|
||||
</td>
|
||||
<td>
|
||||
Use {@link angular.Module#animation module.animation()} to register a JavaScript animation. Once registered the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
Use {@link angular.Module#animation module.animation()} to register a JavaScript animation. Once registered, the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -12,10 +12,10 @@ $controller(MyController);
|
||||
$controller(MyController, {scope: newScope});
|
||||
```
|
||||
|
||||
To fix the example above please provide a scope to the $controller call:
|
||||
To fix the example above please provide a scope (using the `$scope` property in the locals object) to the $controller call:
|
||||
|
||||
```
|
||||
$controller(MyController, {$scope, newScope});
|
||||
$controller(MyController, {$scope: newScope});
|
||||
```
|
||||
|
||||
Please consult the {@link ng.$controller $controller} service api docs to learn more.
|
||||
|
||||
@@ -54,4 +54,18 @@ angular.module('myModule')
|
||||
.directive('myDirective', ['myCoolService', function (myCoolService) {
|
||||
// This directive definition does not throw unknown provider.
|
||||
}]);
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
Attempting to inject one controller into another will also throw an `Unknown provider` error:
|
||||
|
||||
```
|
||||
angular.module('myModule', [])
|
||||
.controller('MyFirstController', function() { /* ... */ });
|
||||
.controller('MySecondController', ['MyFirstController', function(MyFirstController) {
|
||||
// This controller throws an unknown provider error because
|
||||
// MyFirstController cannot be injected.
|
||||
}]);
|
||||
```
|
||||
|
||||
Use the `$controller` service if you want to instantiate controllers yourself.
|
||||
|
||||
@@ -15,7 +15,7 @@ For example the issue can be triggered by this *invalid* code:
|
||||
|
||||
To resolve this error either ensure that the items in the collection have unique identity or use the `track by` syntax to specify how to track the association between models and DOM.
|
||||
|
||||
To resolve the example above can be resolved by using `track by $index`, which will cause the items to be keyed by their position in the array instead of their value:
|
||||
The example above can be resolved by using `track by $index`, which will cause the items to be keyed by their position in the array instead of their value:
|
||||
|
||||
```
|
||||
<div ng-repeat="value in [4, 4] track by $index"></div>
|
||||
|
||||
@@ -164,7 +164,7 @@ encoded.
|
||||
|
||||
`$location` service has two configuration modes which control the format of the URL in the browser
|
||||
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
|
||||
HTML5 [History API](http://www.w3.org/TR/html5/history.html). Applications use the same API in
|
||||
HTML5 [History API](http://www.w3.org/TR/html5/introduction.html#history-0). Applications use the same API in
|
||||
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
|
||||
facilitate the browser URL change and history management.
|
||||
|
||||
@@ -211,6 +211,10 @@ facilitate the browser URL change and history management.
|
||||
## Hashbang mode (default mode)
|
||||
|
||||
In this mode, `$location` uses Hashbang URLs in all browsers.
|
||||
Angular also does not intercept and rewrite links in this mode. I.e. links work
|
||||
as expected and also perform full page reloads when other parts of the url
|
||||
than the hash fragment was changed.
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
@@ -241,7 +245,7 @@ it('should show example', inject(
|
||||
## HTML5 mode
|
||||
|
||||
In HTML5 mode, the `$location` service getters and setters interact with the browser URL address
|
||||
through the HTML5 history API, which allows for use of regular URL path and search segments,
|
||||
through the HTML5 history API. This allows for use of regular URL path and search segments,
|
||||
instead of their hashbang equivalents. If the HTML5 History API is not supported by a browser, the
|
||||
`$location` service will fall back to using the hashbang URLs automatically. This frees you from
|
||||
having to worry about whether the browser displaying your app supports the history API or not; the
|
||||
@@ -250,6 +254,10 @@ having to worry about whether the browser displaying your app supports the histo
|
||||
- Opening a regular URL in a legacy browser -> redirects to a hashbang URL
|
||||
- Opening hashbang URL in a modern browser -> rewrites to a regular URL
|
||||
|
||||
Note that in this mode, Angular intercepts all links (subject to the "Html link rewriting" rules below)
|
||||
and updates the url in a way that never performs a full page reload.
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
@@ -298,8 +306,8 @@ history API or not; the `$location` service makes this transparent to you.
|
||||
|
||||
### Html link rewriting
|
||||
|
||||
When you use HTML5 history API mode, you will need different links in different browsers, but all you
|
||||
have to do is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
|
||||
When you use HTML5 history API mode, you will not need special hashbang links. All you have to do
|
||||
is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
|
||||
|
||||
When a user clicks on this link,
|
||||
|
||||
@@ -314,17 +322,9 @@ reload to the original link.
|
||||
Example: `<a href="/ext/link?a=b" target="_self">link</a>`
|
||||
- Absolute links that go to a different domain<br>
|
||||
Example: `<a href="http://angularjs.org/">link</a>`
|
||||
- Links starting with '/' that lead to a different base path when base is defined<br>
|
||||
- Links starting with '/' that lead to a different base path<br>
|
||||
Example: `<a href="/not-my-base/link">link</a>`
|
||||
|
||||
When running Angular in the root of a domain, along side perhaps a normal application in the same
|
||||
directory, the "otherwise" route handler will try to handle all the URLs, including ones that map
|
||||
to static files.
|
||||
|
||||
To prevent this, you can set your base href for the app to `<base href=".">` and then prefix links
|
||||
to URLs that should be handled with `.`. Now, links to locations, which are not to be routed by Angular,
|
||||
are not prefixed with `.` and will not be intercepted by the `otherwise` rule in your `$routeProvider`.
|
||||
|
||||
|
||||
### Server side
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ initialization.
|
||||
<html xmlns:ng="http://angularjs.org" ng-app>
|
||||
<body>
|
||||
...
|
||||
<script src="angular.js">
|
||||
<script src="angular.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -91,7 +91,9 @@ Here is an example of manually initializing Angular:
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
Hello {{greetMe}}!
|
||||
<div ng-controller="MyController">
|
||||
Hello {{greetMe}}!
|
||||
</div>
|
||||
<script src="http://code.angularjs.org/snapshot/angular.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
@sortOrder 330
|
||||
@description
|
||||
|
||||
# HTML Compiler
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** this guide is targeted towards developers who are already familiar with AngularJS basics.
|
||||
|
||||
@@ -12,7 +14,7 @@ If you want a deeper look into Angular's compilation process, you're in the righ
|
||||
</div>
|
||||
|
||||
|
||||
# Overview
|
||||
## Overview
|
||||
|
||||
Angular's {@link ng.$compile HTML compiler} allows the developer to teach the
|
||||
browser new HTML syntax. The compiler allows you to attach behavior to any HTML element or attribute
|
||||
@@ -85,7 +87,9 @@ Here is a directive which makes any element draggable. Notice the `draggable` at
|
||||
position: 'relative',
|
||||
border: '1px solid red',
|
||||
backgroundColor: 'lightgrey',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
display: 'block',
|
||||
width: '65px'
|
||||
});
|
||||
element.on('mousedown', function(event) {
|
||||
// Prevent default dragging of selected content
|
||||
@@ -328,7 +332,7 @@ The first issue we have to solve is that the dialog box template expects `title`
|
||||
But we would like the template's scope property `title` to be the result of interpolating the
|
||||
`<dialog>` element's `title` attribute (i.e. `"Hello {{username}}"`). Furthermore, the buttons expect
|
||||
the `onOk` and `onCancel` functions to be present in the scope. This limits the usefulness of the
|
||||
widget. To solve the mapping issue we use the `locals` to create local variables which the template
|
||||
widget. To solve the mapping issue we use the `scope` to create local variables which the template
|
||||
expects as follows:
|
||||
|
||||
```js
|
||||
|
||||
@@ -56,10 +56,10 @@ Try out the Live Preview above, and then let's walk through the example and desc
|
||||
|
||||
This looks like normal HTML, with some new markup. In Angular, a file like this is called a
|
||||
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
|
||||
processes this new markup from the template using the so called <a name="compiler">"{@link compiler compiler}"</a>.
|
||||
processes this new markup from the template using the so-called <a name="compiler">"{@link compiler compiler}"</a>.
|
||||
The loaded, transformed and rendered DOM is then called the <a name="view">"view"</a>.
|
||||
|
||||
The first kind of new markup are the so called <a name="directive">"{@link directive directives}"</a>.
|
||||
The first kind of new markup are the so-called <a name="directive">"{@link directive directives}"</a>.
|
||||
They apply special behavior to attributes or elements in the HTML. In the example above we use the
|
||||
{@link ng.directive:ngApp `ng-app`} attribute, which is linked to a directive that automatically
|
||||
initializes our application. Angular also defines a directive for the {@link ng.directive:input `input`}
|
||||
@@ -67,8 +67,8 @@ element that adds extra behavior to the element. The {@link ng.directive:ngModel
|
||||
stores/updates the value of the input field into/from a variable.
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Custom directives to access the DOM**: In Angular, the only place where an application touches the DOM is
|
||||
within directives. This is good as artifacts that access the DOM are hard to test.
|
||||
**Custom directives to access the DOM**: In Angular, the only place where an application should access the DOM is
|
||||
within directives. This is important because artifacts that access the DOM are hard to test.
|
||||
If you need to access the DOM directly you should write a custom directive for this. The
|
||||
{@link directive directives guide} explains how to do this.
|
||||
</div>
|
||||
@@ -89,7 +89,7 @@ A filter formats the value of an expression for display to the user.
|
||||
In the example above, the filter {@link ng.filter:currency `currency`} formats a number
|
||||
into an output that looks like money.
|
||||
|
||||
The important thing in the example is that angular provides _live_ bindings:
|
||||
The important thing in the example is that Angular provides _live_ bindings:
|
||||
Whenever the input values change, the value of the expressions are automatically
|
||||
recalculated and the DOM is updated with their values.
|
||||
The concept behind this is <a name="databinding">"{@link databinding two-way data binding}"</a>.
|
||||
@@ -150,13 +150,13 @@ different currencies and also pay the invoice.
|
||||
|
||||
What changed?
|
||||
|
||||
First, there is a new JavaScript file that contains a so called <a name="controller">"{@link controller controller}"</a>.
|
||||
First, there is a new JavaScript file that contains a so-called <a name="controller">"{@link controller controller}"</a>.
|
||||
More exactly, the file contains a constructor function that creates the actual controller instance.
|
||||
The purpose of controllers is to expose variables and functionality to expressions and directives.
|
||||
|
||||
Besides the new file that contains the controller code we also added a
|
||||
{@link ng.directive:ngController `ng-controller`} directive to the HTML.
|
||||
This directive tells angular that the new `InvoiceController` is responsible for the element with the directive
|
||||
This directive tells Angular that the new `InvoiceController` is responsible for the element with the directive
|
||||
and all of the element's children.
|
||||
The syntax `InvoiceController as invoice` tells Angular to instantiate the controller
|
||||
and save it in the variable `invoice` in the current scope.
|
||||
@@ -263,7 +263,7 @@ services, ...) is created and wired using dependency injection. Within Angular,
|
||||
the DI container is called the <a name="injector">"{@link di injector}"</a>.
|
||||
|
||||
To use DI, there needs to be a place where all the things that should work together are registered.
|
||||
In Angular, this is the purpose of the so called <a name="module">"{@link module modules}"</a>.
|
||||
In Angular, this is the purpose of the so-called <a name="module">"{@link module modules}"</a>.
|
||||
When Angular starts, it will use the configuration of the module with the name defined by the `ng-app` directive,
|
||||
including the configuration of all modules that this module depends on.
|
||||
|
||||
|
||||
@@ -326,7 +326,7 @@ describe('state', function() {
|
||||
expect(childScope.timeOfDay).toBe('morning');
|
||||
expect(childScope.name).toBe('Mattie');
|
||||
expect(grandChildScope.timeOfDay).toBe('evening');
|
||||
expect(grandChildScope.name).toBe('Gingerbreak Baby');
|
||||
expect(grandChildScope.name).toBe('Gingerbread Baby');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@@ -160,7 +160,7 @@ restrictions, you cannot simply write `cx="{{cx}}"`.
|
||||
With `ng-attr-cx` you can work around this problem.
|
||||
|
||||
If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`)
|
||||
then during the binding will be applied to the corresponding unprefixed attribute. This allows
|
||||
then during the binding it will be applied to the corresponding unprefixed attribute. This allows
|
||||
you to bind to attributes that would otherwise be eagerly processed by browsers
|
||||
(e.g. an SVG element's `circle[cx]` attributes).
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ Protractor is a [Node.js](http://nodejs.org) program, and runs end to end tests
|
||||
written in JavaScript and run with node. Protractor uses [WebDriver](https://code.google.com/p/selenium/wiki/GettingStarted)
|
||||
to control browsers and simulate user actions.
|
||||
|
||||
For more information on Protractor, view [getting started](https://github.com/angular/protractor/blob/master/docs/getting-started.md)
|
||||
or the [api docs](https://github.com/angular/protractor/blob/master/docs/api.md).
|
||||
For more information on Protractor, view [getting started](http://angular.github.io/protractor/#/getting-started)
|
||||
or the [api docs](http://angular.github.io/protractor/#/api).
|
||||
|
||||
Protractor uses [Jasmine](http://jasmine.github.io/1.3/introduction.html) for its test syntax.
|
||||
As in unit testing, a test file is comprised of one or
|
||||
|
||||
@@ -41,7 +41,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
### Other AngularJS Features
|
||||
|
||||
* **Animation:** {@link guide/animations Core concepts}, {@link ngAnimate ngAnimate API}, and [Animation in AngularJS 1.2](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html)
|
||||
* **Security:** {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
|
||||
* **Security:** {@link guide/security Security Docs}, {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
|
||||
* **Internationalization and Localization:** {@link guide/i18n Angular Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
|
||||
* **Mobile:** {@link ngTouch Touch events}
|
||||
|
||||
@@ -70,7 +70,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
|
||||
This is a short list of libraries with specific support and documentation for working with Angular. You can find a full list of all known Angular external libraries at [ngmodules.org](http://ngmodules.org/).
|
||||
|
||||
* **Internationalization:** [angular-translate](http://angular-translate.github.io), [angular-gettext](http://angular-gettext.rocketeer.be/)
|
||||
* **Internationalization:** [angular-translate](http://angular-translate.github.io), [angular-gettext](http://angular-gettext.rocketeer.be/), [angular-localization](http://doshprompt.github.io/angular-localization/)
|
||||
* **RESTful services:** [Restangular](https://github.com/mgonto/restangular)
|
||||
* **SQL and NoSQL backends:** [BreezeJS](http://www.breezejs.com/), [AngularFire](http://angularfire.com/)
|
||||
* **UI Widgets: **[KendoUI](http://kendo-labs.github.io/angular-kendo/#/), [UI Bootstrap](http://angular-ui.github.io/bootstrap/), [Wijmo](http://wijmo.com/tag/angularjs-2/), [ngTagsInput](https://github.com/mbenford/ngTagsInput)
|
||||
@@ -89,11 +89,12 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
|
||||
* **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)
|
||||
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/resources/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
|
||||
* **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)
|
||||
* **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails)
|
||||
* **PHP: **[Building a RESTful web service](http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource), [End to End with Laravel 4 (video)](http://www.youtube.com/watch?v=hqAyiqUs93c)
|
||||
* **Meteor: **[angular-meteor package](https://github.com/Urigo/angular-meteor)
|
||||
|
||||
## Learning Resources
|
||||
|
||||
@@ -104,6 +105,8 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
|
||||
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
|
||||
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
|
||||
* [AngularJS : Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
|
||||
* [AngularJS UI Development](http://www.amazon.com/AngularJS-UI-Development-Amit-Ghart-ebook/dp/B00OXVAK7A) by Amit Gharat and Matthias Nehlsen
|
||||
|
||||
###Videos:
|
||||
* [egghead.io](http://egghead.io/)
|
||||
@@ -112,12 +115,12 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
### Courses
|
||||
* **Free online:**
|
||||
[thinkster.io](http://thinkster.io),
|
||||
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1)
|
||||
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1),
|
||||
[CodeSchool](https://www.codeschool.com/courses/shaping-up-with-angular-js)
|
||||
* **Paid online:**
|
||||
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
|
||||
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
|
||||
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html)
|
||||
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html),
|
||||
[WintellectNOW (4 lessons)](http://www.wintellectnow.com/Course/Detail/mastering-angularjs)
|
||||
* **Paid onsite:**
|
||||
[angularbootcamp.com](http://angularbootcamp.com/)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template
|
||||
language and lets you extend HTML's syntax to express your application's components clearly and
|
||||
succinctly. Angular's data binding and dependency injection eliminate much of the code you
|
||||
currently have to write. And it all happens within the browser, making it
|
||||
would otherwise have to write. And it all happens within the browser, making it
|
||||
an ideal partner with any server technology.
|
||||
|
||||
Angular is what HTML would have been had it been designed for applications. HTML is a great
|
||||
@@ -33,7 +33,7 @@ browser new syntax through a construct we call directives. Examples include:
|
||||
* Data binding, as in `{{}}`.
|
||||
* DOM control structures for repeating/hiding DOM fragments.
|
||||
* Support for forms and form validation.
|
||||
* Attaching code-behind to DOM elements.
|
||||
* Attaching new behavior to DOM elements, such as DOM event handling.
|
||||
* Grouping of HTML into reusable components.
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ Angular frees you from the following pains:
|
||||
* **Writing tons of initialization code just to get started:** Typically you need to write a lot
|
||||
of plumbing just to get a basic "Hello World" AJAX app working. With Angular you can bootstrap
|
||||
your app easily using services, which are auto-injected into your application in a
|
||||
[Guice](http://code.google.com/p/google-guice/)-like dependency-injection style. This allows you
|
||||
[Guice](https://github.com/google/guice)-like dependency-injection style. This allows you
|
||||
to get started developing features quickly. As a bonus, you get full control over the
|
||||
initialization process in automated tests.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ linking} phase the {@link ng.$compileProvider#directive directives} set up
|
||||
render the updated value to the DOM.
|
||||
|
||||
Both controllers and directives have reference to the scope, but not to each other. This
|
||||
arrangement isolates the controller from the directive as well as from DOM. This is an important
|
||||
arrangement isolates the controller from the directive as well as from the DOM. This is an important
|
||||
point since it makes the controllers view agnostic, which greatly improves the testing story of
|
||||
the applications.
|
||||
|
||||
@@ -339,6 +339,18 @@ the dirty checking function must be efficient. Care should be taken that the dir
|
||||
function does not do any DOM access, as DOM access is orders of magnitude slower than property
|
||||
access on JavaScript object.
|
||||
|
||||
### Scope `$watch` Depths
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-scope-watch-strategies.png">
|
||||
|
||||
Dirty checking can be done with three strategies: By reference, by collection contents, and by value. The strategies differ in the kinds of changes they detect, and in their performance characteristics.
|
||||
|
||||
- Watching *by reference* ({@link
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient stategy.
|
||||
- Watching *collection contents* ({@link
|
||||
ng.$rootScope.Scope#$watchCollection scope.$watchCollection} `(watchExpression, listener)`) detects changes that occur inside an array or an object: When items are added, removed, or reordered. The detection is shallow - it does not reach into nested collections. Watching collection contents is more expensive than watching by reference, because copies of the collection contents need to be maintained. However, the strategy attempts to minimize the amount of copying required.
|
||||
- Watching *by value* ({@link
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener, true)`) detects any change in an arbitrarily nested data structure. It is the most powerful change detection strategy, but also the most expensive. A full traversal of the nested data structure is needed on each digest, and a full copy of it needs to be held in memory.
|
||||
|
||||
## Integration with the browser event loop
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-runtime.png">
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
@ngdoc overview
|
||||
@name Security
|
||||
@sortOrder 525
|
||||
@description
|
||||
|
||||
# Security
|
||||
|
||||
This document explains some of AngularJS's security features and best practices that you should
|
||||
keep in mind as you build your application.
|
||||
|
||||
|
||||
## Expression Sandboxing
|
||||
|
||||
AngularJS's expressions are sandboxed not for security reasons, but instead to maintain a proper
|
||||
separation of application responsibilities. For example, access to `window` is disallowed
|
||||
because it makes it easy to introduce brittle global state into your application.
|
||||
|
||||
However, this sandbox is not intended to stop attackers who can edit the template before it's
|
||||
processed by Angular. It may be possible to run arbitrary JavaScript inside double-curly bindings
|
||||
if an attacker can modify them.
|
||||
|
||||
But if an attacker can change arbitrary HTML templates, there's nothing stopping them from doing:
|
||||
|
||||
```html
|
||||
<script>somethingEvil();</script>
|
||||
```
|
||||
|
||||
It's better to design your application in such a way that users cannot change client-side templates.
|
||||
For instance:
|
||||
|
||||
* Do not mix client and server templates
|
||||
* Do not use user input to generate templates dynamically
|
||||
* Do not run user input through `$scope.$eval`
|
||||
* Consider using {@link ng.directive:ngCsp CSP} (but don't rely only on CSP)
|
||||
|
||||
## Mixing client-side and server-side templates
|
||||
|
||||
In general, we recommend against this because it can create unintended XSS vectors.
|
||||
|
||||
However, it's ok to mix server-side templating in the bootstrap template (`index.html`) as long
|
||||
as user input cannot be used on the server to output html that would then be processed by Angular
|
||||
in a way that would cause allow for arbitrary code execution.
|
||||
|
||||
For instance, you can use server-side templating to dynamically generate CSS, URLs, etc, but not
|
||||
for generating templates that are bootstrapped/compiled by Angular.
|
||||
|
||||
|
||||
## Reporting a security issue
|
||||
|
||||
Email us at [security@angularjs.org](mailto:security@angularjs.org) to report any potential
|
||||
security issues in AngularJS.
|
||||
|
||||
Please keep in mind the above points about Angular's expression language.
|
||||
|
||||
|
||||
## See also
|
||||
|
||||
* {@link ng.directive:ngCsp Content Security Policy}
|
||||
* {@link ng.$sce Strict Contextual Escaping}
|
||||
* {@link ngSanitize.$sanitize $sanitize}
|
||||
@@ -268,8 +268,8 @@ logic, further simplifying the application logic.
|
||||
|
||||
```js
|
||||
myModule.filter('length', function() {
|
||||
return function(text){
|
||||
return (''+(text||'')).length;
|
||||
return function(text) {
|
||||
return ('' + (text || '')).length;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ development web server, run tests, and generate distributable files. Depending o
|
||||
pre-packaged bundle.
|
||||
|
||||
* [Java](http://www.java.com): We minify JavaScript using our
|
||||
[Closure Tools](https://developers.google.com/closure/) jar. Make sure you have Java (version 6 or higher) installed
|
||||
[Closure Tools](https://developers.google.com/closure/) jar. Make sure you have Java (version 7 or higher) installed
|
||||
and included in your [PATH](http://docs.oracle.com/javase/tutorial/essential/environment/paths.html) variable.
|
||||
|
||||
* [Grunt](http://gruntjs.com): We use Grunt as our build system. Install the grunt command-line tool globally with:
|
||||
|
||||
@@ -22,7 +22,7 @@ So it's definitely not a plugin or some other native browser extension.
|
||||
|
||||
### Is AngularJS a templating system?
|
||||
|
||||
At the highest level, Angular does look like a just another templating system. But there is one
|
||||
At the highest level, Angular does look like just another templating system. But there is one
|
||||
important reason why the Angular templating system is different, that makes it very good fit for
|
||||
application development: bidirectional data binding. The template is compiled in the browser and
|
||||
the compilation step produces a live view. This means you, the developers, don't need to write
|
||||
@@ -39,7 +39,7 @@ for server-side communication.
|
||||
|
||||
AngularJS was designed to be compatible with other security measures like Content Security Policy
|
||||
(CSP), HTTPS (SSL/TLS) and server-side authentication and authorization that greatly reduce the
|
||||
possible attack vectors and we highly recommended their use.
|
||||
possible attack vectors and we highly recommend their use.
|
||||
|
||||
|
||||
### Can I download the source, build, and host the AngularJS environment locally?
|
||||
@@ -204,7 +204,7 @@ If you want to apply a directive to each inner piece of the repeat, put it on a
|
||||
|
||||
### `$rootScope` exists, but it can be used for evil
|
||||
|
||||
Scopes in Angular form a hierarchy, prototypically inheriting from a root scope at the top of the tree.
|
||||
Scopes in Angular form a hierarchy, prototypally inheriting from a root scope at the top of the tree.
|
||||
Usually this can be ignored, since most views have a controller, and therefore a scope, of their own.
|
||||
|
||||
Occasionally there are pieces of data that you want to make global to the whole app.
|
||||
|
||||
@@ -110,8 +110,9 @@ suggested solution is to also install the `nodejs-legacy` apt package, which ren
|
||||
`nodejs`.
|
||||
|
||||
```
|
||||
apt-get install nodejs-legacy
|
||||
apt-get install nodejs-legacy npm
|
||||
nodejs --version
|
||||
npm --version
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ browser is limited, which results in your karma tests running extremely slow.
|
||||
|
||||
Refresh your browser and verify that it says "Hello, World!".
|
||||
|
||||
* Update the unit test for the controller in ./test/unit/controllersSpec.js to reflect the previous change. For example by adding:
|
||||
* Update the unit test for the controller in `./test/unit/controllersSpec.js` to reflect the previous change. For example by adding:
|
||||
|
||||
expect(scope.name).toBe('World');
|
||||
|
||||
@@ -251,7 +251,7 @@ browser is limited, which results in your karma tests running extremely slow.
|
||||
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
|
||||
</table>
|
||||
|
||||
Extra points: try and make an 8x8 table using an additional ng-repeat.
|
||||
Extra points: try and make an 8x8 table using an additional `ng-repeat`.
|
||||
|
||||
* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use `toBe(4)`.
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('PhoneCat App', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should filter the phone list as user types into the search box', function() {
|
||||
it('should filter the phone list as a user types into the search box', function() {
|
||||
|
||||
var phoneList = element.all(by.repeater('phone in phones'));
|
||||
var query = element(by.model('query'));
|
||||
@@ -120,7 +120,7 @@ really is that easy to set up any functional, readable, end-to-end test.
|
||||
### Running End to End Tests with Protractor
|
||||
Even though the syntax of this test looks very much like our controller unit test written with
|
||||
Jasmine, the end-to-end test uses APIs of [Protractor](https://github.com/angular/protractor). Read
|
||||
about the Protractor APIs at https://github.com/angular/protractor/blob/master/docs/api.md.
|
||||
about the Protractor APIs at http://angular.github.io/protractor/#/api.
|
||||
|
||||
Much like Karma is the test runner for unit tests, we use Protractor to run end-to-end tests.
|
||||
Try it with `npm run protractor`. End-to-end tests are slow, so unlike with unit tests, Protractor
|
||||
@@ -159,7 +159,7 @@ Let's see how we can get the current value of the `query` model to appear in the
|
||||
var phoneList = element.all(by.repeater('phone in phones'));
|
||||
var query = element(by.model('query'));
|
||||
|
||||
it('should filter the phone list as user types into the search box', function() {
|
||||
it('should filter the phone list as a user types into the search box', function() {
|
||||
expect(phoneList.count()).toBe(3);
|
||||
|
||||
query.sendKeys('nexus');
|
||||
|
||||
@@ -182,7 +182,7 @@ You can now rerun `npm run protractor` to see the tests run.
|
||||
# Experiments
|
||||
|
||||
* In the `PhoneListCtrl` controller, remove the statement that sets the `orderProp` value and
|
||||
you'll see that Angular will temporarily add a new "unknown" option to the drop-down list and the
|
||||
you'll see that Angular will temporarily add a new blank ("unknown") option to the drop-down list and the
|
||||
ordering will default to unordered/natural order.
|
||||
|
||||
* Add an `{{orderProp}}` binding into the `index.html` template to display its current value as
|
||||
|
||||
@@ -11,7 +11,7 @@ from our server using one of Angular's built-in {@link guide/services services}
|
||||
ng.$http $http}. We will use Angular's {@link guide/di dependency
|
||||
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
|
||||
|
||||
* There are now a list of 20 phones, loaded from the server.
|
||||
* There is now a list of 20 phones, loaded from the server.
|
||||
|
||||
<div doc-tutorial-reset="5"></div>
|
||||
|
||||
@@ -236,7 +236,9 @@ the response is received:
|
||||
```
|
||||
|
||||
* We flush the request queue in the browser by calling `$httpBackend.flush()`. This causes the
|
||||
promise returned by the `$http` service to be resolved with the trained response.
|
||||
promise returned by the `$http` service to be resolved with the trained response. See
|
||||
'Flushing HTTP requests' in the {@link ngMock.$httpBackend mock $httpBackend} documentation for
|
||||
a full explanation of why this is necessary.
|
||||
|
||||
* We make the assertions, verifying that the phone model now exists on the scope.
|
||||
|
||||
@@ -256,8 +258,8 @@ You should now see the following output in the Karma tab:
|
||||
|
||||
# Experiments
|
||||
|
||||
* At the bottom of `index.html`, add a `<pre>{{phones | json}}</pre>` binding to see the list of phones
|
||||
displayed in json format.
|
||||
* At the bottom of `index.html`, add a `<pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>`
|
||||
binding to see the list of phones displayed in json format.
|
||||
|
||||
* In the `PhoneListCtrl` controller, pre-process the http response by limiting the number of phones
|
||||
to the first 5 in the list. Use the following code in the `$http` callback:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
In this step, you will learn how to create a layout template and how to build an app that has
|
||||
multiple views by adding routing, using an Angular module called 'ngRoute'.
|
||||
|
||||
* When you now navigate to `app/index.html`, you are redirected to `app/index.html#/phones`
|
||||
* When you now navigate to `app/index.html`, you are redirected to `app/index.html/#/phones`
|
||||
and the phone list appears in the browser.
|
||||
* When you click on a phone link the url changes to one specific to that phone and the stub of a
|
||||
phone detail page is displayed.
|
||||
|
||||
@@ -184,7 +184,7 @@ You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
# Experiments
|
||||
|
||||
* Using the [Protractor API](https://github.com/angular/protractor/blob/master/docs/api.md),
|
||||
* Using the [Protractor API](http://angular.github.io/protractor/#/api),
|
||||
write a test that verifies that we display 4 thumbnail images on the Nexus S details page.
|
||||
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ we require, so in these cases, we can add a callback to process the server respo
|
||||
|
||||
## Test
|
||||
|
||||
Because we're now using the {@link ngResource ngResource} module, it's necessary to also need to
|
||||
Because we're now using the {@link ngResource ngResource} module, it's necessary to
|
||||
update the Karma config file with angular-resource so the new tests will pass.
|
||||
|
||||
__`test/karma.conf.js`:__
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
In this final step, we will enhance our phonecat web application by attaching CSS and JavaScript
|
||||
animations on top of the template code we created before.
|
||||
|
||||
* Used the `ngAnimate` to enable animations throughout the application.
|
||||
* Common `ng` directives automatically trigger hooks for animations to tap into.
|
||||
* We now use the `ngAnimate` module to enable animations throughout the application.
|
||||
* We also use common `ng` directives to automatically trigger hooks for animations to tap into.
|
||||
* When an animation is found then the animation will run in between the standard DOM operation that
|
||||
is being issued on the element at the given time (e.g. inserting and removing nodes on
|
||||
{@link api/ng.directive:ngRepeat `ngRepeat`} or adding and removing classes on
|
||||
@@ -21,7 +21,7 @@ animations on top of the template code we created before.
|
||||
## Dependencies
|
||||
|
||||
The animation functionality is provided by Angular in the `ngAnimate` module, which is distributed
|
||||
separately from the core Angular framework. In addition we will use `JQuery` in this project to do
|
||||
separately from the core Angular framework. In addition we will use `jQuery` in this project to do
|
||||
extra JavaScript animations.
|
||||
|
||||
We are using [Bower][bower] to install client side dependencies. This step updates the
|
||||
@@ -49,8 +49,8 @@ We are using [Bower][bower] to install client side dependencies. This step upda
|
||||
|
||||
* `"angular-animate": "~1.2.x"` tells bower to install a version of the
|
||||
angular-animate component that is compatible with version 1.2.x.
|
||||
* `"jquery": "1.10.2"` tells bower to install the 1.10.2 version of JQuery. Note that this is not an
|
||||
Angular library, it is the standard JQuery library. We can use bower to install a wide range of 3rd
|
||||
* `"jquery": "1.10.2"` tells bower to install the 1.10.2 version of jQuery. Note that this is not an
|
||||
Angular library, it is the standard jQuery library. We can use bower to install a wide range of 3rd
|
||||
party libraries.
|
||||
|
||||
We must ask bower to download and install this dependency. We can do this by running:
|
||||
@@ -254,7 +254,7 @@ which are described in detail below.
|
||||
Next let's add an animation for transitions between route changes in {@link api/ngRoute.directive:ngView `ngView`}.
|
||||
|
||||
To start, let's add a new CSS class to our HTML like we did in the example above.
|
||||
This time, instead of the `ng-repeat` element, let's add it to the element containing the ng-view directive.
|
||||
This time, instead of the `ng-repeat` element, let's add it to the element containing the `ng-view` directive.
|
||||
In order to do this, we'll have to make some small changes to the HTML code so that we can have more control over our
|
||||
animations between view changes.
|
||||
|
||||
@@ -339,13 +339,13 @@ a cross fade animation in between. So as the previous page is just about to be r
|
||||
while the new page fades in right on top of it.
|
||||
|
||||
Once the leave animation is over then element is removed and once the enter animation is complete
|
||||
then the `ng-enter` and `ng-enter-active` CSS classes are removed from the element rendering it to
|
||||
be position itself with its default CSS code (so no more absolute positioning once the animation is
|
||||
then the `ng-enter` and `ng-enter-active` CSS classes are removed from the element, causing it to rerender and
|
||||
reposition itself with its default CSS code (so no more absolute positioning once the animation is
|
||||
over). This works fluidly so that pages flow naturally between route changes without anything
|
||||
jumping around.
|
||||
|
||||
The CSS classes applied (the start and end classes) are much the same as with `ng-repeat`. Each time
|
||||
a new page is loaded the ng-view directive will create a copy of itself, download the template and
|
||||
a new page is loaded the `ng-view` directive will create a copy of itself, download the template and
|
||||
append the contents. This ensures that all views are contained within a single HTML element which
|
||||
allows for easy animation control.
|
||||
|
||||
@@ -368,7 +368,8 @@ occur whenever the CSS class itself changes.
|
||||
Whenever a new phone thumbnail is selected, the state changes and the `.active` CSS class is added
|
||||
to the matching profile image and the animation plays.
|
||||
|
||||
Let's get started and tweak our HTML code on the `phone-detail.html` page first:
|
||||
Let's get started and tweak our HTML code on the `phone-detail.html` page first. Notice that we
|
||||
have changed the way we display our large image:
|
||||
|
||||
__`app/partials/phone-detail.html`.__
|
||||
|
||||
|
||||
+37
-5
@@ -8,7 +8,10 @@ var bower = require('bower');
|
||||
var Dgeni = require('dgeni');
|
||||
var merge = require('event-stream').merge;
|
||||
var path = require('canonical-path');
|
||||
|
||||
var foreach = require('gulp-foreach');
|
||||
var uglify = require('gulp-uglify');
|
||||
var sourcemaps = require('gulp-sourcemaps');
|
||||
var rename = require('gulp-rename');
|
||||
|
||||
// We indicate to gulp that tasks are async by returning the stream.
|
||||
// Gulp can then wait for the stream to close before starting dependent tasks.
|
||||
@@ -17,6 +20,9 @@ var path = require('canonical-path');
|
||||
var outputFolder = '../build/docs';
|
||||
var bowerFolder = 'bower_components';
|
||||
|
||||
var src = 'app/src/**/*.js';
|
||||
var assets = 'app/assets/**/*';
|
||||
|
||||
|
||||
var copyComponent = function(component, pattern, sourceFolder, packageFile) {
|
||||
pattern = pattern || '/**/*';
|
||||
@@ -40,14 +46,37 @@ gulp.task('bower', function() {
|
||||
});
|
||||
|
||||
gulp.task('build-app', function() {
|
||||
gulp.src('app/src/**/*.js')
|
||||
.pipe(concat('docs.js'))
|
||||
.pipe(gulp.dest(outputFolder + '/js/'));
|
||||
var file = 'docs.js';
|
||||
var minFile = 'docs.min.js';
|
||||
var folder = outputFolder + '/js/';
|
||||
|
||||
return gulp.src(src)
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat(file))
|
||||
.pipe(gulp.dest(folder))
|
||||
.pipe(rename(minFile))
|
||||
.pipe(uglify())
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest(folder));
|
||||
});
|
||||
|
||||
gulp.task('assets', ['bower'], function() {
|
||||
var JS_EXT = /\.js$/;
|
||||
return merge(
|
||||
gulp.src(['app/assets/**/*']).pipe(gulp.dest(outputFolder)),
|
||||
gulp.src([assets])
|
||||
.pipe(gulp.dest(outputFolder)),
|
||||
gulp.src([assets])
|
||||
.pipe(foreach(function(stream, file) {
|
||||
if (JS_EXT.test(file.relative)) {
|
||||
var minFile = file.relative.replace(JS_EXT, '.min.js');
|
||||
return stream
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat(minFile))
|
||||
.pipe(uglify())
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest(outputFolder));
|
||||
}
|
||||
})),
|
||||
copyComponent('bootstrap', '/dist/**/*'),
|
||||
copyComponent('open-sans-fontface'),
|
||||
copyComponent('lunr.js','/*.js'),
|
||||
@@ -77,3 +106,6 @@ gulp.task('jshint', ['doc-gen'], function() {
|
||||
// The default task that will be run if no task is supplied
|
||||
gulp.task('default', ['assets', 'doc-gen', 'build-app', 'jshint']);
|
||||
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch([src, assets], ['assets', 'build-app']);
|
||||
});
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
@@ -12,7 +12,7 @@ set -e
|
||||
# before_script:
|
||||
# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash
|
||||
|
||||
CONNECT_URL="https://d2nkw87yt5k0to.cloudfront.net/downloads/sc-4.3-linux.tar.gz"
|
||||
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3-linux.tar.gz"
|
||||
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
|
||||
CONNECT_DOWNLOAD="sc-4.3-linux.tar.gz"
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ var getTaggedVersion = function() {
|
||||
if ( version && semver.satisfies(version, currentPackage.branchVersion)) {
|
||||
version.codeName = getCodeName(tag);
|
||||
version.full = version.version;
|
||||
version.branch = 'v' + currentPackage.branchVersion.replace('*', 'x');
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -90,15 +91,6 @@ var getTaggedVersion = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stable versions have an even minor version and have no prerelease
|
||||
* @param {SemVer} version The version to test
|
||||
* @return {Boolean} True if the version is stable
|
||||
*/
|
||||
var isStable = function(version) {
|
||||
return semver.satisfies(version, '1.0 || 1.2') && version.prerelease.length === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a collection of all the previous versions sorted by semantic version
|
||||
* @return {Array.<SemVer>} The collection of previous versions
|
||||
@@ -118,8 +110,6 @@ var getPreviousVersions = function() {
|
||||
})
|
||||
.filter()
|
||||
.map(function(version) {
|
||||
version.isStable = isStable(version);
|
||||
|
||||
version.docsUrl = 'http://code.angularjs.org/' + version.version + '/docs';
|
||||
// Versions before 1.0.2 had a different docs folder name
|
||||
if ( version.major < 1 || (version.major === 1 && version.minor === 0 && version.dot < 2 ) ) {
|
||||
@@ -197,6 +187,7 @@ var getSnapshotVersion = function() {
|
||||
version.isSnapshot = true;
|
||||
version.format();
|
||||
version.full = version.version + '+' + version.build;
|
||||
version.branch = 'master';
|
||||
|
||||
return version;
|
||||
};
|
||||
|
||||
Generated
+832
-344
File diff suppressed because it is too large
Load Diff
+7
-6
@@ -13,7 +13,6 @@
|
||||
"canonical-path": "0.0.2",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.10.0",
|
||||
"es6-shim": "^0.14.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"grunt": "~0.4.2",
|
||||
"grunt-bump": "~0.0.13",
|
||||
@@ -29,8 +28,12 @@
|
||||
"grunt-parallel": "~0.3.1",
|
||||
"grunt-shell": "~0.4.0",
|
||||
"gulp": "~3.8.0",
|
||||
"gulp-concat": "~2.1.7",
|
||||
"gulp-concat": "^2.4.1",
|
||||
"gulp-foreach": "0.0.1",
|
||||
"gulp-jshint": "~1.4.2",
|
||||
"gulp-rename": "^1.2.0",
|
||||
"gulp-sourcemaps": "^1.2.2",
|
||||
"gulp-uglify": "^1.0.1",
|
||||
"gulp-util": "^3.0.1",
|
||||
"jasmine-node": "~1.11.0",
|
||||
"jasmine-reporters": "~0.2.1",
|
||||
@@ -49,7 +52,7 @@
|
||||
"marked": "~0.3.0",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"promises-aplus-tests": "~1.3.2",
|
||||
"protractor": "1.2.0",
|
||||
"protractor": "1.4.0",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
@@ -57,9 +60,7 @@
|
||||
"semver": "~2.1.0",
|
||||
"shelljs": "~0.2.6",
|
||||
"sorted-object": "^1.0.0",
|
||||
"stringmap": "^0.2.2",
|
||||
"winston": "~0.7.2",
|
||||
"yaml-js": "~0.0.8"
|
||||
"stringmap": "^0.2.2"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
|
||||
@@ -21,9 +21,9 @@ exports.config = {
|
||||
|
||||
// Disable animations so e2e tests run more quickly
|
||||
var disableNgAnimate = function() {
|
||||
angular.module('disableNgAnimate', []).run(function($animate) {
|
||||
angular.module('disableNgAnimate', []).run(['$animate', function($animate) {
|
||||
$animate.enabled(false);
|
||||
});
|
||||
}]);
|
||||
};
|
||||
|
||||
browser.addMockModule('disableNgAnimate', disableNgAnimate);
|
||||
|
||||
@@ -12,9 +12,9 @@ exports.config = {
|
||||
|
||||
// Disable animations so e2e tests run more quickly
|
||||
var disableNgAnimate = function() {
|
||||
angular.module('disableNgAnimate', []).run(function($animate) {
|
||||
angular.module('disableNgAnimate', []).run(['$animate', function($animate) {
|
||||
$animate.enabled(false);
|
||||
});
|
||||
}]);
|
||||
};
|
||||
|
||||
browser.addMockModule('disableNgAnimate', disableNgAnimate);
|
||||
|
||||
@@ -71,6 +71,8 @@ function prepare {
|
||||
cd $TMP_DIR/bower-$repo
|
||||
replaceJsonProp "bower.json" "version" ".*" "$NEW_VERSION"
|
||||
replaceJsonProp "bower.json" "angular.*" ".*" "$NEW_VERSION"
|
||||
replaceJsonProp "package.json" "version" ".*" "$NEW_VERSION"
|
||||
replaceJsonProp "package.json" "angular.*" ".*" "$NEW_VERSION"
|
||||
|
||||
git add -A
|
||||
|
||||
@@ -88,6 +90,24 @@ function publish {
|
||||
cd $TMP_DIR/bower-$repo
|
||||
git push origin master
|
||||
git push origin v$NEW_VERSION
|
||||
|
||||
# don't publish every build to npm
|
||||
if [ "${NEW_VERSION/+sha}" = "$NEW_VERSION" ] ; then
|
||||
if [ "${NEW_VERSION/-}" = "$NEW_VERSION" ] ; then
|
||||
if [[ $NEW_VERSION =~ ^1\.2\.[0-9]+$ ]] ; then
|
||||
# publish 1.2.x releases with the appropriate tag
|
||||
# this ensures that `npm install` by default will not grab `1.2.x` releases
|
||||
npm publish --tag=old
|
||||
else
|
||||
# publish releases as "latest"
|
||||
npm publish
|
||||
fi
|
||||
else
|
||||
# publish prerelease builds with the beta tag
|
||||
npm publish --tag=beta
|
||||
fi
|
||||
fi
|
||||
|
||||
cd $SCRIPT_DIR
|
||||
done
|
||||
}
|
||||
|
||||
+3
-3
@@ -155,8 +155,8 @@ if ('i' !== 'I'.toLowerCase()) {
|
||||
}
|
||||
|
||||
|
||||
var /** holds major version number for IE or NaN for real browsers */
|
||||
msie,
|
||||
var
|
||||
msie, // holds major version number for IE, or NaN if UA is not IE.
|
||||
jqLite, // delay binding since jQuery could be loaded after us.
|
||||
jQuery, // delay binding
|
||||
slice = [].slice,
|
||||
@@ -338,7 +338,7 @@ function setHashKey(obj, h) {
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Extends the destination object `dst` by copying all of the properties from the `src` object(s)
|
||||
* Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
|
||||
* to `dst`. You can specify multiple `src` objects.
|
||||
*
|
||||
* @param {Object} dst Destination object.
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Creates an injector function that can be used for retrieving services as well as for
|
||||
* Creates an injector object that can be used for retrieving services as well as for
|
||||
* dependency injection (see {@link guide/di dependency injection}).
|
||||
*
|
||||
|
||||
* @param {Array.<string|Function>} modules A list of module functions or their aliases. See
|
||||
* {@link angular.module}. The `ng` module must be explicitly added.
|
||||
* @returns {function()} Injector function. See {@link auto.$injector $injector}.
|
||||
* @returns {injector} Injector object. See {@link auto.$injector $injector}.
|
||||
*
|
||||
* @example
|
||||
* Typical usage
|
||||
@@ -101,7 +101,6 @@ function annotate(fn) {
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $injector
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
*
|
||||
@@ -116,7 +115,7 @@ function annotate(fn) {
|
||||
* expect($injector.get('$injector')).toBe($injector);
|
||||
* expect($injector.invoke(function($injector){
|
||||
* return $injector;
|
||||
* }).toBe($injector);
|
||||
* })).toBe($injector);
|
||||
* ```
|
||||
*
|
||||
* # Injection Function Annotation
|
||||
@@ -183,8 +182,8 @@ function annotate(fn) {
|
||||
* @description
|
||||
* Allows the user to query if the particular service exists.
|
||||
*
|
||||
* @param {string} Name of the service to query.
|
||||
* @returns {boolean} returns true if injector has given service.
|
||||
* @param {string} name Name of the service to query.
|
||||
* @returns {boolean} `true` if injector has given service.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@
|
||||
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
|
||||
* - [`clone()`](http://api.jquery.com/clone/)
|
||||
* - [`contents()`](http://api.jquery.com/contents/)
|
||||
* - [`css()`](http://api.jquery.com/css/)
|
||||
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()`
|
||||
* - [`data()`](http://api.jquery.com/data/)
|
||||
* - [`empty()`](http://api.jquery.com/empty/)
|
||||
* - [`eq()`](http://api.jquery.com/eq/)
|
||||
|
||||
@@ -53,6 +53,19 @@ function $AnchorScrollProvider() {
|
||||
|
||||
var autoScrollingEnabled = true;
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $anchorScrollProvider#disableAutoScrolling
|
||||
*
|
||||
* @description
|
||||
* By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
|
||||
* {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
|
||||
* Use this method to disable automatic scrolling.
|
||||
*
|
||||
* If automatic scrolling is disabled, one must explicitly call
|
||||
* {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
|
||||
* current hash.
|
||||
*/
|
||||
this.disableAutoScrolling = function() {
|
||||
autoScrollingEnabled = false;
|
||||
};
|
||||
|
||||
+14
-7
@@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
/* global stripHash: true */
|
||||
|
||||
/**
|
||||
* ! This is a private undocumented service !
|
||||
@@ -124,7 +125,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
|
||||
var lastBrowserUrl = location.href,
|
||||
baseElement = document.find('base'),
|
||||
newLocation = null;
|
||||
reloadLocation = null;
|
||||
|
||||
/**
|
||||
* @name $browser#url
|
||||
@@ -153,8 +154,13 @@ function Browser(window, document, $log, $sniffer) {
|
||||
// setter
|
||||
if (url) {
|
||||
if (lastBrowserUrl == url) return;
|
||||
var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
|
||||
lastBrowserUrl = url;
|
||||
if ($sniffer.history) {
|
||||
// Don't use history API if only the hash changed
|
||||
// due to a bug in IE10/IE11 which leads
|
||||
// to not firing a `hashchange` nor `popstate` event
|
||||
// in some cases (see #9143).
|
||||
if (!sameBase && $sniffer.history) {
|
||||
if (replace) history.replaceState(null, '', url);
|
||||
else {
|
||||
history.pushState(null, '', url);
|
||||
@@ -162,7 +168,9 @@ function Browser(window, document, $log, $sniffer) {
|
||||
baseElement.attr('href', baseElement.attr('href'));
|
||||
}
|
||||
} else {
|
||||
newLocation = url;
|
||||
if (!sameBase) {
|
||||
reloadLocation = url;
|
||||
}
|
||||
if (replace) {
|
||||
location.replace(url);
|
||||
} else {
|
||||
@@ -172,10 +180,10 @@ function Browser(window, document, $log, $sniffer) {
|
||||
return self;
|
||||
// getter
|
||||
} else {
|
||||
// - newLocation is a workaround for an IE7-9 issue with location.replace and location.href
|
||||
// methods not updating location.href synchronously.
|
||||
// - reloadLocation is needed as browsers don't allow to read out
|
||||
// the new location.href if a reload happened.
|
||||
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
|
||||
return newLocation || location.href.replace(/%27/g,"'");
|
||||
return reloadLocation || location.href.replace(/%27/g,"'");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -183,7 +191,6 @@ function Browser(window, document, $log, $sniffer) {
|
||||
urlChangeInit = false;
|
||||
|
||||
function fireUrlChange() {
|
||||
newLocation = null;
|
||||
if (lastBrowserUrl == self.url()) return;
|
||||
|
||||
lastBrowserUrl = self.url();
|
||||
|
||||
@@ -369,7 +369,8 @@ function $CacheFactoryProvider() {
|
||||
* ```
|
||||
*
|
||||
* **Note:** the `script` tag containing the template does not need to be included in the `head` of
|
||||
* the document, but it must be below the `ng-app` definition.
|
||||
* the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
|
||||
* element with ng-app attribute), otherwise the template will be ignored.
|
||||
*
|
||||
* Adding via the $templateCache service:
|
||||
*
|
||||
|
||||
+23
-10
@@ -255,8 +255,13 @@
|
||||
* scope. This makes it possible for the widget to have private state, and the transclusion to
|
||||
* be bound to the parent (pre-`isolate`) scope.
|
||||
*
|
||||
* * `true` - transclude the content of the directive.
|
||||
* * `'element'` - transclude the whole element including any directives defined at lower priority.
|
||||
* There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
|
||||
* directive's element or the entire element:
|
||||
*
|
||||
* * `true` - transclude the content (i.e. the child nodes) of the directive's element.
|
||||
* * `'element'` - transclude the whole of the directive's element including any directives on this
|
||||
* element that defined at a lower priority than this directive. When used, the `template`
|
||||
* property is ignored.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** When testing an element transclude directive you must not place the directive at the root of the
|
||||
@@ -392,7 +397,7 @@
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Below is an example using `$compileProvider`.
|
||||
* ## Example
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note**: Typically directives are registered with `module.directive`. The example below is
|
||||
@@ -648,6 +653,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
};
|
||||
|
||||
Attributes.prototype = {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compile.directive.Attributes#$normalize
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
|
||||
* `data-`) to its normalized, camelCase form.
|
||||
*
|
||||
* Also there is special case for Moz prefix starting with upper case letter.
|
||||
*
|
||||
* For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
|
||||
*
|
||||
* @param {string} name Name to normalize
|
||||
*/
|
||||
$normalize: directiveNormalize,
|
||||
|
||||
|
||||
@@ -1981,13 +2001,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
|
||||
/**
|
||||
* Converts all accepted directives format into proper directive name.
|
||||
* All of these will become 'myDirective':
|
||||
* my:Directive
|
||||
* my-directive
|
||||
* x-my-directive
|
||||
* data-my:directive
|
||||
*
|
||||
* Also there is special case for Moz prefix starting with upper case letter.
|
||||
* @param name Name to normalize
|
||||
*/
|
||||
function directiveNormalize(name) {
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
* make the link go to the wrong URL if the user clicks it before
|
||||
* Angular has a chance to replace the `{{hash}}` markup with its
|
||||
* value. Until Angular replaces the markup the link will be broken
|
||||
* and will most likely return a 404 error.
|
||||
*
|
||||
* The `ngHref` directive solves this problem.
|
||||
* and will most likely return a 404 error. The `ngHref` directive
|
||||
* solves this problem.
|
||||
*
|
||||
* The wrong way to write it:
|
||||
* ```html
|
||||
|
||||
@@ -969,7 +969,7 @@ var VALID_CLASS = 'ng-valid',
|
||||
*
|
||||
* We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
|
||||
* module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
|
||||
* However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks
|
||||
* However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
|
||||
* that content using the `$sce` service.
|
||||
*
|
||||
* <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
|
||||
@@ -1001,7 +1001,7 @@ var VALID_CLASS = 'ng-valid',
|
||||
|
||||
// Listen for change events to enable binding
|
||||
element.on('blur keyup change', function() {
|
||||
scope.$apply(read);
|
||||
scope.$evalAsync(read);
|
||||
});
|
||||
read(); // initialize
|
||||
|
||||
@@ -1284,7 +1284,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
*
|
||||
* For best practices on using `ngModel`, see:
|
||||
*
|
||||
* - [https://github.com/angular/angular.js/wiki/Understanding-Scopes]
|
||||
* - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
|
||||
*
|
||||
* For basic examples, how to use `ngModel`, see:
|
||||
*
|
||||
|
||||
@@ -139,7 +139,10 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
|
||||
* element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link
|
||||
* ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize`
|
||||
* is available, for example, by including {@link ngSanitize} in your module's dependencies (not in
|
||||
* core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to
|
||||
* core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to
|
||||
* include "angular-sanitize.js" in your application.
|
||||
*
|
||||
* You may also bypass sanitization for values you know are safe. To do so, bind to
|
||||
* an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
|
||||
* under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}.
|
||||
*
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*
|
||||
* @element ANY
|
||||
* @scope
|
||||
* @priority 500
|
||||
* @param {expression} ngController Name of a globally accessible constructor function or an
|
||||
* {@link guide/expression expression} that on the current scope evaluates to a
|
||||
* constructor function. The controller instance can be published into a scope property
|
||||
|
||||
@@ -33,10 +33,8 @@
|
||||
</example>
|
||||
*/
|
||||
/*
|
||||
* A directive that allows creation of custom onclick handlers that are defined as angular
|
||||
* expressions and are compiled and executed within the current scope.
|
||||
*
|
||||
* Events that are handled via these handler are always configured not to propagate further.
|
||||
* A collection of directives that allows creation of custom event handlers that are defined as
|
||||
* angular expressions and are compiled and executed within the current scope.
|
||||
*/
|
||||
var ngEventDirectives = {};
|
||||
|
||||
@@ -54,7 +52,11 @@ forEach(
|
||||
ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
|
||||
return {
|
||||
compile: function($element, attr) {
|
||||
var fn = $parse(attr[directiveName]);
|
||||
// We expose the powerful $event object on the scope that provides access to the Window,
|
||||
// etc. that isn't protected by the fast paths in $parse. We explicitly request better
|
||||
// checks at the cost of speed since event handler expressions are not executed as
|
||||
// frequently as regular change detection.
|
||||
var fn = $parse(attr[directiveName], /* expensiveChecks */ true);
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(eventName, function(event) {
|
||||
var callback = function() {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
|
||||
* is created when the element is restored. The scope created within `ngIf` inherits from
|
||||
* its parent scope using
|
||||
* [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance).
|
||||
* [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
|
||||
* An important implication of this is if `ngModel` is used within `ngIf` to bind to
|
||||
* a javascript primitive defined in the parent scope. In this case any modifications made to the
|
||||
* variable within the child scope will override (hide) the value in the parent scope.
|
||||
@@ -33,8 +33,8 @@
|
||||
* 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
|
||||
* 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
|
||||
*
|
||||
* @element ANY
|
||||
* @scope
|
||||
|
||||
@@ -40,7 +40,6 @@ var scriptDirective = ['$templateCache', function($templateCache) {
|
||||
compile: function(element, attr) {
|
||||
if (attr.type == 'text/ng-template') {
|
||||
var templateUrl = attr.id,
|
||||
// IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
|
||||
text = element[0].text;
|
||||
|
||||
$templateCache.put(templateUrl, text);
|
||||
|
||||
@@ -551,6 +551,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
lastElement = existingOption.element;
|
||||
if (existingOption.label !== option.label) {
|
||||
lastElement.text(existingOption.label = option.label);
|
||||
lastElement.prop('label', existingOption.label);
|
||||
}
|
||||
if (existingOption.id !== option.id) {
|
||||
lastElement.val(existingOption.id = option.id);
|
||||
@@ -580,6 +581,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
.val(option.id)
|
||||
.prop('selected', option.selected)
|
||||
.attr('selected', option.selected)
|
||||
.prop('label', option.label)
|
||||
.text(option.label);
|
||||
}
|
||||
|
||||
@@ -589,6 +591,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
id: option.id,
|
||||
selected: option.selected
|
||||
});
|
||||
selectCtrl.addOption(option.label, element);
|
||||
if (lastElement) {
|
||||
lastElement.after(element);
|
||||
} else {
|
||||
@@ -600,7 +603,9 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// remove any excessive OPTIONs in a group
|
||||
index++; // increment since the existingOptions[0] is parent element not OPTION
|
||||
while(existingOptions.length > index) {
|
||||
existingOptions.pop().element.remove();
|
||||
option = existingOptions.pop();
|
||||
selectCtrl.removeOption(option.label);
|
||||
option.element.remove();
|
||||
}
|
||||
}
|
||||
// remove any excessive OPTGROUPs from select
|
||||
|
||||
+11
-11
@@ -154,17 +154,17 @@ function filterFilter() {
|
||||
}
|
||||
|
||||
var search = function(obj, text){
|
||||
if (typeof text == 'string' && text.charAt(0) === '!') {
|
||||
if (typeof text === 'string' && text.charAt(0) === '!') {
|
||||
return !search(obj, text.substr(1));
|
||||
}
|
||||
switch (typeof obj) {
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
return comparator(obj, text);
|
||||
case "object":
|
||||
case 'object':
|
||||
switch (typeof text) {
|
||||
case "object":
|
||||
case 'object':
|
||||
return comparator(obj, text);
|
||||
default:
|
||||
for ( var objKey in obj) {
|
||||
@@ -175,7 +175,7 @@ function filterFilter() {
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
case "array":
|
||||
case 'array':
|
||||
for ( var i = 0; i < obj.length; i++) {
|
||||
if (search(obj[i], text)) {
|
||||
return true;
|
||||
@@ -187,13 +187,13 @@ function filterFilter() {
|
||||
}
|
||||
};
|
||||
switch (typeof expression) {
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
// Set up expression object and fall through
|
||||
expression = {$:expression};
|
||||
// jshint -W086
|
||||
case "object":
|
||||
case 'object':
|
||||
// jshint +W086
|
||||
for (var key in expression) {
|
||||
(function(path) {
|
||||
|
||||
+11
-10
@@ -31,9 +31,9 @@
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
Limit {{numbers}} to: <input type="integer" ng-model="numLimit">
|
||||
Limit {{numbers}} to: <input type="number" step="1" ng-model="numLimit">
|
||||
<p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
|
||||
Limit {{letters}} to: <input type="integer" ng-model="letterLimit">
|
||||
Limit {{letters}} to: <input type="number" step="1" ng-model="letterLimit">
|
||||
<p>Output letters: {{ letters | limitTo:letterLimit }}</p>
|
||||
</div>
|
||||
</file>
|
||||
@@ -50,14 +50,15 @@
|
||||
expect(limitedLetters.getText()).toEqual('Output letters: abc');
|
||||
});
|
||||
|
||||
it('should update the output when -3 is entered', function() {
|
||||
numLimitInput.clear();
|
||||
numLimitInput.sendKeys('-3');
|
||||
letterLimitInput.clear();
|
||||
letterLimitInput.sendKeys('-3');
|
||||
expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
|
||||
expect(limitedLetters.getText()).toEqual('Output letters: ghi');
|
||||
});
|
||||
// There is a bug in safari and protractor that doesn't like the minus key
|
||||
// it('should update the output when -3 is entered', function() {
|
||||
// numLimitInput.clear();
|
||||
// numLimitInput.sendKeys('-3');
|
||||
// letterLimitInput.clear();
|
||||
// letterLimitInput.sendKeys('-3');
|
||||
// expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
|
||||
// expect(limitedLetters.getText()).toEqual('Output letters: ghi');
|
||||
// });
|
||||
|
||||
it('should not exceed the maximum size of input array', function() {
|
||||
numLimitInput.clear();
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* correctly, make sure they are actually being saved as numbers and not strings.
|
||||
*
|
||||
* @param {Array} array The array to sort.
|
||||
* @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
|
||||
* @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
|
||||
* used by the comparator to determine the order of elements.
|
||||
*
|
||||
* Can be one of:
|
||||
@@ -24,10 +24,13 @@
|
||||
* is interpreted as a property name to be used in comparisons (for example `"special name"`
|
||||
* to sort object by the value of their `special name` property). An expression can be
|
||||
* optionally prefixed with `+` or `-` to control ascending or descending sort order
|
||||
* (for example, `+name` or `-name`).
|
||||
* (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
|
||||
* element itself is used to compare where sorting.
|
||||
* - `Array`: An array of function or string predicates. The first predicate in the array
|
||||
* is used for sorting, but when two items are equivalent, the next predicate is used.
|
||||
*
|
||||
* If the predicate is missing or empty then it defaults to `'+'`.
|
||||
*
|
||||
* @param {boolean=} reverse Reverse the order of the array.
|
||||
* @returns {Array} Sorted copy of the source array.
|
||||
*
|
||||
@@ -116,8 +119,8 @@ orderByFilter.$inject = ['$parse'];
|
||||
function orderByFilter($parse){
|
||||
return function(array, sortPredicate, reverseOrder) {
|
||||
if (!(isArrayLike(array))) return array;
|
||||
if (!sortPredicate) return array;
|
||||
sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
|
||||
if (sortPredicate.length === 0) { sortPredicate = ['+']; }
|
||||
sortPredicate = map(sortPredicate, function(predicate){
|
||||
var descending = false, get = predicate || identity;
|
||||
if (isString(predicate)) {
|
||||
@@ -125,6 +128,12 @@ function orderByFilter($parse){
|
||||
descending = predicate.charAt(0) == '-';
|
||||
predicate = predicate.substring(1);
|
||||
}
|
||||
if ( predicate === '' ) {
|
||||
// Effectively no predicate was passed so we compare identity
|
||||
return reverseComparator(function(a,b) {
|
||||
return compare(a, b);
|
||||
}, descending);
|
||||
}
|
||||
get = $parse(predicate);
|
||||
if (get.constant) {
|
||||
var key = get();
|
||||
@@ -137,9 +146,7 @@ function orderByFilter($parse){
|
||||
return compare(get(a),get(b));
|
||||
}, descending);
|
||||
});
|
||||
var arrayCopy = [];
|
||||
for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
|
||||
return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
|
||||
return slice.call(array).sort(reverseComparator(comparator, reverseOrder));
|
||||
|
||||
function comparator(o1, o2){
|
||||
for ( var i = 0; i < sortPredicate.length; i++) {
|
||||
|
||||
+57
-19
@@ -144,9 +144,18 @@ function $HttpProvider() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Are ordered by request, i.e. they are applied in the same order as the
|
||||
* @ngdoc property
|
||||
* @name $httpProvider#interceptors
|
||||
* @description
|
||||
*
|
||||
* Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
|
||||
* pre-processing of request or postprocessing of responses.
|
||||
*
|
||||
* These service factories are ordered by request, i.e. they are applied in the same order as the
|
||||
* array, on request, but reverse order, on response.
|
||||
*/
|
||||
*
|
||||
* {@link ng.$http#interceptors Interceptors detailed info}
|
||||
**/
|
||||
var interceptorFactories = this.interceptors = [];
|
||||
|
||||
/**
|
||||
@@ -308,6 +317,21 @@ function $HttpProvider() {
|
||||
* In addition, you can supply a `headers` property in the config object passed when
|
||||
* calling `$http(config)`, which overrides the defaults without changing them globally.
|
||||
*
|
||||
* To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
|
||||
* Use the `headers` property, setting the desired header to `undefined`. For example:
|
||||
*
|
||||
* ```js
|
||||
* var req = {
|
||||
* method: 'POST',
|
||||
* url: 'http://example.com',
|
||||
* headers: {
|
||||
* 'Content-Type': undefined
|
||||
* },
|
||||
* data: { test: 'test' },
|
||||
* }
|
||||
*
|
||||
* $http(req).success(function(){...}).error(function(){...});
|
||||
* ```
|
||||
*
|
||||
* # Transforming Requests and Responses
|
||||
*
|
||||
@@ -671,12 +695,13 @@ function $HttpProvider() {
|
||||
expect(data.getText()).toMatch(/Hello, \$http!/);
|
||||
});
|
||||
|
||||
it('should make a JSONP request to angularjs.org', function() {
|
||||
sampleJsonpBtn.click();
|
||||
fetchBtn.click();
|
||||
expect(status.getText()).toMatch('200');
|
||||
expect(data.getText()).toMatch(/Super Hero!/);
|
||||
});
|
||||
// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
|
||||
// it('should make a JSONP request to angularjs.org', function() {
|
||||
// sampleJsonpBtn.click();
|
||||
// fetchBtn.click();
|
||||
// expect(status.getText()).toMatch('200');
|
||||
// expect(data.getText()).toMatch(/Super Hero!/);
|
||||
// });
|
||||
|
||||
it('should make JSONP request to invalid URL and invoke the error handler',
|
||||
function() {
|
||||
@@ -886,18 +911,31 @@ function $HttpProvider() {
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
createShortMethodsWithData('post', 'put');
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name $http#defaults
|
||||
*
|
||||
* @description
|
||||
* Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
|
||||
* default headers, withCredentials as well as request and response transformations.
|
||||
*
|
||||
* See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
|
||||
*/
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $http#patch
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `PATCH` request.
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {*} data Request content
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
createShortMethodsWithData('post', 'put', 'patch');
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name $http#defaults
|
||||
*
|
||||
* @description
|
||||
* Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
|
||||
* default headers, withCredentials as well as request and response transformations.
|
||||
*
|
||||
* See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
|
||||
*/
|
||||
$http.defaults = defaults;
|
||||
|
||||
|
||||
|
||||
+24
-24
@@ -57,33 +57,33 @@ function $IntervalProvider() {
|
||||
* // Don't start a new fight if we are already fighting
|
||||
* if ( angular.isDefined(stop) ) return;
|
||||
*
|
||||
* stop = $interval(function() {
|
||||
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
|
||||
* $scope.blood_1 = $scope.blood_1 - 3;
|
||||
* $scope.blood_2 = $scope.blood_2 - 4;
|
||||
* } else {
|
||||
* $scope.stopFight();
|
||||
* stop = $interval(function() {
|
||||
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
|
||||
* $scope.blood_1 = $scope.blood_1 - 3;
|
||||
* $scope.blood_2 = $scope.blood_2 - 4;
|
||||
* } else {
|
||||
* $scope.stopFight();
|
||||
* }
|
||||
* }, 100);
|
||||
* };
|
||||
*
|
||||
* $scope.stopFight = function() {
|
||||
* if (angular.isDefined(stop)) {
|
||||
* $interval.cancel(stop);
|
||||
* stop = undefined;
|
||||
* }
|
||||
* }, 100);
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* $scope.stopFight = function() {
|
||||
* if (angular.isDefined(stop)) {
|
||||
* $interval.cancel(stop);
|
||||
* stop = undefined;
|
||||
* }
|
||||
* };
|
||||
* $scope.resetFight = function() {
|
||||
* $scope.blood_1 = 100;
|
||||
* $scope.blood_2 = 120;
|
||||
* };
|
||||
*
|
||||
* $scope.resetFight = function() {
|
||||
* $scope.blood_1 = 100;
|
||||
* $scope.blood_2 = 120;
|
||||
* };
|
||||
*
|
||||
* $scope.$on('$destroy', function() {
|
||||
* // Make sure that the interval is destroyed too
|
||||
* $scope.stopFight();
|
||||
* });
|
||||
* }])
|
||||
* $scope.$on('$destroy', function() {
|
||||
* // Make sure that the interval is destroyed too
|
||||
* $scope.stopFight();
|
||||
* });
|
||||
* }])
|
||||
* // Register the 'myCurrentTime' directive factory method.
|
||||
* // We inject $interval and dateFilter service since the factory method is DI.
|
||||
* .directive('myCurrentTime', ['$interval', 'dateFilter',
|
||||
|
||||
+40
-57
@@ -127,21 +127,26 @@ function LocationHtml5Url(appBase, basePrefix) {
|
||||
this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
|
||||
};
|
||||
|
||||
this.$$rewrite = function(url) {
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
var appUrl, prevAppUrl;
|
||||
var rewrittenUrl;
|
||||
|
||||
if ( (appUrl = beginsWith(appBase, url)) !== undefined ) {
|
||||
prevAppUrl = appUrl;
|
||||
if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) {
|
||||
return appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
|
||||
rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
|
||||
} else {
|
||||
return appBase + prevAppUrl;
|
||||
rewrittenUrl = appBase + prevAppUrl;
|
||||
}
|
||||
} else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) {
|
||||
return appBaseNoFile + appUrl;
|
||||
rewrittenUrl = appBaseNoFile + appUrl;
|
||||
} else if (appBaseNoFile == url + '/') {
|
||||
return appBaseNoFile;
|
||||
rewrittenUrl = appBaseNoFile;
|
||||
}
|
||||
if (rewrittenUrl) {
|
||||
this.$$parse(rewrittenUrl);
|
||||
}
|
||||
return !!rewrittenUrl;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,10 +236,12 @@ function LocationHashbangUrl(appBase, hashPrefix) {
|
||||
this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
|
||||
};
|
||||
|
||||
this.$$rewrite = function(url) {
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
if(stripHash(appBase) == stripHash(url)) {
|
||||
return url;
|
||||
this.$$parse(url);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -254,16 +261,21 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {
|
||||
|
||||
var appBaseNoFile = stripFile(appBase);
|
||||
|
||||
this.$$rewrite = function(url) {
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
var rewrittenUrl;
|
||||
var appUrl;
|
||||
|
||||
if ( appBase == stripHash(url) ) {
|
||||
return url;
|
||||
rewrittenUrl = url;
|
||||
} else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) {
|
||||
return appBase + hashPrefix + appUrl;
|
||||
rewrittenUrl = appBase + hashPrefix + appUrl;
|
||||
} else if ( appBaseNoFile === url + '/') {
|
||||
return appBaseNoFile;
|
||||
rewrittenUrl = appBaseNoFile;
|
||||
}
|
||||
if (rewrittenUrl) {
|
||||
this.$$parse(rewrittenUrl);
|
||||
}
|
||||
return !!rewrittenUrl;
|
||||
};
|
||||
|
||||
this.$$compose = function() {
|
||||
@@ -391,7 +403,7 @@ LocationHashbangInHtml5Url.prototype =
|
||||
* @return {string} path
|
||||
*/
|
||||
path: locationGetterSetter('$$path', function(path) {
|
||||
path = path ? path.toString() : '';
|
||||
path = path !== null ? path.toString() : '';
|
||||
return path.charAt(0) == '/' ? path : '/' + path;
|
||||
}),
|
||||
|
||||
@@ -488,7 +500,7 @@ LocationHashbangInHtml5Url.prototype =
|
||||
* @return {string} hash
|
||||
*/
|
||||
hash: locationGetterSetter('$$hash', function(hash) {
|
||||
return hash ? hash.toString() : '';
|
||||
return hash !== null ? hash.toString() : '';
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -636,7 +648,7 @@ function $LocationProvider(){
|
||||
LocationMode = LocationHashbangUrl;
|
||||
}
|
||||
$location = new LocationMode(appBase, '#' + hashPrefix);
|
||||
$location.$$parse($location.$$rewrite(initialUrl));
|
||||
$location.$$parseLinkUrl(initialUrl, initialUrl);
|
||||
|
||||
var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
|
||||
|
||||
@@ -655,6 +667,9 @@ function $LocationProvider(){
|
||||
}
|
||||
|
||||
var absHref = elm.prop('href');
|
||||
// get the actual href attribute - see
|
||||
// http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
|
||||
var relHref = elm.attr('href') || elm.attr('xlink:href');
|
||||
|
||||
if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
|
||||
// SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
|
||||
@@ -665,50 +680,18 @@ function $LocationProvider(){
|
||||
// Ignore when url is started with javascript: or mailto:
|
||||
if (IGNORE_URI_REGEXP.test(absHref)) return;
|
||||
|
||||
// Make relative links work in HTML5 mode for legacy browsers (or at least IE8 & 9)
|
||||
// The href should be a regular url e.g. /link/somewhere or link/somewhere or ../somewhere or
|
||||
// somewhere#anchor or http://example.com/somewhere
|
||||
if (LocationMode === LocationHashbangInHtml5Url) {
|
||||
// get the actual href attribute - see
|
||||
// http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
|
||||
var href = elm.attr('href') || elm.attr('xlink:href');
|
||||
|
||||
if (href && href.indexOf('://') < 0) { // Ignore absolute URLs
|
||||
var prefix = '#' + hashPrefix;
|
||||
if (href[0] == '/') {
|
||||
// absolute path - replace old path
|
||||
absHref = appBase + prefix + href;
|
||||
} else if (href[0] == '#') {
|
||||
// local anchor
|
||||
absHref = appBase + prefix + ($location.path() || '/') + href;
|
||||
} else {
|
||||
// relative path - join with current path
|
||||
var stack = $location.path().split("/"),
|
||||
parts = href.split("/");
|
||||
if (stack.length === 2 && !stack[1]) stack.length = 1;
|
||||
for (var i=0; i<parts.length; i++) {
|
||||
if (parts[i] == ".")
|
||||
continue;
|
||||
else if (parts[i] == "..")
|
||||
stack.pop();
|
||||
else if (parts[i].length)
|
||||
stack.push(parts[i]);
|
||||
}
|
||||
absHref = appBase + prefix + stack.join('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rewrittenUrl = $location.$$rewrite(absHref);
|
||||
|
||||
if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) {
|
||||
event.preventDefault();
|
||||
if (rewrittenUrl != $browser.url()) {
|
||||
if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
|
||||
if ($location.$$parseLinkUrl(absHref, relHref)) {
|
||||
// We do a preventDefault for all urls that are part of the angular application,
|
||||
// in html5mode and also without, so that we are able to abort navigation without
|
||||
// getting double entries in the location history.
|
||||
event.preventDefault();
|
||||
// update location manually
|
||||
$location.$$parse(rewrittenUrl);
|
||||
$rootScope.$apply();
|
||||
// hack to work around FF6 bug 684208 when scenario runner clicks on links
|
||||
window.angular['ff-684208-preventDefault'] = true;
|
||||
if ($location.absUrl() != $browser.url()) {
|
||||
$rootScope.$apply();
|
||||
// hack to work around FF6 bug 684208 when scenario runner clicks on links
|
||||
window.angular['ff-684208-preventDefault'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+89
-42
@@ -7,7 +7,7 @@ var promiseWarning;
|
||||
// Sandboxing Angular Expressions
|
||||
// ------------------------------
|
||||
// Angular expressions are generally considered safe because these expressions only have direct
|
||||
// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by
|
||||
// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
|
||||
// obtaining a reference to native JS functions such as the Function constructor.
|
||||
//
|
||||
// As an example, consider the following Angular expression:
|
||||
@@ -16,7 +16,7 @@ var promiseWarning;
|
||||
//
|
||||
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
|
||||
// against the expression language, but not to prevent exploits that were enabled by exposing
|
||||
// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good
|
||||
// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
|
||||
// practice and therefore we are not even trying to protect against interaction with an object
|
||||
// explicitly exposed in this way.
|
||||
//
|
||||
@@ -24,6 +24,8 @@ var promiseWarning;
|
||||
// window or some DOM object that has a reference to window is published onto a Scope.
|
||||
// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
|
||||
// native objects.
|
||||
//
|
||||
// See https://docs.angularjs.org/guide/security
|
||||
|
||||
|
||||
function ensureSafeMemberName(name, fullExpression) {
|
||||
@@ -760,7 +762,7 @@ Parser.prototype = {
|
||||
ensureSafeObject(context, parser.text);
|
||||
ensureSafeFunction(fnPtr, parser.text);
|
||||
|
||||
// IE stupidity! (IE doesn't have apply for some native functions)
|
||||
// IE doesn't have apply for some native functions
|
||||
var v = fnPtr.apply
|
||||
? fnPtr.apply(context, args)
|
||||
: fnPtr(args[0], args[1], args[2], args[3], args[4]);
|
||||
@@ -874,7 +876,12 @@ function setter(obj, path, setValue, fullExp, options) {
|
||||
return setValue;
|
||||
}
|
||||
|
||||
var getterFnCache = {};
|
||||
var getterFnCacheDefault = {};
|
||||
var getterFnCacheExpensive = {};
|
||||
|
||||
function isPossiblyDangerousMemberName(name) {
|
||||
return name == 'constructor';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the "Black Hole" variant from:
|
||||
@@ -887,29 +894,38 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
|
||||
ensureSafeMemberName(key2, fullExp);
|
||||
ensureSafeMemberName(key3, fullExp);
|
||||
ensureSafeMemberName(key4, fullExp);
|
||||
var eso = function(o) {
|
||||
return ensureSafeObject(o, fullExp);
|
||||
};
|
||||
var expensiveChecks = options.expensiveChecks;
|
||||
var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity;
|
||||
var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity;
|
||||
var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity;
|
||||
var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity;
|
||||
var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity;
|
||||
|
||||
return !options.unwrapPromises
|
||||
? function cspSafeGetter(scope, locals) {
|
||||
var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
|
||||
|
||||
if (pathVal == null) return pathVal;
|
||||
pathVal = pathVal[key0];
|
||||
pathVal = eso0(pathVal[key0]);
|
||||
|
||||
if (!key1) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key1];
|
||||
pathVal = eso1(pathVal[key1]);
|
||||
|
||||
if (!key2) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key2];
|
||||
pathVal = eso2(pathVal[key2]);
|
||||
|
||||
if (!key3) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key3];
|
||||
pathVal = eso3(pathVal[key3]);
|
||||
|
||||
if (!key4) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key4];
|
||||
pathVal = eso4(pathVal[key4]);
|
||||
|
||||
return pathVal;
|
||||
}
|
||||
@@ -919,73 +935,81 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
|
||||
|
||||
if (pathVal == null) return pathVal;
|
||||
|
||||
pathVal = pathVal[key0];
|
||||
pathVal = eso0(pathVal[key0]);
|
||||
if (pathVal && pathVal.then) {
|
||||
promiseWarning(fullExp);
|
||||
if (!("$$v" in pathVal)) {
|
||||
promise = pathVal;
|
||||
promise.$$v = undefined;
|
||||
promise.then(function(val) { promise.$$v = val; });
|
||||
promise.then(function(val) { promise.$$v = eso0(val); });
|
||||
}
|
||||
pathVal = pathVal.$$v;
|
||||
pathVal = eso0(pathVal.$$v);
|
||||
}
|
||||
|
||||
if (!key1) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key1];
|
||||
pathVal = eso1(pathVal[key1]);
|
||||
if (pathVal && pathVal.then) {
|
||||
promiseWarning(fullExp);
|
||||
if (!("$$v" in pathVal)) {
|
||||
promise = pathVal;
|
||||
promise.$$v = undefined;
|
||||
promise.then(function(val) { promise.$$v = val; });
|
||||
promise.then(function(val) { promise.$$v = eso1(val); });
|
||||
}
|
||||
pathVal = pathVal.$$v;
|
||||
pathVal = eso1(pathVal.$$v);
|
||||
}
|
||||
|
||||
if (!key2) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key2];
|
||||
pathVal = eso2(pathVal[key2]);
|
||||
if (pathVal && pathVal.then) {
|
||||
promiseWarning(fullExp);
|
||||
if (!("$$v" in pathVal)) {
|
||||
promise = pathVal;
|
||||
promise.$$v = undefined;
|
||||
promise.then(function(val) { promise.$$v = val; });
|
||||
promise.then(function(val) { promise.$$v = eso2(val); });
|
||||
}
|
||||
pathVal = pathVal.$$v;
|
||||
pathVal = eso2(pathVal.$$v);
|
||||
}
|
||||
|
||||
if (!key3) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key3];
|
||||
pathVal = eso3(pathVal[key3]);
|
||||
if (pathVal && pathVal.then) {
|
||||
promiseWarning(fullExp);
|
||||
if (!("$$v" in pathVal)) {
|
||||
promise = pathVal;
|
||||
promise.$$v = undefined;
|
||||
promise.then(function(val) { promise.$$v = val; });
|
||||
promise.then(function(val) { promise.$$v = eso3(val); });
|
||||
}
|
||||
pathVal = pathVal.$$v;
|
||||
pathVal = eso3(pathVal.$$v);
|
||||
}
|
||||
|
||||
if (!key4) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key4];
|
||||
pathVal = eso4(pathVal[key4]);
|
||||
if (pathVal && pathVal.then) {
|
||||
promiseWarning(fullExp);
|
||||
if (!("$$v" in pathVal)) {
|
||||
promise = pathVal;
|
||||
promise.$$v = undefined;
|
||||
promise.then(function(val) { promise.$$v = val; });
|
||||
promise.then(function(val) { promise.$$v = eso4(val); });
|
||||
}
|
||||
pathVal = pathVal.$$v;
|
||||
pathVal = eso4(pathVal.$$v);
|
||||
}
|
||||
return pathVal;
|
||||
};
|
||||
}
|
||||
|
||||
function getterFnWithExtraArgs(fn, fullExpression) {
|
||||
return function(s, l) {
|
||||
return fn(s, l, promiseWarning, ensureSafeObject, fullExpression);
|
||||
};
|
||||
}
|
||||
|
||||
function getterFn(path, options, fullExp) {
|
||||
var expensiveChecks = options.expensiveChecks;
|
||||
var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault);
|
||||
// Check whether the cache has this getter already.
|
||||
// We can use hasOwnProperty directly on the cache because we ensure,
|
||||
// see below, that the cache never stores a path called 'hasOwnProperty'
|
||||
@@ -1017,35 +1041,48 @@ function getterFn(path, options, fullExp) {
|
||||
}
|
||||
} else {
|
||||
var code = 'var p;\n';
|
||||
if (expensiveChecks) {
|
||||
code += 's = eso(s, fe);\nl = eso(l, fe);\n';
|
||||
}
|
||||
var needsEnsureSafeObject = expensiveChecks;
|
||||
forEach(pathKeys, function(key, index) {
|
||||
ensureSafeMemberName(key, fullExp);
|
||||
code += 'if(s == null) return undefined;\n' +
|
||||
's='+ (index
|
||||
var lookupJs = (index
|
||||
// we simply dereference 's' on any .dot notation
|
||||
? 's'
|
||||
// but if we are first then we check locals first, and if so read it first
|
||||
: '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
|
||||
(options.unwrapPromises
|
||||
? 'if (s && s.then) {\n' +
|
||||
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '["' + key + '"]';
|
||||
var wrapWithEso = expensiveChecks || isPossiblyDangerousMemberName(key);
|
||||
if (wrapWithEso) {
|
||||
lookupJs = 'eso(' + lookupJs + ', fe)';
|
||||
needsEnsureSafeObject = true;
|
||||
}
|
||||
code += 'if(s == null) return undefined;\n' +
|
||||
's=' + lookupJs + ';\n';
|
||||
if (options.unwrapPromises) {
|
||||
code += 'if (s && s.then) {\n' +
|
||||
' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' +
|
||||
' if (!("$$v" in s)) {\n' +
|
||||
' p=s;\n' +
|
||||
' p.$$v = undefined;\n' +
|
||||
' p.then(function(v) {p.$$v=v;});\n' +
|
||||
' p.then(function(v) {p.$$v=' + (wrapWithEso ? 'eso(v)' : 'v') + ';});\n' +
|
||||
'}\n' +
|
||||
' s=s.$$v\n' +
|
||||
'}\n'
|
||||
: '');
|
||||
' s=' + (wrapWithEso ? 'eso(s.$$v)' : 's.$$v') + '\n' +
|
||||
'}\n';
|
||||
|
||||
}
|
||||
});
|
||||
code += 'return s;';
|
||||
|
||||
/* jshint -W054 */
|
||||
var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning
|
||||
// s=scope, l=locals, pw=promiseWarning, eso=ensureSafeObject, fe=fullExpression
|
||||
var evaledFnGetter = new Function('s', 'l', 'pw', 'eso', 'fe', code);
|
||||
/* jshint +W054 */
|
||||
evaledFnGetter.toString = valueFn(code);
|
||||
fn = options.unwrapPromises ? function(scope, locals) {
|
||||
return evaledFnGetter(scope, locals, promiseWarning);
|
||||
} : evaledFnGetter;
|
||||
if (needsEnsureSafeObject || options.unwrapPromises) {
|
||||
evaledFnGetter = getterFnWithExtraArgs(evaledFnGetter, fullExp);
|
||||
}
|
||||
fn = evaledFnGetter;
|
||||
}
|
||||
|
||||
// Only cache the value if it's not going to mess up the cache object
|
||||
@@ -1109,12 +1146,14 @@ function getterFn(path, options, fullExp) {
|
||||
* service.
|
||||
*/
|
||||
function $ParseProvider() {
|
||||
var cache = {};
|
||||
var cacheDefault = {};
|
||||
var cacheExpensive = {};
|
||||
|
||||
var $parseOptions = {
|
||||
csp: false,
|
||||
unwrapPromises: false,
|
||||
logPromiseWarnings: true
|
||||
logPromiseWarnings: true,
|
||||
expensiveChecks: false
|
||||
};
|
||||
|
||||
|
||||
@@ -1201,6 +1240,12 @@ function $ParseProvider() {
|
||||
|
||||
this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) {
|
||||
$parseOptions.csp = $sniffer.csp;
|
||||
var $parseOptionsExpensive = {
|
||||
csp: $parseOptions.csp,
|
||||
unwrapPromises: $parseOptions.unwrapPromises,
|
||||
logPromiseWarnings: $parseOptions.logPromiseWarnings,
|
||||
expensiveChecks: true
|
||||
};
|
||||
|
||||
promiseWarning = function promiseWarningFn(fullExp) {
|
||||
if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return;
|
||||
@@ -1209,18 +1254,20 @@ function $ParseProvider() {
|
||||
'Automatic unwrapping of promises in Angular expressions is deprecated.');
|
||||
};
|
||||
|
||||
return function(exp) {
|
||||
return function(exp, expensiveChecks) {
|
||||
var parsedExpression;
|
||||
|
||||
switch (typeof exp) {
|
||||
case 'string':
|
||||
|
||||
var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
|
||||
if (cache.hasOwnProperty(exp)) {
|
||||
return cache[exp];
|
||||
}
|
||||
|
||||
var lexer = new Lexer($parseOptions);
|
||||
var parser = new Parser(lexer, $filter, $parseOptions);
|
||||
var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
|
||||
var lexer = new Lexer(parseOptions);
|
||||
var parser = new Parser(lexer, $filter, parseOptions);
|
||||
parsedExpression = parser.parse(exp);
|
||||
|
||||
if (exp !== 'hasOwnProperty') {
|
||||
|
||||
+9
-1
@@ -6,7 +6,11 @@
|
||||
* @requires $rootScope
|
||||
*
|
||||
* @description
|
||||
* A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
|
||||
* A service that helps you run functions asynchronously, and use their return values (or exceptions)
|
||||
* when they are done processing.
|
||||
*
|
||||
* This is an implementation of promises/deferred objects inspired by
|
||||
* [Kris Kowal's Q](https://github.com/kriskowal/q).
|
||||
*
|
||||
* [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
|
||||
* interface for interacting with an object that represents the result of an action that is
|
||||
@@ -100,6 +104,10 @@
|
||||
*
|
||||
* - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
|
||||
*
|
||||
* Because `catch` is a reserved word in JavaScript and reserved keywords are not supported as
|
||||
* property names by ES3, you'll need to invoke the method like `promise['catch'](callback)` or
|
||||
* `promise.then(null, errorCallback)` to make your code IE8 and Android 2.x compatible.
|
||||
*
|
||||
* - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise,
|
||||
* but to do so without modifying the final value. This is useful to release resources or do some
|
||||
* clean-up that needs to be done whether the promise was rejected or resolved. See the [full
|
||||
|
||||
+5
-2
@@ -979,8 +979,11 @@ function $RootScopeProvider(){
|
||||
|
||||
var self = this;
|
||||
return function() {
|
||||
namedListeners[indexOf(namedListeners, listener)] = null;
|
||||
decrementListenerCount(self, 1, name);
|
||||
var indexOfListener = indexOf(namedListeners, listener);
|
||||
if (indexOfListener !== -1) {
|
||||
namedListeners[indexOfListener] = null;
|
||||
decrementListenerCount(self, 1, name);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -1156,6 +1156,16 @@ angular.module('ngAnimate', ['ng'])
|
||||
var parentCounter = 0;
|
||||
var animationReflowQueue = [];
|
||||
var cancelAnimationReflow;
|
||||
function clearCacheAfterReflow() {
|
||||
if (!cancelAnimationReflow) {
|
||||
cancelAnimationReflow = $$animateReflow(function() {
|
||||
animationReflowQueue = [];
|
||||
cancelAnimationReflow = null;
|
||||
lookupCache = {};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function afterReflow(element, callback) {
|
||||
if(cancelAnimationReflow) {
|
||||
cancelAnimationReflow();
|
||||
@@ -1524,7 +1534,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
//cancellation function then it means that there is no animation
|
||||
//to perform at all
|
||||
var preReflowCancellation = animateBefore(animationEvent, element, className);
|
||||
if(!preReflowCancellation) {
|
||||
if (!preReflowCancellation) {
|
||||
clearCacheAfterReflow();
|
||||
animationComplete();
|
||||
return;
|
||||
}
|
||||
@@ -1599,6 +1610,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
});
|
||||
return cancellationMethod;
|
||||
}
|
||||
clearCacheAfterReflow();
|
||||
animationCompleted();
|
||||
},
|
||||
|
||||
@@ -1623,6 +1635,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
});
|
||||
return cancellationMethod;
|
||||
}
|
||||
clearCacheAfterReflow();
|
||||
animationCompleted();
|
||||
},
|
||||
|
||||
|
||||
Vendored
+33
-17
@@ -1186,7 +1186,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*
|
||||
* - respond –
|
||||
@@ -1222,7 +1222,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1234,7 +1234,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1246,7 +1246,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1260,7 +1260,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1274,7 +1274,21 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $httpBackend#whenPATCH
|
||||
* @description
|
||||
* Creates a new backend definition for PATCH requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1285,7 +1299,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* Creates a new backend definition for JSONP requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
createShortMethods('when');
|
||||
@@ -1304,7 +1318,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* is in JSON format.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*
|
||||
* - respond –
|
||||
@@ -1333,7 +1347,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled. See #expect for more info.
|
||||
*/
|
||||
|
||||
@@ -1345,7 +1359,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1357,7 +1371,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1372,7 +1386,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1387,7 +1401,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1402,7 +1416,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1413,7 +1427,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
createShortMethods('expect');
|
||||
@@ -1506,7 +1520,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
|
||||
|
||||
function createShortMethods(prefix) {
|
||||
angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) {
|
||||
angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) {
|
||||
$httpBackend[prefix + method] = function(url, headers) {
|
||||
return $httpBackend[prefix](method, url, undefined, headers);
|
||||
};
|
||||
@@ -1549,7 +1563,9 @@ function MockHttpExpectation(method, url, data, headers) {
|
||||
if (angular.isUndefined(data)) return true;
|
||||
if (data && angular.isFunction(data.test)) return data.test(d);
|
||||
if (data && angular.isFunction(data)) return data(d);
|
||||
if (data && !angular.isString(data)) return angular.equals(data, angular.fromJson(d));
|
||||
if (data && !angular.isString(data)) {
|
||||
return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d));
|
||||
}
|
||||
return data == d;
|
||||
};
|
||||
|
||||
|
||||
@@ -265,9 +265,6 @@ function $RouteProvider(){
|
||||
* This example shows how changing the URL hash causes the `$route` to match a route against the
|
||||
* URL, and the `ngView` pulls in the partial.
|
||||
*
|
||||
* Note that this example is using {@link ng.directive:script inlined templates}
|
||||
* to get it working on jsfiddle as well.
|
||||
*
|
||||
* <example name="$route-service" module="ngRouteExample"
|
||||
* deps="angular-route.js" fixBase="true">
|
||||
* <file name="index.html">
|
||||
@@ -580,7 +577,7 @@ function $RouteProvider(){
|
||||
if (i === 0) {
|
||||
result.push(segment);
|
||||
} else {
|
||||
var segmentMatch = segment.match(/(\w+)(.*)/);
|
||||
var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/);
|
||||
var key = segmentMatch[1];
|
||||
result.push(params[key]);
|
||||
result.push(segmentMatch[2] || '');
|
||||
|
||||
@@ -141,9 +141,9 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
||||
html.push(target);
|
||||
html.push('" ');
|
||||
}
|
||||
html.push('href="');
|
||||
html.push(url);
|
||||
html.push('">');
|
||||
html.push('href="',
|
||||
url.replace('"', '"'),
|
||||
'">');
|
||||
addText(text);
|
||||
html.push('</a>');
|
||||
}
|
||||
|
||||
+2
-1
@@ -162,6 +162,7 @@
|
||||
"provideLog": false,
|
||||
"spyOnlyCallsWithArgs": false,
|
||||
"createMockStyleSheet": false,
|
||||
"browserTrigger": false
|
||||
"browserTrigger": false,
|
||||
"jqLiteCacheSize": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,15 @@ function dealoc(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function jqLiteCacheSize() {
|
||||
var size = 0;
|
||||
forEach(jqLite.cache, function() { size++; });
|
||||
return size - jqLiteCacheSize.initSize;
|
||||
}
|
||||
jqLiteCacheSize.initSize = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @param {DOMElement} element
|
||||
* @param {boolean=} showNgClass
|
||||
|
||||
+216
-18
@@ -440,6 +440,17 @@ describe('browser', function() {
|
||||
expect(locationReplace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set location.href and not use pushState when the url only changed in the hash fragment to please IE10/11', function() {
|
||||
sniffer.history = true;
|
||||
browser.url('http://server/#123');
|
||||
|
||||
expect(fakeWindow.location.href).toEqual('http://server/#123');
|
||||
|
||||
expect(pushState).not.toHaveBeenCalled();
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
expect(locationReplace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use location.replace when history.replaceState not available', function() {
|
||||
sniffer.history = false;
|
||||
browser.url('http://new.org', true);
|
||||
@@ -451,6 +462,17 @@ describe('browser', function() {
|
||||
expect(fakeWindow.location.href).toEqual('http://server/');
|
||||
});
|
||||
|
||||
it('should use location.replace and not use replaceState when the url only changed in the hash fragment to please IE10/11', function() {
|
||||
sniffer.history = true;
|
||||
browser.url('http://server/#123', true);
|
||||
|
||||
expect(locationReplace).toHaveBeenCalledWith('http://server/#123');
|
||||
|
||||
expect(pushState).not.toHaveBeenCalled();
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
expect(fakeWindow.location.href).toEqual('http://server/');
|
||||
});
|
||||
|
||||
it('should return $browser to allow chaining', function() {
|
||||
expect(browser.url('http://any.com')).toBe(browser);
|
||||
});
|
||||
@@ -468,6 +490,55 @@ describe('browser', function() {
|
||||
browser.url(current);
|
||||
expect(fakeWindow.location.href).toBe('dontchange');
|
||||
});
|
||||
|
||||
it('should not read out location.href if a reload was triggered but still allow to change the url', function() {
|
||||
sniffer.history = false;
|
||||
browser.url('http://server/someOtherUrlThatCausesReload');
|
||||
expect(fakeWindow.location.href).toBe('http://server/someOtherUrlThatCausesReload');
|
||||
|
||||
fakeWindow.location.href = 'http://someNewUrl';
|
||||
expect(browser.url()).toBe('http://server/someOtherUrlThatCausesReload');
|
||||
|
||||
browser.url('http://server/someOtherUrl');
|
||||
expect(browser.url()).toBe('http://server/someOtherUrl');
|
||||
expect(fakeWindow.location.href).toBe('http://server/someOtherUrl');
|
||||
});
|
||||
|
||||
it('assumes that changes to location.hash occur in sync', function() {
|
||||
// This is an asynchronous integration test that changes the
|
||||
// hash in all possible ways and checks
|
||||
// - whether the change to the hash can be read out in sync
|
||||
// - whether the change to the hash can be read out in the hashchange event
|
||||
var realWin = window,
|
||||
$realWin = jqLite(realWin),
|
||||
hashInHashChangeEvent = [];
|
||||
|
||||
runs(function() {
|
||||
$realWin.on('hashchange', hashListener);
|
||||
|
||||
realWin.location.hash = '1';
|
||||
realWin.location.href += '2';
|
||||
realWin.location.replace(realWin.location.href + '3');
|
||||
realWin.location.assign(realWin.location.href + '4');
|
||||
|
||||
expect(realWin.location.hash).toBe('#1234');
|
||||
});
|
||||
waitsFor(function() {
|
||||
return hashInHashChangeEvent.length > 3;
|
||||
});
|
||||
runs(function() {
|
||||
$realWin.off('hashchange', hashListener);
|
||||
|
||||
forEach(hashInHashChangeEvent, function(hash) {
|
||||
expect(hash).toBe('#1234');
|
||||
});
|
||||
});
|
||||
|
||||
function hashListener() {
|
||||
hashInHashChangeEvent.push(realWin.location.hash);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('urlChange', function() {
|
||||
@@ -546,15 +617,15 @@ describe('browser', function() {
|
||||
beforeEach(function() {
|
||||
sniffer.history = false;
|
||||
sniffer.hashchange = false;
|
||||
browser.url("http://server.current");
|
||||
browser.url("http://server/#current");
|
||||
});
|
||||
|
||||
it('should fire callback with the correct URL on location change outside of angular', function() {
|
||||
browser.onUrlChange(callback);
|
||||
|
||||
fakeWindow.location.href = 'http://server.new';
|
||||
fakeWindow.location.href = 'http://server/#new';
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(callback).toHaveBeenCalledWith('http://server.new');
|
||||
expect(callback).toHaveBeenCalledWith('http://server/#new');
|
||||
|
||||
fakeWindow.fire('popstate');
|
||||
fakeWindow.fire('hashchange');
|
||||
@@ -618,29 +689,156 @@ describe('browser', function() {
|
||||
|
||||
describe('integration tests with $location', function() {
|
||||
|
||||
beforeEach(module(function($provide, $locationProvider) {
|
||||
spyOn(fakeWindow.history, 'pushState').andCallFake(function(stateObj, title, newUrl) {
|
||||
fakeWindow.location.href = newUrl;
|
||||
function setup(options) {
|
||||
module(function($provide, $locationProvider) {
|
||||
spyOn(fakeWindow.history, 'pushState').andCallFake(function(stateObj, title, newUrl) {
|
||||
fakeWindow.location.href = newUrl;
|
||||
});
|
||||
spyOn(fakeWindow.location, 'replace').andCallFake(function(newUrl) {
|
||||
fakeWindow.location.href = newUrl;
|
||||
});
|
||||
$provide.value('$browser', browser);
|
||||
browser.pollFns = [];
|
||||
|
||||
sniffer.history = options.history;
|
||||
$provide.value('$sniffer', sniffer);
|
||||
|
||||
$locationProvider.html5Mode(options.html5Mode);
|
||||
});
|
||||
$provide.value('$browser', browser);
|
||||
browser.pollFns = [];
|
||||
}
|
||||
|
||||
$locationProvider.html5Mode(true);
|
||||
}));
|
||||
|
||||
it('should update $location when it was changed outside of Angular in sync '+
|
||||
describe('update $location when it was changed outside of Angular in sync '+
|
||||
'before $digest was called', function() {
|
||||
inject(function($rootScope, $location) {
|
||||
fakeWindow.history.pushState(null, '', 'http://server/someTestHash');
|
||||
|
||||
// Verify that infinite digest reported in #6976 no longer occurs
|
||||
expect(function() {
|
||||
it('should work with no history support, no html5Mode', function() {
|
||||
setup({
|
||||
history: false,
|
||||
html5Mode: false
|
||||
});
|
||||
inject(function($rootScope, $location) {
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('/initialPath');
|
||||
});
|
||||
expect(fakeWindow.location.href).toBe('http://server/#/initialPath');
|
||||
|
||||
fakeWindow.location.href = 'http://server/#/someTestHash';
|
||||
|
||||
$rootScope.$digest();
|
||||
}).not.toThrow();
|
||||
|
||||
expect($location.path()).toBe('/someTestHash');
|
||||
expect($location.path()).toBe('/someTestHash');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with history support, no html5Mode', function() {
|
||||
setup({
|
||||
history: true,
|
||||
html5Mode: false
|
||||
});
|
||||
inject(function($rootScope, $location) {
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('/initialPath');
|
||||
});
|
||||
expect(fakeWindow.location.href).toBe('http://server/#/initialPath');
|
||||
|
||||
fakeWindow.location.href = 'http://server/#/someTestHash';
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/someTestHash');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with no history support, with html5Mode', function() {
|
||||
setup({
|
||||
history: false,
|
||||
html5Mode: true
|
||||
});
|
||||
inject(function($rootScope, $location) {
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('/initialPath');
|
||||
});
|
||||
expect(fakeWindow.location.href).toBe('http://server/#/initialPath');
|
||||
|
||||
fakeWindow.location.href = 'http://server/#/someTestHash';
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/someTestHash');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with history support, with html5Mode', function() {
|
||||
setup({
|
||||
history: true,
|
||||
html5Mode: true
|
||||
});
|
||||
inject(function($rootScope, $location) {
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('/initialPath');
|
||||
});
|
||||
expect(fakeWindow.location.href).toBe('http://server/initialPath');
|
||||
|
||||
fakeWindow.location.href = 'http://server/someTestHash';
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/someTestHash');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should not reload the page on every $digest when the page will be reloaded due to url rewrite on load', function() {
|
||||
setup({
|
||||
history: false,
|
||||
html5Mode: true
|
||||
});
|
||||
fakeWindow.location.href = 'http://server/some/deep/path';
|
||||
var changeUrlCount = 0;
|
||||
var _url = browser.url;
|
||||
browser.url = function(newUrl, replace) {
|
||||
if (newUrl) {
|
||||
changeUrlCount++;
|
||||
}
|
||||
return _url.call(this, newUrl, replace);
|
||||
};
|
||||
spyOn(browser, 'url').andCallThrough();
|
||||
inject(function($rootScope, $location) {
|
||||
$rootScope.$digest();
|
||||
$rootScope.$digest();
|
||||
$rootScope.$digest();
|
||||
$rootScope.$digest();
|
||||
|
||||
// from $location for rewriting the initial url into a hash url
|
||||
expect(browser.url).toHaveBeenCalledWith('http://server/#/some/deep/path', true);
|
||||
// from the initial call to the watch in $location for watching $location
|
||||
expect(browser.url).toHaveBeenCalledWith('http://server/#/some/deep/path', false);
|
||||
expect(changeUrlCount).toBe(2);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration test with $rootScope', function() {
|
||||
|
||||
beforeEach(module(function($provide, $locationProvider) {
|
||||
$provide.value('$browser', browser);
|
||||
browser.pollFns = [];
|
||||
}));
|
||||
|
||||
it('should not interfere with legacy browser url replace behavior', function() {
|
||||
inject(function($rootScope) {
|
||||
var current = fakeWindow.location.href;
|
||||
var newUrl = 'notyet';
|
||||
sniffer.history = false;
|
||||
browser.url(newUrl, true);
|
||||
expect(browser.url()).toBe(newUrl);
|
||||
$rootScope.$digest();
|
||||
expect(browser.url()).toBe(newUrl);
|
||||
expect(fakeWindow.location.href).toBe(current);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -82,6 +82,25 @@ describe('event directives', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('security', function() {
|
||||
it('should allow access to the $event object', inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
element = $compile('<button ng-click="e = $event">BTN</button>')(scope);
|
||||
element.triggerHandler('click');
|
||||
expect(scope.e.target).toBe(element[0]);
|
||||
}));
|
||||
|
||||
it('should block access to DOM nodes (e.g. exposed via $event)', inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
element = $compile('<button ng-click="e = $event.target">BTN</button>')(scope);
|
||||
expect(function() {
|
||||
element.triggerHandler('click');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is disallowed! ' +
|
||||
'Expression: e = $event.target');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('blur', function() {
|
||||
|
||||
describe('call the listener asynchronously during $apply', function() {
|
||||
|
||||
+127
-24
@@ -38,8 +38,28 @@ describe('select', function() {
|
||||
};
|
||||
|
||||
return equals(expectedValues, actualValues);
|
||||
},
|
||||
|
||||
toEqualOption: function(value, text, label) {
|
||||
var errors = [];
|
||||
if (this.actual.attr('value') !== value) {
|
||||
errors.push('Expected option value "' + this.actual.attr('value') + '" to equal "' + value + '"');
|
||||
}
|
||||
if (text && this.actual.text() !== text) {
|
||||
errors.push('Expected option value "' + this.actual.attr('value') + '" to equal "' + value + '"');
|
||||
}
|
||||
if (label && this.actual.attr('label') !== label) {
|
||||
errors.push('Expected option value "' + this.actual.attr('value') + '" to equal "' + value + '"');
|
||||
}
|
||||
|
||||
this.message = function() {
|
||||
return errors.join('\n');
|
||||
};
|
||||
|
||||
return errors.length === 0;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -378,6 +398,39 @@ describe('select', function() {
|
||||
expect(element).toEqualSelect(['? string:r2d2 ?']);
|
||||
expect(scope.robot).toBe('r2d2');
|
||||
});
|
||||
|
||||
describe('selectController.hasOption', function() {
|
||||
it('should return true for options added via ngOptions', function() {
|
||||
scope.robots = [
|
||||
{key: 1, value: 'c3p0'},
|
||||
{key: 2, value: 'r2d2'}
|
||||
];
|
||||
scope.robot = 'r2d2';
|
||||
|
||||
compile('<select ng-model="robot" ' +
|
||||
'ng-options="item.key as item.value for item in robots">' +
|
||||
'</select>');
|
||||
|
||||
var selectCtrl = element.data().$selectController;
|
||||
|
||||
expect(selectCtrl.hasOption('c3p0')).toBe(true);
|
||||
expect(selectCtrl.hasOption('r2d2')).toBe(true);
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robots.pop();
|
||||
});
|
||||
|
||||
expect(selectCtrl.hasOption('c3p0')).toBe(true);
|
||||
expect(selectCtrl.hasOption('r2d2')).toBe(false);
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robots.push({key: 2, value: 'r2d2'});
|
||||
});
|
||||
|
||||
expect(selectCtrl.hasOption('c3p0')).toBe(true);
|
||||
expect(selectCtrl.hasOption('r2d2')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -454,6 +507,39 @@ describe('select', function() {
|
||||
expect(element).toBeValid();
|
||||
expect(element).toBeDirty();
|
||||
});
|
||||
|
||||
describe('selectController.hasOption', function() {
|
||||
it('should return true for options added via ngOptions', function() {
|
||||
scope.robots = [
|
||||
{key: 1, value: 'c3p0'},
|
||||
{key: 2, value: 'r2d2'}
|
||||
];
|
||||
scope.robot = 'r2d2';
|
||||
|
||||
compile('<select ng-model="robot" multiple ' +
|
||||
'ng-options="item.key as item.value for item in robots">' +
|
||||
'</select>');
|
||||
|
||||
var selectCtrl = element.data().$selectController;
|
||||
|
||||
expect(selectCtrl.hasOption('c3p0')).toBe(true);
|
||||
expect(selectCtrl.hasOption('r2d2')).toBe(true);
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robots.pop();
|
||||
});
|
||||
|
||||
expect(selectCtrl.hasOption('c3p0')).toBe(true);
|
||||
expect(selectCtrl.hasOption('r2d2')).toBe(false);
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robots.push({key: 2, value: 'r2d2'});
|
||||
});
|
||||
|
||||
expect(selectCtrl.hasOption('c3p0')).toBe(true);
|
||||
expect(selectCtrl.hasOption('r2d2')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -508,9 +594,9 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="0">A</option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="1">B</option>');
|
||||
expect(sortedHtml(options[2])).toEqual('<option value="2">C</option>');
|
||||
expect(options.eq(0)).toEqualOption('0', 'A');
|
||||
expect(options.eq(1)).toEqualOption('1', 'B');
|
||||
expect(options.eq(2)).toEqualOption('2', 'C');
|
||||
});
|
||||
|
||||
it('should render zero as a valid display value', function() {
|
||||
@@ -523,9 +609,9 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="0">0</option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="1">1</option>');
|
||||
expect(sortedHtml(options[2])).toEqual('<option value="2">2</option>');
|
||||
expect(options.eq(0)).toEqualOption('0', '0');
|
||||
expect(options.eq(1)).toEqualOption('1', '1');
|
||||
expect(options.eq(2)).toEqualOption('2', '2');
|
||||
});
|
||||
|
||||
|
||||
@@ -542,9 +628,9 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="blue">blue</option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="green">green</option>');
|
||||
expect(sortedHtml(options[2])).toEqual('<option value="red">red</option>');
|
||||
expect(options.eq(0)).toEqualOption('blue', 'blue');
|
||||
expect(options.eq(1)).toEqualOption('green', 'green');
|
||||
expect(options.eq(2)).toEqualOption('red', 'red');
|
||||
expect(options[2].selected).toEqual(true);
|
||||
|
||||
scope.$apply(function() {
|
||||
@@ -564,7 +650,7 @@ describe('select', function() {
|
||||
});
|
||||
|
||||
expect(element.find('option').length).toEqual(1); // because we add special empty option
|
||||
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="?"></option>');
|
||||
expect(element.find('option')).toEqualOption('?','');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values.push({name:'A'});
|
||||
@@ -572,15 +658,15 @@ describe('select', function() {
|
||||
});
|
||||
|
||||
expect(element.find('option').length).toEqual(1);
|
||||
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(element.find('option')).toEqualOption('0', 'A');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values.push({name:'B'});
|
||||
});
|
||||
|
||||
expect(element.find('option').length).toEqual(2);
|
||||
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(sortedHtml(element.find('option')[1])).toEqual('<option value="1">B</option>');
|
||||
expect(element.find('option').eq(0)).toEqualOption('0', 'A');
|
||||
expect(element.find('option').eq(1)).toEqualOption('1', 'B');
|
||||
});
|
||||
|
||||
|
||||
@@ -599,15 +685,15 @@ describe('select', function() {
|
||||
});
|
||||
|
||||
expect(element.find('option').length).toEqual(2);
|
||||
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(sortedHtml(element.find('option')[1])).toEqual('<option value="1">B</option>');
|
||||
expect(element.find('option').eq(0)).toEqualOption('0', 'A');
|
||||
expect(element.find('option').eq(1)).toEqualOption('1', 'B');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values.pop();
|
||||
});
|
||||
|
||||
expect(element.find('option').length).toEqual(1);
|
||||
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(element.find('option')).toEqualOption('0', 'A');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values.pop();
|
||||
@@ -659,9 +745,9 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="0">B</option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="1">C</option>');
|
||||
expect(sortedHtml(options[2])).toEqual('<option value="2">D</option>');
|
||||
expect(options.eq(0)).toEqualOption('0', 'B');
|
||||
expect(options.eq(1)).toEqualOption('1', 'C');
|
||||
expect(options.eq(2)).toEqualOption('2', 'D');
|
||||
});
|
||||
|
||||
|
||||
@@ -705,7 +791,7 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(1);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="regularProperty">visible</option>');
|
||||
expect(options.eq(0)).toEqualOption('regularProperty', 'visible');
|
||||
});
|
||||
|
||||
it('should allow expressions over multiple lines', function() {
|
||||
@@ -729,8 +815,8 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="0">2</option>');
|
||||
expect(sortedHtml(options[2])).toEqual('<option value="1">3</option>');
|
||||
expect(options.eq(1)).toEqualOption('0', '2');
|
||||
expect(options.eq(2)).toEqualOption('1', '3');
|
||||
});
|
||||
|
||||
it('should not update selected property of an option element on digest with no change event',
|
||||
@@ -761,6 +847,23 @@ describe('select', function() {
|
||||
expect(scope.selected).toBe(scope.values[0]);
|
||||
});
|
||||
|
||||
// bug fix #9621
|
||||
it('should update the label property', function() {
|
||||
// ng-options="value.name for value in values"
|
||||
// ng-model="selected"
|
||||
createSingleSelect();
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
});
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.eq(0).prop('label')).toEqual('A');
|
||||
expect(options.eq(1).prop('label')).toEqual('B');
|
||||
expect(options.eq(2).prop('label')).toEqual('C');
|
||||
});
|
||||
|
||||
describe('binding', function() {
|
||||
|
||||
it('should bind to scope value', function() {
|
||||
@@ -887,8 +990,8 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(2);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="0">C</option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="1">B</option>');
|
||||
expect(options.eq(0)).toEqualOption('0', 'C');
|
||||
expect(options.eq(1)).toEqualOption('1', 'B');
|
||||
});
|
||||
|
||||
|
||||
|
||||
+191
-73
@@ -1,92 +1,210 @@
|
||||
'use strict';
|
||||
|
||||
describe('Filter: orderBy', function() {
|
||||
var orderBy;
|
||||
var orderBy, orderByFilter;
|
||||
beforeEach(inject(function($filter) {
|
||||
orderBy = $filter('orderBy');
|
||||
orderBy = orderByFilter = $filter('orderBy');
|
||||
}));
|
||||
|
||||
it('should return same array if predicate is falsy', function() {
|
||||
var array = [1, 2, 3];
|
||||
expect(orderBy(array)).toBe(array);
|
||||
});
|
||||
|
||||
it('shouldSortArrayInReverse', function() {
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "T")).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "reverse")).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
describe('(Arrays)', function() {
|
||||
it('should return sorted array if predicate is not provided', function() {
|
||||
expect(orderBy([2, 1, 3])).toEqual([1, 2, 3]);
|
||||
|
||||
it('should sort inherited from array', function(){
|
||||
function BaseCollection(){}
|
||||
BaseCollection.prototype = Array.prototype;
|
||||
var child = new BaseCollection();
|
||||
child.push({a:2});
|
||||
child.push({a:15});
|
||||
expect(orderBy([2, 1, 3], '')).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], [])).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], [''])).toEqual([1, 2, 3]);
|
||||
|
||||
expect(orderBy(child, 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
expect(orderBy([2, 1, 3], '+')).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], ['+'])).toEqual([1, 2, 3]);
|
||||
|
||||
it('should sort array by predicate', function() {
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['a', 'b'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['b', 'a'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['+b', '-a'])).toEqualData([{a:15, b:1}, {a:2, b:1}]);
|
||||
expect(orderBy([2, 1, 3], '-')).toEqual([3, 2, 1]);
|
||||
expect(orderBy([2, 1, 3], ['-'])).toEqual([3, 2, 1]);
|
||||
});
|
||||
|
||||
|
||||
it('shouldSortArrayInReverse', function() {
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "T")).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "reverse")).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
|
||||
|
||||
it('should sort inherited from array', function() {
|
||||
function BaseCollection(){}
|
||||
BaseCollection.prototype = Array.prototype;
|
||||
var child = new BaseCollection();
|
||||
child.push({a:2});
|
||||
child.push({a:15});
|
||||
expect(orderBy(child, 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
|
||||
|
||||
it('should sort array by predicate', function() {
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['a', 'b'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['b', 'a'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['+b', '-a'])).toEqualData([{a:15, b:1}, {a:2, b:1}]);
|
||||
});
|
||||
|
||||
|
||||
it('should sort array by date predicate', function() {
|
||||
// same dates
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 }]);
|
||||
|
||||
// one different date
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 }]);
|
||||
});
|
||||
|
||||
|
||||
it('should use function', function() {
|
||||
expect(
|
||||
orderBy(
|
||||
[{a:15, b:1},{a:2, b:1}],
|
||||
function(value) { return value.a; })).
|
||||
toEqual([{a:2, b:1},{a:15, b:1}]);
|
||||
});
|
||||
|
||||
|
||||
it('should support string predicates with names containing non-identifier characters', function() {
|
||||
/*jshint -W008 */
|
||||
expect(orderBy([{"Tip %": .25}, {"Tip %": .15}, {"Tip %": .40}], '"Tip %"'))
|
||||
.toEqualData([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}]);
|
||||
expect(orderBy([{"원": 76000}, {"원": 31000}, {"원": 156000}], '"원"'))
|
||||
.toEqualData([{"원": 31000}, {"원": 76000}, {"원": 156000}]);
|
||||
});
|
||||
|
||||
|
||||
it('should throw if quoted string predicate is quoted incorrectly', function() {
|
||||
/*jshint -W008 */
|
||||
expect(function() {
|
||||
return orderBy([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}], '"Tip %\'');
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should sort array by date predicate', function() {
|
||||
// same dates
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }
|
||||
],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 }
|
||||
]);
|
||||
|
||||
// one different date
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }
|
||||
],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 }
|
||||
]);
|
||||
});
|
||||
describe('(Array-Like Objects)', function() {
|
||||
function arrayLike(args) {
|
||||
var result = {};
|
||||
var i;
|
||||
for (i = 0; i < args.length; ++i) {
|
||||
result[i] = args[i];
|
||||
}
|
||||
result.length = i;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
it('should use function', function() {
|
||||
expect(
|
||||
orderBy(
|
||||
[{a:15, b:1},{a:2, b:1}],
|
||||
function(value) { return value.a; })).
|
||||
toEqual([{a:2, b:1},{a:15, b:1}]);
|
||||
});
|
||||
beforeEach(inject(function($filter) {
|
||||
orderBy = function(collection) {
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
args[0] = arrayLike(args[0]);
|
||||
return orderByFilter.apply(null, args);
|
||||
};
|
||||
}));
|
||||
|
||||
it('should support string predicates with names containing non-identifier characters', function() {
|
||||
/* jshint -W008 */
|
||||
expect(orderBy([{"Tip %": .25}, {"Tip %": .15}, {"Tip %": .40}], '"Tip %"'))
|
||||
.toEqualData([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}]);
|
||||
expect(orderBy([{"원": 76000}, {"원": 31000}, {"원": 156000}], '"원"'))
|
||||
.toEqualData([{"원": 31000}, {"원": 76000}, {"원": 156000}]);
|
||||
});
|
||||
|
||||
it('should throw if quoted string predicate is quoted incorrectly', function() {
|
||||
/* jshint -W008 */
|
||||
expect(function() {
|
||||
return orderBy([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}], '"Tip %\'');
|
||||
}).toThrow();
|
||||
it('should return sorted array if predicate is not provided', function() {
|
||||
expect(orderBy([2, 1, 3])).toEqual([1, 2, 3]);
|
||||
|
||||
expect(orderBy([2, 1, 3], '')).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], [])).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], [''])).toEqual([1, 2, 3]);
|
||||
|
||||
expect(orderBy([2, 1, 3], '+')).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], ['+'])).toEqual([1, 2, 3]);
|
||||
|
||||
expect(orderBy([2, 1, 3], '-')).toEqual([3, 2, 1]);
|
||||
expect(orderBy([2, 1, 3], ['-'])).toEqual([3, 2, 1]);
|
||||
});
|
||||
|
||||
|
||||
it('shouldSortArrayInReverse', function() {
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "T")).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "reverse")).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
|
||||
|
||||
it('should sort array by predicate', function() {
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['a', 'b'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['b', 'a'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['+b', '-a'])).toEqualData([{a:15, b:1}, {a:2, b:1}]);
|
||||
});
|
||||
|
||||
|
||||
it('should sort array by date predicate', function() {
|
||||
// same dates
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 }]);
|
||||
|
||||
// one different date
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 }]);
|
||||
});
|
||||
|
||||
|
||||
it('should use function', function() {
|
||||
expect(
|
||||
orderBy(
|
||||
[{a:15, b:1},{a:2, b:1}],
|
||||
function(value) { return value.a; })).
|
||||
toEqual([{a:2, b:1},{a:15, b:1}]);
|
||||
});
|
||||
|
||||
|
||||
it('should support string predicates with names containing non-identifier characters', function() {
|
||||
/*jshint -W008 */
|
||||
expect(orderBy([{"Tip %": .25}, {"Tip %": .15}, {"Tip %": .40}], '"Tip %"'))
|
||||
.toEqualData([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}]);
|
||||
expect(orderBy([{"원": 76000}, {"원": 31000}, {"원": 156000}], '"원"'))
|
||||
.toEqualData([{"원": 31000}, {"원": 76000}, {"원": 156000}]);
|
||||
});
|
||||
|
||||
|
||||
it('should throw if quoted string predicate is quoted incorrectly', function() {
|
||||
/*jshint -W008 */
|
||||
expect(function() {
|
||||
return orderBy([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}], '"Tip %\'');
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user