Compare commits
465 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b8cf8539d | |||
| f109604315 | |||
| ac3dbae370 | |||
| cf1d365f57 | |||
| 78a0f41058 | |||
| eccd9bfbb3 | |||
| 2d9dd1c172 | |||
| a2c4271128 | |||
| 56c00800c7 | |||
| 15ec78f5ef | |||
| a4863d5244 | |||
| 96a1df192a | |||
| 89c25fe713 | |||
| c06c5a36b1 | |||
| 9985104dc0 | |||
| 94514a91f8 | |||
| 4da65d0e8c | |||
| e1d122a4b7 | |||
| 885c3ad5dd | |||
| 9312bed472 | |||
| faf29dd047 | |||
| 510b5f3d90 | |||
| 1e59822df7 | |||
| d95a6925cd | |||
| b4d680a921 | |||
| ff4480be65 | |||
| cf0513dc6f | |||
| dfba8fb2e7 | |||
| a0af13f672 | |||
| 69e6379d19 | |||
| 1094b3471e | |||
| 3224862a9c | |||
| 0084cb5ca4 | |||
| c578f8c3ed | |||
| 5b05c0de03 | |||
| d19c0ac6d3 | |||
| 5343deb3da | |||
| 26bad2bf87 | |||
| d304b0c3df | |||
| 9d5c533791 | |||
| 7414e7b533 | |||
| 5432dd289a | |||
| 194b2c1ea0 | |||
| f7a9ea6a41 | |||
| 627eba2b7c | |||
| 73ee5fc008 | |||
| 4c762bfe5c | |||
| 749b3e8763 | |||
| fd409bd2df | |||
| e80a64883d | |||
| 8b2753eee2 | |||
| 02aa7978d3 | |||
| 5bf7ff5a3e | |||
| a01aa7055c | |||
| a01d888eec | |||
| 28800a48ad | |||
| dea72be0cc | |||
| cd139f5767 | |||
| 10a7521f0b | |||
| 887da5684b | |||
| 95a29d7bde | |||
| 945056b166 | |||
| 128feb2674 | |||
| a709dc19b8 | |||
| 65585a2d3c | |||
| edbe9d8ca8 | |||
| 9e67da420b | |||
| 5fc2b96b97 | |||
| a7ee4a8884 | |||
| eea0de6db4 | |||
| d15165f207 | |||
| c7998f5f99 | |||
| 08e3b1edbb | |||
| fe743e31f8 | |||
| 55ce859998 | |||
| 5a3c9190dc | |||
| e160944bfa | |||
| 6f8940c5d0 | |||
| 87cbf9f591 | |||
| fd6e5e3f31 | |||
| 7d4aee31bb | |||
| 7a54d2791f | |||
| 65243b7d60 | |||
| 9e30baad3f | |||
| a070ff5ad0 | |||
| c90abf057b | |||
| cdc093a463 | |||
| 00cc9eb32a | |||
| ef4bb28be1 | |||
| 496e6bf901 | |||
| 23b255a8b7 | |||
| e2154cbc0b | |||
| 0a5c00abf8 | |||
| a004d487c4 | |||
| 037f30a0c9 | |||
| c37bb2dc28 | |||
| 7cf70c587e | |||
| 1777110958 | |||
| d2089a1633 | |||
| e9ce22592a | |||
| 4f6fe1d479 | |||
| 0f19cd3625 | |||
| c1caf2560a | |||
| b07cc0e392 | |||
| a26f192e01 | |||
| f943180e34 | |||
| e6a6e32c72 | |||
| e1cfb99ae9 | |||
| 1cdc050ce7 | |||
| d600c608e3 | |||
| 86321d1f57 | |||
| 8724e97b7e | |||
| bf8013ad57 | |||
| a6c45c3e66 | |||
| e626f95469 | |||
| 2fab1a2da9 | |||
| f3a39a6418 | |||
| f00b157841 | |||
| ae20f0c1b3 | |||
| 9f6c5db2a6 | |||
| ea9ca651d2 | |||
| d6db4b1749 | |||
| 9798f5e35f | |||
| 94bf24e3b6 | |||
| fbc7f1b454 | |||
| bf7c9d9900 | |||
| 568574b915 | |||
| 46d690ff01 | |||
| 882f412d57 | |||
| 0d4def68ae | |||
| d35c1ac8b0 | |||
| 2a9f7b7287 | |||
| 245b60d69a | |||
| b6a01bd27d | |||
| aaaad298ac | |||
| ba6b68b6ae | |||
| ed768ebc53 | |||
| 9fd3dfe49d | |||
| d7686a429c | |||
| 6c0cf17404 | |||
| c648fee5c2 | |||
| 9462c78fbf | |||
| c4497d60bc | |||
| 5690627766 | |||
| 2389c71238 | |||
| ce7ab3d1ee | |||
| 7db3b54c1f | |||
| 21ad176246 | |||
| df1d222dd0 | |||
| 7eea26323b | |||
| f0fb4a3928 | |||
| 0144fa1ca9 | |||
| b612826158 | |||
| e5a3acacad | |||
| 9f947e5b8b | |||
| 75de7395bb | |||
| 9f73b1f290 | |||
| bd33f60276 | |||
| 8682befc72 | |||
| 3f0a37f380 | |||
| f5d08963b0 | |||
| 7a48ee6aa9 | |||
| 6512a736ac | |||
| 9e0fa5b7c8 | |||
| 9368ea3814 | |||
| a6a4c18ecd | |||
| d8acfe7389 | |||
| b036c0bc58 | |||
| bf03eb007c | |||
| c2f2587a79 | |||
| 5d0d34ae72 | |||
| 70c74a9c4e | |||
| 22c5b7059b | |||
| 190498efd7 | |||
| a5990050d4 | |||
| 8d507df8c9 | |||
| 567f33823b | |||
| 5340d1e0b1 | |||
| 8d91ec4173 | |||
| 746e7a9768 | |||
| 94737cd017 | |||
| c8bb044be1 | |||
| 4a569560d8 | |||
| 17ee0f031a | |||
| 10646c9f6f | |||
| 964e394555 | |||
| 2c0f7ffe3a | |||
| a5eb3ed107 | |||
| 63690d1892 | |||
| 050e5d773f | |||
| da4b8a74c3 | |||
| 5be92f7b9c | |||
| 459a01e582 | |||
| 72e554ffeb | |||
| 3bd96609e9 | |||
| d73635d573 | |||
| ad3b811b8b | |||
| d3f7bd699e | |||
| 1c55123f9c | |||
| 1d7b9d5626 | |||
| 347be5ae9a | |||
| 934f44f69e | |||
| d3d2a3a374 | |||
| 527d0a1600 | |||
| 23875cb330 | |||
| b0be87f663 | |||
| 9ccd2f0412 | |||
| 99004b0aed | |||
| ab040254f0 | |||
| 4f5d5029c2 | |||
| f534def0c6 | |||
| c3e32f1a51 | |||
| 4f22d6866c | |||
| aab3df7aea | |||
| 0a6cf70deb | |||
| c79aba92f6 | |||
| 84dedb81e7 | |||
| e999740044 | |||
| 0ad39dde4f | |||
| 4c71824a69 | |||
| 47c454a315 | |||
| 16086aa37c | |||
| c0a26b1853 | |||
| 9db2170dcf | |||
| b28dee7fd5 | |||
| 142a985f33 | |||
| bd5ec7c32a | |||
| bdc251c5a5 | |||
| ad9537cdf6 | |||
| 807d8c92b3 | |||
| 454626ad39 | |||
| 247c99a8a4 | |||
| da1d50fbe9 | |||
| 67d064820c | |||
| b2631f6170 | |||
| 1430c6d6b1 | |||
| 3ea5941f0e | |||
| d0270d9256 | |||
| 5f080193cb | |||
| cf891428bf | |||
| 5b9967518e | |||
| 38f462d572 | |||
| 56eeba0f3c | |||
| 5a534235b6 | |||
| e7a0fb250f | |||
| e3ddc2bcc4 | |||
| d11088eb43 | |||
| a5df1fc41f | |||
| ec4d446f89 | |||
| b225083a21 | |||
| e84d3334b0 | |||
| 7989c7d24a | |||
| 5c36f466e1 | |||
| f8151afd90 | |||
| 74120eaa0f | |||
| b370fac4fc | |||
| 23fc73081f | |||
| e5e69d9b90 | |||
| fa722447f8 | |||
| 81d10e819e | |||
| 809ca94e1c | |||
| 824eab9029 | |||
| d503dfe99b | |||
| e4d33917e3 | |||
| 6326e2028b | |||
| 8aed2047f0 | |||
| f60b6b0938 | |||
| eea7113abe | |||
| c8f34e7f6b | |||
| 011fa39c2a | |||
| 58d0e8945d | |||
| 2bbced212e | |||
| 5a8ad8fe32 | |||
| 41d5938883 | |||
| 5480d2a80b | |||
| 95adf3a4d8 | |||
| cc315ef4cc | |||
| 41c233ada1 | |||
| 46091f811b | |||
| fde2ccb3f5 | |||
| 1cc2ad2443 | |||
| 1aa46fac62 | |||
| 5bde02a8ca | |||
| d07e9f77f1 | |||
| aa21c521eb | |||
| bd14a90610 | |||
| 9f1b9849d8 | |||
| 47f159cdf3 | |||
| 99eb123d79 | |||
| 6515adc118 | |||
| b7aff92354 | |||
| 6b3b00e095 | |||
| 921f7ce49e | |||
| 17eee57c52 | |||
| 4fc3ee8040 | |||
| 39d3ae80d9 | |||
| 480f2f33c1 | |||
| 9c9a89f7ff | |||
| 73194009a9 | |||
| 162f41a1ab | |||
| 7c82c4f837 | |||
| 97b1371199 | |||
| 95d1768c77 | |||
| c3d99d68da | |||
| 303a683081 | |||
| a0e8c45880 | |||
| 870547d185 | |||
| 0d1f8a0532 | |||
| b94600d71e | |||
| 3e5a4ef86c | |||
| efec0c358d | |||
| 1f59de35c9 | |||
| 9b53b25f15 | |||
| 3fbfa357ca | |||
| 50ef1f8e35 | |||
| 66c0bfaa8e | |||
| 1719b0aca5 | |||
| 7ee102eecf | |||
| fc7f11d03b | |||
| 3c7874b07b | |||
| 7f339a1782 | |||
| 72a5f007d8 | |||
| 63380bbbda | |||
| c635b69f5c | |||
| 522ec1a9ec | |||
| 9cb57772a4 | |||
| d54f09ef29 | |||
| 65989c6f0d | |||
| 4491bbdede | |||
| a6978b201b | |||
| 28e72cbe6b | |||
| 916dadd8ec | |||
| e509ec37f5 | |||
| ee0e9a4452 | |||
| 9d36368ff9 | |||
| d4bcee0799 | |||
| dd687e2bf5 | |||
| 4c69d694d7 | |||
| ff7c738c21 | |||
| 51a22cf435 | |||
| c2c60ab49a | |||
| 71c2f24fc6 | |||
| fc78738cc6 | |||
| c7052f098d | |||
| 7d6f5f986e | |||
| beeb5ff908 | |||
| b2d63ac48b | |||
| 4af32de84a | |||
| a130bb899d | |||
| cc749760fd | |||
| b467a50bc7 | |||
| a1652057a5 | |||
| 7e6f999221 | |||
| 625cc7609c | |||
| c51273b1fb | |||
| 0a8b3161b1 | |||
| ba554eeb1b | |||
| 5f0af2cd0e | |||
| 7411b24812 | |||
| ae5f6f48b4 | |||
| c5b2bf083c | |||
| 0499c47270 | |||
| 43a4ff4cdf | |||
| 6b8ed42670 | |||
| c57df3dc77 | |||
| 6d53808475 | |||
| a7e8a503fd | |||
| 324694a58b | |||
| effcd340e9 | |||
| 264f960800 | |||
| 257e97a65f | |||
| c048f0d8e8 | |||
| 96e37a0866 | |||
| 5062d32621 | |||
| d458f31711 | |||
| fc9ce9ec07 | |||
| da17c61444 | |||
| e5c135ac50 | |||
| 1a43f36e23 | |||
| 1c305dc67a | |||
| a397645537 | |||
| f077649f48 | |||
| f3ac2cd434 | |||
| 7779630989 | |||
| 00ca67e4be | |||
| 91b6c5f7ff | |||
| 5be325a0c1 | |||
| b7027b9d87 | |||
| fe8353bc5e | |||
| c780030c6e | |||
| d5e9f38f3d | |||
| dc66687149 | |||
| 3d6a099d6e | |||
| 8767e766d1 | |||
| 47066e70e1 | |||
| c0d30aedfc | |||
| b246d6e2ab | |||
| 3b04b48b7c | |||
| 0462422089 | |||
| bb460d6355 | |||
| bbb19a4d16 | |||
| ee5e881b9e | |||
| 690dfe000b | |||
| 91c835dc0e | |||
| dfa8baf59a | |||
| a8efd43d25 | |||
| 96abeb1074 | |||
| 5159eb7635 | |||
| 1ba8c2a33a | |||
| 659af29adb | |||
| 1fe7e3a130 | |||
| aec3c8478c | |||
| 5c887ddb66 | |||
| 0bd4a473a7 | |||
| dcf76e6816 | |||
| 56a3d52f45 | |||
| faa7d81b67 | |||
| 6bb2cd6ee2 | |||
| 2d61040fb0 | |||
| 99f25050a3 | |||
| ba5f8ee27f | |||
| 9a532002cf | |||
| 5524d2b0fb | |||
| d4839bac32 | |||
| 602369c6a2 | |||
| 34909520ae | |||
| 92e31b556f | |||
| 62c0e5c460 | |||
| c67af8a038 | |||
| 2da1de5a6d | |||
| ee8465bf10 | |||
| 9c6794a044 | |||
| 0ffb47bdb6 | |||
| ec885489a2 | |||
| 01c52e92b1 | |||
| 943377a091 | |||
| 72b7a1c531 | |||
| 9c0225512c | |||
| 40d7e66f40 | |||
| 1d52349440 | |||
| 3eb0c8bc67 | |||
| 42855e4363 | |||
| 4c61fc01f9 | |||
| 4fdab37659 | |||
| 841013a4c4 | |||
| 4e9a2aa10e | |||
| 4fc2141458 | |||
| 5b40e87ac6 | |||
| 1391f19fb4 | |||
| 04a4d8b061 | |||
| bbd87c9425 | |||
| 64063b5d41 | |||
| 833e0ae343 | |||
| d74ef497de | |||
| 6ddcf91861 | |||
| 8a867cee22 | |||
| 68217d427c | |||
| 1efef67b5f | |||
| a6cfa43c19 | |||
| b41bc98c54 | |||
| aaabeb8c5e | |||
| 05d4971abb | |||
| c6107fe8ac | |||
| 68f074c299 | |||
| c53a37ed91 |
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
|
||||
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
|
||||
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:<?xml version="1.0" encoding="UTF-8"?> <launchConfigurationWorkingSet editPageId="org.eclipse.ui.resourceWorkingSetPage" factoryID="org.eclipse.ui.internal.WorkingSetFactory" id="1262905463390_2" label="workingSet" name="workingSet"> <item factoryID="org.eclipse.ui.internal.model.ResourceFactory" path="/angular.js/test" type="2"/> <item factoryID="org.eclipse.ui.internal.model.ResourceFactory" path="/angular.js/src" type="2"/> </launchConfigurationWorkingSet>}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js}/test.sh"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
|
||||
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:<?xml version="1.0" encoding="UTF-8"?> <resources> <item path="/angular.js/build" type="2"/> </resources>}"/>
|
||||
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
|
||||
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:<?xml version="1.0" encoding="UTF-8"?> <resources> <item path="/angular.js/docs" type="2"/> <item path="/angular.js/src" type="2"/> </resources>}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/gen_docs.sh}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
|
||||
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/angular.js}"/>
|
||||
</launchConfiguration>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
|
||||
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:<?xml version="1.0" encoding="UTF-8"?> <resources> <item path="/angular.js/docs" type="2"/> </resources>}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LAUNCH_CONFIGURATION_BUILD_SCOPE" value="${none}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/gen_docs.sh}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/angular.js}"/>
|
||||
</launchConfiguration>
|
||||
@@ -1,3 +1,8 @@
|
||||
build/
|
||||
angularjs.netrc
|
||||
jstd.log
|
||||
.DS_Store
|
||||
regression/temp*.html
|
||||
performance/temp*.html
|
||||
.idea/workspace.xml
|
||||
*~
|
||||
|
||||
Generated
-1
@@ -1 +0,0 @@
|
||||
workspace.xml
|
||||
Generated
-7
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Settings><!--This file was automatically generated by Ruby plugin.
|
||||
You are allowed to:
|
||||
1. Remove rake task
|
||||
2. Add existing rake tasks
|
||||
To add existing rake tasks automatically delete this file and reload the project.
|
||||
--><RakeGroup description="" fullCmd="" taksId="rake"><RakeTask description="Compile JavaScript" fullCmd="compile" taksId="compile" /><RakeTask description="Generate Externs" fullCmd="compileexterns" taksId="compileexterns" /><RakeTask description="Lint" fullCmd="lint" taksId="lint" /><RakeGroup description="" fullCmd="" taksId="server"><RakeTask description="Run JsTestDriver Server" fullCmd="server:start" taksId="start" /><RakeTask description="Run JavaScript tests against the server" fullCmd="server:test" taksId="test" /></RakeGroup><RakeTask description="Run JavaScript tests" fullCmd="test" taksId="test" /><RakeTask description="" fullCmd="default" taksId="default" /></RakeGroup></Settings>
|
||||
Generated
+67
@@ -0,0 +1,67 @@
|
||||
IMPORTANT: THIS IS TO CERTIFY THE RIGHT TO USE THE JETBRAINS SOFTWARE PRODUCT, GRANTED BY JETBRAINS S.R.O. UNDER THE TERMS AND CONDITIONS OF THE LICENSE AGREEMENT INCLUDED WITH THE SOFTWARE. PLEASE SAVE A COPY OF THIS EMAIL FOR FUTURE REFERENCES.
|
||||
|
||||
========LICENSE DETAILS========
|
||||
|
||||
Type: Open Source License
|
||||
Reference No*: LC-93161-D352729080
|
||||
Date of Issue: 3 November 2010
|
||||
Expiration Date: 3 November 2011
|
||||
Number of Authorized Users: not limited
|
||||
|
||||
* Please quote this reference when contacting JetBrains
|
||||
|
||||
===========LICENSEE============
|
||||
|
||||
Name: angularjs
|
||||
Customer ID: 93161
|
||||
Address:
|
||||
|
||||
=======SOFTWARE PRODUCT========
|
||||
|
||||
Product Name: WebStorm
|
||||
Licensed Version: 1.0 and any new product release which is made generally available before 3 November 2011
|
||||
|
||||
The software is shipped electronically and is available for download from:
|
||||
http://www.jetbrains.com/webstorm/download/
|
||||
|
||||
Your WebStorm license includes a 1-year upgrade subscription qualifying you for free upgrades to any new versions of WebStorm released by JetBrains during your upgrade subscription period starting on your license issue date and ending on 3 November 2011. To check availability of the new versions of WebStorm, please visit http://www.jetbrains.com.
|
||||
|
||||
For running any new version of WebStorm released by JetBrains during your upgrade subscription period, please use the included below licensing details.
|
||||
|
||||
You can renew your upgrade subscription before or after its expiration. Your new subscription period will start on the date following the expiration date of your current upgrade subscription.
|
||||
|
||||
=========INSTALLATION==========
|
||||
|
||||
Run WebStorm and follow the Installation Wizard's instructions. To register for use of the software or change your existing registration details, go to Help/Register menu of the software and enter the included below the User Name and License Key(s) into the registration dialog:
|
||||
|
||||
User Name: angularjs
|
||||
|
||||
===== LICENSE BEGIN =====
|
||||
93161-03112010
|
||||
00000jBsEx59XVlc79fV"aAqWXQ09e
|
||||
jQsg5TNp5X4HGhc10LNBdu!!ejRcFG
|
||||
7h3S6T09YcRWs23TH0RgaM87!HqmQo
|
||||
===== LICENSE END =====
|
||||
|
||||
===DOCUMENTATION AND SUPPORT===
|
||||
|
||||
WebStorm documentation:
|
||||
http://www.jetbrains.com/webstorm/documentation/
|
||||
|
||||
Available support resources:
|
||||
http://www.jetbrains.com/support/
|
||||
|
||||
Technical support contact:
|
||||
support@jetbrains.com
|
||||
|
||||
Contact for the license renewal requests:
|
||||
opensource@jetbrains.com
|
||||
|
||||
For questions, please contact:
|
||||
sales@jetbrains.com
|
||||
|
||||
|
||||
JetBrains Sales Team
|
||||
http://www.jetbrains.com
|
||||
"Develop with pleasure!"
|
||||
|
||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="RUBY_MODULE" version="4">
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
Generated
+1
-4
@@ -3,10 +3,7 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</component>
|
||||
<component name="ProjectDetails">
|
||||
<option name="projectName" value="master" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Ruby SDK 1.8.7 (/usr/bin/ruby)" project-jdk-type="RUBY_SDK" />
|
||||
<component name="ProjectRootManager" version="2" />
|
||||
<component name="SvnBranchConfigurationManager">
|
||||
<option name="mySupportsUserInfoFilter" value="true" />
|
||||
</component>
|
||||
|
||||
Generated
+1
-1
@@ -2,7 +2,7 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/master.iml" filepath="$PROJECT_DIR$/.idea/master.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/angular.js.iml" filepath="$PROJECT_DIR$/.idea/angular.js.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
|
||||
Generated
+68
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CodeStyleSettingsManager">
|
||||
<option name="PER_PROJECT_SETTINGS">
|
||||
<value>
|
||||
<option name="USE_SAME_INDENTS" value="true" />
|
||||
<option name="OTHER_INDENT_OPTIONS">
|
||||
<value>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</value>
|
||||
</option>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="js">
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="jsp">
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="sass">
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="xml">
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="yml">
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
</value>
|
||||
</option>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="gen_docs" type="BashConfigurationType" factoryName="Bash">
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="INTERPRETER_PATH" value="/bin/bash" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs />
|
||||
<module name="angular.js" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/gen_docs.sh" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<RunnerSettings RunnerId="BashRunner" />
|
||||
<ConfigurationWrapper RunnerId="BashRunner" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="rake compile" type="BashConfigurationType" factoryName="Bash">
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="INTERPRETER_PATH" value="/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs />
|
||||
<module name="angular.js" />
|
||||
<option name="SCRIPT_NAME" value="/usr/bin/rake" />
|
||||
<option name="PARAMETERS" value="compile" />
|
||||
<RunnerSettings RunnerId="BashRunner" />
|
||||
<ConfigurationWrapper RunnerId="BashRunner" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
+1
-2
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
|
||||
@@ -10,6 +10,16 @@
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
|
||||
<triggers>auto,full,incremental,</triggers>
|
||||
<arguments>
|
||||
<dictionary>
|
||||
<key>LaunchConfigHandle</key>
|
||||
<value><project>/.externalToolBuilders/docs.launch</value>
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
|
||||
<triggers>auto,full,incremental,</triggers>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry excluding="test/" kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="src/test"/>
|
||||
<classpathentry excluding="docs-data.js|docs-scenario.js" kind="src" path="docs"/>
|
||||
<classpathentry excluding="test/" kind="src" path="test"/>
|
||||
<classpathentry kind="src" path="test/test"/>
|
||||
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
|
||||
<classpathentry kind="output" path=""/>
|
||||
</classpath>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#Mon Jan 24 10:31:47 PST 2011
|
||||
activeContentFilterList=*.makefile,makefile,*.Makefile,Makefile,Makefile.*,*.mk,MANIFEST.MF
|
||||
addNewLine=true
|
||||
convertActionOnSaave=AnyEdit.CnvrtTabToSpaces
|
||||
eclipse.preferences.version=1
|
||||
inActiveContentFilterList=
|
||||
javaTabWidthForJava=true
|
||||
org.eclipse.jdt.ui.editor.tab.width=2
|
||||
projectPropsEnabled=false
|
||||
removeTrailingSpaces=true
|
||||
replaceAllSpaces=false
|
||||
replaceAllTabs=false
|
||||
saveAndAddLine=true
|
||||
saveAndConvert=true
|
||||
saveAndTrim=true
|
||||
useModulo4Tabs=false
|
||||
+471
@@ -0,0 +1,471 @@
|
||||
<a name="0.9.14"><a/>
|
||||
# <angular/> 0.9.14 key-maker (2011-04-01) #
|
||||
|
||||
### Performance
|
||||
- [ng:repeat] grows (adds children) significantly faster. (commit 15ec78f5)
|
||||
- [$xhr.cache] optionally executes callbacks synchronously. (commit c06c5a36)
|
||||
- [ng:view] and [ng:include] use sync [$xhr.cache]
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed [$resource] encoding of query params. (commits e1d122a4, 78a0f410)
|
||||
|
||||
|
||||
### House cleaning
|
||||
- code cleanup
|
||||
- better minification (min is now 2.5% or almost 1kb smaller)
|
||||
- minor documentation fixes
|
||||
- JsTestDriver 1.3.2 upgrade with fixed coverage support
|
||||
|
||||
|
||||
|
||||
<a name="0.9.13"><a/>
|
||||
# <angular/> 0.9.13 curdling-stare (2011-03-13) #
|
||||
|
||||
### New Features
|
||||
- Added XSRF protection for the [$xhr] service. (commit c578f8c3)
|
||||
- Targeted auto-bootstrap — [ng:autobind] now takes an optional value which specifies an element id
|
||||
to be compiled instead of compiling the entire html document. (commit 9d5c5337)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed IE7 regression which prevented angular from bootstrapping in this browser.
|
||||
- Cookies which contain unescaped '=' are now visible via the [$cookies] service. (commit 26bad2bf)
|
||||
- [$xhr] service now executes "success" callback for all 2xx responses, not just 200.
|
||||
(commit 5343deb3)
|
||||
- Always remove the script tag after successful JSONP request. (commit 0084cb5c)
|
||||
- Removal of all `document.write` statements to make angular compabile with async script loaders.
|
||||
(commit 3224862a)
|
||||
|
||||
|
||||
### Breaking changes
|
||||
- The `post` parameter of [$browser.xhr][$browser] is now non-optional. Since everyone should be
|
||||
using the [$xhr] service instead of $browser.xhr, this should not break anyone. If you do use
|
||||
$browser.xhr then just add null for the post value argument where post was not passed in.
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="0.9.12"><a/>
|
||||
# <angular/> 0.9.12 thought-implanter (2011-03-03) #
|
||||
|
||||
### API
|
||||
- Added a delay parameter to the [$defer] service. (commit edbe9d8c)
|
||||
- Added `scope()` method to [angular.element][element] (jQuery) instances to retrieve a [scope]
|
||||
associated with a given DOM element. (commit 0a5c00ab)
|
||||
- Added inference of DI dependencies from function signature. This feature is experimental, check
|
||||
out [dependency injection][guide.di] docs. (commit 7d4aee31)
|
||||
|
||||
|
||||
### New Features
|
||||
- Angular now correctly recognizes and uses jQuery even if it was loaded after angular's script.
|
||||
More info at [angular.element][element]. (commit a004d487)
|
||||
- All built-in angular services are now lazy-loaded. (commit a070ff5a)
|
||||
- To make styling of custom html tags created via [widgets][widget] and [directives][directive]
|
||||
easier, all of these elements now contain a css class with name in form of
|
||||
`<namespace>-<directive/widget name>`, e.g. `<ng:include class="ng-include">`. (commit c7998f5f)
|
||||
- [$xhr] service now automatically detects and strips google-style JSON security prefix from http
|
||||
responses. (commit cd139f57)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- Rewrite of JQuery lite implementation for better supports operations on multiple nodes when
|
||||
matched by a selector and remove other bugs. (commit 00cc9eb3)
|
||||
- Corrected an issue where properties inherited from \_\_proto\_\_ show up in ng:repeat.
|
||||
(commit 9e67da42)
|
||||
- Fixed url encoding issue affecting [$resource] service. (commits e9ce2259 + 9e30baad)
|
||||
- Removed `$eval()` call from the [$cookies] factory function, which was causing duplicate
|
||||
instances of singleton services to be created. (commit 65585a2d)
|
||||
|
||||
|
||||
### Docs
|
||||
- New docs [contribution guidelines][contribute].
|
||||
- New [description of release artifacts][downloading].
|
||||
- Lots of improvements and other new content.
|
||||
|
||||
|
||||
### Breaking changes
|
||||
- Removed the `$init()` method that used to be called after compilation of a template. This should
|
||||
affect only fraction of angular apps because the api was primarily being used by low level widgets
|
||||
tests.
|
||||
|
||||
The old way of compiling the DOM element was angular.compile(element).$init(); The $init was there
|
||||
to allow the users to do any work to the scope before the view would be bound. This is a left over
|
||||
from not having proper MVC. The new recommended way to deal with initializing scope is to put it
|
||||
in the root constructor controller. To migrate simply remove the call to $init() and move any code
|
||||
you had before $init() to the root controller.
|
||||
|
||||
(commit 23b255a8)
|
||||
- Changed [angular.compile][compile] API from `angular.compile(element[, scope])` to
|
||||
`angular.compile(element)([scope], [cloneAttachFn])` (commits ef4bb28b + 945056b1)
|
||||
- Removed ng:watch directives since it encourages logic in the UI. (commit 87cbf9f5)
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="0.9.11"><a/>
|
||||
# <angular/> 0.9.11 snow-maker (2011-02-08) #
|
||||
|
||||
### Documentation
|
||||
- completed migration of docs from the wiki site to
|
||||
[http://docs.angularjs.org/](http://docs.angularjs.org/)
|
||||
- many, but by far not all, docs were updated, improved and cleaned up
|
||||
|
||||
### Features
|
||||
- [`$route`](http://docs.angularjs.org/#!angular.service.$route) service now supports these
|
||||
features:
|
||||
- route not found handling via `#otherwise()`
|
||||
- redirection support via `#when('/foo', {redirectTo: '/bar'})` (including param interpolation)
|
||||
- setting the parent scope for scopes created by the service via `#parent()`
|
||||
- reloading the current route via `#reload()`
|
||||
|
||||
### API
|
||||
- added `angular.element(...).scope()` method to retrieve scope for a given element.
|
||||
|
||||
### Bug Fixes
|
||||
- <option> value attribute gets clobbered when the element contains new line character(s).
|
||||
- <ng:view> widget now works when nested inside an <ng:include> widget
|
||||
- other various small fixes
|
||||
|
||||
### Breaking changes
|
||||
- mock [`$browser`](http://docs.angularjs.org/#!angular.mock.service.$browser) now throws an
|
||||
exception if the `flush()` method is called when there are no requests to be flushed. If you
|
||||
experience `No xhr requests to be flushed!` errors in your tests, it's because you called
|
||||
`$browser.xhr.flush()` unexpectedly. To make the error go away, either make sure your code makes a
|
||||
request via the `$xhr` service or remove all unneeded `flush()` calls.
|
||||
|
||||
|
||||
<a name="0.9.10"><a/>
|
||||
# <angular/> 0.9.10 flea-whisperer (2011-01-26) #
|
||||
|
||||
### Features
|
||||
- new [`ng:view`](http://docs.angularjs.org/#!angular.widget.ng:view) widget to simplify integration
|
||||
with the `$route` service
|
||||
- the content of all standard HTML widgets is now being processed
|
||||
(e.g. `<button>{{foo}}</button>` works now) (commit 1d7b9d56)
|
||||
- new [`$log`](http://docs.angularjs.org/#!angular.mock.service.$log) and
|
||||
[`$exceptionHandler`](http://docs.angularjs.org/#!angular.mock.service.$exceptionHandler) service
|
||||
mocks now part of `angular-mocks.js` (commit f5d08963)
|
||||
|
||||
### Bug Fixes
|
||||
- <select> (one/multiple) could not chose from a list of objects (commit 347be5ae)
|
||||
- null and other falsy values should not be rendered in the view (issue #242)
|
||||
|
||||
### Docs
|
||||
- rewrite of several major portions of angular.service.*, angular.Array.*, angular.Object.* docs
|
||||
- added support for [sitemap]((http://docs.angularjs.org/sitemap.xml) to make the docs indexable by
|
||||
search crawlers
|
||||
- transition of Developer Guide docs from the wiki into docs.angularjs.org
|
||||
- lots of improvements related to formatting of the content of docs.anguarjs.org
|
||||
|
||||
|
||||
<a name="0.9.9"><a/>
|
||||
# <angular/> 0.9.9 time-shift (2011-01-13) #
|
||||
|
||||
### Security
|
||||
- Added a just in case security check for JSON parsing. (commit 5f080193)
|
||||
- Completed security review with the Google Security Team.
|
||||
|
||||
### Performance
|
||||
- $location and $cookies services are now lazily initialized to avoid the polling overhead when
|
||||
not needed.
|
||||
- $location service now listens for `onhashchange` events (if supported by browser) instead of
|
||||
constant polling. (commit 16086aa3)
|
||||
- input widgets known listens on keydown events instead of keyup which improves perceived
|
||||
performance (commit 47c454a3)
|
||||
- angular boots significantly sooner by listening for DOMContentLoaded event instead of
|
||||
window.load when supported by browser (commit c79aba92)
|
||||
- new service $updateView which may be used in favor of $root.$eval() to run a complete eval on
|
||||
the entire document. This service bulks and throttles DOM updates to improve performance.
|
||||
(commit 47c454a3)
|
||||
|
||||
### Docs
|
||||
- Major improvements to the doc parser (commit 4f22d686)
|
||||
- Docs now offline enabled (all dependencies are bundled in the tarball) (commit 4f5d5029)
|
||||
- Added support for navigating the docs app with keyboard shortcuts (tab and ctrl+alt+s)
|
||||
|
||||
### Bugfixes
|
||||
- `angular.Object.equals` now properly handless comparing an object with a null (commit b0be87f6)
|
||||
- Several issues were addressed in the `$location` service (commit 23875cb3)
|
||||
- angular.filter.date now properly handles some corner-cases (issue #159 - fix contributed by Vojta)
|
||||
|
||||
### Breaking changes
|
||||
- API for accessing registered services — `scope.$inject` — was renamed to
|
||||
[`scope.$service`](http://docs.angularjs.org/#!angular.scope.$service). (commit b2631f61)
|
||||
|
||||
- Support for `eager-published` services was removed. This change was done to make explicit
|
||||
dependency declaration always required in order to allow making relatively expensive services
|
||||
lazily initialized (e.g. $cookie, $location), as well as remove 'magic' and reduce unnecessary
|
||||
scope namespace pollution. (commit 3ea5941f)
|
||||
|
||||
Complete list of affected services:
|
||||
|
||||
- $location
|
||||
- $route
|
||||
- $cookies
|
||||
- $window
|
||||
- $document
|
||||
- $exceptionHandler
|
||||
- $invalidWidgets
|
||||
|
||||
To temporarily preserve the 'eager-published' status for these services, you may use `ng:init`
|
||||
(e.g. `ng:init="$location = $service('$location'), ...`) in the view or more correctly create
|
||||
a service like this:
|
||||
|
||||
angular.service('published-svc-shim', function($location, $route, $cookies, $window,
|
||||
$document, $exceptionHandler, $invalidWidgets) {
|
||||
this.$location = $location;
|
||||
this.$route = $route;
|
||||
this.$cookies = $cookies;
|
||||
this.$window = $window;
|
||||
this.$document = $document;
|
||||
this.$exceptionHandler = $exceptionHandler;
|
||||
this.$invalidWidgets = $invalidWidgets;
|
||||
}, {$inject: ['$location', '$route', '$cookies', '$window', '$document', '$exceptionHandler',
|
||||
'$invalidWidgets'],
|
||||
$eager: true});
|
||||
|
||||
- In the light of the `eager-published` change, to complete the cleanup we renamed `$creation`
|
||||
property of services to `$eager` with its value being a boolean.
|
||||
To transition, please rename all `$creation: 'eager'` declarations to `$eager: true`.
|
||||
(commit 1430c6d6)
|
||||
|
||||
- `angular.foreach` was renamed to `angular.forEach` to make the api consistent. (commit 0a6cf70d)
|
||||
|
||||
- The `toString` method of the `angular.service.$location` service was removed. (commit 23875cb3)
|
||||
|
||||
|
||||
<a name="0.9.8"><a/>
|
||||
# <angular/> 0.9.8 astral-projection (2010-12-23) #
|
||||
|
||||
### Docs/Getting started
|
||||
- angular-seed project to get you hacking on an angular apps quickly
|
||||
https://github.com/angular/angular-seed
|
||||
|
||||
### Performance
|
||||
- Delegate JSON parsing to native parser (JSON.parse) if available
|
||||
|
||||
### Bug Fixes
|
||||
- Ignore input widgets which have no name (issue #153)
|
||||
|
||||
|
||||
<a name="0.9.7"><a/>
|
||||
# <angular/> 0.9.7 sonic-scream (2010-12-10) #
|
||||
|
||||
### Bug Fixes
|
||||
- $defer service should always call $eval on the root scope after a callback runs (issue #189)
|
||||
- fix for failed assignments of form obj[0].name=value (issue #169)
|
||||
- significant parser improvements that resulted in lower memory usage
|
||||
(commit 23fc73081feb640164615930b36ef185c23a3526)
|
||||
|
||||
### Docs
|
||||
- small docs improvements (mainly docs for the $resource service)
|
||||
|
||||
### Breaking changes
|
||||
- Angular expressions in the view used to support regular expressions. This feature was rarely
|
||||
used and added unnecessary complexity. It not a good idea to have regexps in the view anyway,
|
||||
so we removed this support. If you had any regexp in your views, you will have to move them to
|
||||
your controllers. (commit e5e69d9b90850eb653883f52c76e28dd870ee067)
|
||||
|
||||
|
||||
<a name="0.9.6"><a/>
|
||||
# <angular/> 0.9.6 night-vision (2010-12-06) #
|
||||
|
||||
### Security
|
||||
- several improvements in the HTML sanitizer code to prevent code execution via `href`s and other
|
||||
attributes.
|
||||
Commits:
|
||||
- 41d5938883a3d06ffe8a88a51efd8d1896f7d747
|
||||
- 2bbced212e2ee93948c45360fee00b2e3f960392
|
||||
|
||||
### Docs
|
||||
- set up http://docs.angularjs.org domain, the docs for the latest release will from now on be
|
||||
deployed here.
|
||||
- docs app UI polishing with dual scrolling and other improvements
|
||||
|
||||
### Bug Fixes
|
||||
- `select` widget now behaves correctly when it's `option` items are created via `ng:repeat`
|
||||
(issue #170)
|
||||
- fix for async xhr cache issue #152 by adding `$browser.defer` and `$defer` service
|
||||
|
||||
### Breaking Changes
|
||||
- Fix for issue #152 might break some tests that were relying on the incorrect behavior. The
|
||||
breakage will usually affect code that tests resources, xhr or services/widgets build on top of
|
||||
these. All that is typically needed to resolve the issue is adding a call to
|
||||
`$browser.defer.flush()` in your test just before the point where you expect all cached
|
||||
resource/xhr requests to return any results. Please see 011fa39c2a0b5da843395b538fc4e52e5ade8287
|
||||
for more info.
|
||||
- The HTML sanitizer is slightly more strinct now. Please see info in the "Security" section above.
|
||||
|
||||
|
||||
<a name="0.9.5"><a/>
|
||||
# <angular/> 0.9.5 turkey-blast (2010-11-25) #
|
||||
|
||||
### Docs
|
||||
- 99% of the content from the angular wiki is now in the docs
|
||||
|
||||
### Api
|
||||
- added `angular.Array.limitTo` to make it easy to select first or last few items of an array
|
||||
|
||||
|
||||
<a name="0.9.4"><a/>
|
||||
# <angular/> 0.9.4 total-recall (2010-11-18) #
|
||||
|
||||
### Docs
|
||||
- searchable docs
|
||||
- UI improvements
|
||||
- we now have ~85% of the wiki docs migrated to ng docs
|
||||
- some but not all docs were updated along the way
|
||||
|
||||
|
||||
### Api
|
||||
- ng:include now supports `onload` attribute (commit cc749760)
|
||||
|
||||
### Misc
|
||||
- Better error handling - compilation exception now contain stack trace (commit b2d63ac4)
|
||||
|
||||
|
||||
<a name="0.9.3"><a/>
|
||||
# <angular/> 0.9.3 cold-resistance (2010-11-10) #
|
||||
|
||||
### Docs
|
||||
- prettier docs app with syntax highlighting for examples, etc
|
||||
- added documentation, examples and scenario tests for many more apis including:
|
||||
- all directives
|
||||
- all formatters
|
||||
- all validators
|
||||
- some widgets
|
||||
|
||||
### Api
|
||||
- date filter now accepts strings that angular.String.toDate can convert to Date objects
|
||||
- angular.String.toDate supports ISO8061 formated strings with all time fractions being optional
|
||||
- ng:repeat now exposes $position with values set to 'first', 'middle' or 'last'
|
||||
- ng:switch now supports ng:switch-default as fallback switch option
|
||||
|
||||
### Breaking changes
|
||||
- we now support ISO 8601 extended format datetime strings (YYYY-MM-DDTHH:mm:ss.SSSZ) as defined
|
||||
in EcmaScript 5 throughout angular. This means that the following apis switched from
|
||||
YYYY-MM-DDTHH:mm:ssZ to YYYY-MM-DDTHH:mm:ss.SSSZ (note the added millis) when representing dates:
|
||||
- angular.Date.toString
|
||||
- angular.String.fromDate
|
||||
- JSON serialization and deserialization (used by json filter, $xhr and $resource)
|
||||
- removed SSN validator. It's unlikely that most people will need it and if they do, it can be added
|
||||
simple RegExp validator.
|
||||
|
||||
|
||||
<a name="0.9.2"><a/>
|
||||
# <angular/> 0.9.2 faunal-mimicry (2010-11-03) #
|
||||
|
||||
### Docs
|
||||
- created documentation framework based on jsdoc syntax (commit 659af29a)
|
||||
- jsdoc parser
|
||||
- template generator
|
||||
- json generator
|
||||
- angular doc viewer app
|
||||
- scenario runner for all example code
|
||||
- documentation for all angular filters (commits 1fe7e3a1 & 1ba8c2a33)
|
||||
- docs
|
||||
- example code
|
||||
- scenario tests for example code
|
||||
|
||||
### Testability
|
||||
#### Scenario Runner
|
||||
- binding DSL in Scenario can now match bindings without specifying filters
|
||||
- dsl statements now accept a label argument to make test output more readable (issue #94)
|
||||
- dsl element() statement now implements most of the jQuery API (issue #106)
|
||||
- new browser() dsl statement for getting info about the emulated browser running the app
|
||||
(issue #109)
|
||||
- scenario runner is now compatible with IE8 (issue #93)
|
||||
- scenarior runner checks if URL would return a non-success status code (issue #100)
|
||||
- binding() DSL now accepts regular expressions
|
||||
- new textarea() scenario runner DSL for entering text into textareas
|
||||
|
||||
### Misc
|
||||
- lots of small bugfixes
|
||||
|
||||
### Breaking changes
|
||||
#### Scenario Runner
|
||||
- navigating to about:blank is no longer supported. It results in a sandbox error
|
||||
- navigateTo() is now browser().navigateTo(). Old code must be updated
|
||||
- file:// URLs are no longer supported for running a scenario. You must use a web server that
|
||||
implements HEAD
|
||||
|
||||
|
||||
<a name="0.9.1"><a/>
|
||||
# <angular/> 0.9.1 repulsion-field (2010-10-26) #
|
||||
|
||||
### Security
|
||||
- added html sanitizer to fix the last few known security issues (issues #33 and #34)
|
||||
|
||||
### API
|
||||
- new ng:submit directive for creating onSubmit handlers on forms (issue #76)
|
||||
- the date filter now accepts milliseconds as well as date strings (issue #78)
|
||||
- the html filter now supports 'unsafe' option to bypass html sanitization
|
||||
|
||||
### Testability
|
||||
- lots of improvements related to the scenario runner (commit 40d7e66f)
|
||||
|
||||
### Demo
|
||||
- added a new demo application: Personal Log (src example/personalLog)
|
||||
|
||||
### Chores
|
||||
- lots of fixes to get all tests pass on IE
|
||||
- added TzDate type to allow us to create timezone idependent tests (issue #88)
|
||||
|
||||
### Breaking changes
|
||||
- $cookieStore service is not globally published any more, if you use it, you must request it via
|
||||
$inject as any other non-global service
|
||||
- html filter now sanitizes html content for XSS attacks which may result in different behavior
|
||||
|
||||
|
||||
<a name="0.9.0"><a/>
|
||||
# <angular/> 0.9.0 dragon-breath (2010-10-20) #
|
||||
|
||||
### Security
|
||||
- angular.fromJson not safer (issue #57)
|
||||
- readString consumes invalid escapes (issue #56)
|
||||
- use new Function instead of eval (issue #52)
|
||||
|
||||
### Speed
|
||||
- css cleanup + inline all css and images in the main js (issue #64)
|
||||
|
||||
### Testability
|
||||
- initial version of the built-in end-to-end scenario runner (issues #50, #67, #70)
|
||||
|
||||
### API
|
||||
- allow ng:controller nesting (issue #39)
|
||||
- new built-in date format filter (issue #45)
|
||||
- $location needs method you call on updates (issue #32)
|
||||
|
||||
|
||||
### Chores
|
||||
- release versioning + file renaming (issue #69)
|
||||
|
||||
### Breaking changes
|
||||
- $location.parse was replaced with $location.update
|
||||
- all css and img files were inlined into the main js file, to support IE7 and older app must host
|
||||
angular-ie-compat.js file
|
||||
|
||||
### Big Thanks to Our Community Contributors
|
||||
- Vojta Jina
|
||||
|
||||
|
||||
|
||||
|
||||
[scope]: http://docs.angularjs.org/#!angular.scope
|
||||
[compile]: http://docs.angularjs.org/#!angular.compile
|
||||
[element]: http://docs.angularjs.org/#!angular.element
|
||||
[widget]: http://docs.angularjs.org/#!angular.widget
|
||||
[ng:repeat]: http://docs.angularjs.org/#!angular.widget.@ng:repeat
|
||||
[ng:view]: http://docs.angularjs.org/#!angular.widget.ng:view
|
||||
[ng:include]: http://docs.angularjs.org/#!angular.widget.ng:include
|
||||
[$defer]: http://docs.angularjs.org/#!angular.service.$defer
|
||||
[$cookies]: http://docs.angularjs.org/#!angular.service.$cookies
|
||||
[$xhr]: http://docs.angularjs.org/#!angular.service.$xhr
|
||||
[$xhr.cache]: http://docs.angularjs.org/#!angular.service.$xhr.cache
|
||||
[$resource]: http://docs.angularjs.org/#!angular.service.$resource
|
||||
[directive]: http://docs.angularjs.org/#!angular.directive
|
||||
[ng:autobind]: http://docs.angularjs.org/#!angular.directive.ng:autobind
|
||||
[guide.di]: http://docs.angularjs.org/#!guide.di
|
||||
[downloading]: http://docs.angularjs.org/#!downloading
|
||||
[contribute]: http://docs.angularjs.org/#!contribute
|
||||
@@ -1,3 +1,4 @@
|
||||
require 'yaml'
|
||||
include FileUtils
|
||||
|
||||
ANGULAR = [
|
||||
@@ -6,15 +7,32 @@ ANGULAR = [
|
||||
'src/Compiler.js',
|
||||
'src/Scope.js',
|
||||
'src/Injector.js',
|
||||
'src/Parser.js',
|
||||
'src/parser.js',
|
||||
'src/Resource.js',
|
||||
'src/Browser.js',
|
||||
'src/sanitizer.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
'src/filters.js',
|
||||
'src/formatters.js',
|
||||
'src/validators.js',
|
||||
'src/services.js',
|
||||
'src/service/cookieStore.js',
|
||||
'src/service/cookies.js',
|
||||
'src/service/defer.js',
|
||||
'src/service/document.js',
|
||||
'src/service/exceptionHandler.js',
|
||||
'src/service/hover.js',
|
||||
'src/service/invalidWidgets.js',
|
||||
'src/service/location.js',
|
||||
'src/service/log.js',
|
||||
'src/service/resource.js',
|
||||
'src/service/route.js',
|
||||
'src/service/updateView.js',
|
||||
'src/service/window.js',
|
||||
'src/service/xhr.bulk.js',
|
||||
'src/service/xhr.cache.js',
|
||||
'src/service/xhr.error.js',
|
||||
'src/service/xhr.js',
|
||||
'src/directives.js',
|
||||
'src/markups.js',
|
||||
'src/widgets.js',
|
||||
@@ -26,12 +44,16 @@ ANGULAR_SCENARIO = [
|
||||
'src/scenario/Application.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Future.js',
|
||||
'src/scenario/HtmlUI.js',
|
||||
'src/scenario/ObjectModel.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Runner.js',
|
||||
'src/scenario/SpecRunner.js',
|
||||
'src/scenario/dsl.js',
|
||||
'src/scenario/matchers.js',
|
||||
'src/scenario/output/Html.js',
|
||||
'src/scenario/output/Json.js',
|
||||
'src/scenario/output/Xml.js',
|
||||
'src/scenario/output/Object.js',
|
||||
]
|
||||
|
||||
BUILD_DIR = 'build'
|
||||
@@ -39,6 +61,12 @@ BUILD_DIR = 'build'
|
||||
task :default => [:compile, :test]
|
||||
|
||||
|
||||
desc 'Init the build workspace'
|
||||
task :init do
|
||||
FileUtils.mkdir(BUILD_DIR) unless File.directory?(BUILD_DIR)
|
||||
end
|
||||
|
||||
|
||||
desc 'Clean Generated Files'
|
||||
task :clean do
|
||||
FileUtils.rm_r(BUILD_DIR, :force => true)
|
||||
@@ -47,7 +75,7 @@ end
|
||||
|
||||
|
||||
desc 'Compile Scenario'
|
||||
task :compile_scenario do
|
||||
task :compile_scenario => :init do
|
||||
|
||||
deps = [
|
||||
'lib/jquery/jquery-1.4.2.js',
|
||||
@@ -67,11 +95,8 @@ task :compile_scenario do
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
desc 'Generate IE css js patch'
|
||||
task :generate_ie_compat do
|
||||
task :generate_ie_compat => :init do
|
||||
css = File.open('css/angular.css', 'r') {|f| f.read }
|
||||
|
||||
# finds all css rules that contain backround images and extracts the rule name(s), content type of
|
||||
@@ -127,7 +152,7 @@ end
|
||||
|
||||
|
||||
desc 'Compile JavaScript'
|
||||
task :compile => [:compile_scenario, :generate_ie_compat] do
|
||||
task :compile => [:init, :compile_scenario, :generate_ie_compat] do
|
||||
|
||||
deps = [
|
||||
'src/angular.prefix',
|
||||
@@ -141,15 +166,21 @@ task :compile => [:compile_scenario, :generate_ie_compat] do
|
||||
f.write(gen_css('css/angular.css', true))
|
||||
end
|
||||
|
||||
%x(java -jar lib/compiler-closure/compiler.jar \
|
||||
%x(java -jar lib/closure-compiler/compiler.jar \
|
||||
--compilation_level SIMPLE_OPTIMIZATIONS \
|
||||
--js #{path_to('angular.js')} \
|
||||
--js_output_file #{path_to('angular.min.js')})
|
||||
end
|
||||
|
||||
|
||||
desc 'Generate docs'
|
||||
task :docs do
|
||||
`node docs/src/gen-docs.js`
|
||||
end
|
||||
|
||||
|
||||
desc 'Create angular distribution'
|
||||
task :package => :compile do
|
||||
task :package => [:clean, :compile, :docs] do
|
||||
v = YAML::load( File.open( 'version.yaml' ) )['version']
|
||||
match = v.match(/^([^-]*)(-snapshot)?$/)
|
||||
version = match[1] + (match[2] ? ('-' + %x(git rev-parse HEAD)[0..7]) : '')
|
||||
@@ -160,7 +191,7 @@ task :package => :compile do
|
||||
FileUtils.rm_r(path_to('pkg'), :force => true)
|
||||
FileUtils.mkdir_p(pkg_dir)
|
||||
|
||||
['test/angular-mocks.js',
|
||||
['src/angular-mocks.js',
|
||||
path_to('angular.js'),
|
||||
path_to('angular.min.js'),
|
||||
path_to('angular-ie-compat.js'),
|
||||
@@ -170,6 +201,20 @@ task :package => :compile do
|
||||
FileUtils.cp(src, pkg_dir + '/' + dest)
|
||||
end
|
||||
|
||||
FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{version}"
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{version}/index.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{version}.min.js")
|
||||
end
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{version}/docs-scenario.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.rewind
|
||||
f.write text.sub('angular-scenario.js', "angular-scenario-#{version}.js")
|
||||
end
|
||||
|
||||
%x(tar -czf #{path_to(tarball)} -C #{path_to('pkg')} .)
|
||||
|
||||
puts "Package created: #{path_to(tarball)}"
|
||||
@@ -240,7 +285,7 @@ def gen_css(cssFile, minify = false)
|
||||
css.gsub! /'/, "\\\\'"
|
||||
css.gsub! /\n/, "\\n"
|
||||
|
||||
return %Q{document.write('<style type="text/css">#{css}</style>');}
|
||||
return %Q{angular.element(document).find('head').append('<style type="text/css">#{css}</style>');}
|
||||
end
|
||||
|
||||
|
||||
@@ -248,5 +293,5 @@ end
|
||||
# returns path to the file in the build directory
|
||||
#
|
||||
def path_to(filename)
|
||||
return File.join(BUILD_DIR, filename)
|
||||
return File.join(BUILD_DIR, *filename)
|
||||
end
|
||||
|
||||
@@ -8,6 +8,15 @@ body {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#system-error {
|
||||
font-size: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#json, #xml {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#header {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
@@ -32,7 +41,7 @@ body {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#frame h2,
|
||||
#application h2,
|
||||
#specs h2 {
|
||||
margin: 0;
|
||||
padding: 0.5em;
|
||||
@@ -45,26 +54,26 @@ body {
|
||||
}
|
||||
|
||||
#header,
|
||||
#frame,
|
||||
#application,
|
||||
.test-info,
|
||||
.test-actions li {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#frame {
|
||||
#application {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#frame iframe {
|
||||
#application iframe {
|
||||
width: 100%;
|
||||
height: 758px;
|
||||
}
|
||||
|
||||
#frame .popout {
|
||||
#application .popout {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#frame iframe {
|
||||
#application iframe {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -154,6 +163,10 @@ body {
|
||||
margin-left: 6em;
|
||||
}
|
||||
|
||||
.test-describe {
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.test-describe .test-describe {
|
||||
margin: 5px 5px 10px 2em;
|
||||
}
|
||||
@@ -178,11 +191,11 @@ body {
|
||||
}
|
||||
|
||||
#specs h2,
|
||||
#frame h2 {
|
||||
#application h2 {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
#frame {
|
||||
#application {
|
||||
border: 1px solid #BABAD1;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
border: 2px solid #FF0000;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-size: smaller;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.ng-validation-error {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.attrMarkup
|
||||
|
||||
@description
|
||||
Attribute markup extends the angular compiler in a very similar way as {@link angular.markup} except
|
||||
that it allows you to modify the state of the attribute text rather then the contest of a node.
|
||||
|
||||
<pre>
|
||||
angular.attrMarkup('extraClass', function(attrValue, attrName, element){
|
||||
if (attrName == 'additional-class') {
|
||||
element.addClass(attrValue);
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
@@ -0,0 +1,53 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.directive
|
||||
@namespace Namespace for all directives.
|
||||
|
||||
@description
|
||||
A directive is an HTML attribute that you can use in an existing HTML element type or in a
|
||||
DOM element type that you create as {@link angular.widget}, to modify that element's
|
||||
properties. You can use any number of directives per element.
|
||||
|
||||
For example, you can add the ng:bind directive as an attribute of an HTML span element, as in
|
||||
`<span ng:bind="1+2"></span>`. How does this work? The compiler passes the attribute value
|
||||
`1+2` to the ng:bind extension, which in turn tells the {@link angular.scope} to watch that
|
||||
expression and report changes. On any change it sets the span text to the expression value.
|
||||
|
||||
Here's how to define {@link angular.directive.ng:bind ng:bind}:
|
||||
<pre>
|
||||
angular.directive('ng:bind', function(expression, compiledElement) {
|
||||
var compiler = this;
|
||||
return function(linkElement) {
|
||||
var currentScope = this;
|
||||
currentScope.$watch(expression, function(value) {
|
||||
linkElement.text(value);
|
||||
});
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
# Directive vs. Attribute Widget
|
||||
Both [attribute widgets](#!angular.widget) and directives can compile a DOM element
|
||||
attribute. So why have two different ways to do the same thing? The answer is that order
|
||||
matters, but we have no control over the order in which attributes are read. To solve this
|
||||
we apply attribute widget before the directive.
|
||||
|
||||
For example, consider this piece of HTML, which uses the directives `ng:repeat`, `ng:init`,
|
||||
and `ng:bind`:
|
||||
<pre>
|
||||
<ul ng:init="people=['mike', 'mary']">
|
||||
<li ng:repeat="person in people" ng:init="a=a+1" ng:bind="person"></li>
|
||||
</ul>
|
||||
</pre>
|
||||
|
||||
Notice that the order of execution matters here. We need to execute
|
||||
{@link angular.directive.ng:repeat ng:repeat} before we run the
|
||||
{@link angular.directive.ng:init ng:init} and `ng:bind` on the `<li/>;`. This is because we
|
||||
want to run the `ng:init="a=a+1` and `ng:bind="person"` once for each person in people. We
|
||||
could not have used directive to create this template because attributes are read in an
|
||||
unspecified order and there is no way of guaranteeing that the repeater attribute would
|
||||
execute first. Using the `ng:repeat` attribute directive ensures that we can transform the
|
||||
DOM element into a template.
|
||||
|
||||
Widgets run before directives. Widgets may manipulate the DOM whereas directives are not
|
||||
expected to do so, and so they run last.
|
||||
@@ -0,0 +1,49 @@
|
||||
@workInProgress
|
||||
@ngdoc function
|
||||
@name angular.element
|
||||
@function
|
||||
|
||||
@description
|
||||
Wraps a raw DOM element or HTML string as [jQuery](http://jquery.com) element.
|
||||
`angular.element` is either an alias for [jQuery](http://api.jquery.com/jQuery/) function if
|
||||
jQuery is loaded or a function that wraps the element or string in angular's jQuery lite
|
||||
implementation.
|
||||
|
||||
Real jQuery always takes precedence (as long as it was loaded before `DOMContentEvent`)
|
||||
|
||||
Angular's jQuery lite implementation is a tiny API-compatible subset of jQuery which allows
|
||||
angular to manipulate DOM. The jQuery lite implements only a subset of jQuery api, with the
|
||||
focus on the most commonly needed functionality and minimal footprint. For this reason only a
|
||||
limited number of jQuery methods, arguments and invocation styles are supported.
|
||||
|
||||
NOTE: All element references in angular are always wrapped with jQuery (lite) and are never
|
||||
raw DOM references.
|
||||
|
||||
## Angular's jQuery lite implements these functions:
|
||||
|
||||
- [addClass()](http://api.jquery.com/addClass/)
|
||||
- [after()](http://api.jquery.com/after/)
|
||||
- [append()](http://api.jquery.com/append/)
|
||||
- [attr()](http://api.jquery.com/attr/)
|
||||
- [bind()](http://api.jquery.com/bind/)
|
||||
- [children()](http://api.jquery.com/children/)
|
||||
- [clone()](http://api.jquery.com/clone/)
|
||||
- [css()](http://api.jquery.com/css/)
|
||||
- [data()](http://api.jquery.com/data/)
|
||||
- [hasClass()](http://api.jquery.com/hasClass/)
|
||||
- [parent()](http://api.jquery.com/parent/)
|
||||
- [remove()](http://api.jquery.com/remove/)
|
||||
- [removeAttr()](http://api.jquery.com/removeAttr/)
|
||||
- [removeClass()](http://api.jquery.com/removeClass/)
|
||||
- [removeData()](http://api.jquery.com/removeData/)
|
||||
- [replaceWith()](http://api.jquery.com/replaceWith/)
|
||||
- [text()](http://api.jquery.com/text/)
|
||||
- [trigger()](http://api.jquery.com/trigger/)
|
||||
|
||||
## Additionally these methods extend the jQuery and are available in both jQuery and jQuery lite
|
||||
version:
|
||||
|
||||
- `scope()` - retrieves the current angular scope of the element.
|
||||
|
||||
@param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
|
||||
@returns {Object} jQuery object.
|
||||
@@ -0,0 +1,87 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.filter
|
||||
@namespace Namespace for all filters.
|
||||
@description
|
||||
# Overview
|
||||
Filters are a standard way to format your data for display to the user. For example, you
|
||||
might have the number 1234.5678 and would like to display it as US currency: $1,234.57.
|
||||
Filters allow you to do just that. In addition to transforming the data, filters also modify
|
||||
the DOM. This allows the filters to for example apply css styles to the filtered output if
|
||||
certain conditions were met.
|
||||
|
||||
|
||||
# Standard Filters
|
||||
|
||||
The Angular framework provides a standard set of filters for common operations, including:
|
||||
{@link angular.filter.currency currency}, {@link angular.filter.json json},
|
||||
{@link angular.filter.number number}, and {@link angular.filter.html html}. You can also add
|
||||
your own filters.
|
||||
|
||||
|
||||
# Syntax
|
||||
|
||||
Filters can be part of any {@link angular.scope} evaluation but are typically used with
|
||||
{{bindings}}. Filters typically transform the data to a new data type, formating the data in
|
||||
the process. Filters can be chained and take optional arguments. Here are few examples:
|
||||
|
||||
* No filter: {{1234.5678}} => 1234.5678
|
||||
* Number filter: {{1234.5678|number}} => 1,234.57. Notice the “,” and rounding to two
|
||||
significant digits.
|
||||
* Filter with arguments: {{1234.5678|number:5}} => 1,234.56780. Filters can take optional
|
||||
arguments, separated by colons in a binding. To number, the argument “5” requests 5 digits
|
||||
to the right of the decimal point.
|
||||
|
||||
|
||||
# Writing your own Filters
|
||||
|
||||
Writing your own filter is very easy: just define a JavaScript function on `angular.filter`.
|
||||
The framework passes in the input value as the first argument to your function. Any filter
|
||||
arguments are passed in as additional function arguments.
|
||||
|
||||
You can use these variables in the function:
|
||||
|
||||
* `this` — The current scope.
|
||||
* `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
|
||||
the DOM in addition to transforming the input.
|
||||
|
||||
|
||||
@example
|
||||
The following example filter reverses a text string. In addition, it conditionally makes the
|
||||
text upper-case (to demonstrate optional arguments) and assigns color (to demonstrate DOM
|
||||
modification).
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script type="text/javascript">
|
||||
angular.filter('reverse', function(input, uppercase, color) {
|
||||
var out = "";
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
out = input.charAt(i) + out;
|
||||
}
|
||||
if (uppercase) {
|
||||
out = out.toUpperCase();
|
||||
}
|
||||
if (color) {
|
||||
this.$element.css('color', color);
|
||||
}
|
||||
return out;
|
||||
});
|
||||
</script>
|
||||
|
||||
<input name="text" type="text" value="hello" /><br>
|
||||
No filter: {{text}}<br>
|
||||
Reverse: {{text|reverse}}<br>
|
||||
Reverse + uppercase: {{text|reverse:true}}<br>
|
||||
Reverse + uppercase + blue: {{text|reverse:true:"blue"}}
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should reverse text', function(){
|
||||
expect(binding('text|reverse')).toEqual('olleh');
|
||||
input('text').enter('ABC');
|
||||
expect(binding('text|reverse')).toEqual('CBA');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.formatter
|
||||
@namespace Namespace for all formats.
|
||||
@description
|
||||
# Overview
|
||||
The formatters are responsible for translating user readable text in an input widget to a
|
||||
data model stored in an application.
|
||||
|
||||
# Writting your own Formatter
|
||||
Writing your own formatter is easy. Just register a pair of JavaScript functions with
|
||||
`angular.formatter`. One function for parsing user input text to the stored form,
|
||||
and one for formatting the stored data to user-visible text.
|
||||
|
||||
Here is an example of a "reverse" formatter: The data is stored in uppercase and in
|
||||
reverse, while it is displayed in lower case and non-reversed. User edits are
|
||||
automatically parsed into the internal form and data changes are automatically
|
||||
formatted to the viewed form.
|
||||
|
||||
<pre>
|
||||
function reverse(text) {
|
||||
var reversed = [];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
reversed.unshift(text.charAt(i));
|
||||
}
|
||||
return reversed.join('');
|
||||
}
|
||||
|
||||
angular.formatter('reverse', {
|
||||
parse: function(value){
|
||||
return reverse(value||'').toUpperCase();
|
||||
},
|
||||
format: function(value){
|
||||
return reverse(value||'').toLowerCase();
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
@example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script type="text/javascript">
|
||||
function reverse(text) {
|
||||
var reversed = [];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
reversed.unshift(text.charAt(i));
|
||||
}
|
||||
return reversed.join('');
|
||||
}
|
||||
|
||||
angular.formatter('reverse', {
|
||||
parse: function(value){
|
||||
return reverse(value||'').toUpperCase();
|
||||
},
|
||||
format: function(value){
|
||||
return reverse(value||'').toLowerCase();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Formatted:
|
||||
<input type="text" name="data" value="angular" ng:format="reverse"/>
|
||||
<br/>
|
||||
|
||||
Stored:
|
||||
<input type="text" name="data"/><br/>
|
||||
<pre>{{data}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should store reverse', function(){
|
||||
expect(element('.doc-example-live input:first').val()).toEqual('angular');
|
||||
expect(element('.doc-example-live input:last').val()).toEqual('RALUGNA');
|
||||
|
||||
this.addFutureAction('change to XYZ', function($window, $document, done){
|
||||
$document.elements('.doc-example-live input:last').val('XYZ').trigger('change');
|
||||
done();
|
||||
});
|
||||
expect(element('.doc-example-live input:first').val()).toEqual('zyx');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.markup
|
||||
|
||||
@description
|
||||
#Overview
|
||||
Markups allow the angular compiler to transform content of DOM elements or portions of this content
|
||||
into other text or DOM elements for further compilation. Markup extensions do not themselves produce
|
||||
linking functions. Think of markup as a way to produce shorthand for a {@link angular.widget widget}
|
||||
or a {@link angular.directive directive}.
|
||||
|
||||
#`{{}}` (double curly) built-in markup
|
||||
`{{}}` markup is a built-in markup, which translates the enclosed expression into an
|
||||
{@link angular.directive.ng:bind ng:bind} directive. It simply transforms
|
||||
|
||||
<pre>
|
||||
{{expression}}
|
||||
</pre>
|
||||
|
||||
to:
|
||||
|
||||
<pre>
|
||||
<span ng:bind="expression"></span>
|
||||
</pre>
|
||||
|
||||
For example `{{1+2}}` is easier to write and understand than `<span ng:bind="1+2"></span>`. The
|
||||
expanded elements are then {@link guide.compiler compiled} normally.
|
||||
|
||||
# Custom markup
|
||||
Let's say you want to define this shorthand for a horizontal rule: `---` for `<hr/>`.
|
||||
|
||||
In other words, this HTML:
|
||||
<pre>
|
||||
header
|
||||
---
|
||||
footer
|
||||
</pre>
|
||||
|
||||
should translate to:
|
||||
<pre>
|
||||
header
|
||||
<hr/>
|
||||
footer
|
||||
</pre>
|
||||
|
||||
Here's how the angular compiler could be extended to achieve this:
|
||||
<pre>
|
||||
angular.markup('---', function(text, textNode, parentElement) {
|
||||
var compiler = this;
|
||||
var index = text.indexOf('---');
|
||||
if (index > -1) {
|
||||
var before = compiler.text(text.substring(0, index));
|
||||
var hr = compiler.element('hr');
|
||||
var after = compiler.text(text.substring(index + 3));
|
||||
textNode.after(after);
|
||||
textNode.after(hr);
|
||||
textNode.after(before);
|
||||
textNode.remove();
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
Unlike {@link angular.widget widgets} and {@link angular.directive directives}, in which the
|
||||
compiler matches the name of handler function to a DOM element or attribute name, for markup the
|
||||
compiler calls every markup handler for every text node, giving the handler a chance to transform
|
||||
the text. The markup handler needs to find all the matches in the text.
|
||||
@@ -0,0 +1,4 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular
|
||||
@namespace The exported angular namespace.
|
||||
@@ -0,0 +1,175 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.service
|
||||
|
||||
@description
|
||||
# Overview
|
||||
Services are substituable objects, which are wired together using dependency injection (DI).
|
||||
Each service could have dependencies (other services), which are passed in constructor.
|
||||
Because JS is dynamicaly typed language, dependency injection can not use static types
|
||||
to identify these dependencies, so each service must explicitely define its dependencies.
|
||||
This is done by `$inject` property.
|
||||
|
||||
|
||||
# Built-in services
|
||||
angular provides a set of services for common operations. These services can be overriden by custom
|
||||
services if needed.
|
||||
|
||||
Like other core angular variables and identifiers, the built-in services always start with `$`.
|
||||
|
||||
* {@link angular.service.$browser $browser}
|
||||
* {@link angular.service.$window $window}
|
||||
* {@link angular.service.$document $document}
|
||||
* {@link angular.service.$location $location}
|
||||
* {@link angular.service.$log $log}
|
||||
* {@link angular.service.$exceptionHandler $exceptionHandler}
|
||||
* {@link angular.service.$hover $hover}
|
||||
* {@link angular.service.$invalidWidgets $invalidWidgets}
|
||||
* {@link angular.service.$route $route}
|
||||
* {@link angular.service.$xhr $xhr}
|
||||
* {@link angular.service.$xhr.error $xhr.error}
|
||||
* {@link angular.service.$xhr.bulk $xhr.bulk}
|
||||
* {@link angular.service.$xhr.cache $xhr.cache}
|
||||
* {@link angular.service.$resource $resource}
|
||||
* {@link angular.service.$cookies $cookies}
|
||||
* {@link angular.service.$cookieStore $cookieStore}
|
||||
|
||||
# Writing your own custom services
|
||||
angular provides only set of basic services, so for any nontrivial application it will be necessary
|
||||
to write one or more custom services. To do so, a factory function that creates a services needs to
|
||||
be registered with angular's dependency injector. This factory function must return an object - the
|
||||
service (it is not called with the `new` operator).
|
||||
|
||||
**angular.service** accepts three parameters:
|
||||
|
||||
- `{string} name` - Name of the service.
|
||||
- `{function()} factory` - Factory function (called just once by DI).
|
||||
- `{Object} config` - Configuration object with following properties:
|
||||
- `$inject` - {Array.<string>} - Array of service ids that this service depends on. These
|
||||
services will be passed as arguments into the factory function in the same order as specified
|
||||
in the `$inject` array. Defaults to `[]`.
|
||||
- `$eager` - {boolean} - If true, the service factory will be called and thus, the service will
|
||||
be instantiated when angular boots. If false, service will be lazily instantiated when it is
|
||||
first requested during instantiation of a dependant. Defaults to `false`.
|
||||
|
||||
The `this` of the factory function is bound to the root scope of the angular application.
|
||||
|
||||
angular enables services to participate in dependency injection (DI) by registering themselves with
|
||||
angular's DI system (injector) under a `name` (id) as well as by declaring dependencies which need
|
||||
to be provided for the factory function of the registered service. The ability to swap dependencies
|
||||
for mocks/stubs/dummies in tests allows for services to be highly testable.
|
||||
|
||||
Here is an example of very simple service. This service requires $window service (it's
|
||||
passed as a parameter to factory function) and it's just a function.
|
||||
|
||||
This service simple stores all notifications and after third one, it displays all of them by
|
||||
window alert.
|
||||
<pre>
|
||||
angular.service('notify', function(win) {
|
||||
var msgs = [];
|
||||
return function(msg) {
|
||||
msgs.push(msg);
|
||||
if (msgs.length == 3) {
|
||||
win.alert(msgs.join("\n"));
|
||||
msgs = [];
|
||||
}
|
||||
};
|
||||
}, {$inject: ['$window']});
|
||||
</pre>
|
||||
|
||||
And here is a unit test for this service. We use Jasmine spy (mock) instead of real browser's alert.
|
||||
<pre>
|
||||
var mock, notify;
|
||||
|
||||
beforeEach(function() {
|
||||
mock = {alert: jasmine.createSpy()};
|
||||
notify = angular.service('notify')(mock);
|
||||
});
|
||||
|
||||
it('should not alert first two notifications', function() {
|
||||
notify('one');
|
||||
notify('two');
|
||||
expect(mock.alert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should alert all after third notification', function() {
|
||||
notify('one');
|
||||
notify('two');
|
||||
notify('three');
|
||||
expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
|
||||
});
|
||||
|
||||
it('should clear messages after alert', function() {
|
||||
notify('one');
|
||||
notify('two');
|
||||
notify('third');
|
||||
notify('more');
|
||||
notify('two');
|
||||
notify('third');
|
||||
expect(mock.alert.callCount).toEqual(2);
|
||||
expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
|
||||
});
|
||||
</pre>
|
||||
|
||||
# Injecting services into controllers
|
||||
Using services as dependencies for controllers is very similar to using them as dependencies for
|
||||
another service.
|
||||
|
||||
JavaScript is dynamic language, so DI is not able to figure out which services to inject by
|
||||
static types (like in static typed languages). Therefore you must specify the service name
|
||||
by the `$inject` property - it's an array that contains strings with names of services to be
|
||||
injected. The name must match the id that service has been registered as with angular.
|
||||
The order of the services in the array matters, because this order will be used when calling
|
||||
the factory function with injected parameters. The names of parameters in factory function
|
||||
don't matter, but by convention they match the service ids.
|
||||
<pre>
|
||||
function myController($loc, $log) {
|
||||
this.firstMethod = function() {
|
||||
// use $location service
|
||||
$loc.setHash();
|
||||
};
|
||||
this.secondMethod = function() {
|
||||
// use $log service
|
||||
$log.info('...');
|
||||
};
|
||||
}
|
||||
// which services to inject ?
|
||||
myController.$inject = ['$location', '$log'];
|
||||
</pre>
|
||||
|
||||
@example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script type="text/javascript">
|
||||
angular.service('notify', function(win) {
|
||||
var msgs = [];
|
||||
return function(msg) {
|
||||
msgs.push(msg);
|
||||
if (msgs.length == 3) {
|
||||
win.alert(msgs.join("\n"));
|
||||
msgs = [];
|
||||
}
|
||||
};
|
||||
}, {$inject: ['$window']});
|
||||
|
||||
function myController(notifyService) {
|
||||
this.callNotify = function(msg) {
|
||||
notifyService(msg);
|
||||
};
|
||||
}
|
||||
|
||||
myController.$inject = ['notify'];
|
||||
</script>
|
||||
|
||||
<div ng:controller="myController">
|
||||
<p>Let's try this simple notify service, injected into the controller...</p>
|
||||
<input ng:init="message='test'" type="text" name="message" />
|
||||
<button ng:click="callNotify(message);">NOTIFY</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should test service', function(){
|
||||
expect(element(':input[name=message]').val()).toEqual('test');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -0,0 +1,77 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.validator
|
||||
@namespace Namespace for all filters.
|
||||
@description
|
||||
# Overview
|
||||
Validators are a standard way to check the user input against a specific criteria. For
|
||||
example, you might need to check that an input field contains a well-formed phone number.
|
||||
|
||||
# Syntax
|
||||
Attach a validator on user input widgets using the `ng:validate` attribute.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Change me: <input type="text" name="number" ng:validate="integer" value="123">
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should validate the default number string', function() {
|
||||
expect(element('input[name=number]').attr('class')).
|
||||
not().toMatch(/ng-validation-error/);
|
||||
});
|
||||
it('should not validate "foo"', function() {
|
||||
input('number').enter('foo');
|
||||
expect(element('input[name=number]').attr('class')).
|
||||
toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
# Writing your own Validators
|
||||
Writing your own validator is easy. To make a function available as a
|
||||
validator, just define the JavaScript function on the `angular.validator`
|
||||
object. <angular/> passes in the input to validate as the first argument
|
||||
to your function. Any additional validator arguments are passed in as
|
||||
additional arguments to your function.
|
||||
|
||||
You can use these variables in the function:
|
||||
|
||||
* `this` — The current scope.
|
||||
* `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
|
||||
the DOM in addition to transforming the input.
|
||||
|
||||
In this example we have written a upsTrackingNo validator.
|
||||
It marks the input text "valid" only when the user enters a well-formed
|
||||
UPS tracking number.
|
||||
|
||||
@css ng-validation-error
|
||||
When validation fails, this css class is applied to the binding, making its borders red by
|
||||
default.
|
||||
|
||||
@example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
angular.validator('upsTrackingNo', function(input, format) {
|
||||
var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$");
|
||||
return input.match(regexp)?"":"The format must match " + format;
|
||||
});
|
||||
</script>
|
||||
<input type="text" name="trackNo" size="40"
|
||||
ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'"
|
||||
value="1Z 123 456 78 9012 345 6"/>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should validate correct UPS tracking number', function() {
|
||||
expect(element('input[name=trackNo]').attr('class')).
|
||||
not().toMatch(/ng-validation-error/);
|
||||
});
|
||||
|
||||
it('should not validate in correct UPS tracking number', function() {
|
||||
input('trackNo').enter('foo');
|
||||
expect(element('input[name=trackNo]').attr('class')).
|
||||
toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -0,0 +1,78 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.widget
|
||||
@namespace Namespace for all widgets.
|
||||
@description
|
||||
# Overview
|
||||
Widgets allow you to create DOM elements that the browser doesn't
|
||||
already understand. You create the widget in your namespace and
|
||||
assign it behavior. You can only bind one widget per DOM element
|
||||
(unlike directives, in which you can use any number per DOM
|
||||
element). Widgets are expected to manipulate the DOM tree by
|
||||
adding new elements whereas directives are expected to only modify
|
||||
element properties.
|
||||
|
||||
Widgets come in two flavors: element and attribute.
|
||||
|
||||
# Element Widget
|
||||
Let's say we would like to create a new element type in the
|
||||
namespace `my` that can watch an expression and alert() the user
|
||||
with each new value.
|
||||
|
||||
<pre>
|
||||
<my:watch exp="name"/>
|
||||
</pre>
|
||||
|
||||
You can implement `my:watch` like this:
|
||||
<pre>
|
||||
angular.widget('my:watch', function(compileElement) {
|
||||
var compiler = this;
|
||||
var exp = compileElement.attr('exp');
|
||||
return function(linkElement) {
|
||||
var currentScope = this;
|
||||
currentScope.$watch(exp, function(value){
|
||||
alert(value);
|
||||
}};
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
# Attribute Widget
|
||||
Let's implement the same widget, but this time as an attribute
|
||||
that can be added to any existing DOM element.
|
||||
<pre>
|
||||
<div my-watch="name">text</div>
|
||||
</pre>
|
||||
You can implement `my:watch` attribute like this:
|
||||
<pre>
|
||||
angular.widget('@my:watch', function(expression, compileElement) {
|
||||
var compiler = this;
|
||||
return function(linkElement) {
|
||||
var currentScope = this;
|
||||
currentScope.$watch(expression, function(value){
|
||||
alert(value);
|
||||
});
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
@example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
angular.widget('my:time', function(compileElement){
|
||||
compileElement.css('display', 'block');
|
||||
return function(linkElement){
|
||||
function update(){
|
||||
linkElement.text('Current time is: ' + new Date());
|
||||
setTimeout(update, 1000);
|
||||
}
|
||||
update();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<my:time></my:time>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -0,0 +1,233 @@
|
||||
@ngdoc overview
|
||||
@name Contributing
|
||||
@description
|
||||
|
||||
<a name="H1_1"></a>
|
||||
# Open Source
|
||||
|
||||
`Angular` is an open source project licensed under the {@link
|
||||
http://github.com/angular/angular.js/blob/master/LICENSE MIT license}. Your contributions are
|
||||
always welcome. When working with `angular` source base, please follow the guidelines provided on
|
||||
this page.
|
||||
|
||||
* <a href="#H1_2">Contributing to Source Code</a>
|
||||
* <a href="#H1_3">Applying Code Standards</a>
|
||||
* <a href="#H1_4">Checking Out and Building `Angular`</a>
|
||||
* <a href="#H1_5">Submitting Your Changes</a>
|
||||
|
||||
|
||||
|
||||
<a name="H1_2"></a>
|
||||
# Contributing to Source Code
|
||||
|
||||
We'd love for you to contribute to our source code and to make `angular` even better than it is
|
||||
today! Here are the guidelines we'd like you to use:
|
||||
|
||||
* Major changes that you intend to contribute to the project must be discussed first on our {@link
|
||||
https://groups.google.com/forum/?hl=en#!forum/angular mailing list} so that we can better
|
||||
coordinate our efforts, prevent duplication of work, and help you to craft the change so that it
|
||||
is successfully accepted upstream.
|
||||
* Small changes and bug fixes can be crafted and submitted to Github as a <a href="#H1_5">pull
|
||||
request</a>.
|
||||
|
||||
|
||||
|
||||
<a name="H1_3"></a>
|
||||
# Applying Code Standards
|
||||
|
||||
To ensure consistency throughout the source code, keep these rules in mind as you are working:
|
||||
|
||||
* All features or bug fixes must be tested by one or more <a href="#unit-tests">specs</a>.
|
||||
* All public API methods must be documented with ngdoc, an extended version of jsdoc (we added
|
||||
support for markdown and templating via `@ngdoc` tag). To see how we document our APIs, please
|
||||
check out the existing ngdocs.
|
||||
* With the exceptions listed below, we follow the rules contained in {@link
|
||||
http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml Google's JavaScript Style
|
||||
Guide}:
|
||||
|
||||
* Do not use namespaces: Instead, we 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
|
||||
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, internally we use aliases that map to the
|
||||
external API. See our existing code to see what we mean.
|
||||
* We don't go crazy with type annotations for private internal APIs unless it's an internal API
|
||||
that is used throughout `angular`. The best guidance is to do what makes the most sense.
|
||||
|
||||
|
||||
<a name="H1_4"></a>
|
||||
# Checking Out and Building Angular
|
||||
|
||||
The `angular` source code is hosted at {@link http://github.com Github}, which we also use to
|
||||
accept code contributions. Several steps are needed to check out and build `angular`:
|
||||
|
||||
|
||||
## Installation Dependencies
|
||||
|
||||
Before you can build `angular`, you must install or configure the following dependencies on your
|
||||
machine:
|
||||
|
||||
* {@link http://rake.rubyforge.org Rake}: We use Rake as our build system, which is pre-installed
|
||||
on most Macintosh and Linux machines. If that is not true in your case, you can grab it from the
|
||||
Rake website.
|
||||
|
||||
* {@link http://nodejs.org Node.js}: We use Node to generate the documentation and to run a
|
||||
development web server. Depending on your system, you can install Node either from source or as a
|
||||
pre-packaged bundle.
|
||||
|
||||
* Java: The Java runtime is used to run {@link http://code.google.com/p/js-test-driver
|
||||
JsTestDriver} (JSTD), which we use to run our unit test suite. JSTD binaries are part of the
|
||||
`angular` source base, which means there is no need to install or configure it separately.
|
||||
* Git: The {@link http://help.github.com/mac-git-installation Github Guide to Installing Git} is
|
||||
quite a good source for information on Git.
|
||||
|
||||
|
||||
## Creating a Github Account and Forking Angular
|
||||
|
||||
To create a Github account, follow the instructions {@link https://github.com/signup/free here}.
|
||||
Afterwards, go ahead and {@link http://help.github.com/forking fork} the {@link
|
||||
https://github.com/angular/angular.js main angular repository}.
|
||||
|
||||
|
||||
## Building `Angular`
|
||||
|
||||
To build `angular`, you check out the source code and use Rake to generate the non-minified and
|
||||
minified `angular` files:
|
||||
|
||||
1. To clone your Github repository, run:
|
||||
|
||||
git clone git@github.com:<github username>/angular.js.git
|
||||
|
||||
2. To go to the `angular` directory, run:
|
||||
|
||||
cd angular.js
|
||||
|
||||
3. To add the main `angular` repository as an upstream remote to your repository, run:
|
||||
|
||||
git remote add upstream https://github.com/angular/angular.js.git
|
||||
|
||||
4. To build `angular`, run:
|
||||
|
||||
rake package
|
||||
|
||||
The build output can be located under the `build` directory. It consists of the following files and
|
||||
directories:
|
||||
|
||||
* `angular-x.y.z-<git sha>.tgz` — This is the complete tarball, which contains all of the release
|
||||
build artifacts.
|
||||
* `angular.js` — The non-minified `angular` script.
|
||||
* `angular.min.js` — The minified `angular` script.
|
||||
* `angular-scenario.js` — The `angular` End2End test runner.
|
||||
* `angular-ie-compat.js` — The Internet Explorer compatibility patch file.
|
||||
* `docs/` — A directory that contains all of the files needed to run `docs.angularjs.org`.
|
||||
* `docs/index.html` — The main page for the documentation.
|
||||
* `docs/docs-scenario.html` — The End2End test runner for the documentation application.
|
||||
|
||||
|
||||
## Running a Local Development Web Server
|
||||
|
||||
To debug or test code, it is often useful to have a local HTTP server. For this purpose, we have
|
||||
made available a local web server based on Node.js.
|
||||
|
||||
1. To start the web server, run:
|
||||
|
||||
./nodeserver.sh
|
||||
|
||||
2. To access the local server, go to this website:
|
||||
|
||||
http://localhost:8000/
|
||||
|
||||
By default, it serves the contents of the `angular` project directory.
|
||||
|
||||
|
||||
<a name="unit-tests"></a>
|
||||
## Running the Unit Test Suite
|
||||
|
||||
Our unit and integration tests are written with Jasmine and executed with JsTestDriver. To run the
|
||||
tests:
|
||||
|
||||
1. To start the JSTD server, run:
|
||||
|
||||
./server.sh
|
||||
|
||||
2. To capture one or more browsers, go to this website:
|
||||
|
||||
http://localhost:9876/
|
||||
|
||||
3. To trigger a test execution, run:
|
||||
|
||||
./test.sh
|
||||
|
||||
4. To automatically run the test suite each time one or more of the files in the project directory
|
||||
is changed, you can install `watchr` and then run:
|
||||
|
||||
watchr watchr.rb
|
||||
|
||||
5. To view the output of each test run, you can tail this log file:
|
||||
|
||||
./logs/jstd.log
|
||||
|
||||
|
||||
## Running the End2End Test Suite
|
||||
|
||||
To run the End2End test suite:
|
||||
|
||||
1. Start the local web server.
|
||||
2. In a browser, go to:
|
||||
|
||||
http://localhost:8000/build/docs/docs-scenario.html
|
||||
|
||||
The tests are executed automatically.
|
||||
|
||||
|
||||
|
||||
<a name="H1_5"></a>
|
||||
# Submitting Your Changes
|
||||
|
||||
To create and submit a change:
|
||||
|
||||
1. Create a new branch off the master for your changes:
|
||||
|
||||
git branch my-fix-branch
|
||||
|
||||
2. Check out the branch:
|
||||
|
||||
git checkout my-fix-branch
|
||||
|
||||
3. Create your patch, make sure to have plenty of tests (that pass).
|
||||
|
||||
4. Commit your changes:
|
||||
|
||||
git commit -a
|
||||
|
||||
5. Run JavaScript Lint and be sure to address all new warnings and errors:
|
||||
|
||||
rake lint
|
||||
|
||||
6. Push your branch to Github:
|
||||
|
||||
git push origin my-fix-branch
|
||||
|
||||
7. In Github, send a pull request to `angular:master`.
|
||||
|
||||
8. When the patch is reviewed and merged, delete your branch and pull yours — and other — changes
|
||||
from the main (upstream) repository:
|
||||
* To delete the branch in Github, run:
|
||||
|
||||
git push origin :my-fix-branch
|
||||
|
||||
* To check out the master branch, run:
|
||||
|
||||
git checkout master
|
||||
|
||||
* To delete a local branch, run:
|
||||
|
||||
git branch -D my-fix-branch
|
||||
|
||||
* To update your master with the latest upstream version, run:
|
||||
|
||||
git pull --ff upstream master
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
@@ -0,0 +1,63 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Resources - Buzz
|
||||
@description
|
||||
|
||||
External resources are URLs that provide JSON data, which are then rendered with the help of
|
||||
templates. angular has a resource factory that can be used to give names to the URLs and then
|
||||
attach behavior to them. For example you can use the
|
||||
{@link http://code.google.com/apis/buzz/v1/getting_started.html#background-operations| Google Buzz API}
|
||||
to retrieve Buzz activity and comments.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
BuzzController.$inject = ['$resource'];
|
||||
function BuzzController($resource){
|
||||
this.Activity = $resource(
|
||||
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
|
||||
{alt:'json', callback:'JSON_CALLBACK'},
|
||||
{ get: {method:'JSON', params:{visibility:'@self'}},
|
||||
replies: {method:'JSON', params:{visibility:'@self', comments:'@comments'}}
|
||||
});
|
||||
}
|
||||
BuzzController.prototype = {
|
||||
fetch: function(){
|
||||
this.activities = this.Activity.get({userId:this.userId});
|
||||
},
|
||||
expandReplies: function(activity) {
|
||||
activity.replies =
|
||||
this.Activity.replies({userId:this.userId, activityId:activity.id});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="BuzzController">
|
||||
<input name="userId" value="googlebuzz"/>
|
||||
<button ng:click="fetch()">fetch</button>
|
||||
<hr/>
|
||||
<div class="buzz" ng:repeat="item in activities.data.items">
|
||||
<h1 style="font-size: 15px;">
|
||||
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
|
||||
<a href="" ng:click="expandReplies(item)" style="float: right;">
|
||||
Expand replies: {{item.links.replies[0].count}}
|
||||
</a>
|
||||
</h1>
|
||||
{{item.object.content | html}}
|
||||
<div class="reply" ng:repeat="reply in item.replies.data.items" style="margin-left: 20px;">
|
||||
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
|
||||
{{reply.content | html}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('fetch buzz and expand', function(){
|
||||
element(':button:contains(fetch)').click();
|
||||
expect(repeater('div.buzz').count()).toBeGreaterThan(0);
|
||||
element('.buzz a:contains(Expand replies):first').click();
|
||||
expect(repeater('div.reply').count()).toBeGreaterThan(0);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -0,0 +1,114 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Deep Linking
|
||||
@description
|
||||
|
||||
Deep linking allows you to encode the state of the application in the URL so that it can be
|
||||
bookmarked and the application can be restored from the URL to the same state.
|
||||
|
||||
While <angular/> does not force you to deal with bookmarks in any particular way, it has services
|
||||
which make the common case described here very easy to implement.
|
||||
|
||||
# Assumptions
|
||||
|
||||
Your application consists of a single HTML page which bootstraps the application. We will refer
|
||||
to this page as the chrome.
|
||||
Your application is divided into several screens (or views) which the user can visit. For example,
|
||||
the home screen, settings screen, details screen, etc. For each of these screens, we would like to
|
||||
assign a URL so that it can be bookmarked and later restored. Each of these screens will be
|
||||
associated with a controller which define the screen's behavior. The most common case is that the
|
||||
screen will be constructed from an HTML snippet, which we will refer to as the partial. Screens can
|
||||
have multiple partials, but a single partial is the most common construct. This example makes the
|
||||
partial boundary visible using a blue line.
|
||||
|
||||
You can make a routing table which shows which URL maps to which partial view template and which
|
||||
controller.
|
||||
|
||||
# Example
|
||||
|
||||
In this example we have a simple app which consist of two screens:
|
||||
|
||||
* Welcome: url `#` Show the user contact information.
|
||||
* Settings: url `#/settings` Show an edit screen for user contact information.
|
||||
|
||||
|
||||
The two partials are defined in the following URLs:
|
||||
|
||||
* {@link ./static/settings.html}
|
||||
* {@link ./static/welcome.html}
|
||||
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
AppCntl.$inject = ['$route']
|
||||
function AppCntl($route) {
|
||||
// define routes
|
||||
$route.when("", {template:'./static/welcome.html', controller:WelcomeCntl});
|
||||
$route.when("/settings", {template:'./static/settings.html', controller:SettingsCntl});
|
||||
$route.parent(this);
|
||||
|
||||
// initialize the model to something useful
|
||||
this.person = {
|
||||
name:'anonymous',
|
||||
contacts:[{type:'email', url:'anonymous@example.com'}]
|
||||
};
|
||||
}
|
||||
|
||||
function WelcomeCntl($route){}
|
||||
WelcomeCntl.prototype = {
|
||||
greet: function(){
|
||||
alert("Hello " + this.person.name);
|
||||
}
|
||||
};
|
||||
|
||||
function SettingsCntl(){
|
||||
this.cancel();
|
||||
}
|
||||
SettingsCntl.prototype = {
|
||||
cancel: function(){
|
||||
this.form = angular.copy(this.person);
|
||||
},
|
||||
|
||||
save: function(){
|
||||
angular.copy(this.form, this.person);
|
||||
window.location.hash = "#";
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="AppCntl">
|
||||
<h1>Your App Chrome</h1>
|
||||
[ <a href="#">Welcome</a> | <a href="#/settings">Settings</a> ]
|
||||
<hr/>
|
||||
<span style="background-color: blue; color: white; padding: 3px;">
|
||||
Partial: {{$route.current.template}}
|
||||
</span>
|
||||
<ng:view style="border: 1px solid blue; margin: 0; display:block; padding:1em;"></ng:view>
|
||||
<small>Your app footer </small>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should navigate to URL', function(){
|
||||
element('a:contains(Welcome)').click();
|
||||
expect(element('ng\\:view').text()).toMatch(/Hello anonymous/);
|
||||
element('a:contains(Settings)').click();
|
||||
input('form.name').enter('yourname');
|
||||
element(':button:contains(Save)').click();
|
||||
element('a:contains(Welcome)').click();
|
||||
expect(element('ng\\:view').text()).toMatch(/Hello yourname/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
|
||||
# Things to notice
|
||||
|
||||
* Routes are defined in the `AppCntl` class. The initialization of the controller causes the
|
||||
initialization of the {@link angular.service.$rouet $route} service with the proper URL routes.
|
||||
* The {@link angular.service.$route $route} service then watches the URL and instantiates the
|
||||
appropriate controller when the URL changes.
|
||||
* The {@link angular.widget.ng:view ng:view} widget loads the view when the URL changes. It also
|
||||
sets the view scope to the newly instantiated controller.
|
||||
* Changing the URL is sufficient to change the controller and view. It makes no difference whether
|
||||
the URL is changed programatically or by the user.
|
||||
@@ -0,0 +1,103 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Form
|
||||
@description
|
||||
|
||||
A web application's main purpose is to present and gather data. For this reason angular strives
|
||||
to make both of these operations trivial. This example shows off how you can build a simple form to
|
||||
allow a user to enter data.
|
||||
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function FormController(){
|
||||
this.user = {
|
||||
name: 'John Smith',
|
||||
address:{line1: '123 Main St.', city:'Anytown', state:'AA', zip:'12345'},
|
||||
contacts:[{type:'phone', value:'1(234) 555-1212'}]
|
||||
};
|
||||
this.state = /^\w\w$/;
|
||||
this.zip = /^\d\d\d\d\d$/;
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="FormController" class="example">
|
||||
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" name="user.name" ng:required/> <br/><br/>
|
||||
|
||||
<label>Address:</label><br/>
|
||||
<input type="text" name="user.address.line1" size="33" ng:required/> <br/>
|
||||
<input type="text" name="user.address.city" size="12" ng:required/>,
|
||||
<input type="text" name="user.address.state" size="2" ng:required ng:validate="regexp:state"/>
|
||||
<input type="text" name="user.address.zip" size="5" ng:required ng:validate="regexp:zip"/><br/><br/>
|
||||
|
||||
<label>Phone:</label>
|
||||
[ <a href="" ng:click="user.contacts.$add()">add</a> ]
|
||||
<div ng:repeat="contact in user.contacts">
|
||||
<select name="contact.type">
|
||||
<option>email</option>
|
||||
<option>phone</option>
|
||||
<option>pager</option>
|
||||
<option>IM</option>
|
||||
</select>
|
||||
<input type="text" name="contact.value" ng:required/>
|
||||
[ <a href="" ng:click="user.contacts.$remove(contact)">X</a> ]
|
||||
</div>
|
||||
<hr/>
|
||||
Debug View:
|
||||
<pre>user={{user}}</pre>
|
||||
</div>
|
||||
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should show debug', function(){
|
||||
expect(binding('user')).toMatch(/John Smith/);
|
||||
});
|
||||
it('should add contact', function(){
|
||||
using('.example').element('a:contains(add)').click();
|
||||
using('.example div:last').input('contact.value').enter('you@example.org');
|
||||
expect(binding('user')).toMatch(/\(234\) 555\-1212/);
|
||||
expect(binding('user')).toMatch(/you@example.org/);
|
||||
});
|
||||
|
||||
it('should remove contact', function(){
|
||||
using('.example').element('a:contains(X)').click();
|
||||
expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
|
||||
});
|
||||
|
||||
it('should validate zip', function(){
|
||||
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
|
||||
.not().toMatch(/ng-validation-error/)
|
||||
|
||||
using('.example').input('user.address.zip').enter('abc');
|
||||
|
||||
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
|
||||
.toMatch(/ng-validation-error/)
|
||||
});
|
||||
|
||||
it('should validate state', function(){
|
||||
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
|
||||
.not().toMatch(/ng-validation-error/)
|
||||
|
||||
using('.example').input('user.address.state').enter('XXX');
|
||||
|
||||
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
|
||||
.toMatch(/ng-validation-error/)
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
# Things to notice
|
||||
|
||||
* The user data model is initialized {@link angular.ng:controller controller} and is available in
|
||||
the {@link angular.scope scope} with the initial data.
|
||||
* For debugging purposes we have included a debug view of the model to better understand what
|
||||
is going on.
|
||||
* The {@link angular.widget.HTML input widgets} simply refer to the model and are auto bound.
|
||||
* The inputs {@link angular.validator validate}. (Try leaving them blank or entering non digits
|
||||
in the zip field)
|
||||
* In your application you can simply read from or write to the model and the form will be updated.
|
||||
* By clicking the 'add' link you are adding new items into the `user.contacts` array which are then
|
||||
reflected in the view.
|
||||
@@ -0,0 +1,105 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Advanced Form
|
||||
@description
|
||||
|
||||
Here we extend the basic form example to include common features such as reverting, dirty state
|
||||
detection, and preventing invalid form submission.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
UserForm.$inject = ['$invalidWidgets'];
|
||||
function UserForm($invalidWidgets){
|
||||
this.$invalidWidgets = $invalidWidgets;
|
||||
this.state = /^\w\w$/;
|
||||
this.zip = /^\d\d\d\d\d$/;
|
||||
this.master = {
|
||||
name: 'John Smith',
|
||||
address:{
|
||||
line1: '123 Main St.',
|
||||
city:'Anytown',
|
||||
state:'AA',
|
||||
zip:'12345'
|
||||
},
|
||||
contacts:[
|
||||
{type:'phone', value:'1(234) 555-1212'}
|
||||
]
|
||||
};
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
UserForm.prototype = {
|
||||
cancel: function(){
|
||||
this.form = angular.copy(this.master);
|
||||
},
|
||||
|
||||
save: function(){
|
||||
this.master = this.form;
|
||||
this.cancel();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="UserForm">
|
||||
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" name="form.name" ng:required/> <br/><br/>
|
||||
|
||||
<label>Address:</label><br/>
|
||||
<input type="text" name="form.address.line1" size="33" ng:required/> <br/>
|
||||
<input type="text" name="form.address.city" size="12" ng:required/>,
|
||||
<input type="text" name="form.address.state" size="2" ng:required ng:validate="regexp:state"/>
|
||||
<input type="text" name="form.address.zip" size="5" ng:required ng:validate="regexp:zip"/><br/><br/>
|
||||
|
||||
<label>Phone:</label>
|
||||
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
|
||||
<div ng:repeat="contact in form.contacts">
|
||||
<select name="contact.type">
|
||||
<option>email</option>
|
||||
<option>phone</option>
|
||||
<option>pager</option>
|
||||
<option>IM</option>
|
||||
</select>
|
||||
<input type="text" name="contact.value" ng:required/>
|
||||
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
|
||||
</div>
|
||||
<button ng:click="cancel()" disabled="{{master.$equals(form)}}">Cancel</button>
|
||||
<button ng:click="save()" disabled="{{$invalidWidgets.visible() || master.$equals(form)}}">Save</button>
|
||||
|
||||
<hr/>
|
||||
Debug View:
|
||||
<pre>form={{form}}
|
||||
master={{master}}</pre>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should enable save button', function(){
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
input('form.name').enter('');
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
input('form.name').enter('change');
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
|
||||
element(':button:contains(Save)').click();
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
});
|
||||
it('should enable cancel button', function(){
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
|
||||
input('form.name').enter('change');
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
|
||||
element(':button:contains(Cancel)').click();
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
|
||||
expect(element(':input[name=form.name]').val()).toEqual('John Smith');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
#Things to notice
|
||||
|
||||
* Cancel & save buttons are only enabled if the form is dirty -- there is something to cancel or
|
||||
save.
|
||||
* Save button is only enabled if there are no validation errors on the form.
|
||||
* Cancel reverts the form changes back to original state.
|
||||
* Save updates the internal model of the form.
|
||||
* Debug view shows the two models. One presented to the user form and the other being the pristine
|
||||
copy master.
|
||||
@@ -0,0 +1,31 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Hello World
|
||||
@description
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Your name: <input type="text" name="name" value="World"/>
|
||||
<hr/>
|
||||
Hello {{name}}!
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should change the binding when user enters text', function(){
|
||||
expect(binding('name')).toEqual('World');
|
||||
input('name').enter('angular');
|
||||
expect(binding('name')).toEqual('angular');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
# Things to notice
|
||||
|
||||
Take a look through the source and note:
|
||||
|
||||
* The script tag that {@link guide.bootstrap bootstraps} the angular environment.
|
||||
* The text {@link angular.widget.HTML input widget} which is bound to the greeting name text.
|
||||
* No need for listener registration and event firing on change events.
|
||||
* The implicit presence of the `name` variable which is in the root {@link angular.scope scope}.
|
||||
* The double curly brace `{{markup}}`, which binds the name variable to the greeting text.
|
||||
* The concept of {@link guide.data-binding data binding}, which reflects any changes to the
|
||||
input field in the greeting text.
|
||||
@@ -0,0 +1,125 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: MVC
|
||||
@description
|
||||
|
||||
MVC allows for a clean an testable separation between the behavior (controller) and the view
|
||||
(HTML template). A Controller is just a JavaScript class which is grafted onto the scope of the
|
||||
view. This makes it very easy for the controller and the view to share the model.
|
||||
|
||||
The model is simply the controller's this. This makes it very easy to test the controller in
|
||||
isolation since one can simply instantiate the controller and test without a view, because there is
|
||||
no connection between the controller and the view.
|
||||
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function TicTacToeCntl(){
|
||||
this.cellStyle= {
|
||||
'height': '20px',
|
||||
'width': '20px',
|
||||
'border': '1px solid black',
|
||||
'text-align': 'center',
|
||||
'vertical-align': 'middle',
|
||||
'cursor': 'pointer'
|
||||
};
|
||||
this.reset();
|
||||
this.$watch('$location.hashSearch.board', this.readUrl);
|
||||
}
|
||||
TicTacToeCntl.prototype = {
|
||||
dropPiece: function(row, col) {
|
||||
if (!this.winner && !this.board[row][col]) {
|
||||
this.board[row][col] = this.nextMove;
|
||||
this.nextMove = this.nextMove == 'X' ? 'O' : 'X';
|
||||
this.setUrl();
|
||||
}
|
||||
},
|
||||
reset: function(){
|
||||
this.board = [
|
||||
['', '', ''],
|
||||
['', '', ''],
|
||||
['', '', '']
|
||||
];
|
||||
this.nextMove = 'X';
|
||||
this.winner = '';
|
||||
this.setUrl();
|
||||
},
|
||||
grade: function(){
|
||||
var b = this.board;
|
||||
this.winner =
|
||||
row(0) || row(1) || row(2) ||
|
||||
col(0) || col(1) || col(2) ||
|
||||
diagonal(-1) || diagonal(1);
|
||||
function row(r) { return same(b[r][0], b[r][1], b[r][2]);}
|
||||
function col(c) { return same(b[0][c], b[1][c], b[2][c]);}
|
||||
function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);}
|
||||
function same(a, b, c) { return (a==b && b==c) ? a : '';};
|
||||
},
|
||||
setUrl: function(){
|
||||
var rows = [];
|
||||
angular.forEach(this.board, function(row){
|
||||
rows.push(row.join(','));
|
||||
});
|
||||
this.$location.hashSearch.board = rows.join(';') + '/' + this.nextMove;
|
||||
},
|
||||
readUrl: function(value) {
|
||||
if (value) {
|
||||
value = value.split('/');
|
||||
this.nextMove = value[1];
|
||||
angular.forEach(value[0].split(';'), function(row, i){
|
||||
this.board[i] = row.split(',');
|
||||
}, this);
|
||||
this.grade();
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<h3>Tic-Tac-Toe</h3>
|
||||
<div ng:controller="TicTacToeCntl">
|
||||
Next Player: {{nextMove}}
|
||||
<div class="winner" ng:show="winner">Player {{winner}} has won!</div>
|
||||
<table class="board">
|
||||
<tr ng:repeat="row in board" style="height:15px;">
|
||||
<td ng:repeat="cell in row" ng:style="cellStyle"
|
||||
ng:click="dropPiece($parent.$index, $index)">{{cell}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button ng:click="reset()">reset board</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should play a game', function(){
|
||||
piece(1, 1);
|
||||
expect(binding('nextMove')).toEqual('O');
|
||||
piece(3, 1);
|
||||
expect(binding('nextMove')).toEqual('X');
|
||||
piece(1, 2);
|
||||
piece(3, 2);
|
||||
piece(1, 3);
|
||||
expect(element('.winner').text()).toEqual('Player X has won!');
|
||||
});
|
||||
|
||||
function piece(row, col) {
|
||||
element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
|
||||
}
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
# Things to notice
|
||||
|
||||
* The controller is defined in JavaScript and has no reference to the rendering logic.
|
||||
* The controller is instantiated by <angular/> and injected into the view.
|
||||
* The controller can be instantiated in isolation (without a view) and the code will still execute.
|
||||
This makes it very testable.
|
||||
* The HTML view is a projection of the model. In the above example, the model is stored in the
|
||||
board variable.
|
||||
* All of the controller's properties (such as board and nextMove) are available to the view.
|
||||
* Changing the model changes the view.
|
||||
* The view can call any controller function.
|
||||
* In this example, the `setUrl()` and `readUrl()` functions copy the game state to/from the URL's
|
||||
hash so the browser's back button will undo game steps. See deep-linking. This example calls
|
||||
{@link angular.Scope.$watch $watch()} to set up a listener that invokes `readUrl()` when needed.
|
||||
@@ -0,0 +1,60 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook
|
||||
@description
|
||||
|
||||
Welcome to the angular cookbook. Here we will show you typical uses of angular by example.
|
||||
|
||||
|
||||
# Hello World
|
||||
|
||||
{@link cookbook.helloworld Hello World}: The simplest possible application that demonstrates the
|
||||
classic Hello World!
|
||||
|
||||
|
||||
# Basic Form
|
||||
|
||||
{@link cookbook.form Basic Form}: Displaying forms to the user for editing is the bread and butter
|
||||
of web applications. Angular makes forms easy through bidirectional data binding.
|
||||
|
||||
|
||||
# Advanced Form
|
||||
|
||||
{@link cookbook.formadvanced Advanced Form}: Taking the form example to the next level and
|
||||
providing advanced features such as dirty detection, form reverting and submit disabling if
|
||||
validation errors exist.
|
||||
|
||||
|
||||
# Model View Controller
|
||||
|
||||
{@link cookbook.mvc MVC}: Tic-Tac-Toe: Model View Controller (MVC) is a time-tested design pattern
|
||||
to separate the behavior (JavaScript controller) from the presentation (HTML view). This
|
||||
separation aids in maintainability and testability of your project.
|
||||
|
||||
|
||||
# Multi-page App and Deep Linking
|
||||
|
||||
{@link cookbook.deeplinking Deep Linking}: An AJAX application never navigates away from the
|
||||
first page it loads. Instead, it changes the DOM of its single page. Eliminating full-page reloads
|
||||
is what makes AJAX apps responsive, but it creates a problem in that apps with a single URL
|
||||
prevent you from emailing links to a particular screen within your application.
|
||||
|
||||
Deep linking tries to solve this by changing the URL anchor without reloading a page, thus
|
||||
allowing you to send links to specific screens in your app.
|
||||
|
||||
|
||||
# Services
|
||||
|
||||
{@link angular.service Services}: Services are long lived objects in your applications that are
|
||||
available across controllers. A collection of useful services are pre-bundled with angular but you
|
||||
will likely add your own. Services are initialized using dependency injection, which resolves the
|
||||
order of initialization. This safeguards you from the perils of global state (a common way to
|
||||
implement long lived objects).
|
||||
|
||||
|
||||
# External Resources
|
||||
|
||||
{@link cookbook.buzz Resources}: Web applications must be able to communicate with the external
|
||||
services to get and update data. Resources are the abstractions of external URLs which are
|
||||
specially tailored to angular data binding.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Downloading
|
||||
@description
|
||||
|
||||
# Including angular scripts from code.angularjs.org
|
||||
|
||||
To get started quickly you without worrying about downloading anything and maintaining a local copy,
|
||||
you can point your html `script` tag directly to <http://code.angularjs.org/> urls.
|
||||
|
||||
There are two kinds of urls you care about:
|
||||
|
||||
* http://code.angularjs.org/angular-<version>.js
|
||||
* http://code.angularjs.org/angular-<version>.min.js
|
||||
|
||||
The first one is non-minified version, suitable for web development. The latter one is minified
|
||||
version, which we strongly suggest you use in production.
|
||||
|
||||
To point your code to let's say angular version 0.9.12, use the following template:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Angular App</title>
|
||||
<script src="http://code.angularjs.org/angular-0.9.12.js" ng:autobind></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
|
||||
# Downloading and hosting the files on your own
|
||||
|
||||
This options is for those who want to work with angular offline, or want to host the angular files
|
||||
on their own servers.
|
||||
|
||||
If you navigate to <http://code.angularjs.org/>, you'll see a directory listing with all angular
|
||||
versions since we started releasing versioned build artifacts (quite late in the project lifetime).
|
||||
|
||||
Each directory contains all artifacts that we released for a particular version. Once you navigate
|
||||
to one of these directories you'll see the following list of files:
|
||||
|
||||
* `angular-<version>.js` - This file is non-obfuscated, non-minified, and human-readable by opening
|
||||
it it any editor or browser. In order to get better error messages during development, you should
|
||||
always use this non-minified angular script.
|
||||
|
||||
* `angular-<version>.min.js` - This is a minified and obfuscated version of
|
||||
`angular-<version>.js` created with Closure compiler. Use this version for production in order to
|
||||
minimize the size of the application that is downloaded by your user's browser.
|
||||
|
||||
* `angular-<version>.tgz` - This is a tarball archive which contains all the other files released
|
||||
for this angular version. Use this file to get everything in a single download.
|
||||
|
||||
* `angular-ie-compat-<version>.js` - This is a special file that contains code and data specifically
|
||||
tailored for getting Internet Explorer to work with angular. If you host your own copy of angular
|
||||
files, make sure that this file is available for download and resides under the same parent path as
|
||||
`angular-<version>.js` or `angular-<version>.min.js`.
|
||||
|
||||
* `angular-mocks-<version>.js` - This file contains implementation of mocks that we provide to you
|
||||
to make testing angular apps even easier. Your unit/integration test harness should load this file
|
||||
after `angular-<version>.js` is loaded.
|
||||
|
||||
* `angular-scenario-<version>.js` - This file is a very nifty JavaScript file, which allows you to
|
||||
write and execute end to end tests for angular applications.
|
||||
|
||||
* `docs-<version>` - this directory contains all the files that compose the
|
||||
<http://docs.angularjs.org/> documentation app. These files are handy to see the older version of
|
||||
our docs, or even more importantly, view the docs offline!
|
||||
@@ -0,0 +1,81 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name FAQ
|
||||
@description
|
||||
|
||||
#FAQ
|
||||
|
||||
### Why is this project called "angular"? Why is the namespace called "ng"?
|
||||
|
||||
Because HTML has angular brackets and "ng" sounds like "angular".
|
||||
|
||||
### Is <angular/> an HTML5 tag?
|
||||
|
||||
No, <angular/> is not an HTML5 tag. angular is an orthogonal project to HTML5; you can use the two
|
||||
together.
|
||||
|
||||
### Is angular a {library, framework, DOM manipulation library, widget library, native plugin}?
|
||||
|
||||
No, angular is none of these. You don't call its functions, it does not call your functions,
|
||||
it does not provide a way to manipulate DOM, but does provide primitives to create UI projections
|
||||
of your data. There are lots of existing widget libraries which you can integrate with angular.
|
||||
It is 100% JavaScript, 100% client side and compatible with both desktop and mobile browsers.
|
||||
|
||||
### Do I need to worry about security holes in angular?
|
||||
|
||||
Like with any technology, angular is not impervious to attack. angular does, however, provide
|
||||
built-in protection from basic security holes including cross-site scripting and HTML injection
|
||||
attacks. angular does round-trip escaping on all strings for you.
|
||||
|
||||
### Can I download the source, build, and host the angular environment locally?
|
||||
|
||||
Yes. See instructions in {@link downloading downloading}.
|
||||
|
||||
### Is angular a templating system?
|
||||
|
||||
At the highest level, angular does look like a just another templating system. But there is one
|
||||
important reason why angular templating system is different and makes it very good fit for
|
||||
application development: bidirectional data binding. The template is compiled on the browser and
|
||||
the compilation step produces a live view. This means you, the developer, don't need to write
|
||||
code to constantly sync the view with the model and the model with the view as in other
|
||||
templating systems.
|
||||
|
||||
### What browsers does angular work with?
|
||||
|
||||
Webkit-based browsers (Safari, Chrome, iPhone, Android, WebOS, BlackBerry 6), Firefox, IE6 and
|
||||
above. Note that CSS only works on IE7 and above.
|
||||
|
||||
### What's angular's performance like?
|
||||
|
||||
angular takes ~300ms to load, render, and compile. In Chrome it uses about 2-5MB of memory. Your
|
||||
app's performance will vary depending on how many bindings you use.
|
||||
|
||||
### How big is the angular bootstrap JS file that I need to include?
|
||||
|
||||
The size of the library itself is < 50KB compressed and obfuscated.
|
||||
|
||||
### Can I use the open-source Closure Library with angular?
|
||||
|
||||
Yes, you can use widgets from the {@link http://code.google.com/closure/library Closure Library}
|
||||
in angular.
|
||||
|
||||
### Does angular use the jQuery library?
|
||||
|
||||
Yes, angular uses {@link http://jquery.com/ jQuery}, the open source DOM manipulation library.
|
||||
If jQuery is not present in your script path, angular falls back on its own implementation of
|
||||
{@link angular.element jQuery lite}. If jQuery is present in the path, angular uses it to
|
||||
manipulate the DOM.
|
||||
|
||||
### What is testability like in angular?
|
||||
|
||||
Very testable. It has an integrated dependency injection framework. See
|
||||
{@link angular.service service} for details.
|
||||
|
||||
### How can I learn more about angular?
|
||||
|
||||
Watch the July 28, 2010 talk
|
||||
"{@link http://www.youtube.com/watch?v=elvcgVSynRg| Angular: A Radically Different Way of Building AJAX Apps}".
|
||||
|
||||
### How is angular licensed?
|
||||
|
||||
The MIT License.
|
||||
@@ -0,0 +1,97 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Bootstrap
|
||||
@description
|
||||
|
||||
# Bootstrap
|
||||
This section explains how to bootstrap your application to the angular environment using either
|
||||
the `angular.js` or `angular.min.js` script.
|
||||
|
||||
## The bootstrap code
|
||||
|
||||
Note that there are two versions of the bootstrap code that you can use:
|
||||
|
||||
* `angular-0.0.0.js` - this file is unobfuscated, uncompressed, and thus human-readable.
|
||||
* `angular-0.0.0.min.js` - this is a compressed and obfuscated version of angular-debug.js.
|
||||
|
||||
In this section and throughout the Developer Guide, feel free to use `angular.min.js` instead of
|
||||
`angular.js` when working through code examples.
|
||||
|
||||
## ng:autobind
|
||||
|
||||
The simplest way to get an angular application up and running is by inserting a script tag in your
|
||||
HTML file that bootstraps the `angular.js` code and uses the special `ng:autobind` attribute,
|
||||
like in this snippet of HTML:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Hello {{'World'}}!
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
|
||||
The `ng:autobind` attribute tells angular to compile and manage the whole HTML document. The
|
||||
compilation occurs in the page's onLoad handler. Note that you don't need to explicitly add an
|
||||
onLoad event; auto bind mode takes care of all the magic for you.
|
||||
|
||||
## Manual bind
|
||||
|
||||
Using autobind mode is a handy way to start using angular, but advanced users who want more
|
||||
control over the initialization process might prefer to use manual bind mode instead.
|
||||
|
||||
The best way to get started with manual bind mode is to look at the magic behind `ng:autobind`
|
||||
by writing out each step of the autobind process explicitly. Note that the following code is
|
||||
equivalent to the code in the previous section.
|
||||
|
||||
<pre>
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<script type="text/javascript" src="http://code.angularjs.org/angular-0.0.0.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
(function(window, previousOnLoad){
|
||||
window.onload = function(){
|
||||
try { (previousOnLoad||angular.noop)(); } catch(e) {}
|
||||
angular.compile(window.document)();
|
||||
};
|
||||
})(window, window.onload);
|
||||
</script>
|
||||
<body>
|
||||
Hello {{'World'}}!
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
This is the sequence that your code should follow if you're writing your own manual binding code:
|
||||
|
||||
* After the page is loaded, find the root of the HTML template, which is typically the root of
|
||||
the document.
|
||||
* Run the HTML compiler, which converts the templates into an executable, bi-directionally
|
||||
bound application.
|
||||
|
||||
|
||||
# XML Namespace
|
||||
|
||||
**IMPORTANT:** When using angular you must declare the `ng` namespace using the `xmlns` tag.
|
||||
If you don't declare the namespace, Internet Explorer does not render widgets properly.
|
||||
|
||||
<pre>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
</pre>
|
||||
|
||||
|
||||
# Create your own namespace
|
||||
|
||||
If you want to define your own widgets, you must create your own namespace and use that namespace
|
||||
to form the fully qualified widget name. For example, you could map the alias my to your domain
|
||||
and create a widget called my:widget. To create your own namespace, simply add another xmlns tag
|
||||
to your page, create an alias, and set it to your unique domain:
|
||||
|
||||
<pre>
|
||||
<html xmlns:my="http://mydomain.com">
|
||||
</pre>
|
||||
|
||||
|
||||
# Global Object
|
||||
|
||||
The angular script creates a single global variable `angular` in the global namespace. All APIs are
|
||||
bound to fields of this global object.
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Compiler
|
||||
@description
|
||||
|
||||
#Compiler
|
||||
|
||||
While angular might look like just a cool way to build web applications, the core of angular is
|
||||
actually an HTML compiler. The default HTML transformations that this compiler provides are useful
|
||||
for building generic apps, but you can also use them to create a domain-specific language for
|
||||
building specific types of web applications.
|
||||
|
||||
The compiler allows you to add behavior to existing HTML through widgets, directives, and text
|
||||
markup.
|
||||
|
||||
All of this compilation happens in the web browser, meaning no server is involved.
|
||||
|
||||
# The compilation process
|
||||
This section describes the steps that angular's HTML compiler goes through. If you use
|
||||
`ng:autobind` in your application, this compilation process happens automatically when the
|
||||
application is initialized (e.g. when the user loads the app in a browser). If you're an advanced
|
||||
user using manual bind mode, you can decide when and how often the compilation happens.
|
||||
|
||||
First, a bit of background of what the compilation step is for. Every type of
|
||||
{@link angular.widget widget}, {@link angular.markup markup}, and
|
||||
{@link angular.directive directive} in angular is defined with a compile function, and that
|
||||
compile function returns an optional link function. Here is the relationship between the two:
|
||||
|
||||
* **compile function** - registers a listener for the widget, markup, or directive's expression.
|
||||
This function is called exactly once.
|
||||
* **link function** - sets up the listener. This function can be called multiple times, once per
|
||||
cloned DOM element (e.g. repeating element).
|
||||
|
||||
Note that angular's built-in widgets, markup, and directives have predefined compile and link
|
||||
functions that you don't need to modify. However, if you're writing your own widgets, markup, or
|
||||
directives, you write compile and link functions. Refer to the Compiler API for more information.
|
||||
|
||||
When the HTML compiler compiles a page, it goes through 3 phases: Compile, Create Root Scope, and
|
||||
Link.
|
||||
|
||||
## 1. Compile Phase
|
||||
|
||||
* Recursively traverse the DOM, depth-first.
|
||||
* Look for a matching compile function of type widget, then markup, then directive.
|
||||
* If a compile function is found then execute it.
|
||||
* When the compile function completes, it should return a link function. Aggregate this link
|
||||
function with all link functions returned previously by step 1c.
|
||||
* Repeat steps 1c and 1d for all compile functions found. The result of the compilation step is
|
||||
the aggregate link function, which comprises all of the individual link functions.
|
||||
|
||||
## 2. Create Root Scope
|
||||
|
||||
* Inject all of the services into the root scope.
|
||||
|
||||
## 3. Link Phase
|
||||
|
||||
* Execute the aggregate link function with the root scope. The aggregate link function calls all
|
||||
the individual link functions that were generated in the compile phase.
|
||||
* If there are any clones of the DOM caused by repeating elements, call the link function multiple
|
||||
times, one for each repeating item.
|
||||
|
||||
Note that while the compile function is executed exactly once, the link function can be executed
|
||||
multiple times: once for each iteration in a repeater.
|
||||
|
||||
# Example
|
||||
|
||||
The compilation process is best understood through example. Let's say that in your namespace my,
|
||||
you want to create a new DOM element <my:greeter/>, which should display a greeting.
|
||||
|
||||
If we want this HTML source:
|
||||
|
||||
<pre>
|
||||
<div ng:init="salutation='Hello'; name='World'">
|
||||
<my:greeter salutation="salutation" name="name"/>
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
To produce this DOM:
|
||||
|
||||
<pre>
|
||||
<div ng:init="salutation='Hello'; name='World'">
|
||||
<my:greeter salutation="salutation" name="name"/>
|
||||
<span class="salutation">Hello</span>
|
||||
<span class="name">World</span>!
|
||||
</my:greeter>
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
Write this widget definition (assuming you've already declared the my namespace in the page):
|
||||
|
||||
|
||||
<pre>
|
||||
angular.widget('my:greeter', function(compileElement){
|
||||
var compiler = this;
|
||||
compileElement.css('display', 'block');
|
||||
var salutationExp = compileElement.attr('salutation');
|
||||
var nameExp = compileElement.attr('name');
|
||||
return function(linkElement){
|
||||
var salutationSpan = angular.element('<span class="salutation"></span');
|
||||
var nameSpan = angular.element('<span class="name"></span>');
|
||||
linkElement.append(salutationSpan);
|
||||
linkElement.append(compiler.text(' '));
|
||||
linkElement.append(nameSpan);
|
||||
linkElement.append(compiler.text('!'));
|
||||
this.$watch(salutationExp, function(value){
|
||||
salutationSpan.text(value);
|
||||
});
|
||||
this.$watch(nameExp, function(value){
|
||||
nameSpan.text(value);
|
||||
});
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
Note: For more about widgets, see {@link angular.widget Widget}.
|
||||
|
||||
## Compilation process for this example
|
||||
|
||||
Here are the steps that the compiler goes through for the page that contains this widget definition:
|
||||
|
||||
### Compile Phase
|
||||
|
||||
* Recursively traverse the DOM depth-first.
|
||||
* Find the angular.widget definition.
|
||||
* Find and execute the widget's compileElement function, which includes the following steps:
|
||||
* Add a style element with attribute display: block; to the template DOM so that the browser
|
||||
knows to treat the element as block element for rendering. (Note: because this style element
|
||||
was added on the template compileElement, this style is automatically applied to any clones
|
||||
of the template (i.e. any repeating elements)).
|
||||
* Extract the salutation and name HTML attributes as angular expressions.
|
||||
* Return the aggregate link function, which includes just one link function in this example.
|
||||
|
||||
### Link Phase
|
||||
|
||||
* Execute the aggregate link function, which includes the following steps:
|
||||
* Create a <span> element set to the salutation class
|
||||
* Create a <span> element set to the name class.
|
||||
* Add the span elements to the linkElement. (Note: be careful not to add them to the
|
||||
compileElement, because that's the template.)
|
||||
* Set up watches on the expressions. When an expression changes, copy the data to the
|
||||
corresponding spans.
|
||||
|
||||
|
||||
## Compiler API
|
||||
|
||||
If you define your own widgets, markup, or directives, you need to access the compiler API.
|
||||
This section describes the methods on the compiler that you can call.
|
||||
|
||||
Note: As of 12 August 2010, these methods are subject to change.
|
||||
|
||||
Recall that the compile function's this is a reference to the compiler.
|
||||
|
||||
* `compile(element)` - returns `linker` - Invoke new instance of compiler to compile a DOM element
|
||||
and return a linker function. You can apply the linker function to the original element or a
|
||||
clone of the original element. The linker function returns a scope.
|
||||
* `comment(commentText)` - returns `element` - Create a comment element.
|
||||
* `element(elementName)` - returns `element` - Create an element by name.
|
||||
* `text(text)` - returns `element` - Create a text element.
|
||||
* `descend([set])` - returns `descend` - State Get or set the current descend state. If true the
|
||||
compiler will descend to children elements.
|
||||
* `directives([set])` - returns `directive` - State Get or set the current directives processing
|
||||
state. The compiler will process directives only when directives set to true.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: CSS
|
||||
@description
|
||||
|
||||
# CSS
|
||||
angular includes built-in CSS classes, which in turn have predefined CSS styles.
|
||||
|
||||
# Built-in CSS classes
|
||||
|
||||
## `ng-exception`
|
||||
|
||||
**Usage:** angular applies this class to a DOM element if that element contains an Expression that
|
||||
threw an exception when evaluated.
|
||||
|
||||
**Styling:** The built-in styling of the ng-exception class displays an error message surrounded
|
||||
by a solid red border, for example:
|
||||
|
||||
> <div class="ng-exception">Error message</div>
|
||||
|
||||
|
||||
|
||||
You can try to evaluate malformed expressions in {@link angualr.expression expression} to see the
|
||||
`ng-exception` class' styling.
|
||||
|
||||
## `ng-validation-error`
|
||||
|
||||
**Usage:** angular applies this class to an input widget element if that element's input does not
|
||||
pass validation. Note that you set the validation criteria on the input widget element using the
|
||||
Ng:validate or Ng:required directives.
|
||||
|
||||
**Styling:** The built-in styling of the ng-validation-error class turns the border of the input
|
||||
box red and includes a hovering UI element that includes more details of the validation error. You
|
||||
can see an example in {@link angular.widget.@ng:validate ng:validate example}.
|
||||
|
||||
## How to override the styles for built-in classes
|
||||
|
||||
To override the styles for these built-in classes, you can do any of the following:
|
||||
|
||||
Download the source code, edit angular.css, and host the source on your own server.
|
||||
Create a local css file, overriding any styles that you'd like, and link to it from your HTML file
|
||||
as you normally would:
|
||||
|
||||
<pre>
|
||||
<link href="yourfile.css" rel="stylesheet" type="text/css">
|
||||
</pre>
|
||||
@@ -0,0 +1,41 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Data Binding
|
||||
@description
|
||||
|
||||
# Data Binding
|
||||
|
||||
Data-binding allows you to treat the model as the single-source-of-truth of your application, and
|
||||
consider the view as only a projection of the model, at all times. The process of copying the model
|
||||
values to the view, and any changes to the view by the user to the model, is known as data-binding.
|
||||
|
||||
## Classical Template Systems
|
||||
|
||||
<img class="right" src="img/One_Way_Data_Binding.png"/>
|
||||
At the highest level, angular looks like a just another templating system. But there is one
|
||||
important reason why angular templating system is different and makes it very good fit for
|
||||
application development: two-way data binding.
|
||||
|
||||
Most templating systems bind data in only one direction: they merge a template and model together
|
||||
into a view, as illustrated in the diagram to the right. After the merge occurs, any changes to
|
||||
the model or in related sections of the view are NOT automatically reflected in the view. Worse,
|
||||
any changes that the user makes to the view are not reflected in the model. This means that the
|
||||
developer has to write code that constantly syncs the view with the model and the model with the
|
||||
view.
|
||||
|
||||
|
||||
# angular Template Systems
|
||||
<img class="right" src="img/Two_Way_Data_Binding.png"/>
|
||||
The way angular templates works is different, as illustrated in the diagram on the right. They are
|
||||
different because first the template (which is the uncompiled HTML along with any additional markup
|
||||
or directives) is compiled on the browser, and second, the compilation step produces a live view.
|
||||
We say live because any changes to the view are immediately reflected in the model, and any changes
|
||||
in the model are propagated to the view. This makes the model always the single-source-of-truth for
|
||||
the application state, greatly simplifying the programing model for the developer. You can think of
|
||||
the view as simply an instant projection of your model.
|
||||
|
||||
Because the view is just a projection of the model, the controller is completely separated from the
|
||||
view and unaware of it. This makes testing a snap because it is easy to test your controller in
|
||||
isolation without the view and the related DOM/browser dependency.
|
||||
|
||||
For details about how data binding works in angular, see {@link angular.scope Scope}.
|
||||
@@ -0,0 +1,304 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Dependency Injection
|
||||
@description
|
||||
Dependency injection (DI) is one of the core design patterns in angular and angular applications. DI
|
||||
allows you to replace almost any part of angular framework or angular application with a custom
|
||||
implementation, allowing for a highly flexible, maintainable and testable code-base.
|
||||
|
||||
Dependency injection is a very common pattern in Java and other statically typed languages. While
|
||||
undervalued among JavaScript developers, we feel strongly that DI in JavaScript allows us to achieve
|
||||
the same benefits as in other languages.
|
||||
|
||||
This document will focus on using dependency injection in angular. It is outside of the scope of
|
||||
this document to explain details of dependency injection. For more information on this topic, please
|
||||
refer to these links:
|
||||
|
||||
* {@link http://en.wikipedia.org/wiki/Dependency_injection DI - Wikipedia}
|
||||
* {@link http://martinfowler.com/articles/injection.html Inversion of Control by Martin Fowler}
|
||||
* Java
|
||||
* {@link http://code.google.com/p/google-guice/ Guice}
|
||||
* {@link http://www.devshed.com/c/a/Java/The-Spring-Framework-Understanding-IoC/ Spring}
|
||||
* {@link http://picocontainer.org/injection.html picoContainer}
|
||||
* .NET
|
||||
* {@link http://msdn.microsoft.com/en-us/magazine/cc163739.aspx MSDN Design Patterns - Dependency Inject}
|
||||
* {@link http://www.springframework.net/ Spring.NET}
|
||||
|
||||
|
||||
|
||||
# Dependency Injection in angular
|
||||
|
||||
Angular's dependency injection story begins with a `service`. Service in angular lingo is a
|
||||
JavaScript object, function, or value that is created by angular's injector via a provided factory
|
||||
function. The factory function is registered with angular via {@link angular.service}.
|
||||
|
||||
<pre>
|
||||
// register a factory for a uniqueId service.
|
||||
angular.service('uniqueId', function(){
|
||||
// calling the factory function creates the instance function
|
||||
var id = 0;
|
||||
return function(){
|
||||
// calling the counter instance function will return and increment the count
|
||||
return ++id;
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
At run-time we can access the `uniqueId` service by looking it up with the service locator like
|
||||
this:
|
||||
|
||||
<pre>
|
||||
// create new root scope which has the injector function `$service()`
|
||||
var scope = angular.scope();
|
||||
|
||||
// use the `$service` function to look up the service instance function
|
||||
var idGenerator = scope.$service('uniqueId');
|
||||
expect(idGenerator()).toBe(1);
|
||||
|
||||
// subsequent lookups using the same root scope return the service instance
|
||||
var idGenerator2 = scope.$service('uniqueId');
|
||||
expect(idGenerator).toBe(idGenerator2);
|
||||
|
||||
// since it is same instance calling idGenerator2 returns 2;
|
||||
expect(idGenerator2()).toBe(2);
|
||||
</pre>
|
||||
|
||||
The {@link angular.service service} registry seems like a lot of work, so what are the benefits? To
|
||||
answer this question, it’s important to realize that in large scale applications there are a lot of
|
||||
services which are often dependent on each other, as in this example:
|
||||
|
||||
<pre>
|
||||
angular.service('gadgetFactory', function(uniqueId){
|
||||
return function(){
|
||||
return {gadgetId: uniqueId()};
|
||||
};
|
||||
}, {$inject: ['uniqueId']});
|
||||
</pre>
|
||||
|
||||
Specifically, notice that the `gadgetFactory` takes `uniqueId` service in its arguments. It also
|
||||
declares this dependency with the `$inject` property. There are several benefits to this approach:
|
||||
|
||||
* There is no need for a `main` method for an application responsible for instantiating and wiring
|
||||
these services. The order of service instantiation and wiring can be inferred by examining the
|
||||
`$inject` annotations.
|
||||
* It is easy to replace any one service with a different implementation without having to track down
|
||||
all of the dependencies. This is useful in:
|
||||
* Tests: when mocks of services are needed (for example using mock {@link angular.service.$xhr}.)
|
||||
* Customization: when the service bundled with angular does not do exactly what the application
|
||||
requires.
|
||||
|
||||
More importantly, as we'll soon learn, controllers and other components of angular applications can
|
||||
also declare their dependencies on services and these will be provided without explicitly looking
|
||||
them up, but let's not get ahead of ourselves.
|
||||
|
||||
Lastly, it is important to realize that all angular services are singletons – application singletons
|
||||
to be more precise. This means that there is only one instance of a given service per injector. And
|
||||
since angular is lethally allergic to the global state, it's absolutely possible to create multiple
|
||||
injectors each with its own instance of a given service (but that is not typically needed, except in
|
||||
tests where this property is crucially important).
|
||||
|
||||
|
||||
## Service Locator and Scope
|
||||
|
||||
The {@link angular.injector injector} is responsible for resolving the service dependencies in the
|
||||
application. It gets created and configured with the creation of a root scope in your application.
|
||||
The injector is responsible for caching the instances of services, but this cache is bound to the
|
||||
scope. This means that different root scopes will have different instances of the injector. While
|
||||
typical angular applications will only have one root scope (and hence the services will act like
|
||||
application singletons), in tests it is important to not share singletons across test invocations
|
||||
for isolation reasons. We get this isolation by having each test create its own separate root scope.
|
||||
|
||||
<pre>
|
||||
// crate a root scope
|
||||
var rootScope = angular.scope();
|
||||
// accesss the service locator
|
||||
var myService = rootScope.$service('myService');
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
# Dependency Injection in Controllers
|
||||
|
||||
So far we have been talking about injector as a service locator. This is because we have been
|
||||
explicitly calling the `$service` method to gain access to the service. Service locator is not
|
||||
dependency injection since the caller is still responsible for retrieving the dependencies. *True
|
||||
dependency injection is like Chuck Norris. Chuck does not ask for dependencies; he declares them.*
|
||||
|
||||
The most common place to use dependency injection in angular applications is in
|
||||
{@link angular.ng:controller controllers}. Here’s a simple example:
|
||||
|
||||
<pre>
|
||||
function MyController($route){
|
||||
// configure the route service
|
||||
$route.when(...);
|
||||
}
|
||||
MyController.$inject = ['$route'];
|
||||
</pre>
|
||||
|
||||
In this example, the `MyController` constructor function takes one argument, the
|
||||
{@link angular.service.$route $route} service. Angular is then responsible for supplying the
|
||||
instance of `$route` to the controller when the constructor is instantiated. There are two ways to
|
||||
cause controller instantiation – by configuring routes with the $route service or by referencing the
|
||||
controller from the HTML template, such as:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" ng:controller="MyController">
|
||||
<script src="http://code.angularjs.org/angular.min.js" ng:autobind></script>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
When angular is instantiating your controller, it needs to know what services, if any, should be
|
||||
injected (passed in as arguments) into the controller. Since there is no reflection in JavaScript,
|
||||
we have to supply this information to angular in the form of an additional property on the
|
||||
controller constructor function called `$inject`. Think of it as annotations for JavaScript.
|
||||
|
||||
<pre>
|
||||
MyController.$inject = ['$route'];
|
||||
</pre>
|
||||
|
||||
The information in `$inject` is then used by the {@link angular.injector injector} to call the
|
||||
function with the correct arguments.
|
||||
|
||||
|
||||
|
||||
# Using Dependency Injection pragmatically
|
||||
|
||||
At times you’ll need to use dependency injection pragmatically, usually when instantiating
|
||||
controllers manually or writing unit tests. This section explains how to go about it.
|
||||
|
||||
## Retrieving Services
|
||||
|
||||
The simplest form of dependency injection is manual retrieval of scopes, known as service locator.
|
||||
We say manual because we are asking the injector for an instance of the service (rather then having
|
||||
the injector provide them to the function). This should be rare since most of the time the dependent
|
||||
services should be injected into the controller using the `$inject` property array.
|
||||
|
||||
<pre>
|
||||
// create a root scope. The root scope will automatically have
|
||||
// `$service` method defined which is configured with all services.
|
||||
// Each instance of root scope will have separate instances of services.
|
||||
var rootScope = angular.scope();
|
||||
|
||||
// ask for a service explicitly
|
||||
var $window = rootScope.$service('$window');
|
||||
</pre>
|
||||
|
||||
|
||||
## Creating Controllers using Dependency Injection
|
||||
|
||||
In a typical angular application the dependency injection is most commonly used when creating
|
||||
controllers.
|
||||
<pre>
|
||||
// declare our own service by registering a factory function.
|
||||
angular.service('counter', function(){
|
||||
var count = 0;
|
||||
return function(){ return count++; };
|
||||
});
|
||||
|
||||
// example of a controller which depends on '$window' and 'counter' service
|
||||
// notice that there is an extra unbound parameter 'name' which will not
|
||||
// be injected and must be supplied by the caller.
|
||||
function MyController($window, counter, name) {
|
||||
}
|
||||
|
||||
// we must declare the dependencies explicitly and in the same order as in
|
||||
// the constructor function. This information is used by the dependency
|
||||
// injection to supply the arguments.
|
||||
// Notice the lack of 'name' argument which makes it an unbound argument.
|
||||
MyController.$inject = ['$window', 'counter'];
|
||||
|
||||
|
||||
// Create a root scope which creates the the injector
|
||||
var rootScope = angular.scope();
|
||||
|
||||
// use the '$new()' method instead of standard 'new' keyword operator to
|
||||
// create an instance of MyController and have the dependency injection
|
||||
// supply the arguments to the controller. The dependency injection only
|
||||
// supplies the bound arguments in `$inject` all addition arguments are
|
||||
// curried from the '$new', in our case 'Alexandria' is the argument which
|
||||
// will be curried to the 'name' argument, while '$window' and 'counter'
|
||||
// are supplied by the dependency injection.
|
||||
var myController = rootScope.$new(MyController, 'Alexandria');
|
||||
// NOTE: the returning controller will be a child scope of parent scope,
|
||||
// in this case the root scope.
|
||||
</pre>
|
||||
|
||||
|
||||
## Calling functions and Curring of arguments
|
||||
|
||||
NOTE: this section is quite lame. The concept it is trying to describe is more closely related to
|
||||
scope#new than scope#$service. We need a better example to discuss here. Ideally a parent controller
|
||||
creating a child controller imperatively via $new where the child controller's constructor function
|
||||
declares a portion of its dependencies via $inject property, but another portion is supplied by the
|
||||
caller of $new (e.g. parentCtrl.$new(ChildCtrl, configParam1, configParam2);
|
||||
|
||||
Finally, you may need to call functions but have the `$inject` properties of the function be
|
||||
supplied by the injector.
|
||||
|
||||
<pre>
|
||||
// create a root scope with the `$service` injector.
|
||||
var rootScope = angular.scope();
|
||||
|
||||
// given a function such as
|
||||
function greet ($window, name) {
|
||||
$window.alert(this.salutation + ' ' + name);
|
||||
}
|
||||
greet.$inject = ['$window'];
|
||||
|
||||
// you can call function 'greet' such that the injector supplies the
|
||||
// '$window' and the caller supplies the function 'this' and the 'name'
|
||||
// argument.
|
||||
var fnThis = {salutation: 'Hello'}
|
||||
rootScope.$service(greet, fnThis, 'world');
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
# Inferring `$inject`
|
||||
|
||||
**EXPERIMENTAL: this is an experimental feature, see the important note at the end of this section
|
||||
for drawbacks.**
|
||||
|
||||
We resort to `$inject` and our own annotation because there is no way in JavaScript to get a list of
|
||||
arguments. Or is there? It turns out that calling `.toString()` on a function returns the function
|
||||
declaration along with the argument names as shown below:
|
||||
|
||||
<pre>
|
||||
function myFn(a,b){}
|
||||
expect(myFn.toString()).toEqual('function myFn(a,b){}');
|
||||
</pre>
|
||||
|
||||
This means that angular can infer the function names after all and use that information to generate
|
||||
the `$inject` annotation automatically. Therefore the following two function definitions are
|
||||
equivalent:
|
||||
|
||||
<pre>
|
||||
// given a user defined service
|
||||
angular.service('serviceA', ...);
|
||||
|
||||
// inject '$window', 'serviceA', curry 'name';
|
||||
function fnA($window, serviceA, name){};
|
||||
fnA.$inject = ['$window', 'serviceA'];
|
||||
|
||||
// inject '$window', 'serviceA', curry 'name';
|
||||
function fnB($window, serviceA_, name){};
|
||||
// implies: fnB.$inject = ['$window', 'serviceA'];
|
||||
</pre>
|
||||
|
||||
If angular does not find an `$inject` annotation on the function, then it calls the `.toString()`
|
||||
and tries to infer what should be injected using the following rules:
|
||||
|
||||
* any argument starting with `$` is angular service and will be added to `$inject` property array.
|
||||
* any argument ending with `_` will be added to the `$inject` property array but we strip the `_`
|
||||
* all arguments following an argument which has neither `$` nor `_` , must not have `$` nor `_`
|
||||
(these are free arguments for {@link http://en.wikipedia.org/wiki/Currying curring})
|
||||
|
||||
**IMPORTANT**
|
||||
Minifiers/obfuscators change the names of function arguments and will therefore break the `$inject`
|
||||
inference. For this reason, either explicitly declare the `$inject` or do not use
|
||||
minifiers/obfuscators. In the future, we may provide a pre-processor which will scan the source code
|
||||
and insert the `$inject` into the source code so that it can be minified/obfuscated.
|
||||
@@ -0,0 +1,207 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Expression
|
||||
@description
|
||||
|
||||
# Expressions
|
||||
Expressions are the bindings that you write in HTML and embed in templates in order to create
|
||||
views in angular. They are not equivalent to JavaScript expressions.
|
||||
|
||||
For example, these are all valid expressions in angular:
|
||||
|
||||
* `1+2={{1+2}}`
|
||||
* `3*10|currency`
|
||||
* `Hello {{name}}!`
|
||||
* `Hello {{'World'}}!`
|
||||
|
||||
|
||||
# angular expressions vs. JS expressions
|
||||
It might be tempting to think of angular view expressions as JavaScript expressions, but that is
|
||||
not entirely correct. angular does not use a simple JavaScript eval of the expression text. You
|
||||
can think of angular expressions as JavaScript expressions with these differences:
|
||||
|
||||
* **Attribute Evaluation:** evaluation of all attributes are against the current scope, not to
|
||||
the global window as in JavaScript.
|
||||
* **Forgiving:** expression evaluation is forgiving to undefined and null, unlike in JavaScript.
|
||||
* **No Control Flow Statements:** you cannot do the following from an angular expression:
|
||||
conditionals, loops, or throw.
|
||||
* **Type Augmentation:** the scope expression evaluator augments built-in types.
|
||||
* **Filters:** you can add filters to an expression, for example to convert raw data into a
|
||||
human-readable format.
|
||||
* **The $:** angular reserves this prefix to differentiate its API names from others.
|
||||
|
||||
If you want to run arbitrary JavaScript code, make it a controller method and call that. If you
|
||||
want to eval an angular expression from JavaScript, use the Scope:$eval() method.
|
||||
|
||||
## Example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
1+2={{1+2}}
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should calculate expression in binding', function(){
|
||||
expect(binding('1+2')).toEqual('3');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
You can try evaluating different expressions here:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="exprs=[]" class="expressions">
|
||||
Expression:
|
||||
<input type='text' name="expr" value="3*10|currency" size="80"/>
|
||||
<button ng:click="exprs.$add(expr)">Evaluate</button>
|
||||
<ul>
|
||||
<li ng:repeat="expr in exprs">
|
||||
[ <a href="" ng:click="exprs.$remove(expr)">X</a> ]
|
||||
<tt>{{expr}}</tt> => <span ng:bind="$parent.$eval(expr)"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should allow user expression testing', function(){
|
||||
element('.expressions :button').click();
|
||||
var li = using('.expressions ul').repeater('li');
|
||||
expect(li.count()).toBe(1);
|
||||
expect(li.row(0)).toEqual(["3*10|currency", "$30.00"]);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
# Attribute Evaluation
|
||||
|
||||
Evaluation of all attributes are against the current scope. Unlike JavaScript, where names
|
||||
default to global window properties, angular expressions have to use $window to refer to the
|
||||
global object. E.g. if you want to call alert(), which is defined on window, an expression must
|
||||
use $window.alert(). This is done intentionally to prevent accidental access to the global state
|
||||
(a common source of subtle bugs).
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div class="example2" ng:init="$window = $service('$window')">
|
||||
Name: <input name="name" type="text" value="World"/>
|
||||
<button ng:click="($window.mockWindow || $window).alert('Hello ' + name)">Greet</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should calculate expression in binding', function(){
|
||||
var alertText;
|
||||
this.addFutureAction('set mock', function($window, $document, done) {
|
||||
$window.mockWindow = {
|
||||
alert: function(text){ alertText = text; }
|
||||
};
|
||||
done();
|
||||
});
|
||||
element(':button:contains(Greet)').click();
|
||||
expect(this.addFuture('alert text', function(done) {
|
||||
done(null, alertText);
|
||||
})).toBe('Hello World');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
## Forgiving
|
||||
|
||||
Expression evaluation is forgiving to undefined and null. In JavaScript, evaluating a.b.c throws
|
||||
an exception if a is not an object. While this makes sense for a general purpose language, the
|
||||
expression evaluations are primarily used for data binding, which often look like this: `{{a.b.c}}`.
|
||||
It makes more sense to show nothing than to throw an exception if a is undefined (e.g. perhaps
|
||||
we are waiting for the server response, and it will become defined soon). If expression
|
||||
evaluation wasn't forgiving we'd have to write bindings that clutter the code, for example:
|
||||
`{{((a||{}).b||{}).c}}`
|
||||
|
||||
Similarly, invoking a function a.b.c() on undefined or null simply returns undefined.
|
||||
|
||||
Assignments work the same way in reverse. a.b.c = 10 creates the intermediary objects even if a
|
||||
is undefined.
|
||||
|
||||
|
||||
## No Control Flow Statements
|
||||
|
||||
You cannot write a control flow statement in an expression. The reason behind this is core to
|
||||
the angular philosophy that application logic should be in controllers, not in the view. If you
|
||||
need a conditional (including ternary operators), loop, or to throw from a view expression,
|
||||
delegate to a JavaScript method instead.
|
||||
|
||||
|
||||
## Type Augmentation
|
||||
|
||||
Built-in types have methods like [].push(), but the richness of these methods is limited. Consider
|
||||
the example below, which allows you to do a simple search over a canned set of contacts. The
|
||||
example would be much more complicated if we did not have the Array:$filter(). There is no
|
||||
built-in method on Array called $filter and angular doesn't add it to Array.prototype because that
|
||||
could collide with other JavaScript frameworks.
|
||||
|
||||
For this reason the scope expression evaluator augments the built-in types to make them act like
|
||||
they have extra methods. The actual method for $filter() is angular.Array.filter(). You can call
|
||||
it from JavaScript.
|
||||
|
||||
Extensions: You can further extend the expression vocabulary by adding new methods to
|
||||
`angular.Array` or `angular.String`, etc.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="friends = [
|
||||
{name:'John', phone:'555-1212'},
|
||||
{name:'Mary', phone:'555-9876'},
|
||||
{name:'Mike', phone:'555-4321'},
|
||||
{name:'Adam', phone:'555-5678'},
|
||||
{name:'Julie', phone:'555-8765'}]"></div>
|
||||
Search: <input name="searchText"/>
|
||||
<table class="example3">
|
||||
<tr><th>Name</th><th>Phone</th><tr>
|
||||
<tr ng:repeat="friend in friends.$filter(searchText)">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should filter the list', function(){
|
||||
var tr = using('table.example3').repeater('tr.ng-attr-widget');
|
||||
expect(tr.count()).toBe(5);
|
||||
input('searchText').enter('a');
|
||||
expect(tr.count()).toBe(2);
|
||||
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
## Filters
|
||||
|
||||
When presenting data to the user, you might need to convert the data from its raw format to a
|
||||
user-friendly format. For example, you might have a data object that needs to be formatted
|
||||
according to the locale before displaying it to the user. You can pass expressions through a
|
||||
chain of filters like this:
|
||||
|
||||
<pre>
|
||||
name | uppercase
|
||||
</pre>
|
||||
|
||||
The expression evaluator simply passes the value of name to angular.filter.uppercase.
|
||||
|
||||
Chain filters using this syntax:
|
||||
|
||||
<pre>
|
||||
value | filter1 | filter2
|
||||
</pre>
|
||||
|
||||
You can also pass colon-delimited arguments to filters, for example, to display the number 123
|
||||
with 2 decimal points: 123 | number:2
|
||||
|
||||
# The $
|
||||
|
||||
You might be wondering, what is the significance of the $ prefix? It is simply a prefix that
|
||||
angular chooses to differentiate its API names from others. If angular didn't use $, then
|
||||
evaluating a.length() would return undefined because neither a nor angular define such a property.
|
||||
Consider that in a future version of angular we might choose to add a length method, in which case
|
||||
the behavior of the expression would change. Worse yet, you the developer could create a length
|
||||
property and then we would have collision. This problem exists because angular augments existing
|
||||
objects with additional behavior. By prefixing its additions with $ we are reserving our namespace
|
||||
so that angular developers and developers who use angular can develop in harmony without
|
||||
collisions.
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide
|
||||
@description
|
||||
|
||||
* {@link guide.overview Overview} - An overview of angular, including its philosophy and how it
|
||||
works.
|
||||
* {@link guide.bootstrap Bootstrap} - How to bootstrap your application to the angular environment.
|
||||
* {@link guide.template Template} - How to define your application's view using HTML, CSS, and
|
||||
other built-in angular constructs.
|
||||
* {@link guide.compiler Compiler} - All about the HTML compiler that's at the core of angular.
|
||||
* {@link angular.directive Directive} - How to use XML attributes to augment an existing DOM
|
||||
element.
|
||||
* {@link angular.markup Markup} - How to use markup to create shorthand for a widget or a
|
||||
directive. For example, markup is what allows you to use the double curly brace notation
|
||||
`{{}}` to bind expressions to elements.
|
||||
* {@link guide.data-binding Data Binding} - About the mechanism that keeps the model the single
|
||||
source of truth of your application at all times, with the view as a live projection of the
|
||||
model.
|
||||
* {@link angular.filter Filter} - How to format your data for display to the user.
|
||||
* {@link angular.widget Widget} - How to create new DOM elements that the browser doesn't already
|
||||
understand.
|
||||
* {@link angular.validator Validator} - How to validate user input.
|
||||
* {@link angular.formatter Formatter} - How to format stored data to user-readable text and
|
||||
parse the text back to the stored form.
|
||||
* {@link guide.css CSS} - Built-in CSS classes, when angular assigns them, and how to override
|
||||
their styles.
|
||||
* {@link angular.scope Scope} - The model in the model-view-controller design pattern. You can
|
||||
think about scopes as the JavaScript objects that have extra APIs for registering watchers.
|
||||
* {@link guide.expression Expression} - The bindings that are embedded in an angular View.
|
||||
* {@link angular.service Service} - Objects that are wired through dependency injection and then
|
||||
injected into the root scope.
|
||||
* {@link guide.testing Testing}
|
||||
* service:$browser(mock)
|
||||
* {@link downloading Downloading} - How to download, compile, and host the angular
|
||||
environment on your own server.
|
||||
* {@link guide.contribute Contributing} - How to contribute to angular project.
|
||||
@@ -0,0 +1,337 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Overview
|
||||
@description
|
||||
|
||||
|
||||
* <a href="#H1_1">What Is Angular?</a>
|
||||
* <a href="#H1_3">The Angular Philosophy</a>
|
||||
* <a href="#H1_2">Anatomy Of An Angular App</a>
|
||||
* <a href="#H1_4">Why You Want Angular</a>
|
||||
* <a href="#H1_5">Angular's Ancestors</a>
|
||||
* <a href="#H1_6">Watch a Presentation About Angular</a>
|
||||
|
||||
|
||||
<a name="H1_1"></a>
|
||||
# What Is Angular?
|
||||
|
||||
The short answer: angular is a new, powerful, client-side technology that makes it much easier for
|
||||
you to create dynamic web sites and complex web apps, all without leaving the comfort of your HTML
|
||||
/ JavaScript home.
|
||||
|
||||
The long answer: it kind of depends on where you're coming from...
|
||||
|
||||
* If you're a web designer, you might perceive angular to be a sweet {@link guide.template
|
||||
templating} system, that doesn't get in your way and provides you with lots of nice built-ins that
|
||||
make it easier to do what you want to do.
|
||||
|
||||
* If you're a web developer, you might be thrilled that angular functions as an excellent web
|
||||
framework, one that assists you all the way through the development cycle.
|
||||
|
||||
* If you want to go deeper, you can immerse yourself in angular's extensible HTML {@link
|
||||
guide.compiler compiler} that runs in your browser. This compiler teaches your browser new tricks.
|
||||
|
||||
So then, angular's not just a templating system, but you can create fantastic templates with it;
|
||||
angular's not just a web framework, but it has a very nice one; and angular's not just an
|
||||
extensible HTML compiler, but it has one of those too. Let's put it this way: angular includes
|
||||
these parts along with some others; it evolved naturally from earlier occurrences of these forms;
|
||||
and thus angular is something far greater than the sum of its parts. It sounds like... it's alive!
|
||||
|
||||
## An Intro By Way of Example
|
||||
|
||||
Let's say that you are a web designer, and you've spent many thous — erm, hundreds of hours
|
||||
designing web sites. But at this point, the thought of doing DOM updates, writing listeners, and
|
||||
writing input validators, all to do something as simple as implementing a form!? You either don't
|
||||
want to go there in the first place or you've been there and the thrill is gone.
|
||||
|
||||
You could even be muttering to yourself as you hack another callback, "This is like building my own
|
||||
bike from scratch every time I want to ride to the store." But let's say a clever friend, who keeps
|
||||
tabs on these sorts of things, told you to check out angular.
|
||||
|
||||
So now here you are checking out angular, and here is a simple example. Note that it features only
|
||||
the templating aspect of angular, but this should suffice for now to quickly demonstrates how much
|
||||
easier life can be with angular:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<h2>Bigg Bike Shop</h2>
|
||||
<hr>
|
||||
<b>Invoice:</b>
|
||||
<br/>
|
||||
<br/>
|
||||
<table>
|
||||
<tr><td> </td><td> </td>
|
||||
<tr><td>Quantity</td><td>Cost</td></tr>
|
||||
<tr>
|
||||
<td><input name="qty" value="1" ng:validate="integer:0" ng:required/></td>
|
||||
<td><input name="cost" value="19.95" ng:validate="number" ng:required/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr>
|
||||
<b>Total:</b> {{qty * cost | currency}}
|
||||
<hr>
|
||||
</doc:source>
|
||||
<!--
|
||||
<doc:scenario>
|
||||
it('should show of angular binding', function(){
|
||||
expect(binding('qty * cost')).toEqual('$19.95');
|
||||
input('qty').enter('2');
|
||||
input('cost').enter('5.00');
|
||||
expect(binding('qty * cost')).toEqual('$10.00');
|
||||
});
|
||||
</doc:scenario>
|
||||
-->
|
||||
</doc:example>
|
||||
|
||||
Go ahead, try out the Live Preview above. "Well I _declare_! It's a fully functioning form, with
|
||||
an instantly updating display, and input validation." Speaking of being declarative, let's walk
|
||||
through the example and look at the angular-related lines to see what's going on around here.
|
||||
|
||||
In line __2__ of the example, we let the browser know about the angular namespace:
|
||||
|
||||
2 <html xmlns:ng="http://angularjs.org">
|
||||
|
||||
This ensures angular runs nicely in all major browsers.
|
||||
|
||||
In line __3__ we do two angular setup tasks inside a `<script>` tag:
|
||||
|
||||
1. We pull in `angular.js`.
|
||||
2. The angular {@link angular.directive.ng:autobind ng:autobind} directive tells angular to {@link
|
||||
guide.compiler compile} and manage the whole HTML document.
|
||||
|
||||
3 <script src="file:///Users/krculp/angular.js/build/angular.min.js" ng:autobind></script>
|
||||
|
||||
Lines __14__ and __15__ set up one side of angular's very cool two-way data binding, as well as
|
||||
demonstrate some easy input validation:
|
||||
|
||||
14 Quantity: <input name="qty" value="1" ng:validate="integer:0" ng:required/>
|
||||
15 Cost: <input name="cost" value="199.95" ng:validate="number" ng:required/>
|
||||
|
||||
These input widgets look normal enough, but consider these points:
|
||||
|
||||
* Remember the `ng:autobind` directive from line 3? When this page loaded, angular bound the names
|
||||
of the input widgets (`qty` and `cost`) to variables of the same name. Think of those variables as
|
||||
the "Model" part of the Model-View-Controller design pattern.
|
||||
* Note the angular directives, {@link angular.widget.@ng:validate ng:validate} and {@link
|
||||
ngular.widget.@ng:required ng:required}. You may have noticed that when you enter invalid data or
|
||||
leave the the input fields blank, the borders turn a plainly irritated red color, and the display
|
||||
value disappears. These `ng:` directives make it easier to implement field validators than coding
|
||||
them in JavaScript, no? Yes.
|
||||
|
||||
And finally, the mysterious line #__19__:
|
||||
|
||||
19 Total: {{qty * cost | currency}}
|
||||
|
||||
What's with the curly braces? Those curly braces are your friend. This notation, `{{ _expression_
|
||||
}}`, is a bit of built-in angular {@link angular.markup markup}, a shortcut that you use to display
|
||||
data. The expression within curly braces gets transformed by the angular compiler into an angular
|
||||
directive ({@link angular.directive.ng:bind ng:bind}). The expression itself can be a combination
|
||||
of both an expression and a {@link angular.filter filter}: `{{ expression | filter }}`.
|
||||
|
||||
In our example above, we're saying, "Bind the data we got from the input widgets to the display,
|
||||
multiply them together, and format the resulting number into something that looks like money."
|
||||
|
||||
|
||||
<a name="H1_3"></a>
|
||||
# The Angular Philosophy
|
||||
|
||||
Angular is built around the belief that declarative code is better than imperative when it comes to
|
||||
building UIs and wiring software components together, while imperative code is clearly the way to
|
||||
go for expressing business logic.
|
||||
|
||||
Not to put too fine a point on it, but if you wanted to add a new label to your application, you
|
||||
could do it by simply adding text to the HTML template, saving the code, and refreshing your
|
||||
browser (this here is declarative):
|
||||
|
||||
<pre>
|
||||
<span class="label">Hello</span>
|
||||
</pre>
|
||||
|
||||
Or, as In programmatic systems (like {@link http://code.google.com/webtoolkit/ GWT}), you would
|
||||
have to write the code and then run the code like this:
|
||||
|
||||
<pre>
|
||||
var label = new Label();
|
||||
label.setText('Hello');
|
||||
label.setClass('label');
|
||||
parent.addChild(label);
|
||||
</pre>
|
||||
|
||||
That looks like, let's see, do some math, factor out the `<pre>`s, carry the one, ummm... a little
|
||||
bit of markup versus four times as much code.
|
||||
|
||||
More Angular Philosophy:
|
||||
|
||||
* It is a very good idea to decouple DOM manipulation from app logic. This dramatically improves
|
||||
the testability of the code.
|
||||
* It is a really, _really_ good idea to regard app testing as equal in importance to app writing.
|
||||
Testing difficulty is dramatically affected by the way the code is structured.
|
||||
* It is an excellent idea to decouple the client side of an app from the server side. This allows
|
||||
development work to progress in parallel, and allows for reuse of both sides.
|
||||
* It is very helpful indeed if the framework guides developers through the entire journey of
|
||||
building an app: from designing the UI, through writing the business logic, to testing.
|
||||
* It is always good to make common tasks trivial and difficult tasks possible.
|
||||
|
||||
Now that we're homing in on what angular is, perhaps now would be a good time to list a few things
|
||||
what angular isn't:
|
||||
|
||||
* It's not a Library. You don't just call its functions, although it does provide you with some
|
||||
utility APIs.
|
||||
* It's not a DOM Manipulation Library. angular uses jQuery to manipulate the DOM behind the scenes,
|
||||
rather than give you functions to manipulate the DOM with yourself.
|
||||
* It's not a Widget Library. There are lots of existing widget libraries that you can integrate
|
||||
with angular.
|
||||
* It's not "Just Another Templating System". A part of angular is a templating system. The
|
||||
templating subsystem of angular is different from the traditional approach for these reasons:
|
||||
* It Uses HTML/CSS syntax: This makes it easy to read and can be edited with existing HTML/CSS
|
||||
authoring tools.
|
||||
* It Extends HTML vocabulary: Angular allows you to create new HTML tags, which expand into
|
||||
dynamic UI components.
|
||||
* It Executes in the browser: Removes the round trip to the server for many operations and
|
||||
creates instant feedback for users as well as developers.
|
||||
* It Has Bidirectional data binding: The model is the single source of truth. Programmatic
|
||||
changes to the model are automatically reflected in the view. Any changes by the user to the view
|
||||
are automatically reflected in the model.
|
||||
|
||||
|
||||
<a name="H1_2"></a>
|
||||
# Anatomy Of An Angular App
|
||||
|
||||
This section describes the parts of an angular app in more detail.
|
||||
|
||||
## Templates
|
||||
|
||||
{@link guide.template Templates} are the part of angular that makes it easy and fun to create the
|
||||
UI for your web apps. With angular's templates you can create a dynamic UI using only HTML and
|
||||
CSS, but now you can add your own elements, attributes, and markup. The angular compiler reads the
|
||||
"angularized" HTML when your page loads, and follows the instructions in there to generate a
|
||||
dynamic page for you. This is the View part of MVC. "But wait there's more": since the compiler is
|
||||
extensible, you can build your own declarative language on top of HTML!
|
||||
|
||||
## Application Logic and Behavior
|
||||
|
||||
Application Logic and Behavior, which you define in JavaScript, is the C in MVC. With angular you
|
||||
write the logic (the controllers) for your app, but because angular takes care of reflecting the
|
||||
state of the model in the view, you don't have to write listeners or DOM manipulators. This feature
|
||||
makes your application logic very easy to write, test, maintain, and understand.
|
||||
|
||||
## Data
|
||||
|
||||
In an angular app, all of your data is referenced from inside of a {@link angular.scope scope}.
|
||||
The scope is the data Model, the M in the MVC pattern. A scope is a JavaScript object that has
|
||||
watcher functions that keep tabs on the data that is referenced from that scope. The data could be
|
||||
one or more Javascript objects, arrays, or primitives, it doesn't matter. What matters is that
|
||||
these are all referenced by the scope.
|
||||
|
||||
This "scope thing" is how angular takes care of keeping your data model and your UI in sync.
|
||||
Whenever something occurs to change the state of the scope, angular immediately reflects that
|
||||
change in the UI, and vice versa.
|
||||
|
||||
In addition to the three components described above (the MVC bits), angular comes with a set of
|
||||
{@link angular.service Services} that are very helpful for building web apps. The services include
|
||||
the following features:
|
||||
|
||||
* You can extend and add application-specific behavior to services.
|
||||
* Services include Dependency-Injection, XHR, caching, URL routing, and browser abstraction.
|
||||
|
||||
The following illustration shows the parts of an angular application and how they work together:
|
||||
|
||||
<img class="left" src="img/angular_parts.png" border="0" />
|
||||
|
||||
|
||||
<a name="H1_4"></a>
|
||||
# Why You Want Angular
|
||||
|
||||
Angular frees you from the following pain:
|
||||
|
||||
* **Registering callbacks:** Registering callbacks clutters your code, making it hard to see the
|
||||
forest for the trees. Removing common boilerplate code such as callbacks is a good thing. It vastly
|
||||
reduces the amount of JavaScript coding _you_ have to do, and it makes it easier to see what your
|
||||
application does.
|
||||
* **Manipulating HTML DOM programatically:** Manipulating HTML DOM is a cornerstone of AJAX
|
||||
applications, but it's cumbersome and error-prone. By declaratively describing how the UI should
|
||||
change as your application state changes, you are freed from low level DOM manipulation tasks. Most
|
||||
applications written with angular never have to programatically manipulate the DOM, although you
|
||||
can if you want to, knock yourself out.
|
||||
* **Marshaling data to and from the UI:** CRUD operations make up the majority of AJAX
|
||||
applications. The flow of marshaling data from the server to an internal object to an HTML form,
|
||||
allowing users to modify the form, validating the form, displaying validation errors, returning to
|
||||
an internal model, and then back to the server (gah!) creates a lot of boilerplate code. Angular
|
||||
eliminates almost all of this boilerplate, leaving code that describes the overall flow of the
|
||||
application rather than all of the implementation details.
|
||||
* **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 {@link
|
||||
http://code.google.com/p/google-guice/ 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.
|
||||
|
||||
|
||||
<a name="H1_5"></a>
|
||||
# Angular's Ancestors
|
||||
|
||||
Where does angular come from? What events led to the inevitability of the appearance of something
|
||||
like angular?
|
||||
|
||||
## First There Was HTML
|
||||
|
||||
HTML was initially designed long, long ago, in the great year of 1989, with the intention to create
|
||||
a markup language for sharing scientific documents over the network. Yes, yes, certainly there was
|
||||
SGML even before that, but it was so difficult that even esteemed scientists balked at using it.
|
||||
Thankfully, Tim Berners-Lee saved all of us from that pain with his much friendlier HTML.
|
||||
`<HTML><BODY>Thank You, TB-L!</BODY></HTML>`.
|
||||
|
||||
## Then There Was JavaScript
|
||||
|
||||
Fast forward to 1995: JavaScript was invented. This was done with the best of intentions! But in
|
||||
practice it initially served mainly to annoy Internet users with cheap effects that "enhanced"
|
||||
static HTML documents.
|
||||
|
||||
Fast forward to the mid 2000s, when a new breed of back-then-considered-rich web applications
|
||||
started to appear on the web. These were built with HTML, JavaScript, and CSS, and featured less
|
||||
annoying and more impressive effects. Can you recall the first time you saw apps like Gmail, or
|
||||
Google Maps, and you couldn't believe everything that was going on in the browser?
|
||||
|
||||
## And JavaScript Prevailed
|
||||
|
||||
As of this writing, in 2011, people are building still richer and more interactive web applications
|
||||
that often rival their desktop counterparts. And yet they are essentially still working with
|
||||
technology and programming primitives that were used decades ago for the creation of static
|
||||
documents with cheap graphic effects. At the same time, the web is HUGE now, and we
|
||||
can't just abandon the technologies it was built with. Applets, Flash and Silverlight tried it, and
|
||||
in some ways succeeded. Yet many would argue that in reality they failed, because they tried to
|
||||
work _around_ the web instead of working _with_ it.
|
||||
|
||||
## And Then There Was Angular
|
||||
|
||||
Angular recognizes the strengths of the existing "static" web technologies, as well as their
|
||||
deficiencies. At the same time, angular is learning from the failures of other technologies that
|
||||
tried, or are trying, to work around the web.
|
||||
|
||||
For these reasons angular plays to the strengths of established web technologies, instead of
|
||||
bypassing them. Angular sets out the goal of increasing the abstraction and programming primitives
|
||||
that developers use to build web applications, so as to better reflect the needs of modern web
|
||||
applications and their developers.
|
||||
|
||||
|
||||
<a name="H1_6"></a>
|
||||
# Watch a Presentation About Angular
|
||||
|
||||
Here is an early presentation on angular, but note that substantial development has occurred since
|
||||
the talk was given in July of 2010.
|
||||
|
||||
<object width="480" height="385">
|
||||
<param name="movie" value="http://www.youtube.com/v/elvcgVSynRg&hl=en_US&fs=1"></param>
|
||||
<param name="allowFullScreen" value="true"></param>
|
||||
<param name="allowscriptaccess" value="always"></param>
|
||||
<embed src="http://www.youtube.com/v/elvcgVSynRg&hl=en_US&fs=1"
|
||||
type="application/x-shockwave-flash" allowscriptaccess="always"
|
||||
allowfullscreen="true" width="480" height="385"></embed>
|
||||
</object>
|
||||
|
||||
{@link
|
||||
https://docs.google.com/present/edit?id=0Abz6S2TvsDWSZDQ0OWdjaF8yNTRnODczazdmZg&hl=en&authkey=CO-b7oID
|
||||
Presentation}
|
||||
|
|
||||
{@link
|
||||
https://docs.google.com/document/edit?id=1ZHVhqC0apbzPRQcgnb1Ye-bAUbNJ-IlFMyPBPCZ2cYU&hl=en&authkey=CInnwLYO
|
||||
Source}
|
||||
@@ -0,0 +1,22 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Template
|
||||
@description
|
||||
#Template
|
||||
|
||||
You can think of a template in angular as a domain-specific language that you can use to easily
|
||||
build the view of your web application. You create a template by writing HTML and CSS, and you can
|
||||
add any constructs that you want to the HTML. This means that you can attach rendering and behavior
|
||||
to any HTML element, attribute or markup text.
|
||||
|
||||
In addition to writing HTML and CSS, you can also use the following angular constructs to create
|
||||
your template:
|
||||
|
||||
* **Directive** - XML attributes that augment an existing DOM element.
|
||||
* **Markup** - Lets you create shorthand for a widget or a directive. For example, markup is what
|
||||
allows you to use the double curly brace notation {{}} to bind expressions to
|
||||
elements.
|
||||
* **Filter** - Lets you format your data for display to the user.
|
||||
* **Widget** - Lets you create new DOM elements that the browser doesn't already understand.
|
||||
* **Validator** - Lets you validate user input.
|
||||
* **Formatter** - Lets you format the input object into a user readable view.
|
||||
@@ -0,0 +1,8 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Testing
|
||||
@description
|
||||
|
||||
# Testing Angular Applications
|
||||
|
||||
to be written...
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,57 @@
|
||||
<h1><tt>{{name}}</tt></h1>
|
||||
|
||||
{{#workInProgress}}
|
||||
<fieldset class="workInProgress">
|
||||
<legend>Work In Progress</legend>
|
||||
This page is currently being revised. It might be incomplete or contain inaccuracies.
|
||||
{{{workInProgress.description}}}
|
||||
</fieldset>
|
||||
{{/workInProgress}}
|
||||
|
||||
{{#deprecated}}
|
||||
<fieldset class="deprecated">
|
||||
<legend>Deprecated API</legend>
|
||||
{{deprecated}}
|
||||
</fieldset>
|
||||
{{/deprecated}}
|
||||
|
||||
<h2>Description</h2>
|
||||
{{{description}}}
|
||||
|
||||
<h2>Dependencies</h2>
|
||||
<ul>
|
||||
{{#requires}}
|
||||
<li><tt>{{name}}</tt></li>
|
||||
{{/requires}}
|
||||
</ul>
|
||||
|
||||
{{#method.length}}
|
||||
<h2>Methods</h2>
|
||||
<ul>
|
||||
{{#method}}
|
||||
<li><tt>{{shortName}}()</tt>: {{{description}}}</li>
|
||||
{{/method}}
|
||||
</ul>
|
||||
{{/method.length}}
|
||||
|
||||
{{#property.length}}
|
||||
<h2>Properties</h2>
|
||||
<ul>
|
||||
{{#property}}
|
||||
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}</tt>{{#description}}: {{{description}}}{{/description}}</li>
|
||||
{{/property}}
|
||||
</ul>
|
||||
{{/property.length}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
{{#scenario}}<doc:scenario>{{{scenario}}}</doc:scenario>{{/scenario}}
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -0,0 +1,33 @@
|
||||
var DOM = require('dom.js').DOM;
|
||||
|
||||
describe('dom', function(){
|
||||
var dom;
|
||||
|
||||
beforeEach(function(){
|
||||
dom = new DOM();
|
||||
});
|
||||
|
||||
describe('h', function(){
|
||||
|
||||
it('should render using function', function(){
|
||||
var cbThis;
|
||||
var cdValue;
|
||||
dom.h('heading', 'content', function(value){
|
||||
cbThis = this;
|
||||
cbValue = value;
|
||||
});
|
||||
expect(cbThis).toEqual(dom);
|
||||
expect(cbValue).toEqual('content');
|
||||
});
|
||||
|
||||
it('should update heading numbers', function(){
|
||||
dom.h('heading', function(){
|
||||
this.html('<h1>sub-heading</h1>');
|
||||
});
|
||||
expect(dom.toString()).toContain('<h1>heading</h1>');
|
||||
expect(dom.toString()).toContain('<h2>sub-heading</h2>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,464 @@
|
||||
var ngdoc = require('ngdoc.js');
|
||||
var DOM = require('dom.js').DOM;
|
||||
|
||||
describe('ngdoc', function(){
|
||||
var Doc = ngdoc.Doc;
|
||||
var dom;
|
||||
|
||||
beforeEach(function(){
|
||||
dom = new DOM();
|
||||
this.addMatchers({
|
||||
toContain: function(text) {
|
||||
this.actual = this.actual.toString();
|
||||
return this.actual.indexOf(text) > -1;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Doc', function(){
|
||||
describe('metadata', function(){
|
||||
|
||||
it('should find keywords', function(){
|
||||
expect(new Doc('\nHello: World! @ignore. $abc').keywords()).toEqual('$abc hello world');
|
||||
expect(new Doc('The `ng:class-odd` and').keywords()).toEqual('and ng:class-odd the');
|
||||
});
|
||||
|
||||
it('should have shortName', function(){
|
||||
var d1 = new Doc('@name a.b.c').parse();
|
||||
var d2 = new Doc('@name a.b.ng:c').parse();
|
||||
var d3 = new Doc('@name some text: more text').parse();
|
||||
expect(ngdoc.metadata([d1])[0].shortName).toEqual('c');
|
||||
expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng:c');
|
||||
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
|
||||
});
|
||||
|
||||
it('should have depth information', function(){
|
||||
var d1 = new Doc('@name a.b.c').parse();
|
||||
var d2 = new Doc('@name a.b.ng:c').parse();
|
||||
var d3 = new Doc('@name some text: more text').parse();
|
||||
expect(ngdoc.metadata([d1])[0].depth).toEqual(2);
|
||||
expect(ngdoc.metadata([d2])[0].depth).toEqual(2);
|
||||
expect(ngdoc.metadata([d3])[0].depth).toEqual(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('parse', function(){
|
||||
it('should convert @names into properties', function(){
|
||||
var doc = new Doc('\n@name name\n@desc\ndesc\ndesc2\n@dep\n');
|
||||
doc.parse();
|
||||
expect(doc.name).toEqual('name');
|
||||
expect(doc.desc).toEqual('desc\ndesc2');
|
||||
expect(doc.dep).toEqual('');
|
||||
});
|
||||
|
||||
it('should parse parameters', function(){
|
||||
var doc = new Doc(
|
||||
'@param {*} a short\n' +
|
||||
'@param {Type} b med\n' +
|
||||
'@param {Class=} [c=2] long\nline');
|
||||
doc.parse();
|
||||
expect(doc.param).toEqual([
|
||||
{name:'a', description:'<p>short</p>', type:'*', optional:false, 'default':undefined},
|
||||
{name:'b', description:'<p>med</p>', type:'Type', optional:false, 'default':undefined},
|
||||
{name:'c', description:'<p>long\nline</p>', type:'Class', optional:true, 'default':'2'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse return', function(){
|
||||
var doc = new Doc('@returns {Type} text *bold*.');
|
||||
doc.parse();
|
||||
expect(doc.returns).toEqual({
|
||||
type: 'Type',
|
||||
description: '<p>text <em>bold</em>.</p>'
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse filename', function(){
|
||||
var doc = new Doc('@name friendly name', 'docs/a.b.ngdoc', 1);
|
||||
doc.parse(0);
|
||||
expect(doc.id).toEqual('a.b');
|
||||
expect(doc.name).toEqual('friendly name');
|
||||
});
|
||||
|
||||
it('should escape <doc:source> element', function(){
|
||||
var doc = new Doc('@description before <doc:example>' +
|
||||
'<doc:source>\n<>\n</doc:source></doc:example> after');
|
||||
doc.parse();
|
||||
expect(doc.description).toContain('<p>before </p><doc:example>' +
|
||||
'<pre class="doc-source">\n<>\n</pre></doc:example><p>after</p>');
|
||||
});
|
||||
|
||||
it('should escape <doc:scenario> element', function(){
|
||||
var doc = new Doc('@description before <doc:example>' +
|
||||
'<doc:scenario>\n<>\n</doc:scenario></doc:example> after');
|
||||
doc.parse();
|
||||
expect(doc.description).toContain('<p>before </p><doc:example>' +
|
||||
'<pre class="doc-scenario">\n<>\n</pre></doc:example><p>after</p>');
|
||||
});
|
||||
|
||||
describe('sorting', function(){
|
||||
function property(name) {
|
||||
return function(obj) {return obj[name];};
|
||||
}
|
||||
function noop(){}
|
||||
function doc(type, name){
|
||||
return {
|
||||
id: name,
|
||||
ngdoc: type,
|
||||
keywords: noop
|
||||
};
|
||||
}
|
||||
|
||||
var angular_widget = doc('overview', 'angular.widget');
|
||||
var angular_x = doc('function', 'angular.x');
|
||||
var angular_y = doc('property', 'angular.y');
|
||||
|
||||
it('should put angular.fn() in front of angular.widget, etc', function(){
|
||||
expect(ngdoc.metadata([angular_widget, angular_y, angular_x]).map(property('id')))
|
||||
.toEqual(['angular.x', 'angular.y', 'angular.widget' ]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('markdown', function(){
|
||||
it('should replace angular in markdown', function(){
|
||||
expect(new Doc().markdown('<angular/>')).
|
||||
toEqual('<p><tt><angular/></tt></p>');
|
||||
});
|
||||
|
||||
it('should not replace anything in <pre>, but escape the html escape the content', function(){
|
||||
expect(new Doc().markdown('bah x\n<pre>\n<b>angular</b>.k\n</pre>\n asdf x')).
|
||||
toEqual(
|
||||
'<p>bah x</p>' +
|
||||
'<div ng:non-bindable><pre class="brush: js; html-script: true;">\n' +
|
||||
'<b>angular</b>.k\n' +
|
||||
'</pre></div>' +
|
||||
'<p>asdf x</p>');
|
||||
});
|
||||
|
||||
it('should replace text between two <pre></pre> tags', function() {
|
||||
expect(new Doc().markdown('<pre>x</pre># One<pre>b</pre>')).
|
||||
toMatch('</div><h1>One</h1><div');
|
||||
});
|
||||
|
||||
it('should unindent text before processing based on the second line', function() {
|
||||
expect(new Doc().markdown('first line\n' +
|
||||
' second line\n\n' +
|
||||
' third line\n' +
|
||||
' fourth line\n\n' +
|
||||
' fifth line')).
|
||||
toMatch('<p>first line\n' +
|
||||
'second line</p>\n\n' +
|
||||
'<pre><code>third line\n' +
|
||||
' fourth line\n</code></pre>\n\n' +
|
||||
'<p>fifth line</p>');
|
||||
});
|
||||
|
||||
it('should unindent text before processing based on the first line', function() {
|
||||
expect(new Doc().markdown(' first line\n\n' +
|
||||
' second line\n' +
|
||||
' third line\n' +
|
||||
' fourth line\n\n' +
|
||||
' fifth line')).
|
||||
toMatch('<p>first line</p>\n\n' +
|
||||
'<pre><code>second line\n' +
|
||||
'third line\n' +
|
||||
' fourth line\n</code></pre>\n\n' +
|
||||
'<p>fifth line</p>');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe('trim', function(){
|
||||
var trim = ngdoc.trim;
|
||||
it('should remove leading/trailing space', function(){
|
||||
expect(trim(' \nabc\n ')).toEqual('abc');
|
||||
});
|
||||
|
||||
it('should remove leading space on every line', function(){
|
||||
expect(trim('\n 1\n 2\n 3\n')).toEqual('1\n 2\n 3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('merge', function(){
|
||||
it('should merge child with parent', function(){
|
||||
var parent = new Doc({name:'angular.service.abc'});
|
||||
var methodA = new Doc({name:'methodA', methodOf:'angular.service.abc'});
|
||||
var methodB = new Doc({name:'methodB', methodOf:'angular.service.abc'});
|
||||
var propA = new Doc({name:'propA', propertyOf:'angular.service.abc'});
|
||||
var propB = new Doc({name:'propB', propertyOf:'angular.service.abc'});
|
||||
var docs = [methodB, methodA, propB, propA, parent]; // keep wrong order;
|
||||
ngdoc.merge(docs);
|
||||
expect(docs.length).toEqual(1);
|
||||
expect(docs[0].name).toEqual('angular.service.abc');
|
||||
expect(docs[0].methods).toEqual([methodA, methodB]);
|
||||
expect(docs[0].properties).toEqual([propA, propB]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
describe('TAG', function(){
|
||||
describe('@param', function(){
|
||||
it('should parse with no default', function(){
|
||||
var doc = new Doc('@param {(number|string)} number Number \n to format.');
|
||||
doc.parse();
|
||||
expect(doc.param).toEqual([{
|
||||
type : '(number|string)',
|
||||
name : 'number',
|
||||
optional: false,
|
||||
'default' : undefined,
|
||||
description : '<p>Number \nto format.</p>' }]);
|
||||
});
|
||||
|
||||
it('should parse with default and optional', function(){
|
||||
var doc = new Doc('@param {(number|string)=} [fractionSize=2] desc');
|
||||
doc.parse();
|
||||
expect(doc.param).toEqual([{
|
||||
type : '(number|string)',
|
||||
name : 'fractionSize',
|
||||
optional: true,
|
||||
'default' : '2',
|
||||
description : '<p>desc</p>' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@requires', function() {
|
||||
it('should parse more @requires tag into array', function() {
|
||||
var doc = new Doc('@requires $service for \n`A`\n@requires $another for `B`');
|
||||
doc.ngdoc = 'service';
|
||||
doc.parse();
|
||||
expect(doc.requires).toEqual([
|
||||
{name:'$service', text:'<p>for \n<code>A</code></p>'},
|
||||
{name:'$another', text:'<p>for <code>B</code></p>'}]);
|
||||
expect(doc.html()).toContain('<a href="#!angular.service.$service">$service</a>');
|
||||
expect(doc.html()).toContain('<a href="#!angular.service.$another">$another</a>');
|
||||
expect(doc.html()).toContain('<p>for \n<code>A</code></p>');
|
||||
expect(doc.html()).toContain('<p>for <code>B</code></p>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@property', function() {
|
||||
it('should parse @property tags into array', function() {
|
||||
var doc = new Doc("@property {type} name1 desc\n@property {type} name2 desc");
|
||||
doc.parse();
|
||||
expect(doc.properties.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should not parse @property without a type', function() {
|
||||
var doc = new Doc("@property fake");
|
||||
expect(function() { doc.parse(); }).
|
||||
toThrow(new Error("Not a valid 'property' format: fake"));
|
||||
});
|
||||
|
||||
it('should parse @property with type', function() {
|
||||
var doc = new Doc("@property {string} name");
|
||||
doc.parse();
|
||||
expect(doc.properties[0].name).toEqual('name');
|
||||
expect(doc.properties[0].type).toEqual('string');
|
||||
});
|
||||
|
||||
it('should parse @property with optional description', function() {
|
||||
var doc = new Doc("@property {string} name desc rip tion");
|
||||
doc.parse();
|
||||
expect(doc.properties[0].name).toEqual('name');
|
||||
expect(doc.properties[0].description).toEqual('<p>desc rip tion</p>');
|
||||
});
|
||||
|
||||
it('should parse @property with type and description both', function() {
|
||||
var doc = new Doc("@property {bool} name desc rip tion");
|
||||
doc.parse();
|
||||
expect(doc.properties[0].name).toEqual('name');
|
||||
expect(doc.properties[0].type).toEqual('bool');
|
||||
expect(doc.properties[0].description).toEqual('<p>desc rip tion</p>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('@returns', function() {
|
||||
it('should not parse @returns without type', function() {
|
||||
var doc = new Doc("@returns lala");
|
||||
expect(doc.parse).toThrow();
|
||||
});
|
||||
|
||||
it('should parse @returns with type and description', function() {
|
||||
var doc = new Doc("@returns {string} descrip tion");
|
||||
doc.parse();
|
||||
expect(doc.returns).toEqual({type: 'string', description: '<p>descrip tion</p>'});
|
||||
});
|
||||
|
||||
it('should transform description of @returns with markdown', function() {
|
||||
var doc = new Doc("@returns {string} descrip *tion*");
|
||||
doc.parse();
|
||||
expect(doc.returns).toEqual({type: 'string', description: '<p>descrip <em>tion</em></p>'});
|
||||
});
|
||||
|
||||
it('should support multiline content', function() {
|
||||
var doc = new Doc("@returns {string} description\n new line\n another line");
|
||||
doc.parse();
|
||||
expect(doc.returns).
|
||||
toEqual({type: 'string', description: '<p>description\nnew line\nanother line</p>'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('@description', function(){
|
||||
it('should support pre blocks', function(){
|
||||
var doc = new Doc("@description <pre>abc</pre>");
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>');
|
||||
});
|
||||
|
||||
it('should support multiple pre blocks', function() {
|
||||
var doc = new Doc("@description foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>");
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
toBe('<p>foo </p>' +
|
||||
'<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>' +
|
||||
'<h1>bah</h1>\n\n' +
|
||||
'<p>foo </p>' +
|
||||
'<div ng:non-bindable><pre class="brush: js; html-script: true;">cba</pre></div>');
|
||||
|
||||
});
|
||||
|
||||
it('should support nested @link annotations with or without description', function() {
|
||||
var doc = new Doc("@description " +
|
||||
'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' +
|
||||
'dad{@link angular.foo}\n\n' +
|
||||
'external{@link http://angularjs.org}\n\n' +
|
||||
'external{@link ./static.html}\n\n' +
|
||||
'{@link angular.directive.ng:foo ng:foo}');
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
toContain('foo <a href="#!angular.foo"><code>angular.foo</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('da <a href="#!angular.foo"><code>bar foo bar</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('dad<a href="#!angular.foo"><code>angular.foo</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('<a href="#!angular.directive.ng:foo"><code>ng:foo</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('<a href="http://angularjs.org">http://angularjs.org</a>');
|
||||
expect(doc.description).
|
||||
toContain('<a href="./static.html">./static.html</a>');
|
||||
});
|
||||
|
||||
it('shoul support line breaks in @link', function(){
|
||||
var doc = new Doc("@description " +
|
||||
'{@link\nurl\na\nb}');
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
toContain('<a href="#!url">a b</a>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('@example', function(){
|
||||
it('should not remove {{}}', function(){
|
||||
var doc = new Doc('@example text {{ abc }}');
|
||||
doc.parse();
|
||||
expect(doc.example).toEqual('<p>text {{ abc }}</p>');
|
||||
});
|
||||
|
||||
it('should support doc:example', function(){
|
||||
var doc = new Doc('@ngdoc overview\n@example \n' +
|
||||
'<doc:example>\n' +
|
||||
' <doc:source><escapeme></doc:source>\n' +
|
||||
' <doc:scenario><scenario></doc:scenario>\n' +
|
||||
'</doc:example>').parse();
|
||||
var html = doc.html();
|
||||
expect(html).toContain('<pre class="doc-source"><escapeme></pre>');
|
||||
expect(html).toContain('<pre class="doc-scenario"><scenario></pre>');
|
||||
expect(doc.scenarios).toEqual(['<scenario>']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@depricated', function() {
|
||||
it('should parse @depricated', function() {
|
||||
var doc = new Doc('@deprecated Replaced with foo.');
|
||||
doc.parse();
|
||||
expect(doc.deprecated).toBe('Replaced with foo.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@this', function(){
|
||||
it('should render @this', function() {
|
||||
var doc = new Doc('@this I am self.');
|
||||
doc.ngdoc = 'filter';
|
||||
doc.parse();
|
||||
expect(doc.html()).toContain('<h3>Method\'s <code>this</code></h3>\n' +
|
||||
'<div>' +
|
||||
'<p>I am self.</p>' +
|
||||
'</div>\n');
|
||||
expect(doc.html()).toContain('<h3>Method\'s <code>this</code></h3>\n' +
|
||||
'<div><p>I am self.</p></div>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('usage', function(){
|
||||
describe('overview', function(){
|
||||
it('should supress description heading', function(){
|
||||
var doc = new Doc('@ngdoc overview\n@name angular\n@description\n#heading\ntext');
|
||||
doc.parse();
|
||||
expect(doc.html()).toContain('text');
|
||||
expect(doc.html()).toContain('<h2>heading</h2>');
|
||||
expect(doc.html()).not.toContain('Description');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('filter', function(){
|
||||
it('should format', function(){
|
||||
var doc = new Doc({
|
||||
ngdoc:'formatter',
|
||||
shortName:'myFilter',
|
||||
param: [
|
||||
{name:'a'},
|
||||
{name:'b'}
|
||||
]
|
||||
});
|
||||
doc.html_usage_filter(dom);
|
||||
expect(dom).toContain('myFilter_expression | myFilter:b');
|
||||
expect(dom).toContain('angular.filter.myFilter(a, b)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validator', function(){
|
||||
it('should format', function(){
|
||||
var doc = new Doc({
|
||||
ngdoc:'validator',
|
||||
shortName:'myValidator',
|
||||
param: [
|
||||
{name:'a'},
|
||||
{name:'b'}
|
||||
]
|
||||
});
|
||||
doc.html_usage_validator(dom);
|
||||
expect(dom).toContain('ng:validate="myValidator:b"');
|
||||
expect(dom).toContain('angular.validator.myValidator(a, b)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatter', function(){
|
||||
it('should format', function(){
|
||||
var doc = new Doc({
|
||||
ngdoc:'formatter',
|
||||
shortName:'myFormatter',
|
||||
param: [
|
||||
{name:'a'},
|
||||
]
|
||||
});
|
||||
doc.html_usage_formatter(dom);
|
||||
expect(dom).toContain('ng:format="myFormatter:a"');
|
||||
expect(dom).toContain('var userInputString = angular.formatter.myFormatter.format(modelValue, a);');
|
||||
expect(dom).toContain('var modelValue = angular.formatter.myFormatter.parse(userInputString, a);');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
var SiteMap = require('sitemap.js').SiteMap;
|
||||
var Doc = require('ngdoc.js').Doc;
|
||||
|
||||
|
||||
describe('sitemap', function(){
|
||||
it('should render empty sitemap', function(){
|
||||
var map = new SiteMap([]);
|
||||
expect(map.render()).toEqual([
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
||||
'</urlset>', ''].join('\n'));
|
||||
});
|
||||
|
||||
it('should render ngdoc url', function(){
|
||||
var map = new SiteMap([new Doc({name: 'a.b.c<>\'"&'})]);
|
||||
expect(map.render()).toContain([
|
||||
' <url>',
|
||||
'<loc>http://docs.angularjs.org/#!a.b.c<>'"&</loc>',
|
||||
'<changefreq>weekly</changefreq>',
|
||||
'</url>'].join(''));
|
||||
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
if (global.jasmine) return;
|
||||
|
||||
require.paths.push(__dirname + "/../../lib");
|
||||
require.paths.push(__dirname + '/../src');
|
||||
var jasmine = require('jasmine-1.0.1');
|
||||
var sys = require('util');
|
||||
|
||||
for(var key in jasmine) {
|
||||
global[key] = jasmine[key];
|
||||
}
|
||||
|
||||
//Patch Jasmine for proper stack traces
|
||||
jasmine.Spec.prototype.fail = function (e) {
|
||||
var expectationResult = new jasmine.ExpectationResult({
|
||||
passed: false,
|
||||
message: e ? jasmine.util.formatException(e) : 'Exception'
|
||||
});
|
||||
// PATCH
|
||||
if (e) {
|
||||
expectationResult.trace = e;
|
||||
}
|
||||
this.results_.addResult(expectationResult);
|
||||
};
|
||||
|
||||
|
||||
|
||||
var isVerbose = false;
|
||||
var showColors = true;
|
||||
process.argv.forEach(function(arg){
|
||||
switch(arg) {
|
||||
case '--color': showColors = true; break;
|
||||
case '--noColor': showColors = false; break;
|
||||
case '--verbose': isVerbose = true; break;
|
||||
}
|
||||
});
|
||||
|
||||
jasmine.executeSpecsInFolder(__dirname, function(runner, log){
|
||||
process.exit(runner.results().failedCount);
|
||||
}, isVerbose, showColors);
|
||||
@@ -0,0 +1,18 @@
|
||||
var writer = require('writer.js');
|
||||
describe('writer', function(){
|
||||
describe('toString', function(){
|
||||
var toString = writer.toString;
|
||||
|
||||
it('should merge string', function(){
|
||||
expect(toString('abc')).toEqual('abc');
|
||||
});
|
||||
|
||||
it('should merge obj', function(){
|
||||
expect(toString({a:1})).toEqual('{"a":1}');
|
||||
});
|
||||
|
||||
it('should merge array', function(){
|
||||
expect(toString(['abc',{}])).toEqual('abc{}');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
exports.SiteMap = SiteMap;
|
||||
|
||||
/**
|
||||
* @see http://www.sitemaps.org/protocol.php
|
||||
*
|
||||
* @param docs
|
||||
* @returns {SiteMap}
|
||||
*/
|
||||
function SiteMap(docs){
|
||||
this.render = function(){
|
||||
var map = [];
|
||||
map.push('<?xml version="1.0" encoding="UTF-8"?>');
|
||||
map.push('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
|
||||
docs.forEach(function(doc){
|
||||
map.push(' <url><loc>http://docs.angularjs.org/#!' +
|
||||
encode(doc.name) + '</loc><changefreq>weekly</changefreq></url>');
|
||||
});
|
||||
map.push('</urlset>');
|
||||
map.push('');
|
||||
return map.join('\n');
|
||||
};
|
||||
|
||||
function encode(text){
|
||||
return text
|
||||
.replace(/&/mg, '&')
|
||||
.replace(/</mg, '<')
|
||||
.replace(/>/mg, '>')
|
||||
.replace(/'/mg, ''')
|
||||
.replace(/"/mg, '"');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
function noop(){}
|
||||
|
||||
function chain(delegateFn, explicitDone){
|
||||
var onDoneFn = noop;
|
||||
var onErrorFn = function(e){
|
||||
console.error(e.stack || e);
|
||||
process.exit(-1);
|
||||
};
|
||||
var waitForCount = 1;
|
||||
delegateFn = delegateFn || noop;
|
||||
var stackError = new Error('capture stack');
|
||||
|
||||
function decrementWaitFor() {
|
||||
waitForCount--;
|
||||
if (waitForCount == 0)
|
||||
onDoneFn();
|
||||
}
|
||||
|
||||
function self(){
|
||||
try {
|
||||
return delegateFn.apply(self, arguments);
|
||||
} catch (error) {
|
||||
self.error(error);
|
||||
} finally {
|
||||
if (!explicitDone)
|
||||
decrementWaitFor();
|
||||
}
|
||||
};
|
||||
self.onDone = function(callback){
|
||||
onDoneFn = callback;
|
||||
return self;
|
||||
};
|
||||
self.onError = function(callback){
|
||||
onErrorFn = callback;
|
||||
return self;
|
||||
};
|
||||
self.waitFor = function(callback){
|
||||
if (waitForCount == 0)
|
||||
throw new Error("Can not wait on already called callback.");
|
||||
waitForCount++;
|
||||
return chain(callback).onDone(decrementWaitFor).onError(self.error);
|
||||
};
|
||||
|
||||
self.waitMany = function(callback){
|
||||
if (waitForCount == 0)
|
||||
throw new Error("Can not wait on already called callback.");
|
||||
waitForCount++;
|
||||
return chain(callback, true).onDone(decrementWaitFor).onError(self.error);
|
||||
};
|
||||
|
||||
self.done = function(callback){
|
||||
decrementWaitFor();
|
||||
};
|
||||
|
||||
self.error = function(error) {
|
||||
var stack = stackError.stack.split(/\n\r?/).splice(2);
|
||||
var nakedStack = [];
|
||||
stack.forEach(function(frame){
|
||||
if (!frame.match(/callback\.js:\d+:\d+\)$/))
|
||||
nakedStack.push(frame);
|
||||
});
|
||||
error.stack = error.stack + '\nCalled from:\n' + nakedStack.join('\n');
|
||||
onErrorFn(error);
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
exports.chain = chain;
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* DOM generation class
|
||||
*/
|
||||
|
||||
exports.DOM = DOM;
|
||||
exports.htmlEscape = htmlEscape;
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
function htmlEscape(text){
|
||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
|
||||
function DOM(){
|
||||
this.out = [];
|
||||
this.headingDepth = 0;
|
||||
}
|
||||
|
||||
var INLINE_TAGS = {
|
||||
i: true,
|
||||
b: true
|
||||
};
|
||||
|
||||
DOM.prototype = {
|
||||
toString: function() {
|
||||
return this.out.join('');
|
||||
},
|
||||
|
||||
text: function(content) {
|
||||
if (typeof content == "string") {
|
||||
this.out.push(htmlEscape(content));
|
||||
} else if (typeof content == 'function') {
|
||||
content.call(this, this);
|
||||
} else if (content instanceof Array) {
|
||||
this.ul(content);
|
||||
}
|
||||
},
|
||||
|
||||
html: function(html) {
|
||||
if (html) {
|
||||
var headingDepth = this.headingDepth;
|
||||
for ( var i = 10; i > 0; --i) {
|
||||
html = html
|
||||
.replace(new RegExp('(<\/?h)' + i + '(>)', 'gm'), function(all, start, end){
|
||||
return start + (i + headingDepth) + end;
|
||||
});
|
||||
}
|
||||
this.out.push(html);
|
||||
}
|
||||
},
|
||||
|
||||
tag: function(name, attr, text) {
|
||||
if (!text) {
|
||||
text = attr;
|
||||
attr = {};
|
||||
if (name == 'code')
|
||||
attr['ng:non-bindable'] = '';
|
||||
}
|
||||
this.out.push('<' + name);
|
||||
for(var key in attr) {
|
||||
this.out.push(" " + key + '="' + attr[key] + '"');
|
||||
}
|
||||
this.out.push('>');
|
||||
this.text(text);
|
||||
this.out.push('</' + name + '>');
|
||||
if (!INLINE_TAGS[name])
|
||||
this.out.push('\n');
|
||||
},
|
||||
|
||||
code: function(text) {
|
||||
this.tag('div', {'ng:non-bindable':''}, function(){
|
||||
this.tag('pre', {'class':"brush: js; html-script: true;"}, text);
|
||||
});
|
||||
},
|
||||
|
||||
h: function(heading, content, fn){
|
||||
if (content==undefined || (content instanceof Array && content.length == 0)) return;
|
||||
this.headingDepth++;
|
||||
this.tag('h' + this.headingDepth, heading);
|
||||
var className = typeof heading == 'string'
|
||||
? {'class': heading.toLowerCase().replace(/[^\d\w_]/mg, '-').replace(/-+/gm, '-')}
|
||||
: null;
|
||||
if (content instanceof Array) {
|
||||
this.ul(content, className, fn);
|
||||
} else if (fn) {
|
||||
this.tag('div', className, function(){
|
||||
fn.call(this, content);
|
||||
});
|
||||
} else {
|
||||
this.tag('div', className, content);
|
||||
}
|
||||
this.headingDepth--;
|
||||
},
|
||||
|
||||
h1: function(attr, text) {
|
||||
this.tag('h1', attr, text);
|
||||
},
|
||||
|
||||
h2: function(attr, text) {
|
||||
this.tag('h2', attr, text);
|
||||
},
|
||||
|
||||
h3: function(attr, text) {
|
||||
this.tag('h3', attr, text);
|
||||
},
|
||||
|
||||
p: function(attr, text) {
|
||||
this.tag('p', attr, text);
|
||||
},
|
||||
|
||||
ul: function(list, attr, fn) {
|
||||
if (typeof attr == 'function') {
|
||||
fn = attr;
|
||||
attr = {};
|
||||
}
|
||||
this.tag('ul', attr, function(dom){
|
||||
list.forEach(function(item){
|
||||
dom.out.push('<li>');
|
||||
dom.text(fn ? fn(item) : item);
|
||||
dom.out.push('</li>\n');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
require.paths.push(__dirname);
|
||||
require.paths.push('lib');
|
||||
var reader = require('reader.js'),
|
||||
ngdoc = require('ngdoc.js'),
|
||||
writer = require('writer.js'),
|
||||
callback = require('callback.js'),
|
||||
SiteMap = require('SiteMap.js').SiteMap;
|
||||
|
||||
var docs = [];
|
||||
var start;
|
||||
var work = callback.chain(function(){
|
||||
start = now();
|
||||
console.log('Generating Angular Reference Documentation...');
|
||||
reader.collect(work.waitMany(function(text, file, line){
|
||||
var doc = new ngdoc.Doc(text, file, line);
|
||||
docs.push(doc);
|
||||
doc.parse();
|
||||
}));
|
||||
});
|
||||
var writes = callback.chain(function(){
|
||||
ngdoc.merge(docs);
|
||||
docs.forEach(function(doc){
|
||||
writer.output(doc.id + '.html', doc.html(), writes.waitFor());
|
||||
});
|
||||
var metadata = ngdoc.metadata(docs);
|
||||
writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata).replace(/{/g, '\n{'), ';'], writes.waitFor());
|
||||
writer.copyDir('img', writes.waitFor());
|
||||
writer.copyDir('static', writes.waitFor());
|
||||
writer.copy('index.html', writes.waitFor());
|
||||
writer.copy('docs.js', writes.waitFor());
|
||||
writer.copy('docs.css', writes.waitFor());
|
||||
writer.copy('doc_widgets.js', writes.waitFor());
|
||||
writer.copy('doc_widgets.css', writes.waitFor());
|
||||
writer.copy('docs-scenario.html', writes.waitFor());
|
||||
writer.output('docs-scenario.js', ngdoc.scenarios(docs), writes.waitFor());
|
||||
writer.output('sitemap.xml', new SiteMap(docs).render(), writes.waitFor());
|
||||
writer.output('robots.txt', 'Sitemap: http://docs.angularjs.org/sitemap.xml\n', writes.waitFor());
|
||||
writer.copy('syntaxhighlighter/shBrushJScript.js', writes.waitFor());
|
||||
writer.copy('syntaxhighlighter/shBrushXml.js', writes.waitFor());
|
||||
writer.copy('syntaxhighlighter/shCore.css', writes.waitFor());
|
||||
writer.copy('syntaxhighlighter/shCore.js', writes.waitFor());
|
||||
writer.copy('syntaxhighlighter/shThemeDefault.css', writes.waitFor());
|
||||
writer.copy('jquery.min.js', writes.waitFor());
|
||||
});
|
||||
writes.onDone(function(){
|
||||
console.log('DONE. Generated ' + docs.length + ' pages in ' +
|
||||
(now()-start) + 'ms.' );
|
||||
});
|
||||
work.onDone(writes);
|
||||
writer.makeDir('build/docs/syntaxhighlighter', work);
|
||||
|
||||
///////////////////////////////////
|
||||
function now(){ return new Date().getTime(); }
|
||||
@@ -0,0 +1,663 @@
|
||||
/**
|
||||
* All parsing/transformation code goes here. All code here should be sync to ease testing.
|
||||
*/
|
||||
|
||||
var Showdown = require('showdown').Showdown;
|
||||
var DOM = require('dom.js').DOM;
|
||||
var htmlEscape = require('dom.js').htmlEscape;
|
||||
var NEW_LINE = /\n\r?/;
|
||||
|
||||
exports.trim = trim;
|
||||
exports.metadata = metadata;
|
||||
exports.scenarios = scenarios;
|
||||
exports.merge = merge;
|
||||
exports.Doc = Doc;
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function Doc(text, file, line) {
|
||||
if (typeof text == 'object') {
|
||||
for ( var key in text) {
|
||||
this[key] = text[key];
|
||||
}
|
||||
} else {
|
||||
this.text = text;
|
||||
this.file = file;
|
||||
this.line = line;
|
||||
}
|
||||
this.scenarios = this.scenarios || [];
|
||||
this.requires = this.requires || [];
|
||||
this.param = this.param || [];
|
||||
this.properties = this.properties || [];
|
||||
this.methods = this.methods || [];
|
||||
}
|
||||
Doc.METADATA_IGNORE = (function(){
|
||||
var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8');
|
||||
return words.toString().split(/[,\s\n\r]+/gm);
|
||||
})();
|
||||
|
||||
|
||||
|
||||
Doc.prototype = {
|
||||
keywords: function keywords(){
|
||||
var keywords = {};
|
||||
Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; });
|
||||
var words = [];
|
||||
var tokens = this.text.toLowerCase().split(/[,\.\`\'\"\s]+/mg);
|
||||
tokens.forEach(function(key){
|
||||
var match = key.match(/^(([\$\_a-z]|ng\:)[\w\_\-]{2,})/);
|
||||
if (match){
|
||||
key = match[1];
|
||||
if (!keywords[key]) {
|
||||
keywords[key] = true;
|
||||
words.push(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
words.sort();
|
||||
return words.join(' ');
|
||||
},
|
||||
|
||||
markdown: function (text) {
|
||||
var self = this;
|
||||
var IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:|\.|\/)/;
|
||||
var IS_ANGULAR = /^angular\./;
|
||||
if (!text) return text;
|
||||
|
||||
text = trim(text);
|
||||
|
||||
var parts = text.split(/(<pre>[\s\S]*?<\/pre>|<doc:example>[\s\S]*?<\/doc:example>)/);
|
||||
|
||||
parts.forEach(function(text, i){
|
||||
if (text.match(/^<pre>/)) {
|
||||
text = text.replace(/^<pre>([\s\S]*)<\/pre>/mi, function(_, content){
|
||||
return '<div ng:non-bindable><pre class="brush: js; html-script: true;">' +
|
||||
content.replace(/</g, '<').replace(/>/g, '>') +
|
||||
'</pre></div>';
|
||||
});
|
||||
} else if (text.match(/^<doc:example>/)) {
|
||||
text = text.replace(/(<doc:source>)([\s\S]*)(<\/doc:source>)/mi,
|
||||
function(_, before, content, after){
|
||||
return '<pre class="doc-source">' + htmlEscape(content) + '</pre>';
|
||||
});
|
||||
text = text.replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi,
|
||||
function(_, before, content, after){
|
||||
self.scenarios.push(content);
|
||||
return '<pre class="doc-scenario">' + htmlEscape(content) + '</pre>';
|
||||
});
|
||||
} else {
|
||||
text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>');
|
||||
text = text.replace(/{@link\s+([^\s}]+)\s*([^}]*?)\s*}/g,
|
||||
function(_all, url, title){
|
||||
return '<a href="' + (url.match(IS_URL) ? '' : '#!') + url + '">'
|
||||
+ (url.match(IS_ANGULAR) ? '<code>' : '')
|
||||
+ (title || url).replace(/\n/g, ' ')
|
||||
+ (url.match(IS_ANGULAR) ? '</code>' : '')
|
||||
+ '</a>';
|
||||
});
|
||||
text = new Showdown.converter().makeHtml(text);
|
||||
}
|
||||
parts[i] = text;
|
||||
});
|
||||
return parts.join('');
|
||||
},
|
||||
|
||||
parse: function(){
|
||||
var atName;
|
||||
var atText;
|
||||
var match;
|
||||
var self = this;
|
||||
self.text.split(NEW_LINE).forEach(function(line){
|
||||
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
|
||||
// we found @name ...
|
||||
// if we have existing name
|
||||
flush();
|
||||
atName = match[1];
|
||||
atText = [];
|
||||
if(match[3]) atText.push(match[3]);
|
||||
} else {
|
||||
if (atName) {
|
||||
atText.push(line);
|
||||
}
|
||||
}
|
||||
});
|
||||
flush();
|
||||
this.shortName = (this.name || '').split(/[\.#]/).pop();
|
||||
this.id = this.id // if we have an id just use it
|
||||
|| (((this.file||'').match(/.*\/([^\/]*)\.ngdoc/)||{})[1]) // try to extract it from file name
|
||||
|| this.name; // default to name
|
||||
this.description = this.markdown(this.description);
|
||||
this.example = this.markdown(this.example);
|
||||
this['this'] = this.markdown(this['this']);
|
||||
return this;
|
||||
|
||||
function flush(){
|
||||
if (atName) {
|
||||
var text = trim(atText.join('\n'));
|
||||
if (atName == 'param') {
|
||||
var match = text.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
|
||||
// 1 12 2 34 4 5 5 6 6 3 7 7
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'param' format: " + text);
|
||||
}
|
||||
var param = {
|
||||
name: match[5] || match[4],
|
||||
description:self.markdown(text.replace(match[0], match[7])),
|
||||
type: match[1],
|
||||
optional: !!match[2],
|
||||
'default':match[6]
|
||||
};
|
||||
self.param.push(param);
|
||||
} else if (atName == 'returns') {
|
||||
var match = text.match(/^{([^}=]+)}\s+(.*)/);
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'returns' format: " + text);
|
||||
}
|
||||
self.returns = {
|
||||
type: match[1],
|
||||
description: self.markdown(text.replace(match[0], match[2]))
|
||||
};
|
||||
} else if(atName == 'requires') {
|
||||
var match = text.match(/^([^\s]*)\s*([\S\s]*)/);
|
||||
self.requires.push({
|
||||
name: match[1],
|
||||
text: self.markdown(match[2])
|
||||
});
|
||||
} else if(atName == 'property') {
|
||||
var match = text.match(/^{(\S+)}\s+(\S+)(\s+(.*))?/);
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'property' format: " + text);
|
||||
}
|
||||
var property = {
|
||||
type: match[1],
|
||||
name: match[2],
|
||||
description: self.markdown(text.replace(match[0], match[4]))
|
||||
};
|
||||
self.properties.push(property);
|
||||
} else {
|
||||
self[atName] = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
html: function(){
|
||||
var dom = new DOM(),
|
||||
self = this;
|
||||
|
||||
dom.h(this.name, function(){
|
||||
notice('workInProgress', 'Work in Progress',
|
||||
'This page is currently being revised. It might be incomplete or contain inaccuracies.');
|
||||
notice('deprecated', 'Deprecated API', self.deprecated);
|
||||
|
||||
if (self.ngdoc != 'overview') {
|
||||
dom.h('Description', self.description, dom.html);
|
||||
}
|
||||
dom.h('Dependencies', self.requires, function(require){
|
||||
dom.tag('code', function(){
|
||||
dom.tag('a', {href:"#!angular.service." + require.name}, require.name);
|
||||
});
|
||||
dom.html(require.text);
|
||||
});
|
||||
|
||||
(self['html_usage_' + self.ngdoc] || function(){
|
||||
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
|
||||
}).call(self, dom);
|
||||
|
||||
dom.h('Example', self.example, dom.html);
|
||||
});
|
||||
|
||||
return dom.toString();
|
||||
|
||||
//////////////////////////
|
||||
|
||||
function notice(name, legend, msg){
|
||||
if (self[name] == undefined) return;
|
||||
dom.tag('fieldset', {'class':name}, function(dom){
|
||||
dom.tag('legend', legend);
|
||||
dom.text(msg);
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
html_usage_parameters: function(dom) {
|
||||
dom.h('Parameters', this.param, function(param){
|
||||
dom.tag('code', function(){
|
||||
dom.text(param.name);
|
||||
if (param.optional) {
|
||||
dom.tag('i', function(){
|
||||
dom.text('(optional');
|
||||
if(param['default']) {
|
||||
dom.text('=' + param['default']);
|
||||
}
|
||||
dom.text(')');
|
||||
});
|
||||
}
|
||||
dom.text(' – {');
|
||||
dom.text(param.type);
|
||||
dom.text('} – ');
|
||||
});
|
||||
dom.html(param.description);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_returns: function(dom) {
|
||||
var self = this;
|
||||
if (self.returns) {
|
||||
dom.h('Returns', function(){
|
||||
dom.tag('code', '{' + self.returns.type + '}');
|
||||
dom.text('– ');
|
||||
dom.html(self.returns.description);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
html_usage_this: function(dom) {
|
||||
var self = this;
|
||||
if (self['this']) {
|
||||
dom.h(function(dom){
|
||||
dom.html("Method's <code>this</code>");
|
||||
}, function(dom){
|
||||
dom.html(self['this']);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
html_usage_function: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.code(function(){
|
||||
dom.text(self.name);
|
||||
dom.text('(');
|
||||
self.parameters(dom, ', ');
|
||||
dom.text(');');
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_this(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_directive: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){
|
||||
dom.text('<' + self.element + ' ');
|
||||
dom.text(self.shortName);
|
||||
if (self.param.length) {
|
||||
dom.text('="' + self.param[0].name + '"');
|
||||
}
|
||||
dom.text('>\n ...\n');
|
||||
dom.text('</' + self.element + '>');
|
||||
});
|
||||
self.html_usage_parameters(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_filter: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.tag('code', function(){
|
||||
dom.text('{{ ');
|
||||
dom.text(self.shortName);
|
||||
dom.text('_expression | ');
|
||||
dom.text(self.shortName);
|
||||
self.parameters(dom, ':', true);
|
||||
dom.text(' }}');
|
||||
});
|
||||
});
|
||||
|
||||
dom.h('In JavaScript', function(){
|
||||
dom.tag('code', function(){
|
||||
dom.text('angular.filter.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('(');
|
||||
self.parameters(dom, ', ');
|
||||
dom.text(')');
|
||||
});
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_this(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_formatter: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.code(function(){
|
||||
if (self.inputType=='select')
|
||||
dom.text('<select name="bindExpression"');
|
||||
else
|
||||
dom.text('<input type="text" name="bindExpression"');
|
||||
dom.text(' ng:format="');
|
||||
dom.text(self.shortName);
|
||||
self.parameters(dom, ':', false, true);
|
||||
dom.text('">');
|
||||
});
|
||||
});
|
||||
|
||||
dom.h('In JavaScript', function(){
|
||||
dom.code(function(){
|
||||
dom.text('var userInputString = angular.formatter.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('.format(modelValue');
|
||||
self.parameters(dom, ', ', false, true);
|
||||
dom.text(');');
|
||||
dom.text('\n');
|
||||
dom.text('var modelValue = angular.formatter.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('.parse(userInputString');
|
||||
self.parameters(dom, ', ', false, true);
|
||||
dom.text(');');
|
||||
});
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_this(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_validator: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.code(function(){
|
||||
dom.text('<input type="text" ng:validate="');
|
||||
dom.text(self.shortName);
|
||||
self.parameters(dom, ':', true);
|
||||
dom.text('"/>');
|
||||
});
|
||||
});
|
||||
|
||||
dom.h('In JavaScript', function(){
|
||||
dom.code(function(){
|
||||
dom.text('angular.validator.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('(');
|
||||
self.parameters(dom, ', ');
|
||||
dom.text(')');
|
||||
});
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_this(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_widget: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.code(function(){
|
||||
if (self.shortName.match(/^@/)) {
|
||||
dom.text('<');
|
||||
dom.text(self.element);
|
||||
dom.text(' ');
|
||||
dom.text(self.shortName.substring(1));
|
||||
if (self.param.length) {
|
||||
dom.text('="');
|
||||
dom.text(self.param[0].name);
|
||||
dom.text('"');
|
||||
}
|
||||
dom.text('>\n ...\n</');
|
||||
dom.text(self.element);
|
||||
dom.text('>');
|
||||
} else {
|
||||
dom.text('<');
|
||||
dom.text(self.shortName);
|
||||
(self.param||[]).forEach(function(param){
|
||||
if (param.optional) {
|
||||
dom.text(' [' + param.name + '="..."]');
|
||||
} else {
|
||||
dom.text(' ' + param.name + '="..."');
|
||||
}
|
||||
});
|
||||
dom.text('></');
|
||||
dom.text(self.shortName);
|
||||
dom.text('>');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_overview: function(dom){
|
||||
dom.html(this.description);
|
||||
},
|
||||
|
||||
html_usage_service: function(dom){
|
||||
var self = this;
|
||||
|
||||
if (this.param.length) {
|
||||
dom.h('Usage', function(){
|
||||
dom.code(function(){
|
||||
dom.text(self.name.split('.').pop());
|
||||
dom.text('(');
|
||||
self.parameters(dom, ', ');
|
||||
dom.text(');');
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_this(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
}
|
||||
|
||||
dom.h('Methods', this.methods, function(method){
|
||||
var signature = (method.param || []).map(property('name'));
|
||||
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
|
||||
dom.html(method.description);
|
||||
method.html_usage_parameters(dom);
|
||||
dom.h('Example', method.example, dom.html);
|
||||
});
|
||||
});
|
||||
dom.h('Properties', this.properties, function(property){
|
||||
dom.h(property.name, function(){
|
||||
dom.html(property.description);
|
||||
dom.h('Example', property.example, dom.html);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
parameters: function(dom, separator, skipFirst, prefix) {
|
||||
var sep = prefix ? separator : '';
|
||||
(this.param||[]).forEach(function(param, i){
|
||||
if (!(skipFirst && i==0)) {
|
||||
if (param.optional) {
|
||||
dom.text('[' + sep + param.name + ']');
|
||||
} else {
|
||||
dom.text(sep + param.name);
|
||||
}
|
||||
}
|
||||
sep = separator;
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function scenarios(docs){
|
||||
var specs = [];
|
||||
docs.forEach(function(doc){
|
||||
specs.push('describe("' + doc.id + '", function(){');
|
||||
specs.push(' beforeEach(function(){');
|
||||
specs.push(' browser().navigateTo("index.html#!' + doc.id + '");');
|
||||
specs.push(' });');
|
||||
specs.push('');
|
||||
doc.scenarios.forEach(function(scenario){
|
||||
specs.push(indent(trim(scenario), 2));
|
||||
specs.push('');
|
||||
});
|
||||
specs.push('});');
|
||||
specs.push('');
|
||||
if (doc.scenario) {
|
||||
}
|
||||
});
|
||||
return specs.join('\n');
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function metadata(docs){
|
||||
var words = [];
|
||||
docs.forEach(function(doc){
|
||||
var path = (doc.name || '').split(/(\.|\:\s+)/);
|
||||
for ( var i = 1; i < path.length; i++) {
|
||||
path.splice(i, 1);
|
||||
}
|
||||
var depth = path.length - 1;
|
||||
var shortName = path.pop();
|
||||
words.push({
|
||||
id: doc.id,
|
||||
name: doc.name,
|
||||
depth: depth,
|
||||
shortName: shortName,
|
||||
type: doc.ngdoc,
|
||||
keywords:doc.keywords()
|
||||
});
|
||||
});
|
||||
words.sort(keywordSort);
|
||||
return words;
|
||||
}
|
||||
|
||||
var KEYWORD_PRIORITY = {
|
||||
'.started': 1,
|
||||
'.guide': 2,
|
||||
'.guide.overview': 1,
|
||||
'.angular': 7,
|
||||
'.angular.Array': 7,
|
||||
'.angular.Object': 7,
|
||||
'.angular.directive': 7,
|
||||
'.angular.filter': 7,
|
||||
'.angular.formatter': 7,
|
||||
'.angular.scope': 7,
|
||||
'.angular.service': 7,
|
||||
'.angular.validator': 7,
|
||||
'.angular.widget': 7
|
||||
};
|
||||
function keywordSort(a, b){
|
||||
function mangleName(doc) {
|
||||
var path = doc.id.split(/\./);
|
||||
var mangled = [];
|
||||
var partialName = '';
|
||||
path.forEach(function(name){
|
||||
partialName += '.' + name;
|
||||
mangled.push(KEYWORD_PRIORITY[partialName] || 5);
|
||||
mangled.push(name);
|
||||
});
|
||||
return mangled.join('.');
|
||||
}
|
||||
var nameA = mangleName(a);
|
||||
var nameB = mangleName(b);
|
||||
return nameA < nameB ? -1 : (nameA > nameB ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function trim(text) {
|
||||
var MAX_INDENT = 9999;
|
||||
var empty = RegExp.prototype.test.bind(/^\s*$/);
|
||||
var lines = text.split('\n');
|
||||
var minIndent = MAX_INDENT;
|
||||
var indentRegExp;
|
||||
var ignoreLine = (lines[0][0] != ' ' && lines.length > 1);
|
||||
// ignore first line if it has no indentation and there is more than one line
|
||||
|
||||
lines.forEach(function(line){
|
||||
if (ignoreLine) {
|
||||
ignoreLine = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var indent = line.match(/^\s*/)[0].length;
|
||||
if (indent > 0 || minIndent == MAX_INDENT) {
|
||||
minIndent = Math.min(minIndent, indent);
|
||||
}
|
||||
});
|
||||
|
||||
indentRegExp = new RegExp('^\\s{0,' + minIndent + '}');
|
||||
|
||||
for ( var i = 0; i < lines.length; i++) {
|
||||
lines[i] = lines[i].replace(indentRegExp, '');
|
||||
}
|
||||
|
||||
// remove leading lines
|
||||
while (empty(lines[0])) {
|
||||
lines.shift();
|
||||
}
|
||||
|
||||
// remove trailing
|
||||
while (empty(lines[lines.length - 1])) {
|
||||
lines.pop();
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function indent(text, spaceCount) {
|
||||
var lines = text.split('\n'),
|
||||
indent = '',
|
||||
fixedLines = [];
|
||||
|
||||
while(spaceCount--) indent += ' ';
|
||||
|
||||
lines.forEach(function(line) {
|
||||
fixedLines.push(indent + line);
|
||||
});
|
||||
|
||||
return fixedLines.join('\n');
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function merge(docs){
|
||||
var byName = {};
|
||||
docs.forEach(function(doc){
|
||||
byName[doc.name] = doc;
|
||||
});
|
||||
for(var i=0; i<docs.length;) {
|
||||
if (findParent(docs[i], 'method') ||
|
||||
findParent(docs[i], 'property')) {
|
||||
docs.splice(i, 1);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
function findParent(doc, name){
|
||||
var parentName = doc[name+'Of'];
|
||||
if (!parentName) return false;
|
||||
|
||||
var parent = byName[parentName];
|
||||
if (!parent)
|
||||
throw new Error("No parent named '" + parentName + "' for '" +
|
||||
doc.name + "' in @" + name + "Of.");
|
||||
|
||||
var listName = (name + 's').replace(/ys$/, 'ies');
|
||||
var list = parent[listName] = (parent[listName] || []);
|
||||
list.push(doc);
|
||||
list.sort(orderByName);
|
||||
return true;
|
||||
}
|
||||
|
||||
function orderByName(a, b){
|
||||
return a.name < b.name ? -1 : (a.name > b.name ? 1 : 0);
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
function property(name) {
|
||||
return function(value){
|
||||
return value[name];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* All reading related code here. This is so that we can separate the async code from sync code
|
||||
* for testability
|
||||
*/
|
||||
require.paths.push(__dirname);
|
||||
var fs = require('fs'),
|
||||
callback = require('callback');
|
||||
|
||||
var NEW_LINE = /\n\r?/;
|
||||
|
||||
function collect(callback){
|
||||
findJsFiles('src', callback.waitMany(function(file) {
|
||||
//console.log('reading', file, '...');
|
||||
findNgDocInJsFile(file, callback.waitMany(function(doc, line) {
|
||||
callback(doc, file, line);
|
||||
}));
|
||||
}));
|
||||
findNgDocInDir('docs/', callback.waitMany(callback));
|
||||
callback.done();
|
||||
}
|
||||
|
||||
function findJsFiles(dir, callback){
|
||||
fs.readdir(dir, callback.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
var path = dir + '/' + file;
|
||||
fs.lstat(path, callback.waitFor(function(err, stat){
|
||||
if (err) return this.error(err);
|
||||
if (stat.isDirectory())
|
||||
findJsFiles(path, callback.waitMany(callback));
|
||||
else if (/\.js$/.test(path))
|
||||
callback(path);
|
||||
}));
|
||||
});
|
||||
callback.done();
|
||||
}));
|
||||
}
|
||||
|
||||
function findNgDocInDir(directory, docNotify) {
|
||||
fs.readdir(directory, docNotify.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
//console.log('reading', directory + file, '...');
|
||||
if (!file.match(/\.ngdoc$/)) return;
|
||||
fs.readFile(directory + file, docNotify.waitFor(function(err, content){
|
||||
if (err) return this.error(err);
|
||||
docNotify(content.toString(), directory + file, 1);
|
||||
}));
|
||||
});
|
||||
docNotify.done();
|
||||
}));
|
||||
}
|
||||
|
||||
function findNgDocInJsFile(file, callback) {
|
||||
fs.readFile(file, callback.waitFor(function(err, content){
|
||||
var lines = content.toString().split(NEW_LINE);
|
||||
var text;
|
||||
var startingLine ;
|
||||
var match;
|
||||
var inDoc = false;
|
||||
lines.forEach(function(line, lineNumber){
|
||||
lineNumber++;
|
||||
// is the comment starting?
|
||||
if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) {
|
||||
line = match[1];
|
||||
inDoc = true;
|
||||
text = [];
|
||||
startingLine = lineNumber;
|
||||
}
|
||||
// are we done?
|
||||
if (inDoc && line.match(/\*\//)) {
|
||||
text = text.join('\n');
|
||||
text = text.replace(/^\n/, '');
|
||||
if (text.match(/@ngdoc/)){
|
||||
callback(text, startingLine);
|
||||
}
|
||||
doc = null;
|
||||
inDoc = false;
|
||||
}
|
||||
// is the comment add text
|
||||
if (inDoc){
|
||||
text.push(line.replace(/^\s*\*\s?/, ''));
|
||||
}
|
||||
});
|
||||
callback.done();
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
exports.collect = collect;
|
||||
@@ -0,0 +1,35 @@
|
||||
@namespace doc url("http://docs.angularjs.org/");
|
||||
|
||||
doc\:example {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul.doc-example {
|
||||
list-style-type: none;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
ul.doc-example > li {
|
||||
border: 2px solid gray;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
background-color: white;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
ul.doc-example > li.doc-example-heading {
|
||||
border: none;
|
||||
border-radius: none;
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
|
||||
li.doc-example-live {
|
||||
padding: 10px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
div.syntaxhighlighter {
|
||||
padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars http://is.gd/gSMgC */
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
(function(){
|
||||
|
||||
var angularJsUrl;
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
var angularJsRegex = /^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/;
|
||||
for(var j = 0; j < scripts.length; j++) {
|
||||
var src = scripts[j].src;
|
||||
if (src && src.match(angularJsRegex)) {
|
||||
angularJsUrl = src.replace('docs.angularjs.org', 'code.angularjs.org');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var HTML_TEMPLATE =
|
||||
'<!doctype html>\n' +
|
||||
'<html xmlns:ng="http://angularjs.org">\n' +
|
||||
' <script src="' + angularJsUrl + '" ng:autobind></script>\n' +
|
||||
' <body>\n' +
|
||||
'_HTML_SOURCE_\n' +
|
||||
' </body>\n' +
|
||||
'</html>';
|
||||
|
||||
angular.widget('doc:example', function(element){
|
||||
this.descend(true); //compile the example code
|
||||
element.hide();
|
||||
|
||||
var example = element.find('pre.doc-source').eq(0),
|
||||
exampleSrc = example.text(),
|
||||
scenario = element.find('pre.doc-scenario').eq(0);
|
||||
|
||||
var code = indent(exampleSrc);
|
||||
var tabHtml =
|
||||
'<ul class="doc-example">' +
|
||||
'<li class="doc-example-heading"><h3>Source</h3></li>' +
|
||||
'<li class="doc-example-source" ng:non-bindable>' +
|
||||
'<pre class="brush: js; html-script: true; highlight: [' +
|
||||
code.hilite + ']; toolbar: false;"></pre></li>' +
|
||||
'<li class="doc-example-heading"><h3>Live Preview</h3></li>' +
|
||||
'<li class="doc-example-live">' + exampleSrc +'</li>';
|
||||
if (scenario.text()) {
|
||||
tabHtml +=
|
||||
'<li class="doc-example-heading"><h3>Scenario Test</h3></li>' +
|
||||
'<li class="doc-example-scenario"><pre class="brush: js">' + scenario.text() + '</pre></li>';
|
||||
}
|
||||
tabHtml +=
|
||||
'</ul>';
|
||||
var tabs = angular.element(tabHtml);
|
||||
|
||||
tabs.find('li.doc-example-source > pre').text(HTML_TEMPLATE.replace('_HTML_SOURCE_', code.html));
|
||||
|
||||
element.html('');
|
||||
element.append(tabs);
|
||||
element.show();
|
||||
|
||||
var script = (exampleSrc.match(/<script[^\>]*>([\s\S]*)<\/script>/) || [])[1] || '';
|
||||
try {
|
||||
eval(script);
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
}
|
||||
});
|
||||
|
||||
function indent(text) {
|
||||
if (!text) return text;
|
||||
var lines = text.split(/[\n|\r]/);
|
||||
var lineNo = [];
|
||||
// remove any leading blank lines
|
||||
while (lines[0].match(/^\s*$/)) lines.shift();
|
||||
// remove any trailing blank lines
|
||||
while (lines[lines.length - 1].match(/^\s*$/)) lines.pop();
|
||||
var minIndent = 999;
|
||||
for ( var i = 0; i < lines.length; i++) {
|
||||
var line = lines[0];
|
||||
var indent = line.match(/^\s*/)[0];
|
||||
if (indent !== line && indent.length < minIndent) {
|
||||
minIndent = indent.length;
|
||||
}
|
||||
}
|
||||
|
||||
for ( var i = 0; i < lines.length; i++) {
|
||||
lines[i] = ' ' + lines[i].substring(minIndent);
|
||||
lineNo.push(5 + i);
|
||||
}
|
||||
return {html: lines.join('\n'), hilite: lineNo.join(',') };
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org">
|
||||
<head>
|
||||
<title><angular/> Docs Scenario Runner</title>
|
||||
<script type="text/javascript" src="../angular-scenario.js" ng:autobind></script>
|
||||
<script type="text/javascript" src="docs-scenario.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,302 @@
|
||||
/* Common Style */
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
|
||||
/* Main Layout */
|
||||
|
||||
#header {
|
||||
height: 3.5em;
|
||||
}
|
||||
|
||||
#sidebar,
|
||||
#main {
|
||||
position: absolute;
|
||||
top: 3.5em;
|
||||
bottom: 0;
|
||||
margin-top: 1px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
width: 13.8em;
|
||||
padding: 0.8em 0em 1.5em 0.8em;
|
||||
}
|
||||
|
||||
#main {
|
||||
left: 14.6em;
|
||||
right: 0;
|
||||
padding: 1em;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#api-list {
|
||||
position: absolute;
|
||||
top: 3em;
|
||||
bottom: 1em;
|
||||
overflow-y: scroll;
|
||||
padding-right: 0.8em;
|
||||
}
|
||||
|
||||
|
||||
/* App Header */
|
||||
|
||||
#header {
|
||||
background-color: #F2C200;
|
||||
border-bottom: 1px solid #957800;
|
||||
}
|
||||
|
||||
#header h1 {
|
||||
font-weight: normal;
|
||||
font-size: 30px;
|
||||
line-height: 30px;
|
||||
margin: 0;
|
||||
padding: 10px 10px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#header .angular {
|
||||
font-family: "Courier New", monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#header h1 a {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#header h1 a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
/* Main main Style */
|
||||
|
||||
#main h1 {
|
||||
font-family: monospace;
|
||||
margin-top: 0;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
#main h2 {
|
||||
margin-top: 1.8em;
|
||||
}
|
||||
|
||||
#main h1 + h2 {
|
||||
margin-top: 1.3em;
|
||||
}
|
||||
|
||||
#main h3 {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
#main ul.methods,
|
||||
#main ul.properties {
|
||||
margin: 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
#main ul.methods > li,
|
||||
#main ul.properties > li {
|
||||
list-style: none;
|
||||
padding: 0 10px 10px 10px;
|
||||
margin-top: 1.5em;
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
#main ul.methods > li > h3,
|
||||
#main ul.properties > li > h3 {
|
||||
font-family: "Courier New", monospace;
|
||||
background-color: #E0E0E0;
|
||||
margin: 0 -10px 5px -10px;
|
||||
padding: .5em .5em .25em .5em;
|
||||
border-bottom: 1px solid #888;
|
||||
}
|
||||
|
||||
#main ul.parameters li > p,
|
||||
#main div.returns > p {
|
||||
display: inline; /* the top most paragraph should not cause new lines inside lists. */
|
||||
}
|
||||
|
||||
.main-title {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
/* Searchbox & Sidebar Style */
|
||||
|
||||
#search-box, #sidebar {
|
||||
border-right: 1px solid #DDD;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
#search-box {
|
||||
width: 16em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#sidebar a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#sidebar a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#sidebar ul {
|
||||
list-style-type: none;
|
||||
/*TODO(esprehn): Can we just reset globally and not break examples?*/
|
||||
margin: 0;
|
||||
padding: 0 0.8em 0 0;
|
||||
width: 13em;
|
||||
}
|
||||
|
||||
#sidebar ul li {
|
||||
}
|
||||
|
||||
#sidebar ul li a {
|
||||
display: block;
|
||||
padding: 2px 2px 2px 4px;
|
||||
}
|
||||
|
||||
#sidebar ul li.selected a {
|
||||
background-color: #DDD;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
border: 1px solid #CCC;
|
||||
padding: 1px 1px 1px 3px;
|
||||
}
|
||||
|
||||
#sidebar ul li.level-0 {
|
||||
margin-top: 0.5em;
|
||||
margin-left: 0em;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
#sidebar ul li.level-0:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#sidebar ul li.level-1.level-angular {
|
||||
font-family: monospace;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#sidebar ul li.level-1 {
|
||||
margin-left: 1em;
|
||||
margin-top: 5px;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#sidebar ul li.level-2 {
|
||||
margin-left: 2em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#sidebar ul li.level-3 {
|
||||
margin-left: 3em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#sidebar ul li.level-4 {
|
||||
margin-left: 4em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
|
||||
/* Warning and Info Banners */
|
||||
|
||||
.deprecated {
|
||||
border: 2px solid red;
|
||||
}
|
||||
|
||||
.deprecated legend {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.workInProgress {
|
||||
border: 2px solid orange;
|
||||
}
|
||||
|
||||
.workInProgress legend {
|
||||
font-weight: bold;
|
||||
color: orange;
|
||||
}
|
||||
|
||||
|
||||
/* Feedback Link */
|
||||
|
||||
#feedback {
|
||||
float: right;
|
||||
width: 10em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
/* Live Example Style */
|
||||
|
||||
.doc-example-live table td {
|
||||
padding: 0 1.5em;
|
||||
}
|
||||
|
||||
|
||||
/* Scrollbars */
|
||||
|
||||
::-webkit-scrollbar{
|
||||
width:0.8em;
|
||||
margin: 0.2em 0em;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar:hover{
|
||||
background-color:#eee;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb{
|
||||
min-height:0.8em;
|
||||
min-width:0.8em;
|
||||
-webkit-border-radius:0.5em;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover{
|
||||
background-color: #bbb;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:active{
|
||||
background-color:#888;
|
||||
}
|
||||
|
||||
#sidebar::-webkit-scrollbar {
|
||||
background-color:#eee;
|
||||
}
|
||||
|
||||
#main::-webkit-scrollbar {
|
||||
background-color:#fff;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
img.right {
|
||||
float: right;
|
||||
}
|
||||
h1, h2, h3, h4, h5 {
|
||||
clear: both;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
var HAS_HASH = /#/;
|
||||
DocsController.$inject = ['$location', '$browser', '$window'];
|
||||
function DocsController($location, $browser, $window) {
|
||||
this.pages = NG_PAGES;
|
||||
window.$root = this.$root;
|
||||
this.$location = $location;
|
||||
|
||||
if (!HAS_HASH.test($location.href)) {
|
||||
$location.hashPath = '!angular';
|
||||
}
|
||||
|
||||
this.$watch('$location.hashPath', function(hashPath) {
|
||||
if (hashPath.match(/^!/)) {
|
||||
this.partialId = hashPath.substring(1);
|
||||
this.partialTitle = (angular.Array.filter(NG_PAGES, {id:this.partialId})[0]||{}).name;
|
||||
}
|
||||
});
|
||||
|
||||
this.getUrl = function(page){
|
||||
return '#!' + page.id;
|
||||
};
|
||||
|
||||
this.getCurrentPartial = function(){
|
||||
return './' + this.partialId + '.html';
|
||||
};
|
||||
|
||||
this.getClass = function(page) {
|
||||
var depth = page.depth,
|
||||
cssClass = 'level-' + depth + (page.name == this.partialId ? ' selected' : '');
|
||||
|
||||
if (depth == 1 && page.type !== 'overview') cssClass += ' level-angular';
|
||||
|
||||
return cssClass;
|
||||
};
|
||||
|
||||
this.afterPartialLoaded = function() {
|
||||
SyntaxHighlighter.highlight();
|
||||
};
|
||||
|
||||
this.getFeedbackUrl = function() {
|
||||
return "mailto:angular@googlegroups.com?" +
|
||||
"subject=" + escape("Feedback on " + $location.href) + "&" +
|
||||
"body=" + escape("Hi there,\n\nI read " + $location.href + " and wanted to ask ....");
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// prevent compilation of code
|
||||
angular.widget('code', function(element){
|
||||
element.attr('ng:non-bindable', 'true');
|
||||
});
|
||||
|
||||
SyntaxHighlighter['defaults'].toolbar = false;
|
||||
@@ -0,0 +1,46 @@
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org/"
|
||||
xmlns:doc="http://docs.angularjs.org/"
|
||||
ng:controller="DocsController">
|
||||
<head>
|
||||
<title ng:bind-template="<angular/>: {{partialTitle}}"><angular/></title>
|
||||
|
||||
<meta name="fragment" content="!">
|
||||
|
||||
<link rel="stylesheet" href="doc_widgets.css" type="text/css" />
|
||||
<link rel="stylesheet" href="docs.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="syntaxhighlighter/shCore.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="syntaxhighlighter/shThemeDefault.css" type="text/css"/>
|
||||
|
||||
<script src="jquery.min.js"></script>
|
||||
<script src="syntaxhighlighter/shCore.js"></script>
|
||||
<script src="syntaxhighlighter/shBrushJScript.js"></script>
|
||||
<script src="syntaxhighlighter/shBrushXml.js"></script>
|
||||
|
||||
<script src="../angular.min.js" ng:autobind></script>
|
||||
<script src="docs.js"></script>
|
||||
<script src="doc_widgets.js"></script>
|
||||
<script src="docs-keywords.js"></script>
|
||||
</head>
|
||||
<body style="display:none;" ng:show="true">
|
||||
<div id="header">
|
||||
<h1>
|
||||
<span class="main-title">{{partialTitle}}</span>
|
||||
<a href="#" tabindex="0"><span class="angular"><angular/></span> Docs</a>
|
||||
</h1>
|
||||
</div>
|
||||
<div id="sidebar">
|
||||
<input type="text" name="search" id="search-box" placeholder="search the docs"
|
||||
tabindex="1" accesskey="s"/>
|
||||
<ul id="api-list">
|
||||
<li ng:repeat="page in pages.$filter(search)" ng:class="getClass(page)">
|
||||
<a href="{{getUrl(page)}}" ng:click="" tabindex="2">{{page.shortName}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="main">
|
||||
<a id="feedback" ng:href="{{getFeedbackUrl()}}">Report an Issue or Ask a Question</a>
|
||||
<ng:include src="getCurrentPartial()" onload="afterPartialLoaded()"></ng:include>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../lib/jquery/jquery-1.4.2.min.js
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* SyntaxHighlighter
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter
|
||||
*
|
||||
* SyntaxHighlighter is donationware. If you are using it, please donate.
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
|
||||
*
|
||||
* @version
|
||||
* 3.0.83 (July 02 2010)
|
||||
*
|
||||
* @copyright
|
||||
* Copyright (C) 2004-2010 Alex Gorbatchev.
|
||||
*
|
||||
* @license
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
*/
|
||||
;(function()
|
||||
{
|
||||
// CommonJS
|
||||
typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
|
||||
|
||||
function Brush()
|
||||
{
|
||||
var keywords = 'break case catch continue ' +
|
||||
'default delete do else false ' +
|
||||
'for function if in instanceof ' +
|
||||
'new null return super switch ' +
|
||||
'this throw true try typeof var while with'
|
||||
;
|
||||
|
||||
var r = SyntaxHighlighter.regexLib;
|
||||
|
||||
this.regexList = [
|
||||
{ regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings
|
||||
{ regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings
|
||||
{ regex: r.singleLineCComments, css: 'comments' }, // one line comments
|
||||
{ regex: r.multiLineCComments, css: 'comments' }, // multiline comments
|
||||
{ regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion
|
||||
{ regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keywords
|
||||
];
|
||||
|
||||
this.forHtmlScript(r.scriptScriptTags);
|
||||
};
|
||||
|
||||
Brush.prototype = new SyntaxHighlighter.Highlighter();
|
||||
Brush.aliases = ['js', 'jscript', 'javascript'];
|
||||
|
||||
SyntaxHighlighter.brushes.JScript = Brush;
|
||||
|
||||
// CommonJS
|
||||
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
|
||||
})();
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* SyntaxHighlighter
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter
|
||||
*
|
||||
* SyntaxHighlighter is donationware. If you are using it, please donate.
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
|
||||
*
|
||||
* @version
|
||||
* 3.0.83 (July 02 2010)
|
||||
*
|
||||
* @copyright
|
||||
* Copyright (C) 2004-2010 Alex Gorbatchev.
|
||||
*
|
||||
* @license
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
*/
|
||||
;(function()
|
||||
{
|
||||
// CommonJS
|
||||
typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
|
||||
|
||||
function Brush()
|
||||
{
|
||||
function process(match, regexInfo)
|
||||
{
|
||||
var constructor = SyntaxHighlighter.Match,
|
||||
code = match[0],
|
||||
tag = new XRegExp('(<|<)[\\s\\/\\?]*(?<name>[:\\w-\\.]+)', 'xg').exec(code),
|
||||
result = []
|
||||
;
|
||||
|
||||
if (match.attributes != null)
|
||||
{
|
||||
var attributes,
|
||||
regex = new XRegExp('(?<name> [\\w:\\-\\.]+)' +
|
||||
'\\s*=\\s*' +
|
||||
'(?<value> ".*?"|\'.*?\'|\\w+)',
|
||||
'xg');
|
||||
|
||||
while ((attributes = regex.exec(code)) != null)
|
||||
{
|
||||
result.push(new constructor(attributes.name, match.index + attributes.index, 'color1'));
|
||||
result.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string'));
|
||||
}
|
||||
}
|
||||
|
||||
if (tag != null)
|
||||
result.push(
|
||||
new constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword')
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
this.regexList = [
|
||||
{ regex: new XRegExp('(\\<|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\\>|>)', 'gm'), css: 'color2' }, // <![ ... [ ... ]]>
|
||||
{ regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // <!-- ... -->
|
||||
{ regex: new XRegExp('(<|<)[\\s\\/\\?]*(\\w+)(?<attributes>.*?)[\\s\\/\\?]*(>|>)', 'sg'), func: process }
|
||||
];
|
||||
};
|
||||
|
||||
Brush.prototype = new SyntaxHighlighter.Highlighter();
|
||||
Brush.aliases = ['xml', 'xhtml', 'xslt', 'html'];
|
||||
|
||||
SyntaxHighlighter.brushes.Xml = Brush;
|
||||
|
||||
// CommonJS
|
||||
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
|
||||
})();
|
||||
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* SyntaxHighlighter
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter
|
||||
*
|
||||
* SyntaxHighlighter is donationware. If you are using it, please donate.
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
|
||||
*
|
||||
* @version
|
||||
* 3.0.83 (July 02 2010)
|
||||
*
|
||||
* @copyright
|
||||
* Copyright (C) 2004-2010 Alex Gorbatchev.
|
||||
*
|
||||
* @license
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
*/
|
||||
.syntaxhighlighter a,
|
||||
.syntaxhighlighter div,
|
||||
.syntaxhighlighter code,
|
||||
.syntaxhighlighter table,
|
||||
.syntaxhighlighter table td,
|
||||
.syntaxhighlighter table tr,
|
||||
.syntaxhighlighter table tbody,
|
||||
.syntaxhighlighter table thead,
|
||||
.syntaxhighlighter table caption,
|
||||
.syntaxhighlighter textarea {
|
||||
-moz-border-radius: 0 0 0 0 !important;
|
||||
-webkit-border-radius: 0 0 0 0 !important;
|
||||
background: none !important;
|
||||
border: 0 !important;
|
||||
bottom: auto !important;
|
||||
float: none !important;
|
||||
height: auto !important;
|
||||
left: auto !important;
|
||||
line-height: 1.1em !important;
|
||||
margin: 0 !important;
|
||||
outline: 0 !important;
|
||||
overflow: visible !important;
|
||||
padding: 0 !important;
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
text-align: left !important;
|
||||
top: auto !important;
|
||||
vertical-align: baseline !important;
|
||||
width: auto !important;
|
||||
box-sizing: content-box !important;
|
||||
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important;
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
font-size: 1em !important;
|
||||
min-height: inherit !important;
|
||||
min-height: auto !important;
|
||||
}
|
||||
|
||||
.syntaxhighlighter {
|
||||
width: 100% !important;
|
||||
margin: 1em 0 1em 0 !important;
|
||||
position: relative !important;
|
||||
overflow: auto !important;
|
||||
font-size: 1em !important;
|
||||
}
|
||||
.syntaxhighlighter.source {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
.syntaxhighlighter .bold {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
.syntaxhighlighter .italic {
|
||||
font-style: italic !important;
|
||||
}
|
||||
.syntaxhighlighter .line {
|
||||
white-space: pre !important;
|
||||
}
|
||||
.syntaxhighlighter table {
|
||||
width: 100% !important;
|
||||
}
|
||||
.syntaxhighlighter table caption {
|
||||
text-align: left !important;
|
||||
padding: .5em 0 0.5em 1em !important;
|
||||
}
|
||||
.syntaxhighlighter table td.code {
|
||||
width: 100% !important;
|
||||
}
|
||||
.syntaxhighlighter table td.code .container {
|
||||
position: relative !important;
|
||||
}
|
||||
.syntaxhighlighter table td.code .container textarea {
|
||||
box-sizing: border-box !important;
|
||||
position: absolute !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
border: none !important;
|
||||
background: white !important;
|
||||
padding-left: 1em !important;
|
||||
overflow: hidden !important;
|
||||
white-space: pre !important;
|
||||
}
|
||||
.syntaxhighlighter table td.gutter .line {
|
||||
text-align: right !important;
|
||||
padding: 0 0.5em 0 1em !important;
|
||||
}
|
||||
.syntaxhighlighter table td.code .line {
|
||||
padding: 0 1em !important;
|
||||
}
|
||||
.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line {
|
||||
padding-left: 0em !important;
|
||||
}
|
||||
.syntaxhighlighter.show {
|
||||
display: block !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed table {
|
||||
display: none !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar {
|
||||
padding: 0.1em 0.8em 0em 0.8em !important;
|
||||
font-size: 1em !important;
|
||||
position: static !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar span {
|
||||
display: inline !important;
|
||||
margin-right: 1em !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar span a {
|
||||
padding: 0 !important;
|
||||
display: none !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar span a.expandSource {
|
||||
display: inline !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar {
|
||||
position: absolute !important;
|
||||
right: 1px !important;
|
||||
top: 1px !important;
|
||||
width: 11px !important;
|
||||
height: 11px !important;
|
||||
font-size: 10px !important;
|
||||
z-index: 10 !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar span.title {
|
||||
display: inline !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar a {
|
||||
display: block !important;
|
||||
text-align: center !important;
|
||||
text-decoration: none !important;
|
||||
padding-top: 1px !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar a.expandSource {
|
||||
display: none !important;
|
||||
}
|
||||
.syntaxhighlighter.ie {
|
||||
font-size: .9em !important;
|
||||
padding: 1px 0 1px 0 !important;
|
||||
}
|
||||
.syntaxhighlighter.ie .toolbar {
|
||||
line-height: 8px !important;
|
||||
}
|
||||
.syntaxhighlighter.ie .toolbar a {
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .line.alt1 .content,
|
||||
.syntaxhighlighter.printing .line.alt2 .content,
|
||||
.syntaxhighlighter.printing .line.highlighted .number,
|
||||
.syntaxhighlighter.printing .line.highlighted.alt1 .content,
|
||||
.syntaxhighlighter.printing .line.highlighted.alt2 .content {
|
||||
background: none !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .line .number {
|
||||
color: #bbbbbb !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .line .content {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .toolbar {
|
||||
display: none !important;
|
||||
}
|
||||
.syntaxhighlighter.printing a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a {
|
||||
color: #008200 !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a {
|
||||
color: blue !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .keyword {
|
||||
color: #006699 !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .preprocessor {
|
||||
color: gray !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .variable {
|
||||
color: #aa7700 !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .value {
|
||||
color: #009900 !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .functions {
|
||||
color: #ff1493 !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .constants {
|
||||
color: #0066cc !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .script {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a {
|
||||
color: gray !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a {
|
||||
color: #ff1493 !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a {
|
||||
color: red !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a {
|
||||
color: black !important;
|
||||
}
|
||||
+17
File diff suppressed because one or more lines are too long
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* SyntaxHighlighter
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter
|
||||
*
|
||||
* SyntaxHighlighter is donationware. If you are using it, please donate.
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
|
||||
*
|
||||
* @version
|
||||
* 3.0.83 (July 02 2010)
|
||||
*
|
||||
* @copyright
|
||||
* Copyright (C) 2004-2010 Alex Gorbatchev.
|
||||
*
|
||||
* @license
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
*/
|
||||
.syntaxhighlighter {
|
||||
background-color: white !important;
|
||||
}
|
||||
.syntaxhighlighter .line.alt1 {
|
||||
background-color: white !important;
|
||||
}
|
||||
.syntaxhighlighter .line.alt2 {
|
||||
background-color: white !important;
|
||||
}
|
||||
.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 {
|
||||
background-color: #e0e0e0 !important;
|
||||
}
|
||||
.syntaxhighlighter .line.highlighted.number {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter table caption {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter .gutter {
|
||||
color: #afafaf !important;
|
||||
}
|
||||
.syntaxhighlighter .gutter .line {
|
||||
border-right: 3px solid #6ce26c !important;
|
||||
}
|
||||
.syntaxhighlighter .gutter .line.highlighted {
|
||||
background-color: #6ce26c !important;
|
||||
color: white !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .line .content {
|
||||
border: none !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed {
|
||||
overflow: visible !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar {
|
||||
color: blue !important;
|
||||
background: white !important;
|
||||
border: 1px solid #6ce26c !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar a {
|
||||
color: blue !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar a:hover {
|
||||
color: red !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar {
|
||||
color: white !important;
|
||||
background: #6ce26c !important;
|
||||
border: none !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar a {
|
||||
color: white !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar a:hover {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter .plain, .syntaxhighlighter .plain a {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter .comments, .syntaxhighlighter .comments a {
|
||||
color: #008200 !important;
|
||||
}
|
||||
.syntaxhighlighter .string, .syntaxhighlighter .string a {
|
||||
color: blue !important;
|
||||
}
|
||||
.syntaxhighlighter .keyword {
|
||||
color: #006699 !important;
|
||||
}
|
||||
.syntaxhighlighter .preprocessor {
|
||||
color: gray !important;
|
||||
}
|
||||
.syntaxhighlighter .variable {
|
||||
color: #aa7700 !important;
|
||||
}
|
||||
.syntaxhighlighter .value {
|
||||
color: #009900 !important;
|
||||
}
|
||||
.syntaxhighlighter .functions {
|
||||
color: #ff1493 !important;
|
||||
}
|
||||
.syntaxhighlighter .constants {
|
||||
color: #0066cc !important;
|
||||
}
|
||||
.syntaxhighlighter .script {
|
||||
font-weight: bold !important;
|
||||
color: #006699 !important;
|
||||
background-color: none !important;
|
||||
}
|
||||
.syntaxhighlighter .color1, .syntaxhighlighter .color1 a {
|
||||
color: gray !important;
|
||||
}
|
||||
.syntaxhighlighter .color2, .syntaxhighlighter .color2 a {
|
||||
color: #ff1493 !important;
|
||||
}
|
||||
.syntaxhighlighter .color3, .syntaxhighlighter .color3 a {
|
||||
color: red !important;
|
||||
}
|
||||
|
||||
.syntaxhighlighter .keyword {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
3.0.83
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* All writing related code here. This is so that we can separate the async code from sync code
|
||||
* for testability
|
||||
*/
|
||||
require.paths.push(__dirname);
|
||||
var fs = require('fs');
|
||||
var OUTPUT_DIR = "build/docs/";
|
||||
|
||||
function output(docs, content, callback){
|
||||
callback();
|
||||
}
|
||||
|
||||
exports.output = function(file, content, callback){
|
||||
//console.log('writing', OUTPUT_DIR + file, '...');
|
||||
fs.writeFile(
|
||||
OUTPUT_DIR + file,
|
||||
exports.toString(content),
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
exports.toString = function toString(obj){
|
||||
switch (typeof obj) {
|
||||
case 'string':
|
||||
return obj;
|
||||
case 'object':
|
||||
if (obj instanceof Array) {
|
||||
obj.forEach(function (value, key){
|
||||
obj[key] = toString(value);
|
||||
});
|
||||
return obj.join('');
|
||||
} else {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
exports.makeDir = function (path, callback) {
|
||||
var parts = path.split(/\//);
|
||||
path = '.';
|
||||
(function next(){
|
||||
if (parts.length) {
|
||||
path += '/' + parts.shift();
|
||||
fs.mkdir(path, 0777, next);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
exports.copy = function(filename, callback){
|
||||
copy('docs/src/templates/' + filename, OUTPUT_DIR + filename, callback);
|
||||
};
|
||||
|
||||
function copy(from, to, callback) {
|
||||
//console.log('writing', to, '...');
|
||||
fs.readFile(from, function(err, content){
|
||||
if (err) return callback.error(err);
|
||||
fs.writeFile(to, content, callback);
|
||||
});
|
||||
}
|
||||
|
||||
exports.copyDir = function(dir, callback) {
|
||||
exports.makeDir(OUTPUT_DIR + '/' + dir, callback.waitFor(function(){
|
||||
fs.readdir('docs/' + dir, callback.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
copy('docs/' + dir + '/' + file, OUTPUT_DIR + '/' + dir + '/' + file, callback.waitFor());
|
||||
});
|
||||
callback();
|
||||
}));
|
||||
}));
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Getting Started
|
||||
@description
|
||||
|
||||
# Hello World!
|
||||
|
||||
A great way for you to get started with `angular` is to create the tradtional
|
||||
"Hello World!" app:
|
||||
|
||||
1. In your favorite text editor, create an HTML file
|
||||
(for example, `helloworld.html`).
|
||||
2. From the __Source__ box below, copy and paste the code into your HTML file.
|
||||
(Double-click on the source to easily select all.)
|
||||
3. Open the file in your web browser.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Hello {{'World'}}!
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
|
||||
The resulting web page should look something like the following:
|
||||
|
||||
<img class="center" src="img/helloworld.png" border="1" />
|
||||
|
||||
Now let's take a closer look at that code, and see what is going on behind
|
||||
the scenes.
|
||||
|
||||
The first line of interest defines the `ng` namespace, which makes
|
||||
`angular` work across all browsers (especially important for IE):
|
||||
|
||||
<pre>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
</pre>
|
||||
|
||||
The next line downloads the `angular` script, and instructs `angular` to process
|
||||
the entire HTML page when it is loaded:
|
||||
|
||||
<pre>
|
||||
<script type="text/javascript" src="http://code.angularjs.org/angular-?.?.?.min.js" ng:autobind></script>
|
||||
</pre>
|
||||
|
||||
(For details on what happens when `angular` processes an HTML page,
|
||||
see {@link guide.bootstrap Bootstrap}.)
|
||||
|
||||
Finally, this line in the `<body>` of the page is the template that describes
|
||||
how to display our greeting in the UI:
|
||||
|
||||
<pre>
|
||||
Hello {{'World'}}!
|
||||
</pre>
|
||||
|
||||
Note the use of the double curly brace markup (`{{ }}`) to bind the expression to
|
||||
the greeting text. Here the expression is the string literal 'World'.
|
||||
|
||||
Next let's look at a more interesting example, that uses `angular` to
|
||||
bind a dynamic expression to our greeting text.
|
||||
|
||||
# Hello <angular/> World!
|
||||
|
||||
This example demonstrates `angular`'s two-way data binding:
|
||||
|
||||
1. Edit the HTML file you created in the "Hello World!" example above.
|
||||
2. Replace the contents of `<body>` with the code from the __Source__ box below.
|
||||
3. Refresh your browswer window.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Your name: <input type="text" name="yourname" value="World"/>
|
||||
<hr/>
|
||||
Hello {{yourname}}!
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
|
||||
After the refresh, the page should look something like this:
|
||||
|
||||
<img class="left" src="img/helloworld_2way.png" border="1" />
|
||||
|
||||
These are some of the important points to note from this example:
|
||||
|
||||
* The text input {@link angular.widget widget} called `yourname` is bound to a model variable called
|
||||
`yourname`.
|
||||
* The double curly braces notation binds the variable `yourname` to the
|
||||
greeting text.
|
||||
<!--
|
||||
* The variable `yourname` is implicitly created in the root scope.
|
||||
-->
|
||||
* You did not need to explicitly register an event listener or define an event
|
||||
handler for events!
|
||||
|
||||
Now try typing your name into the input box, and notice the immediate change to
|
||||
the displayed greeting. This demonstrates the concept of `angular`'s
|
||||
{@link guide.data-binding bi-directional data binding}. Any changes to the input field are immediately
|
||||
reflected in the model (one direction), and any changes to the model are
|
||||
reflected in the greeting text (the other direction).
|
||||
|
||||
|
||||
# Anatomy of an `angular` App
|
||||
|
||||
This section describes the 3 parts of an `angular` app, and explains how they
|
||||
map to the Model-View-Controller design pattern:
|
||||
|
||||
## Templates
|
||||
|
||||
Templates, which you write in HTML and CSS, serve as the View. You add elements,
|
||||
attributes, and markup to HTML, which serve as instructions to the `angular`
|
||||
compiler. The `angular` compiler is fully extensible, meaning that with angular
|
||||
you can build your own declarative language on top of HTML!
|
||||
|
||||
## Application Logic and Behavior
|
||||
|
||||
Application Logic and Behavior, which you define in JavaScript, serve as the
|
||||
Controller. With `angular` (unlike with standard AJAX applications) you don't
|
||||
need to write additional listeners or DOM manipulators, because they are built-in.
|
||||
This feature makes your application logic very easy to write, test, maintain, and
|
||||
understand.
|
||||
|
||||
## Scope
|
||||
|
||||
The Model consists of one or more JavaScript objects, arrays, or primitive types.
|
||||
These are referenced from the scope. There are no restrictions on what the Model
|
||||
can be or what structure it should have. The only requirement is that it is
|
||||
referenced by the scope.
|
||||
|
||||
The following illustration shows the parts of an `angular` application and how they
|
||||
work together:
|
||||
|
||||
<img class="left" src="img/angular_parts.png" border="0" />
|
||||
|
||||
In addition, `angular` comes with a set of Services, which have the following
|
||||
properties:
|
||||
|
||||
* The services provided are very useful for building web applications.
|
||||
* You can extend and add application-specific behavior to services.
|
||||
* Services include Dependency-Injection, XHR, caching, URL routing,
|
||||
and browser abstraction.
|
||||
|
||||
# Where To Go Next
|
||||
|
||||
* For additional hands-on examples of using `angular`, including more source
|
||||
code that you can copy and paste into your own pages, take a look through
|
||||
the `angular` {@link Cookbook}.
|
||||
|
||||
* For explanations of the `angular` concepts presented in the examples on this
|
||||
page, see the {@link guide Developer Guide}.
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
<label>Name:</label>
|
||||
<input type="text" name="form.name" ng:required>
|
||||
|
||||
<div ng:repeat="contact in form.contacts">
|
||||
<select name="contact.type">
|
||||
<option>url</option>
|
||||
<option>email</option>
|
||||
<option>phone</option>
|
||||
</select>
|
||||
<input type="text" name="contact.url">
|
||||
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
|
||||
</div>
|
||||
<div>
|
||||
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
|
||||
</div>
|
||||
|
||||
<button ng:click="cancel()">Cancel</button>
|
||||
<button ng:click="save()">Save</button>
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
Hello {{person.name}},
|
||||
<div>
|
||||
Your contact information:
|
||||
<div ng:repeat="contact in person.contacts">{{contact.type}}: {{contact.url|linky}}</div>
|
||||
</div>
|
||||
@@ -0,0 +1,33 @@
|
||||
@ngdoc overview
|
||||
@name Testimonials
|
||||
@description
|
||||
|
||||
|
||||
## John Hardy
|
||||
> Also I want to pass on my compliments to Misko and Igor for this fantastic project. I'm currently
|
||||
> rewriting a server-side web application to use this system. I am constantly astounded at how much
|
||||
> simpler it is to do it this way and I still consider myself a learner.
|
||||
|
||||
> This is without question the most productive approach to building webapps that I have seen.
|
||||
|
||||
> The last time I had a coding epiphany was discovering the power and simplicity of JQuery. This is
|
||||
> way better than that.
|
||||
|
||||
> I'm interested in promoting this library as widely as possible. I understand that you are still
|
||||
> developing it and I still have a long way to go before I really understand everything but I think
|
||||
> you really have something here.
|
||||
|
||||
|
||||
## Jerry Jeremiah
|
||||
> angular is the best thing I have used in a long time. I am having so much fun, even thought it is
|
||||
> probably obvious that dynamic web sites are new to me (my experience is more in the back end
|
||||
> embedded world...)
|
||||
|
||||
|
||||
## Dobrica Pavlinusic
|
||||
> Thanks to great help I received at this list, I was basically able to accomplish my goal to write
|
||||
> simple conference submission application within a week of first git clone of angular source from
|
||||
> github.
|
||||
|
||||
> I think it might be useful to summarize my experience here, especially for people who are still
|
||||
> wondering if angular is worth a try. Executive summary is: **yes it is!**
|
||||
@@ -23,7 +23,7 @@
|
||||
<ul>
|
||||
<li ng:repeat="item in activities.data.items.$filter(filterText)">
|
||||
<h1>
|
||||
<img src="{{item.actor.thumbnailUrl}}"/>
|
||||
<img ng:src="{{item.actor.thumbnailUrl}}"/>
|
||||
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
|
||||
<a href="#" ng:click="expandReplies(item)">Replies: {{item.links.replies[0].count}}</a>
|
||||
</h1>
|
||||
@@ -31,14 +31,14 @@
|
||||
{{item.object.content | html}}
|
||||
<div>
|
||||
<a href="{{attachment.links.enclosure[0].href}}" ng:repeat="attachment in item.object.attachments">
|
||||
<img src="{{attachment.links.preview[0].href}}"/>
|
||||
<img ng:src="{{attachment.links.preview[0].href}}"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<my:expand expand="item.replies.show">
|
||||
<ul>
|
||||
<li ng:repeat="reply in item.replies.data.items">
|
||||
<img src="{{reply.actor.thumbnailUrl}}"/>
|
||||
<img ng:src="{{reply.actor.thumbnailUrl}}"/>
|
||||
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
|
||||
{{reply.content | html}}
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<title>Personal Log</title>
|
||||
<script type="text/javascript" src="../../src/angular-bootstrap.js" ng:autobind></script>
|
||||
<script type="text/javascript" src="personalLog.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
<!-- TODO: we need to expose $root so that we can delete cookies in the scenario runner, there
|
||||
must be a better way to do this -->
|
||||
<body ng:controller="example.personalLog.LogCtrl">
|
||||
|
||||
<form action="" ng:submit="addLog(newMsg)">
|
||||
<input type="text" name="newMsg" />
|
||||
<input type="submit" value="add" />
|
||||
<input type="button" value="remove all" ng:click="rmLogs()" />
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
<h2>Logs:</h2>
|
||||
<ul>
|
||||
<li ng:repeat="log in logs.$orderBy('-at')">
|
||||
{{log.at | date:'yy-MM-dd HH:mm'}} {{log.msg}}
|
||||
[<a href="" ng:click="rmLog(log)">x</a>]
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @fileOverview Very simple personal log demo application to demonstrate angular functionality,
|
||||
* especially:
|
||||
* - the MVC model
|
||||
* - testability of controllers
|
||||
* - dependency injection for controllers via $inject and constructor function
|
||||
* - $cookieStore for persistent cookie-backed storage
|
||||
* - simple templating constructs such as ng:repeat and {{}}
|
||||
* - date filter
|
||||
* - and binding onSubmit and onClick events to angular expressions
|
||||
* @author Igor Minar
|
||||
*/
|
||||
|
||||
|
||||
/** @namespace the 'example' namespace */
|
||||
var example = example || {};
|
||||
/** @namespace namespace of the personal log app */
|
||||
example.personalLog = {};
|
||||
|
||||
|
||||
//name space isolating closure
|
||||
(function() {
|
||||
|
||||
var LOGS = 'logs';
|
||||
|
||||
/**
|
||||
* The controller for the personal log app.
|
||||
*/
|
||||
function LogCtrl($cookieStore) {
|
||||
var self = this,
|
||||
logs = self.logs = $cookieStore.get(LOGS) || []; //main model
|
||||
|
||||
|
||||
/**
|
||||
* Adds newMsg to the logs array as a log, persists it and clears newMsg.
|
||||
* @param {string} msg Message to add (message is passed as parameter to make testing easier).
|
||||
*/
|
||||
this.addLog = function(msg) {
|
||||
var newMsg = msg || self.newMsg;
|
||||
if (!newMsg) return;
|
||||
var log = {
|
||||
at: new Date().getTime(),
|
||||
msg: newMsg
|
||||
}
|
||||
|
||||
logs.push(log);
|
||||
$cookieStore.put(LOGS, logs);
|
||||
self.newMsg = '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Persistently removes a log from logs.
|
||||
* @param {object} log The log to remove.
|
||||
*/
|
||||
this.rmLog = function(log) {
|
||||
angular.Array.remove(logs, log);
|
||||
$cookieStore.put(LOGS, logs);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Persistently removes all logs.
|
||||
*/
|
||||
this.rmLogs = function() {
|
||||
logs.splice(0, logs.length);
|
||||
$cookieStore.remove(LOGS);
|
||||
};
|
||||
}
|
||||
|
||||
//inject
|
||||
LogCtrl.$inject = ['$cookieStore'];
|
||||
|
||||
//export
|
||||
example.personalLog.LogCtrl = LogCtrl;
|
||||
})();
|
||||
@@ -0,0 +1,96 @@
|
||||
describe('personal log', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('../personalLog.html');
|
||||
});
|
||||
|
||||
|
||||
afterEach(function() {
|
||||
clearCookies();
|
||||
});
|
||||
|
||||
|
||||
it('should create new logs and order them in reverse chronological order', function(){
|
||||
//create first msg
|
||||
input('newMsg').enter('my first message');
|
||||
element('form input[type="submit"]').click();
|
||||
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
expect(repeater('ul li').column('log.msg')).toEqual('my first message');
|
||||
|
||||
//create second msg
|
||||
input('newMsg').enter('my second message');
|
||||
element('form input[type="submit"]').click();
|
||||
|
||||
expect(repeater('ul li').count()).toEqual(2);
|
||||
expect(repeater('ul li').column('log.msg')).toEqual(['my second message', 'my first message']);
|
||||
});
|
||||
|
||||
|
||||
it('should delete a log when user clicks on the related X link', function() {
|
||||
//create first msg
|
||||
input('newMsg').enter('my first message');
|
||||
element('form input[type="submit"]').click();
|
||||
//create second msg
|
||||
input('newMsg').enter('my second message');
|
||||
element('form input[type="submit"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(2);
|
||||
|
||||
element('ul li a:eq(1)').click();
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
expect(repeater('ul li').column('log.msg')).toEqual('my second message');
|
||||
|
||||
element('ul li a:eq(0)').click();
|
||||
expect(repeater('ul li').count()).toEqual(0);
|
||||
});
|
||||
|
||||
|
||||
it('should delete all cookies when user clicks on "remove all" button', function() {
|
||||
//create first msg
|
||||
input('newMsg').enter('my first message');
|
||||
element('form input[type="submit"]').click();
|
||||
//create second msg
|
||||
input('newMsg').enter('my second message');
|
||||
element('form input[type="submit"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(2);
|
||||
|
||||
element('input[value="remove all"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(0);
|
||||
});
|
||||
|
||||
|
||||
it('should preserve logs over page reloads', function() {
|
||||
input('newMsg').enter('my persistent message');
|
||||
element('form input[type="submit"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
|
||||
browser().reload();
|
||||
|
||||
expect(repeater('ul li').column('log.msg')).toEqual('my persistent message');
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* DSL for deleting all cookies.
|
||||
*/
|
||||
angular.scenario.dsl('clearCookies', function() {
|
||||
/**
|
||||
* Deletes cookies by interacting with the cookie service within the application under test.
|
||||
*/
|
||||
return function() {
|
||||
this.addFutureAction('clear all cookies', function($window, $document, done) {
|
||||
var rootScope = $window.angular.element($document[0]).data('$scope'),
|
||||
$cookies = rootScope.$service('$cookies'),
|
||||
cookieName;
|
||||
|
||||
for (cookieName in $cookies) {
|
||||
delete $cookies[cookieName];
|
||||
}
|
||||
rootScope.$eval();
|
||||
|
||||
done();
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
<!doctype html">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>Personal Log Scenario Runner</title>
|
||||
<script type="text/javascript" src="../../../src/scenario/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript" src="personalLogScenario.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,123 @@
|
||||
describe('example.personalLog.LogCtrl', function() {
|
||||
var logCtrl;
|
||||
|
||||
function createNotesCtrl() {
|
||||
var scope = angular.scope();
|
||||
scope.$cookies = scope.$service('$cookies');
|
||||
return scope.$new(example.personalLog.LogCtrl);
|
||||
}
|
||||
|
||||
|
||||
beforeEach(function() {
|
||||
logCtrl = createNotesCtrl();
|
||||
});
|
||||
|
||||
|
||||
it('should initialize notes with an empty array', function() {
|
||||
expect(logCtrl.logs).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
describe('addLog', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
expect(logCtrl.logs).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should add newMsg to logs as a log entry', function() {
|
||||
logCtrl.newMsg = 'first log message';
|
||||
logCtrl.addLog();
|
||||
|
||||
expect(logCtrl.logs.length).toBe(1);
|
||||
expect(logCtrl.logs[0].msg).toBe('first log message');
|
||||
|
||||
//one more msg, this time passed in as param
|
||||
logCtrl.addLog('second log message');
|
||||
|
||||
expect(logCtrl.logs.length).toBe(2);
|
||||
expect(logCtrl.logs[0].msg).toBe('first log message');
|
||||
expect(logCtrl.logs[1].msg).toBe('second log message');
|
||||
});
|
||||
|
||||
|
||||
it('should clear newMsg when log entry is persisted', function() {
|
||||
logCtrl.addLog('first log message');
|
||||
expect(logCtrl.newMsg).toBe('');
|
||||
});
|
||||
|
||||
|
||||
it('should store logs in the logs cookie', function() {
|
||||
expect(logCtrl.$cookies.logs).not.toBeDefined();
|
||||
logCtrl.addLog('first log message');
|
||||
expect(logCtrl.$cookies.logs).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
it('should do nothing if newMsg is empty', function() {
|
||||
logCtrl.addLog('');
|
||||
expect(logCtrl.logs.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('rmLog', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
logCtrl.addLog('message1');
|
||||
logCtrl.addLog('message2');
|
||||
logCtrl.addLog('message3');
|
||||
logCtrl.addLog('message4');
|
||||
expect(logCtrl.logs.length).toBe(4);
|
||||
});
|
||||
|
||||
|
||||
it('should delete a message identified by index', function() {
|
||||
logCtrl.rmLog(logCtrl.logs[1]);
|
||||
expect(logCtrl.logs.length).toBe(3);
|
||||
|
||||
logCtrl.rmLog(logCtrl.logs[2]);
|
||||
expect(logCtrl.logs.length).toBe(2);
|
||||
expect(logCtrl.logs[0].msg).toBe('message1');
|
||||
expect(logCtrl.logs[1].msg).toBe('message3');
|
||||
});
|
||||
|
||||
|
||||
it('should update cookies when a log is deleted', function() {
|
||||
expect(logCtrl.$cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){3}\]/);
|
||||
|
||||
logCtrl.rmLog(logCtrl.logs[1]);
|
||||
expect(logCtrl.$cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){2}\]/);
|
||||
|
||||
logCtrl.rmLog(logCtrl.logs[0]);
|
||||
logCtrl.rmLog(logCtrl.logs[0]);
|
||||
logCtrl.rmLog(logCtrl.logs[0]);
|
||||
expect(logCtrl.$cookies.logs).toMatch(/\[\]/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('rmLogs', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
logCtrl.addLog('message1');
|
||||
logCtrl.addLog('message2');
|
||||
logCtrl.addLog('message3');
|
||||
logCtrl.addLog('message4');
|
||||
expect(logCtrl.logs.length).toBe(4);
|
||||
});
|
||||
|
||||
|
||||
it('should remove all logs', function() {
|
||||
logCtrl.rmLogs();
|
||||
expect(logCtrl.logs).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should remove logs cookie', function() {
|
||||
expect(logCtrl.$cookies.logs).toBeTruthy();
|
||||
logCtrl.rmLogs();
|
||||
expect(logCtrl.$cookies.logs).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
+17
-31
@@ -1,36 +1,22 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<script type="text/javascript"
|
||||
src="../angular-debug.js" ng:autobind ng:css="css/angular.css"></script>
|
||||
<title>angular dev sandbox</title>
|
||||
<script src="../src/angular-bootstrap.js" ng:autobind></script>
|
||||
<script>
|
||||
angular.service('routeConfig', function($route) {
|
||||
$route.when('/view1', {controller: MyCtrl, template: 'view1.html'});
|
||||
$route.when('/view2', {controller: MyCtrl, template: 'view2.html'});
|
||||
|
||||
function MyCtrl() {};
|
||||
}, {$inject: ['$route'], $eager: true});
|
||||
</script>
|
||||
</head>
|
||||
<body ng:init="$window.$root = this">
|
||||
|
||||
<script>
|
||||
angular.widget('my:greeter', function(compileElement){
|
||||
var compiler = this;
|
||||
compileElement.css('style', 'block');
|
||||
var salutaitonExp = compileElement.attr('salutation');
|
||||
var nameExp = compileElement.attr('name');
|
||||
return function(linkElement){
|
||||
var salutaitonSpan = angular.element('<span class="salutation"></span');
|
||||
var nameSpan = angular.element('<span class="name"></span>');
|
||||
linkElement.append(salutaitonSpan);
|
||||
linkElement.append(compiler.text(' '));
|
||||
linkElement.append(nameSpan);
|
||||
linkElement.append(compiler.text('!'));
|
||||
this.$watch(salutaitonExp, function(value){
|
||||
salutaitonSpan.text(value);
|
||||
});
|
||||
this.$watch(nameExp, function(value){
|
||||
nameSpan.text(value);
|
||||
});
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<div ng:init="salutation='Hello'; name='World'">
|
||||
<my:greeter salutation="salutation" name="name"/>
|
||||
</div>
|
||||
<body ng:init="$service('$window').$root = this">
|
||||
<p>
|
||||
<a href="#/view1">view1</a> | <a href="#/view2">view2</a> | <a href="#">blank</a>
|
||||
</p>
|
||||
|
||||
view: <ng:view></ng:view>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
view1<br>
|
||||
location: {{$service('$location').href}}
|
||||
@@ -0,0 +1,2 @@
|
||||
view2<br/>
|
||||
location: {{$service('$location').href}}<br/>
|
||||
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var fs = require('fs');
|
||||
|
||||
var collections = {
|
||||
'guide': 'http://docs.google.com/feeds/default/private/full/folder%3A0B9PsajIPqzmANGUwMGVhZmYtMTk1ZC00NTdmLWIxMDAtZGI5YWNlZjQ2YjZl/contents',
|
||||
'api': 'http://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDYjMwYTc2YWUtZTgzYy00YjIxLThlZDYtYWJlOTFlNzE2NzEw/contents',
|
||||
'cookbook': 'http://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDNzkxZWM5ZTItN2M5NC00NWIxLTg2ZDMtMmYwNDY1NWM1MGU4/contents',
|
||||
'misc': 'http://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDZjVlNmZkYzQtMjZlOC00NmZhLWI5MjAtMGRjZjlkOGJkMDBi/contents'
|
||||
}
|
||||
|
||||
console.log('Google Docs...');
|
||||
|
||||
var flag = process && process.argv[2];
|
||||
if (flag == '--login')
|
||||
askPassword(function(password){
|
||||
login(process.argv[3], password);
|
||||
});
|
||||
else if (flag == '--fetch') {
|
||||
var collection = process.argv[3];
|
||||
if (collection) {
|
||||
fetch(collection, collections[collection]);
|
||||
} else {
|
||||
for (collection in collections)
|
||||
fetch(collection, collections[collection]);
|
||||
}
|
||||
} else
|
||||
help();
|
||||
|
||||
function help(){
|
||||
console.log('Synopsys');
|
||||
console.log('gdocs.js --login <username>');
|
||||
console.log('gdocs.js --fetch [<docs collection>]');
|
||||
process.exit(-1);
|
||||
};
|
||||
|
||||
|
||||
function fetch(name, url){
|
||||
//https://docs.google.com/feeds/default/private/full/folder%3Afolder_id/contents
|
||||
console.log('fetching a list of docs in collection ' + name + '...');
|
||||
request('GET', url, {
|
||||
headers: {
|
||||
'Gdata-Version': '3.0',
|
||||
'Authorization': 'GoogleLogin auth=' + getAuthToken()
|
||||
}
|
||||
},
|
||||
function(chunk){
|
||||
var entries = chunk.split('<entry');
|
||||
entries.shift();
|
||||
entries.forEach(function(entry){
|
||||
var title = entry.match(/<title>(.*?)<\/title>/)[1];
|
||||
if (title.match(/\.ngdoc$/)) {
|
||||
var exportUrl = entry.match(/<content type='text\/html' src='(.*?)'\/>/)[1];
|
||||
download(title, exportUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function download(name, url) {
|
||||
console.log('Downloading:', name, '...');
|
||||
request('GET', url + '&exportFormat=txt',
|
||||
{
|
||||
headers: {
|
||||
'Gdata-Version': '3.0',
|
||||
'Authorization': 'GoogleLogin auth=' + getAuthToken()
|
||||
}
|
||||
},
|
||||
function(data){
|
||||
data = data.replace('\ufeff', '');
|
||||
data = data.replace(/\r\n/mg, '\n');
|
||||
data = data.replace(/^ /mg, ' '); //for some reason gdocs drop first space for indented lines
|
||||
|
||||
// strip out all text annotation comments
|
||||
data = data.replace(/^\[a\][\S\s]*/m, '');
|
||||
|
||||
// strip out all text annotations
|
||||
data = data.replace(/\[\w{1,3}\]/mg, '');
|
||||
|
||||
// fix smart-quotes
|
||||
data = data.replace(/[“”]/g, '"');
|
||||
data = data.replace(/[‘’]/g, "'");
|
||||
|
||||
|
||||
data = data + '\n';
|
||||
fs.writeFileSync('docs/' + name, reflow(data, 100));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* token=$(curl
|
||||
* -s https://www.google.com/accounts/ClientLogin
|
||||
* -d Email=...username...
|
||||
* -d Passwd=...password...
|
||||
* -d accountType=GOOGLE
|
||||
* -d service=writely
|
||||
* -d Gdata-version=3.0 | cut -d "=" -f 2)
|
||||
*/
|
||||
function login(username, password){
|
||||
request('POST', 'https://www.google.com/accounts/ClientLogin',
|
||||
{
|
||||
data: {
|
||||
Email: username,
|
||||
Passwd: password,
|
||||
accountType: 'GOOGLE',
|
||||
service: 'writely',
|
||||
'Gdata-version': '3.0'
|
||||
},
|
||||
headers: {
|
||||
'Content-type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
},
|
||||
function(chunk){
|
||||
var token;
|
||||
chunk.split('\n').forEach(function(line){
|
||||
var parts = line.split('=');
|
||||
if (parts[0] == 'Auth') {
|
||||
token = parts[1];
|
||||
}
|
||||
});
|
||||
if (token) {
|
||||
fs.writeFileSync('tmp/gdocs.auth', token);
|
||||
console.log("logged in, token saved in 'tmp/gdocs.auth'");
|
||||
} else {
|
||||
console.log('failed to log in');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getAuthToken(){
|
||||
return fs.readFileSync('tmp/gdocs.auth');
|
||||
}
|
||||
|
||||
function request(method, url, options, response) {
|
||||
var url = url.match(/http(s?):\/\/(.+?)(\/.*)/);
|
||||
var request = (url[1] ? https : http).request({
|
||||
host: url[2],
|
||||
port: (url[1] ? 443 : 80),
|
||||
path: url[3],
|
||||
method: method
|
||||
}, function(res){
|
||||
var data = [];
|
||||
res.setEncoding('utf8');
|
||||
res.on('end', function(){
|
||||
response(data.join(''));
|
||||
});
|
||||
res.on('data', function (chunk) {
|
||||
data.push(chunk);
|
||||
});
|
||||
});
|
||||
for(var header in options.headers) {
|
||||
request.setHeader(header, options.headers[header]);
|
||||
}
|
||||
if (options.data)
|
||||
request.write(encodeData(options.data));
|
||||
request.on('end', function(){
|
||||
console.log('end');
|
||||
});
|
||||
request.end();
|
||||
}
|
||||
|
||||
function encodeData(obj) {
|
||||
var pairs = [];
|
||||
for(var key in obj) {
|
||||
pairs.push(key + '=' + obj[key]);
|
||||
}
|
||||
return pairs.join('&') + '\n';
|
||||
}
|
||||
|
||||
function askPassword(callback) {
|
||||
var stdin = process.openStdin(),
|
||||
stdio = process.binding("stdio");
|
||||
|
||||
stdio.setRawMode();
|
||||
|
||||
console.log('Enter your password:');
|
||||
var password = ""
|
||||
stdin.on("data", function (c) {
|
||||
c = c + "";
|
||||
switch (c) {
|
||||
case "\n": case "\r": case "\u0004":
|
||||
stdio.setRawMode(false);
|
||||
stdin.pause();
|
||||
callback(password);
|
||||
break;
|
||||
case "\u0003":
|
||||
process.exit();
|
||||
break;
|
||||
default:
|
||||
password += c;
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function reflow(text, margin) {
|
||||
var lines = [];
|
||||
text.split(/\n/).forEach(function(line) {
|
||||
var col = 0;
|
||||
var reflowLine = '';
|
||||
function flush(){
|
||||
reflowLine = reflowLine.replace(/\s*$/, '');
|
||||
lines.push(reflowLine);
|
||||
reflowLine = '';
|
||||
col = 0;
|
||||
}
|
||||
line.replace(/\s*\S*\s*/g, function(chunk){
|
||||
if (col + chunk.length > margin) flush();
|
||||
reflowLine += chunk;
|
||||
col += chunk.length;
|
||||
});
|
||||
flush();
|
||||
});
|
||||
return lines.join('\n');
|
||||
}
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
. ~/.bashrc
|
||||
node docs/spec/specs.js --noColor | grep -v '/lib/jasmine' && node docs/src/gen-docs.js
|
||||
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user