Compare commits
147 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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,"/>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?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/docs" type="2"/> </resources>}"/>
|
||||
<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="false"/>
|
||||
<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/callback.js" type="1"/> <item path="/angular.js/docs/collect.js" type="1"/> <item path="/angular.js/docs/filter.template" type="1"/> </resources>}"/>
|
||||
<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"/>
|
||||
|
||||
@@ -3,3 +3,4 @@ angularjs.netrc
|
||||
jstd.log
|
||||
.DS_Store
|
||||
regression/temp.html
|
||||
.idea/workspace.xml
|
||||
|
||||
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
-500
@@ -1,500 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" readonly="true" id="2e561485-a685-4e95-bea4-cabeda87d953" name="Default" comment="updated Rakefile to support packaging of the docs">
|
||||
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/filters.js" afterPath="$PROJECT_DIR$/src/filters.js" />
|
||||
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/docs/docs-scenario.html" afterPath="$PROJECT_DIR$/docs/docs-scenario.html" />
|
||||
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/workspace.xml" afterPath="$PROJECT_DIR$/.idea/workspace.xml" />
|
||||
</list>
|
||||
<ignored path=".idea/workspace.xml" />
|
||||
<ignored path="angular.js.iws" />
|
||||
<option name="TRACKING_ENABLED" value="true" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ChangesViewManager" flattened_view="true" show_ignored="false" />
|
||||
<component name="CreatePatchCommitExecutor">
|
||||
<option name="PATCH_PATH" value="" />
|
||||
<option name="REVERSE_PATCH" value="false" />
|
||||
</component>
|
||||
<component name="DaemonCodeAnalyzer">
|
||||
<disable_hints />
|
||||
</component>
|
||||
<component name="FavoritesManager">
|
||||
<favorites_list name="angular.js" />
|
||||
</component>
|
||||
<component name="FileColors" enabled="true" enabledForTabs="true" />
|
||||
<component name="FileEditorManager">
|
||||
<splitter split-orientation="horizontal" split-proportion="0.5">
|
||||
<split-first>
|
||||
<leaf>
|
||||
<file leaf-file-name="filters.js" pinned="false" current="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/src/filters.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="377" column="5" selection-start="12137" selection-end="12137" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="docs-scenario.html" pinned="false" current="true" current-in-tab="true">
|
||||
<entry file="file://$PROJECT_DIR$/docs/docs-scenario.html">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="3" column="26" selection-start="119" selection-end="119" vertical-scroll-proportion="0.039173014">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="validators.js" pinned="false" current="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/src/validators.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="54" column="69" selection-start="1564" selection-end="1611" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="test.sh" pinned="false" current="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/test.sh">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="3" column="13" selection-start="59" selection-end="59" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="docs-scenario.html" pinned="false" current="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/build/docs/docs-scenario.html">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="5" column="44" selection-start="259" selection-end="259" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="index.html" pinned="false" current="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/build/docs/index.html">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="24" column="7" selection-start="1033" selection-end="1033" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="angular.filter.html.html" pinned="false" current="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/build/docs/angular.filter.html.html">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
</leaf>
|
||||
</split-first>
|
||||
<split-second>
|
||||
<leaf>
|
||||
<file leaf-file-name="FiltersSpec.js" pinned="false" current="false" current-in-tab="true">
|
||||
<entry file="file://$PROJECT_DIR$/test/FiltersSpec.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="89" column="20" selection-start="2996" selection-end="2996" vertical-scroll-proportion="0.7635783">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
</leaf>
|
||||
</split-second>
|
||||
</splitter>
|
||||
</component>
|
||||
<component name="FindManager">
|
||||
<FindUsagesManager>
|
||||
<setting name="OPEN_NEW_TAB" value="false" />
|
||||
</FindUsagesManager>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="GIT_EXECUTABLE" value="/usr/local/git/bin/git" />
|
||||
<option name="CHECKOUT_INCLUDE_TAGS" value="false" />
|
||||
</component>
|
||||
<component name="IdeDocumentHistory">
|
||||
<option name="changedFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/lib/nodeserver/server.js" />
|
||||
<option value="$PROJECT_DIR$/Rakefile" />
|
||||
<option value="$PROJECT_DIR$/docs/collect.js" />
|
||||
<option value="$PROJECT_DIR$/src/scenario/dsl.js" />
|
||||
<option value="$PROJECT_DIR$/test.sh" />
|
||||
<option value="$PROJECT_DIR$/test/FiltersSpec.js" />
|
||||
<option value="$PROJECT_DIR$/src/filters.js" />
|
||||
<option value="$PROJECT_DIR$/docs/docs-scenario.html" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectLevelVcsManager">
|
||||
<OptionsSetting value="true" id="Add" />
|
||||
<OptionsSetting value="true" id="Remove" />
|
||||
<OptionsSetting value="true" id="Checkout" />
|
||||
<OptionsSetting value="true" id="Update" />
|
||||
<OptionsSetting value="true" id="Status" />
|
||||
<OptionsSetting value="true" id="Edit" />
|
||||
<ConfirmationsSetting value="0" id="Add" />
|
||||
<ConfirmationsSetting value="0" id="Remove" />
|
||||
</component>
|
||||
<component name="ProjectReloadState">
|
||||
<option name="STATE" value="0" />
|
||||
</component>
|
||||
<component name="ProjectView">
|
||||
<navigator currentView="ProjectPane" proportions="" version="1" splitterProportion="0.5">
|
||||
<flattenPackages />
|
||||
<showMembers />
|
||||
<showModules />
|
||||
<showLibraryContents />
|
||||
<hideEmptyPackages />
|
||||
<abbreviatePackageNames />
|
||||
<autoscrollToSource />
|
||||
<autoscrollFromSource />
|
||||
<sortByType />
|
||||
</navigator>
|
||||
<panes>
|
||||
<pane id="ProjectPane">
|
||||
<subPane>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="docs" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="build" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="build" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="pkg" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular-0.9.2-a838b3ef" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="build" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="docs" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
</subPane>
|
||||
</pane>
|
||||
<pane id="Favorites" />
|
||||
<pane id="Scope" />
|
||||
</panes>
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="options.splitter.main.proportions" value="0.3" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="recentsLimit" value="5" />
|
||||
<property name="options.lastSelected" value="project.propVCSSupport.VCSs.Git" />
|
||||
<property name="GoToClass.includeJavaFiles" value="false" />
|
||||
<property name="options.splitter.details.proportions" value="0.2" />
|
||||
<property name="options.searchVisible" value="true" />
|
||||
</component>
|
||||
<component name="RunManager" selected="Bash.gen_docs">
|
||||
<configuration default="false" name="gen_docs.sh" type="BashConfigurationType" factoryName="Bash" temporary="true">
|
||||
<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>
|
||||
<configuration default="false" name="test.sh" type="BashConfigurationType" factoryName="Bash" temporary="true">
|
||||
<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$/test.sh" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<RunnerSettings RunnerId="BashRunner" />
|
||||
<ConfigurationWrapper RunnerId="BashRunner" />
|
||||
<method />
|
||||
</configuration>
|
||||
<configuration default="true" type="BashConfigurationType" factoryName="Bash">
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="INTERPRETER_PATH" value="/bin/bash" />
|
||||
<option name="WORKING_DIRECTORY" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs />
|
||||
<module name="angular.js" />
|
||||
<option name="SCRIPT_NAME" value="" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<method />
|
||||
</configuration>
|
||||
<list size="4">
|
||||
<item index="0" class="java.lang.String" itemvalue="Bash.gen_docs.sh" />
|
||||
<item index="1" class="java.lang.String" itemvalue="Bash.rake compile" />
|
||||
<item index="2" class="java.lang.String" itemvalue="Bash.gen_docs" />
|
||||
<item index="3" class="java.lang.String" itemvalue="Bash.test.sh" />
|
||||
</list>
|
||||
</component>
|
||||
<component name="ShelveChangesManager" show_recycled="false" />
|
||||
<component name="SvnConfiguration" maxAnnotateRevisions="500">
|
||||
<option name="USER" value="" />
|
||||
<option name="PASSWORD" value="" />
|
||||
<option name="LAST_MERGED_REVISION" />
|
||||
<option name="UPDATE_RUN_STATUS" value="false" />
|
||||
<option name="MERGE_DRY_RUN" value="false" />
|
||||
<option name="MERGE_DIFF_USE_ANCESTRY" value="true" />
|
||||
<option name="UPDATE_LOCK_ON_DEMAND" value="false" />
|
||||
<option name="IGNORE_SPACES_IN_MERGE" value="false" />
|
||||
<option name="DETECT_NESTED_COPIES" value="true" />
|
||||
<option name="CHECK_NESTED_FOR_QUICK_MERGE" value="false" />
|
||||
<option name="IGNORE_SPACES_IN_ANNOTATE" value="true" />
|
||||
<option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" />
|
||||
<configuration useDefault="true">$PROJECT_DIR$/../../.subversion_IDEA</configuration>
|
||||
<myIsUseDefaultProxy>false</myIsUseDefaultProxy>
|
||||
<supportedVersion>125</supportedVersion>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="2e561485-a685-4e95-bea4-cabeda87d953" name="Default" comment="" />
|
||||
<created>1288738700234</created>
|
||||
<updated>1288738700234</updated>
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="ToolWindowManager">
|
||||
<frame x="0" y="22" width="2560" height="1574" extended-state="6" />
|
||||
<editor active="true" />
|
||||
<layout>
|
||||
<window_info id="Changes" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.32943603" sideWeight="0.0" order="7" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="true" content_ui="tabs" />
|
||||
<window_info id="Dependency Viewer" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.18020505" sideWeight="0.66574967" order="0" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.32943603" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
|
||||
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
|
||||
</layout>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<option name="OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT" value="true" />
|
||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true" />
|
||||
<option name="PERFORM_UPDATE_IN_BACKGROUND" value="true" />
|
||||
<option name="PERFORM_COMMIT_IN_BACKGROUND" value="true" />
|
||||
<option name="PERFORM_EDIT_IN_BACKGROUND" value="true" />
|
||||
<option name="PERFORM_CHECKOUT_IN_BACKGROUND" value="true" />
|
||||
<option name="PERFORM_ADD_REMOVE_IN_BACKGROUND" value="true" />
|
||||
<option name="PERFORM_ROLLBACK_IN_BACKGROUND" value="false" />
|
||||
<option name="CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND" value="false" />
|
||||
<option name="ENABLE_BACKGROUND_PROCESSES" value="false" />
|
||||
<option name="CHANGED_ON_SERVER_INTERVAL" value="60" />
|
||||
<option name="FORCE_NON_EMPTY_COMMENT" value="false" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="updated Rakefile to support packaging of the docs" />
|
||||
<option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true" />
|
||||
<option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||
<option name="CHECK_FILES_UP_TO_DATE_BEFORE_COMMIT" value="false" />
|
||||
<option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="false" />
|
||||
<option name="REFORMAT_BEFORE_FILE_COMMIT" value="false" />
|
||||
<option name="FILE_HISTORY_DIALOG_COMMENTS_SPLITTER_PROPORTION" value="0.8" />
|
||||
<option name="FILE_HISTORY_DIALOG_SPLITTER_PROPORTION" value="0.5" />
|
||||
<option name="ACTIVE_VCS_NAME" />
|
||||
<option name="UPDATE_GROUP_BY_PACKAGES" value="false" />
|
||||
<option name="UPDATE_GROUP_BY_CHANGELIST" value="false" />
|
||||
<option name="SHOW_FILE_HISTORY_AS_TREE" value="false" />
|
||||
<option name="FILE_HISTORY_SPLITTER_PROPORTION" value="0.6" />
|
||||
<MESSAGE value="move the output of generation to build" />
|
||||
<MESSAGE value="updated Rakefile to support packaging of the docs" />
|
||||
</component>
|
||||
<component name="XDebuggerManager">
|
||||
<breakpoint-manager />
|
||||
</component>
|
||||
<component name="XSLT-Support.FileAssociations.UIState">
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="angular.js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
</component>
|
||||
<component name="editorHistoryManager">
|
||||
<entry file="file://$PROJECT_DIR$/version.yaml">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="2" column="23" selection-start="50" selection-end="58" vertical-scroll-proportion="0.025559105" />
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/docs/collect.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="78" column="0" selection-start="2539" selection-end="2539" vertical-scroll-proportion="0.99680513">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/Rakefile">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="184" column="5" selection-start="5171" selection-end="5171" vertical-scroll-proportion="0.47603834">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/Resource.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="37" column="1" selection-start="1038" selection-end="1038" vertical-scroll-proportion="0.47284344">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/test/ResourceSpec.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="13" column="35" selection-start="399" selection-end="399" vertical-scroll-proportion="0.16613418" />
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/lib/nodeserver/server.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="37" column="0" selection-start="864" selection-end="864" vertical-scroll-proportion="0.4345048">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/scenario/dsl.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="109" column="11" selection-start="3010" selection-end="3010" vertical-scroll-proportion="-0.021905806">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/validators.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="54" column="69" selection-start="1564" selection-end="1611" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/test.sh">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="3" column="13" selection-start="59" selection-end="59" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/build/docs/docs-scenario.html">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="5" column="44" selection-start="259" selection-end="259" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/build/docs/index.html">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="24" column="7" selection-start="1033" selection-end="1033" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/build/docs/angular.filter.html.html">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/test/FiltersSpec.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="89" column="20" selection-start="2996" selection-end="2996" vertical-scroll-proportion="0.7635783">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/filters.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="377" column="5" selection-start="12137" selection-end="12137" vertical-scroll-proportion="0.0">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/docs/docs-scenario.html">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state line="3" column="26" selection-start="119" selection-end="119" vertical-scroll-proportion="0.039173014">
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</component>
|
||||
</project>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry excluding="test/" kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="docs"/>
|
||||
<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"/>
|
||||
|
||||
@@ -1,3 +1,102 @@
|
||||
# <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)
|
||||
|
||||
|
||||
# <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.
|
||||
|
||||
|
||||
# <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
|
||||
|
||||
|
||||
# <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)
|
||||
|
||||
|
||||
# <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.
|
||||
|
||||
|
||||
# <angular/> 0.9.2 faunal-mimicry (2010-11-03) #
|
||||
|
||||
### Docs
|
||||
|
||||
@@ -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,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,43 @@
|
||||
@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 if it was loaded before angular.
|
||||
|
||||
Angular's jQuery lite implementation is a tiny API-compatible subset of jQuery which allows
|
||||
angular to manipulate DOM. The functions implemented are usually just the basic versions of
|
||||
them and might not support arguments and invocation styles.
|
||||
|
||||
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/)
|
||||
|
||||
@param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
|
||||
@returns {Object} jQuery object.
|
||||
@@ -0,0 +1,76 @@
|
||||
@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.
|
||||
|
||||
|
||||
@exampleDescription
|
||||
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).
|
||||
|
||||
@example
|
||||
<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"}}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
@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
|
||||
<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>
|
||||
|
||||
|
||||
@scenario
|
||||
it('should store reverse', function(){
|
||||
expect(element('.doc-example input:first').val()).toEqual('angular');
|
||||
expect(element('.doc-example input:last').val()).toEqual('RALUGNA');
|
||||
|
||||
this.addFutureAction('change to XYZ', function($window, $document, done){
|
||||
$document.elements('.doc-example input:last').val('XYZ').trigger('change');
|
||||
done();
|
||||
});
|
||||
expect(element('.doc-example input:first').val()).toEqual('zyx');
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular
|
||||
@namespace The exported angular namespace.
|
||||
@@ -0,0 +1,159 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.service
|
||||
|
||||
@description
|
||||
# Overview
|
||||
Services are substituable objects, which are wired together using dependency injection.
|
||||
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 satisfy these dependencies, so each service must explicitely define its dependencies.
|
||||
This is done by `$inject` property.
|
||||
|
||||
For now, life time of all services is the same as the life time of page.
|
||||
|
||||
|
||||
# Built-in services
|
||||
The Angular framework provides a standard set of services for common operations.
|
||||
You can write your own services and rewrite these standard services as well.
|
||||
Like other core angular variables, the built-in services always start with $.
|
||||
|
||||
* `angular.service.$browser`
|
||||
* `angular.service.$window`
|
||||
* `angular.service.$document`
|
||||
* `angular.service.$location`
|
||||
* `angular.service.$log`
|
||||
* `angular.service.$exceptionHandler`
|
||||
* `angular.service.$hover`
|
||||
* `angular.service.$invalidWidgets`
|
||||
* `angular.service.$route`
|
||||
* `angular.service.$xhr`
|
||||
* `angular.service.$xhr.error`
|
||||
* `angular.service.$xhr.bulk`
|
||||
* `angular.service.$xhr.cache`
|
||||
* `angular.service.$resource`
|
||||
* `angular.service.$cookies`
|
||||
* `angular.service.$cookieStore`
|
||||
|
||||
# Writing your own custom services
|
||||
Angular provides only set of basic services, so you will probably need to write your custom
|
||||
service very soon. To do so, you need to write a factory function and register this function
|
||||
to angular's dependency injector. This factory function must return an object - your service
|
||||
(it is not called with new operator).
|
||||
|
||||
**angular.service** has three parameters:
|
||||
|
||||
- `{string} name` - Name of the service
|
||||
- `{function()} factory` - Factory function (called just once by DI)
|
||||
- `{Object} config` - Hash of configuration (`$inject`, `$creation`)
|
||||
|
||||
If your service requires - depends on other services, you need to specify them
|
||||
in config hash - property $inject. This property is an array of strings (service names).
|
||||
These dependencies will be passed as parameters to the factory function by DI.
|
||||
This approach is very useful when testing, as you can inject mocks/stubs/dummies.
|
||||
|
||||
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 in a controllers is very similar to using service in other service.
|
||||
Again, we will use dependency injection.
|
||||
|
||||
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
|
||||
<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>
|
||||
@@ -0,0 +1,73 @@
|
||||
@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
|
||||
<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"/>
|
||||
|
||||
@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/);
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
@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
|
||||
<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>
|
||||
|
||||
+270
-40
@@ -1,52 +1,76 @@
|
||||
require.paths.push("./lib");
|
||||
require.paths.push(__dirname);
|
||||
var fs = require('fs'),
|
||||
spawn = require('child_process').spawn,
|
||||
mustache = require('../lib/mustache'),
|
||||
callback = require('./callback'),
|
||||
markdown = require('../lib/markdown');
|
||||
mustache = require('mustache'),
|
||||
callback = require('callback'),
|
||||
Showdown = require('showdown').Showdown;
|
||||
|
||||
var documentation = {
|
||||
section:{},
|
||||
all:[]
|
||||
pages:[],
|
||||
byName: {}
|
||||
};
|
||||
var keywordPages = [];
|
||||
|
||||
|
||||
var SRC_DIR = "docs/";
|
||||
var OUTPUT_DIR = "build/docs/";
|
||||
var NEW_LINE = /\n\r?/;
|
||||
var TEMPLATES = {};
|
||||
var start = now();
|
||||
|
||||
function now(){ return new Date().getTime(); }
|
||||
var work = callback.chain(function () {
|
||||
console.log('Parsing Angular Reference Documentation');
|
||||
mkdirPath(OUTPUT_DIR, work.waitFor(function(){
|
||||
findJsFiles('src', work.waitMany(function(file) {
|
||||
//console.log('reading', file, '...');
|
||||
findNgDoc(file, work.waitMany(function(doc) {
|
||||
parseNgDoc(doc);
|
||||
if (doc.ngdoc) {
|
||||
delete doc.raw.text;
|
||||
var section = documentation.section;
|
||||
(section[doc.ngdoc] = section[doc.ngdoc] || []).push(doc);
|
||||
documentation.all.push(doc);
|
||||
console.log('Found:', doc.ngdoc + ':' + doc.shortName);
|
||||
mergeTemplate(
|
||||
doc.ngdoc + '.template',
|
||||
doc.name + '.html', doc, work.waitFor());
|
||||
}
|
||||
}));
|
||||
findJsFiles('src', work.waitMany(function(file) {
|
||||
//console.log('reading', file, '...');
|
||||
findNgDocInJsFile(file, work.waitMany(function(doc) {
|
||||
parseNgDoc(doc);
|
||||
processNgDoc(documentation, doc);
|
||||
}));
|
||||
}));
|
||||
findNgDocInDir(SRC_DIR, work.waitMany(function(doc){
|
||||
parseNgDoc(doc);
|
||||
processNgDoc(documentation, doc);
|
||||
}));
|
||||
loadTemplates(TEMPLATES, work.waitFor());
|
||||
mkdirPath(OUTPUT_DIR, work.waitFor());
|
||||
}).onError(function(err){
|
||||
console.log('ERROR:', err.stack || err);
|
||||
}).onDone(function(){
|
||||
mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(documentation)}, callback.chain());
|
||||
keywordPages.sort(keywordSort);
|
||||
writeDoc(documentation.pages);
|
||||
mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(keywordPages)}, callback.chain());
|
||||
mergeTemplate('docs-scenario.js', 'docs-scenario.js', documentation, callback.chain());
|
||||
copy('docs-scenario.html', callback.chain());
|
||||
copy('index.html', callback.chain());
|
||||
copy('docs.css', callback.chain());
|
||||
mergeTemplate('docs.js', 'docs.js', documentation, callback.chain());
|
||||
mergeTemplate('wiki_widgets.css', 'wiki_widgets.css', documentation, callback.chain());
|
||||
mergeTemplate('wiki_widgets.js', 'wiki_widgets.js', documentation, callback.chain());
|
||||
console.log('DONE');
|
||||
mergeTemplate('doc_widgets.css', 'doc_widgets.css', documentation, callback.chain());
|
||||
mergeTemplate('doc_widgets.js', 'doc_widgets.js', documentation, callback.chain());
|
||||
console.log('DONE', now() - start, 'ms.');
|
||||
});
|
||||
work();
|
||||
if (!this.testmode) work();
|
||||
////////////////////
|
||||
|
||||
function keywords(text){
|
||||
var keywords = {};
|
||||
var words = [];
|
||||
var tokens = 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(' ');
|
||||
}
|
||||
|
||||
function noop(){}
|
||||
function mkdirPath(path, callback) {
|
||||
var parts = path.split(/\//);
|
||||
@@ -78,6 +102,37 @@ function mergeTemplate(template, output, doc, callback){
|
||||
}
|
||||
|
||||
|
||||
function trim(text) {
|
||||
var MAX = 9999;
|
||||
var empty = RegExp.prototype.test.bind(/^\s*$/);
|
||||
var lines = text.split('\n');
|
||||
var minIndent = MAX;
|
||||
lines.forEach(function(line){
|
||||
minIndent = Math.min(minIndent, indent(line));
|
||||
});
|
||||
for ( var i = 0; i < lines.length; i++) {
|
||||
lines[i] = lines[i].substring(minIndent);
|
||||
}
|
||||
// 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(line) {
|
||||
for(var i = 0; i < line.length; i++) {
|
||||
if (line.charAt(i) != ' ') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return MAX;
|
||||
}
|
||||
}
|
||||
|
||||
function unknownTag(doc, name) {
|
||||
var error = "[" + doc.raw.file + ":" + doc.raw.line + "]: unknown tag: " + name;
|
||||
console.log(error);
|
||||
@@ -93,7 +148,78 @@ function escapedHtmlTag(doc, name, value) {
|
||||
}
|
||||
|
||||
function markdownTag(doc, name, value) {
|
||||
doc[name] = markdown.toHTML(value);
|
||||
doc[name] = markdown(value.replace(/^#/gm, '##')).
|
||||
replace(/\<pre\>/gmi, '<div ng:non-bindable><pre class="brush: js; html-script: true;">').
|
||||
replace(/\<\/pre\>/gmi, '</pre></div>');
|
||||
}
|
||||
|
||||
var R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m;
|
||||
// 1 123 3 4 42
|
||||
|
||||
function markdown(text) {
|
||||
var parts = text.split(/(<pre>[\s\S]*?<\/pre>)/),
|
||||
match;
|
||||
|
||||
parts.forEach(function(text, i){
|
||||
if (!text.match(/^<pre>/)) {
|
||||
text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>');
|
||||
text = new Showdown.converter().makeHtml(text);
|
||||
|
||||
while (match = text.match(R_LINK)) {
|
||||
text = text.replace(match[0], '<a href="#!' + match[1] + '"><code>' +
|
||||
(match[4] || match[1]) +
|
||||
'</code></a>');
|
||||
}
|
||||
|
||||
parts[i] = text;
|
||||
}
|
||||
});
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
function markdownNoP(text) {
|
||||
var lines = markdown(text).split(NEW_LINE);
|
||||
var last = lines.length - 1;
|
||||
lines[0] = lines[0].replace(/^<p>/, '');
|
||||
lines[last] = lines[last].replace(/<\/p>$/, '');
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function requiresTag(doc, name, value) {
|
||||
doc.requires = doc.requires || [];
|
||||
doc.requires.push({name: value});
|
||||
}
|
||||
|
||||
function propertyTag(doc, name, value) {
|
||||
doc[name] = doc[name] || [];
|
||||
var match = value.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/);
|
||||
|
||||
if (match) {
|
||||
var tag = {
|
||||
type: match[2],
|
||||
name: match[3],
|
||||
description: match[5] || false
|
||||
};
|
||||
} else {
|
||||
throw "[" + doc.raw.file + ":" + doc.raw.line +
|
||||
"]: @" + name + " must be in format '{type} name description' got: " + value;
|
||||
}
|
||||
return doc[name].push(tag);
|
||||
}
|
||||
|
||||
function returnsTag(doc, name, value) {
|
||||
var match = value.match(/^{(\S+)}\s+([\s\S]*)?/);
|
||||
|
||||
if (match) {
|
||||
var tag = {
|
||||
type: match[1],
|
||||
description: markdownNoP(match[2]) || false
|
||||
};
|
||||
} else {
|
||||
throw "[" + doc.raw.file + ":" + doc.raw.line +
|
||||
"]: @" + name + " must be in format '{type} description' got: " + value;
|
||||
}
|
||||
return doc[name] = tag;
|
||||
}
|
||||
|
||||
var TAG = {
|
||||
@@ -103,23 +229,37 @@ var TAG = {
|
||||
namespace: valueTag,
|
||||
css: valueTag,
|
||||
see: valueTag,
|
||||
deprecated: valueTag,
|
||||
workInProgress: function(doc, name, value) {
|
||||
doc[name] = {description: markdown(value)};
|
||||
},
|
||||
usageContent: valueTag,
|
||||
'function': valueTag,
|
||||
description: markdownTag,
|
||||
returns: markdownTag,
|
||||
TODO: markdownTag,
|
||||
paramDescription: markdownTag,
|
||||
exampleDescription: markdownTag,
|
||||
element: valueTag,
|
||||
methodOf: valueTag,
|
||||
name: function(doc, name, value) {
|
||||
var parts = value.split(/\./);
|
||||
doc.name = value;
|
||||
doc.shortName = value.split(/\./).pop();
|
||||
doc.shortName = parts.pop();
|
||||
doc.depth = parts.length;
|
||||
},
|
||||
param: function(doc, name, value){
|
||||
doc.param = doc.param || [];
|
||||
doc.paramRest = doc.paramRest || [];
|
||||
var match = value.match(/^({([^\s=]+)(=)?}\s*)?([^\s]+|\[(\S+)+=([^\]]+)\])\s+(.*)/);
|
||||
var match = value.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
|
||||
// 1 12 2 34 4 5 5 6 6 3 7 7
|
||||
if (match) {
|
||||
var param = {
|
||||
type: match[2],
|
||||
name: match[4] || match[5],
|
||||
type: match[1],
|
||||
name: match[5] || match[4],
|
||||
optional: !!match[2],
|
||||
'default':match[6],
|
||||
description:match[7]};
|
||||
description:markdownNoP(value.replace(match[0], match[7]))
|
||||
};
|
||||
doc.param.push(param);
|
||||
if (!doc.paramFirst) {
|
||||
doc.paramFirst = param;
|
||||
@@ -130,19 +270,22 @@ var TAG = {
|
||||
throw "[" + doc.raw.file + ":" + doc.raw.line +
|
||||
"]: @param must be in format '{type} name=value description' got: " + value;
|
||||
}
|
||||
}
|
||||
},
|
||||
property: propertyTag,
|
||||
requires: requiresTag,
|
||||
returns: returnsTag
|
||||
};
|
||||
|
||||
function parseNgDoc(doc){
|
||||
var atName;
|
||||
var atText;
|
||||
var match;
|
||||
doc.raw.text.split(/\n/).forEach(function(line, lineNumber){
|
||||
if (match = line.match(/^@(\w+)(\s+(.*))?/)) {
|
||||
doc.raw.text.split(NEW_LINE).forEach(function(line, lineNumber){
|
||||
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
|
||||
// we found @name ...
|
||||
// if we have existing name
|
||||
if (atName) {
|
||||
(TAG[atName] || unknownTag)(doc, atName, atText.join('\n'));
|
||||
(TAG[atName] || unknownTag)(doc, atName, trim(atText.join('\n')));
|
||||
}
|
||||
atName = match[1];
|
||||
atText = [];
|
||||
@@ -160,9 +303,9 @@ function parseNgDoc(doc){
|
||||
}
|
||||
}
|
||||
|
||||
function findNgDoc(file, callback) {
|
||||
function findNgDocInJsFile(file, callback) {
|
||||
fs.readFile(file, callback.waitFor(function(err, content){
|
||||
var lines = content.toString().split(/\n\r?/);
|
||||
var lines = content.toString().split(NEW_LINE);
|
||||
var doc;
|
||||
var match;
|
||||
var inDoc = false;
|
||||
@@ -178,8 +321,9 @@ function findNgDoc(file, callback) {
|
||||
if (inDoc && line.match(/\*\//)) {
|
||||
doc.raw.text = doc.raw.text.join('\n');
|
||||
doc.raw.text = doc.raw.text.replace(/^\n/, '');
|
||||
if (doc.raw.text.match(/@ngdoc/))
|
||||
if (doc.raw.text.match(/@ngdoc/)){
|
||||
callback(doc);
|
||||
}
|
||||
doc = null;
|
||||
inDoc = false;
|
||||
}
|
||||
@@ -192,6 +336,22 @@ function findNgDoc(file, callback) {
|
||||
}));
|
||||
}
|
||||
|
||||
function loadTemplates(cache, callback){
|
||||
fs.readdir('docs', callback.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
var match = file.match(/^(.*)\.template$/);
|
||||
if (match) {
|
||||
fs.readFile(SRC_DIR + file, callback.waitFor(function(err, content){
|
||||
if (err) return this.error(err);
|
||||
cache[match[1]] = content.toString();
|
||||
}));
|
||||
}
|
||||
});
|
||||
callback();
|
||||
}));
|
||||
};
|
||||
|
||||
function findJsFiles(dir, callback){
|
||||
fs.readdir(dir, callback.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
@@ -208,3 +368,73 @@ function findJsFiles(dir, callback){
|
||||
callback.done();
|
||||
}));
|
||||
}
|
||||
|
||||
function processNgDoc(documentation, doc) {
|
||||
if (!doc.ngdoc) return;
|
||||
//console.log('Found:', doc.ngdoc + ':' + doc.name);
|
||||
|
||||
documentation.byName[doc.name] = doc;
|
||||
|
||||
if (doc.methodOf) {
|
||||
if (parent = documentation.byName[doc.methodOf]) {
|
||||
(parent.method = parent.method || []).push(doc);
|
||||
} else {
|
||||
throw 'Owner "' + doc.methodOf + '" is not defined.';
|
||||
}
|
||||
} else {
|
||||
documentation.pages.push(doc);
|
||||
keywordPages.push({
|
||||
name:doc.name,
|
||||
type: doc.ngdoc,
|
||||
keywords:keywords(doc.raw.text)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function writeDoc(pages, callback) {
|
||||
pages.forEach(function(doc) {
|
||||
var template = TEMPLATES[doc.ngdoc];
|
||||
if (!template) throw new Error("No template for:" + doc.ngdoc);
|
||||
var content = mustache.to_html(template, doc);
|
||||
fs.writeFile(OUTPUT_DIR + doc.name + '.html', content, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function findNgDocInDir(directory, docNotify) {
|
||||
fs.readdir(directory, docNotify.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
console.log(file);
|
||||
if (!file.match(/\.ngdoc$/)) return;
|
||||
fs.readFile(directory + file, docNotify.waitFor(function(err, content){
|
||||
if (err) return this.error(err);
|
||||
docNotify({
|
||||
raw:{
|
||||
text:content.toString(),
|
||||
file: directory + file,
|
||||
line: 1}
|
||||
});
|
||||
}));
|
||||
});
|
||||
docNotify.done();
|
||||
}));
|
||||
}
|
||||
|
||||
function keywordSort(a,b){
|
||||
// supper ugly comparator that orders all utility methods and objects before all the other stuff
|
||||
// like widgets, directives, services, etc.
|
||||
// Mother of all beautiful code please forgive me for the sin that this code certainly is.
|
||||
|
||||
if (a.name === b.name) return 0;
|
||||
if (a.name === 'angular') return -1;
|
||||
if (b.name === 'angular') return 1;
|
||||
|
||||
function namespacedName(page) {
|
||||
return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name;
|
||||
}
|
||||
|
||||
var namespacedA = namespacedName(a),
|
||||
namespacedB = namespacedName(b);
|
||||
|
||||
return namespacedA < namespacedB ? -1 : 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<h1>{{name}}</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>Usage</h2>
|
||||
<h3>In HTML Template Binding</h3>
|
||||
<tt>
|
||||
<pre>
|
||||
<{{element}} {{shortName}}="{{paramFirst.name}}">
|
||||
...
|
||||
</{{element}}>
|
||||
</pre>
|
||||
</tt>
|
||||
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
{{#param}}
|
||||
<li><tt>{{name}}</tt> –
|
||||
<tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt>
|
||||
<tt>{{#default}}[{{default}}]{{/default}}</tt>
|
||||
– {{{description}}}</li>
|
||||
{{/param}}
|
||||
</ul>
|
||||
{{{paramDescription}}}
|
||||
|
||||
{{#css}}
|
||||
<h3>CSS</h3>
|
||||
{{{css}}}
|
||||
{{/css}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -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,71 @@
|
||||
(function(){
|
||||
|
||||
var angularJsUrl;
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
var filename = /(.*\/)angular([^\/]*)/;
|
||||
for(var j = 0; j < scripts.length; j++) {
|
||||
var src = scripts[j].src;
|
||||
if (src && src.match(filename)) {
|
||||
angularJsUrl = src;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var HTML_TEMPLATE =
|
||||
'<!doctype html>\n' +
|
||||
'<html xmlns:ng="http://angularjs.org">\n' +
|
||||
' <script type="text/javascript" ng:autobind\n' +
|
||||
' src="' + angularJsUrl + '"></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('doc\\:source').eq(0),
|
||||
exampleSrc = example.text(),
|
||||
scenario = element.find('doc\\:scenario').eq(0);
|
||||
|
||||
var code = indent(exampleSrc);
|
||||
var tabs = angular.element(
|
||||
'<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>' +
|
||||
'<li class="doc-example-heading"><h3>Scenario Test</h3></li>' +
|
||||
'<li class="doc-example-scenario"><pre class="brush: js">' + scenario.text() + '</pre></li>' +
|
||||
'</ul>');
|
||||
|
||||
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) {
|
||||
var lines = text.split(/\n/);
|
||||
var lineNo = [];
|
||||
while (lines[0].match(/^\s*$/)) lines.shift();
|
||||
while (lines[lines.length - 1].match(/^\s*$/)) lines.pop();
|
||||
for ( var i = 0; i < lines.length; i++) {
|
||||
lines[i] = ' ' + lines[i];
|
||||
lineNo.push(6 + i);
|
||||
}
|
||||
return {html: lines.join('\n'), hilite: lineNo.join(',') };
|
||||
};
|
||||
|
||||
})();
|
||||
+1
-1
@@ -1 +1 @@
|
||||
NG_DOC={{{JSON}}};
|
||||
NG_PAGES={{{JSON}}};
|
||||
@@ -1,9 +1,9 @@
|
||||
{{#all}}
|
||||
{{#pages}}
|
||||
describe('{{name}}', function(){
|
||||
beforeEach(function(){
|
||||
browser().navigateTo('index.html#{{name}}');
|
||||
browser().navigateTo('index.html#!{{name}}');
|
||||
});
|
||||
// {{raw.file}}:{{raw.line}}
|
||||
{{{scenario}}}
|
||||
});
|
||||
{{/all}}
|
||||
{{/pages}}
|
||||
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
/* 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-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-left: 0em;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
||||
/* 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;
|
||||
}
|
||||
+45
-5
@@ -1,7 +1,47 @@
|
||||
function DocController($resource, $location){
|
||||
this.docs = $resource('documentation.json').get();
|
||||
this.getPartialDoc = function(){
|
||||
return encodeURIComponent($location.hashPath) + '.html';
|
||||
SyntaxHighlighter['defaults'].toolbar = false;
|
||||
|
||||
DocsController.$inject = ['$location', '$browser', '$window'];
|
||||
function DocsController($location, $browser, $window) {
|
||||
this.pages = NG_PAGES;
|
||||
window.$root = this.$root;
|
||||
|
||||
this.getUrl = function(page){
|
||||
return '#!' + page.name;
|
||||
};
|
||||
|
||||
this.getCurrentPartial = function(){
|
||||
return './' + this.getTitle() + '.html';
|
||||
};
|
||||
|
||||
this.getTitle = function(){
|
||||
var hashPath = $location.hashPath || '!angular';
|
||||
if (hashPath.match(/^!angular/)) {
|
||||
this.partialTitle = hashPath.substring(1);
|
||||
}
|
||||
return this.partialTitle;
|
||||
};
|
||||
|
||||
this.getClass = function(page) {
|
||||
var depth = page.name.split(/\./).length - 1,
|
||||
cssClass = 'level-' + depth + (page.name == this.getTitle() ? ' 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 ....");
|
||||
}
|
||||
|
||||
}
|
||||
DocController.$inject=['$resource', '$location'];
|
||||
|
||||
angular.filter('short', function(name){
|
||||
return (name||'').split(/\./).pop();
|
||||
});
|
||||
+41
-9
@@ -1,4 +1,23 @@
|
||||
<h1><tt>{{name}}</tt></h1>
|
||||
<h1>{{name}}</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>Usage</h2>
|
||||
<h3>In HTML Template Binding</h3>
|
||||
<tt>
|
||||
@@ -15,19 +34,32 @@ angular.filter.{{shortName}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/param
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
{{#param}}
|
||||
<li><tt>{{name}}{{#type}}({{type}}){{/type}}</tt>: {{description}}</li>
|
||||
<li><tt>{{name}}</tt> –
|
||||
<tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt>
|
||||
<tt>{{#default}}[{{default}}]{{/default}}</tt>
|
||||
– {{{description}}}</li>
|
||||
{{/param}}
|
||||
</ul>
|
||||
|
||||
{{#returns}}
|
||||
<h3>Returns</h3>
|
||||
{{{returns}}}
|
||||
<tt>{{{{type}}}}</tt> {{{description}}}
|
||||
{{/returns}}
|
||||
|
||||
{{#css}}
|
||||
<h3>CSS</h3>
|
||||
{{{css}}}
|
||||
{{/css}}
|
||||
|
||||
<h2>Description</h2>
|
||||
{{{description}}}
|
||||
|
||||
<WIKI:SOURCE style="display:block;">
|
||||
{{{example}}}
|
||||
</WIKI:SOURCE>
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<h1>{{name}}</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>Usage</h2>
|
||||
<h3>In HTML Template Binding</h3>
|
||||
<tt>
|
||||
<input type="text" ng:format="{{shortName}}">
|
||||
</tt>
|
||||
<h3>In JavaScript</h3>
|
||||
<tt ng:non-bindable>
|
||||
var userInputString = angular.formatter.{{shortName}}.format(modelValue);<br/>
|
||||
var modelValue = angular.formatter.{{shortName}}.parse(userInputString);
|
||||
</tt>
|
||||
|
||||
{{#returns}}
|
||||
<h3>Returns</h3>
|
||||
<tt>{{{{type}}}}</tt> {{{description}}}
|
||||
{{/returns}}
|
||||
|
||||
{{#css}}
|
||||
<h3>CSS</h3>
|
||||
{{{css}}}
|
||||
{{/css}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -0,0 +1,52 @@
|
||||
<h1>{{name}}</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>Usage</h2>
|
||||
<tt ng:non-bindable>
|
||||
{{name}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/paramRest}} );
|
||||
</tt>
|
||||
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
{{#param}}
|
||||
<li><tt>{{name}}</tt> –
|
||||
<tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt>
|
||||
<tt>{{#default}}[{{default}}]{{/default}}</tt>
|
||||
– {{{description}}}</li>
|
||||
{{/param}}
|
||||
</ul>
|
||||
|
||||
{{#returns}}
|
||||
<h3>Returns</h3>
|
||||
<tt>{{{{type}}}}</tt> {{{description}}}
|
||||
{{/returns}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
+42
-22
@@ -1,25 +1,45 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org">
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org/"
|
||||
xmlns:doc="http://docs.angularjs.org/"
|
||||
ng:controller="DocsController">
|
||||
<head>
|
||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="docs-data.js"></script>
|
||||
<script type="text/javascript" src="../angular.min.js" ng:autobind></script>
|
||||
<script type="text/javascript" src="http://angularjs.org/extensions/wiki_widgets.js"></script>
|
||||
<link rel="stylesheet" href="http://angularjs.org/extensions/wiki_widgets.css" type="text/css" media="screen" />
|
||||
<title ng:bind-template="<angular/>: {{getTitle()}}"><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="http://alexgorbatchev.com/pub/sh/current/styles/shCore.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" type="text/css"/>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
|
||||
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js"></script>
|
||||
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js"></script>
|
||||
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/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-data.js"></script>
|
||||
</head>
|
||||
<body ng:init="docs=$window.NG_DOC; $window.$root = $root">
|
||||
<table>
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<div ng:repeat="(name, type) in docs.section">
|
||||
<b>{{name}}</b>
|
||||
<div ng:repeat="page in type">
|
||||
<a href="#{{page.name}}"><tt>{{page.shortName}}</tt></a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td valign="top"><ng:include src="$location.hashPath + '.html' "></ng:include></td>
|
||||
</tr>
|
||||
</table>
|
||||
<body style="display:none;" ng:show="true">
|
||||
<div id="header">
|
||||
<h1>
|
||||
<span class="main-title">{{getTitle()}}</span>
|
||||
<a href="#"><span class="angular"><angular/></span> Docs</a>
|
||||
</h1>
|
||||
</div>
|
||||
<div id="sidebar">
|
||||
<input type="text" name="search" id="search-box" placeholder="search the docs"/>
|
||||
<ul id="api-list">
|
||||
<li ng:repeat="page in pages.$filter(search)" ng:class="getClass(page)">
|
||||
<a href="{{getUrl(page)}}" ng:click="">{{page.name | short}}</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>
|
||||
</html>
|
||||
|
||||
+31
-1
@@ -1 +1,31 @@
|
||||
{{{description}}}
|
||||
<h1>{{name}}</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}}
|
||||
|
||||
{{{description}}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<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>
|
||||
|
||||
<h2>Methods</h2>
|
||||
<ul>
|
||||
{{#method}}
|
||||
<li><tt>{{shortName}}</tt>: {{{description}}}</li>
|
||||
{{/method}}
|
||||
</ul>
|
||||
|
||||
<h2>Properties</h2>
|
||||
<ul>
|
||||
{{#property}}
|
||||
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}</tt>{{#description}}: {{{description}}}{{/description}}</li>
|
||||
{{/property}}
|
||||
</ul>
|
||||
|
||||
{{#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,288 @@
|
||||
console.log(__dirname);
|
||||
require.paths.push(__dirname + "/../");
|
||||
require.paths.push(__dirname + "/../../");
|
||||
var fs = require('fs');
|
||||
var Script = process.binding('evals').Script;
|
||||
var collect = load('docs/collect.js');
|
||||
|
||||
describe('collect', function(){
|
||||
describe('markdown', function(){
|
||||
it('should replace angular in markdown', function(){
|
||||
expect(collect.markdown('<angular/>')).
|
||||
toEqual('<p><tt><angular/></tt></p>');
|
||||
});
|
||||
|
||||
it('should not replace anything in <pre>', function(){
|
||||
expect(collect.markdown('bah x\n<pre>\nangular.k\n</pre>\n asdf x')).
|
||||
toEqual(
|
||||
'<p>bah x</p>' +
|
||||
'<pre>\nangular.k\n</pre>' +
|
||||
'<p>asdf x</p>');
|
||||
});
|
||||
|
||||
it('should replace text between two <pre></pre> tags', function() {
|
||||
expect(collect.markdown('<pre>x</pre># One<pre>b</pre>')).
|
||||
toEqual('<pre>x</pre><h1>One</h1><pre>b</pre>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('processNgDoc', function() {
|
||||
var processNgDoc = collect.processNgDoc,
|
||||
documentation;
|
||||
|
||||
beforeEach(function() {
|
||||
documentation = {
|
||||
pages: [],
|
||||
byName: {}
|
||||
};
|
||||
});
|
||||
|
||||
it('should store references to docs by name', function() {
|
||||
var doc = {ngdoc: 'section', name: 'fake', raw: {text:''}};
|
||||
processNgDoc(documentation, doc);
|
||||
expect(documentation.byName.fake).toBe(doc);
|
||||
});
|
||||
|
||||
it('should connect doc to owner (specified by @methodOf)', function() {
|
||||
var parentDoc = {ngdoc: 'section', name: 'parent', raw: {text:''}};
|
||||
var doc = {ngdoc: 'section', name: 'child', methodOf: 'parent', raw: {text:''}};
|
||||
processNgDoc(documentation, parentDoc);
|
||||
processNgDoc(documentation, doc);
|
||||
expect(documentation.byName.parent.method).toBeDefined();
|
||||
expect(documentation.byName.parent.method[0]).toBe(doc);
|
||||
});
|
||||
|
||||
it('should not add doc to sections if @memberOf specified', function() {
|
||||
var parentDoc = {ngdoc: 'parent', name: 'parent', raw: {text:''}};
|
||||
var doc = {ngdoc: 'child', name: 'child', methodOf: 'parent', raw: {text:''}};
|
||||
processNgDoc(documentation, parentDoc);
|
||||
processNgDoc(documentation, doc);
|
||||
expect(documentation.pages.child).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should throw exception if owner does not exist', function() {
|
||||
expect(function() {
|
||||
processNgDoc(documentation, {ngdoc: 'section', methodOf: 'not.exist', raw: {text:''}});
|
||||
}).toThrow('Owner "not.exist" is not defined.');
|
||||
});
|
||||
|
||||
it('should ignore non-ng docs', function() {
|
||||
var doc = {name: 'anything'};
|
||||
expect(function() {
|
||||
processNgDoc(documentation, doc);
|
||||
}).not.toThrow();
|
||||
expect(documentation.pages).not.toContain(doc);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TAG', function(){
|
||||
var TAG = collect.TAG;
|
||||
var doc;
|
||||
beforeEach(function(){
|
||||
doc = {};
|
||||
});
|
||||
|
||||
describe('@param', function(){
|
||||
it('should parse with no default', function(){
|
||||
TAG.param(doc, 'param',
|
||||
'{(number|string)} number Number \n to format.');
|
||||
expect(doc.param).toEqual([{
|
||||
type : '(number|string)',
|
||||
name : 'number',
|
||||
optional: false,
|
||||
'default' : undefined,
|
||||
description : 'Number \n to format.' }]);
|
||||
});
|
||||
it('should parse with default and optional', function(){
|
||||
TAG.param(doc, 'param',
|
||||
'{(number|string)=} [fractionSize=2] desc');
|
||||
expect(doc.param).toEqual([{
|
||||
type : '(number|string)',
|
||||
name : 'fractionSize',
|
||||
optional: true,
|
||||
'default' : '2',
|
||||
description : 'desc' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@requires', function() {
|
||||
it('should parse more @requires tag into array', function() {
|
||||
TAG.requires(doc, 'requires', '$service');
|
||||
TAG.requires(doc, 'requires', '$another');
|
||||
|
||||
expect(doc.requires).toEqual([
|
||||
{name: '$service'},
|
||||
{name: '$another'}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@property', function() {
|
||||
it('should parse @property tags into array', function() {
|
||||
TAG.property(doc, 'property', '{type} name1 desc');
|
||||
TAG.property(doc, 'property', '{type} name2 desc');
|
||||
expect(doc.property.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should parse @property with only name', function() {
|
||||
TAG.property(doc, 'property', 'fake');
|
||||
expect(doc.property[0].name).toEqual('fake');
|
||||
});
|
||||
|
||||
it('should parse @property with optional type', function() {
|
||||
TAG.property(doc, 'property', '{string} name');
|
||||
expect(doc.property[0].name).toEqual('name');
|
||||
expect(doc.property[0].type).toEqual('string');
|
||||
});
|
||||
|
||||
it('should parse @property with optional description', function() {
|
||||
TAG.property(doc, 'property', 'name desc rip tion');
|
||||
expect(doc.property[0].name).toEqual('name');
|
||||
expect(doc.property[0].description).toEqual('desc rip tion');
|
||||
});
|
||||
|
||||
it('should parse @property with type and description both', function() {
|
||||
TAG.property(doc, 'property', '{bool} name desc rip tion');
|
||||
expect(doc.property[0].name).toEqual('name');
|
||||
expect(doc.property[0].type).toEqual('bool');
|
||||
expect(doc.property[0].description).toEqual('desc rip tion');
|
||||
});
|
||||
|
||||
/**
|
||||
* If property description is undefined, this variable is not set in the template,
|
||||
* so the whole @description tag is used instead
|
||||
*/
|
||||
it('should set undefined description to "false"', function() {
|
||||
TAG.property(doc, 'property', 'name');
|
||||
expect(doc.property[0].description).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@methodOf', function() {
|
||||
it('should parse @methodOf tag', function() {
|
||||
expect(function() {
|
||||
TAG.methodOf(doc, 'methodOf', 'parentName');
|
||||
}).not.toThrow();
|
||||
expect(doc.methodOf).toEqual('parentName');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@returns', function() {
|
||||
it('should not parse @returns without type', function() {
|
||||
expect(function() {TAG.returns(doc, 'returns', 'lala');})
|
||||
.toThrow();
|
||||
});
|
||||
|
||||
it('should parse @returns with type and description', function() {
|
||||
TAG.returns(doc, 'returns', '{string} descrip tion');
|
||||
expect(doc.returns).toEqual({type: 'string', description: 'descrip tion'});
|
||||
});
|
||||
|
||||
it('should transform description of @returns with markdown', function() {
|
||||
TAG.returns(doc, 'returns', '{string} descrip *tion*');
|
||||
expect(doc.returns).toEqual({type: 'string', description: 'descrip <em>tion</em>'});
|
||||
});
|
||||
|
||||
it('should support multiline content', function() {
|
||||
TAG.returns(doc, 'returns', '{string} description\n new line\n another line');
|
||||
expect(doc.returns).
|
||||
toEqual({type: 'string', description: 'description\n new line\n another line'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('@description', function(){
|
||||
it('should support pre blocks', function(){
|
||||
TAG.description(doc, 'description', '<pre>abc</pre>');
|
||||
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() {
|
||||
TAG.description(doc, 'description', 'foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>');
|
||||
expect(doc.description).
|
||||
toBe('<p>foo </p>' +
|
||||
'<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>' +
|
||||
'<h2>bah</h2>\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() {
|
||||
TAG.description(doc, 'description',
|
||||
'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' +
|
||||
'dad{@link angular.foo}\n\n' +
|
||||
'{@link angular.directive.ng:foo ng:foo}');
|
||||
expect(doc.description).
|
||||
toBe('<p>foo <a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' +
|
||||
'<p>da <a href="#!angular.foo"><code>bar foo bar</code></a> </p>\n\n' +
|
||||
'<p>dad<a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' +
|
||||
'<p><a href="#!angular.directive.ng:foo"><code>ng:foo</code></a></p>');
|
||||
});
|
||||
|
||||
it('should increment all headings by one', function() {
|
||||
TAG.description(doc, 'description', '# foo\nabc');
|
||||
expect(doc.description).
|
||||
toBe('<h2>foo</h2>\n\n<p>abc</p>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@example', function(){
|
||||
it('should not remove {{}}', function(){
|
||||
TAG.example(doc, 'example', 'text {{ abc }}');
|
||||
expect(doc.example).toEqual('text {{ abc }}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@deprecated', function() {
|
||||
it('should parse @deprecated', function() {
|
||||
TAG.deprecated(doc, 'deprecated', 'Replaced with foo.');
|
||||
expect(doc.deprecated).toBe('Replaced with foo.');
|
||||
})
|
||||
});
|
||||
|
||||
describe('@workInProgress', function() {
|
||||
it('should parse @workInProgress without a description and default to true', function() {
|
||||
TAG.workInProgress(doc, 'workInProgress', '');
|
||||
expect(doc.workInProgress).toEqual({description: ''});
|
||||
});
|
||||
|
||||
it('should parse @workInProgress with a description', function() {
|
||||
TAG.workInProgress(doc, 'workInProgress', 'my description');
|
||||
expect(doc.workInProgress).toEqual({description: '<p>my description</p>'});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('trim', function(){
|
||||
var trim = collect.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('keywords', function(){
|
||||
var keywords = collect.keywords;
|
||||
it('should collect keywords', function(){
|
||||
expect(keywords('\nHello: World! @ignore.')).toEqual('hello world');
|
||||
expect(keywords('The `ng:class-odd` and ')).toEqual('and ng:class-odd the');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function load(path){
|
||||
var sandbox = {
|
||||
require: require,
|
||||
console: console,
|
||||
__dirname: __dirname,
|
||||
testmode: true
|
||||
};
|
||||
Script.runInNewContext(fs.readFileSync(path), sandbox, path);
|
||||
return sandbox;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
require.paths.push("./lib");
|
||||
var jasmine = require('jasmine-1.0.1');
|
||||
var sys = require('util');
|
||||
|
||||
for(var key in jasmine) {
|
||||
global[key] = jasmine[key];
|
||||
}
|
||||
|
||||
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 + '/spec', function(runner, log){
|
||||
process.exit(runner.results().failedCount);
|
||||
}, isVerbose, showColors);
|
||||
@@ -0,0 +1,59 @@
|
||||
<h1>{{name}}</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>Usage</h2>
|
||||
<h3>In HTML Template Binding</h3>
|
||||
<tt>
|
||||
<input type="text" ng:validate="{{shortName}}{{#paramRest}}{{^default}}:{{name}}{{/default}}{{#default}}<i>[:{{name}}]</i>{{/default}}{{/paramRest}}"/>
|
||||
</tt>
|
||||
|
||||
<h3>In JavaScript</h3>
|
||||
<tt ng:non-bindable>
|
||||
angular.validator.{{shortName}}({{paramFirst.name}}{{#paramRest}}{{^default}}, {{name}}{{/default}}{{#default}}<i>[, {{name}}]</i>{{/default}}{{/paramRest}} );
|
||||
</tt>
|
||||
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
{{#param}}
|
||||
<li><tt>{{name}}</tt> –
|
||||
<tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt>
|
||||
<tt>{{#default}}[{{default}}]{{/default}}</tt>
|
||||
– {{{description}}}</li>
|
||||
{{/param}}
|
||||
</ul>
|
||||
{{{paramDescription}}}
|
||||
|
||||
{{#css}}
|
||||
<h3>CSS</h3>
|
||||
{{{css}}}
|
||||
{{/css}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -0,0 +1,68 @@
|
||||
<h1>{{name}}</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>Usage</h2>
|
||||
<h3>In HTML Template Binding</h3>
|
||||
<tt>
|
||||
{{^element}}
|
||||
<pre>
|
||||
<{{shortName}}{{#param}} {{#default}}<i>[</i>{{/default}}{{name}}="..."{{#default}}<i>]</i>{{/default}}{{/param}}>{{#usageContent}}
|
||||
|
||||
{{usageContent}}
|
||||
{{/usageContent}}</{{shortName}}>
|
||||
</pre>
|
||||
{{/element}}
|
||||
{{#element}}
|
||||
<pre>
|
||||
<{{element}} {{shortName}}{{#paramFirst}}="{{paramFirst.name}}{{/paramFirst}}">
|
||||
...
|
||||
</{{element}}>
|
||||
</pre>
|
||||
{{/element}}
|
||||
</tt>
|
||||
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
{{#param}}
|
||||
<li><tt>{{name}}</tt> –
|
||||
<tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt>
|
||||
<tt>{{#default}}[{{default}}]{{/default}}</tt>
|
||||
– {{{description}}}</li>
|
||||
{{/param}}
|
||||
</ul>
|
||||
{{{paramDescription}}}
|
||||
|
||||
{{#css}}
|
||||
<h3>CSS</h3>
|
||||
{{{css}}}
|
||||
{{/css}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -1,58 +0,0 @@
|
||||
WIKI\:SOURCE,
|
||||
WIKI\:SOURCE>ul.tabs,
|
||||
WIKI\:SOURCE>ul.tabs>li {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
WIKI\:SOURCE>ul.tabs {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
WIKI\:SOURCE > ul.tabs > li.tab {
|
||||
margin: 0 0 0 .8em !important;
|
||||
display: inline-block;
|
||||
border: 1px solid gray;
|
||||
padding: .1em .8em .4em .8em !important;
|
||||
-moz-border-radius-topleft: 5px;
|
||||
-moz-border-radius-topright: 5px;
|
||||
-webkit-border-top-left-radius: 5px;
|
||||
-webkit-border-top-right-radius: 5px;
|
||||
border-bottom: none;
|
||||
cursor: pointer;
|
||||
background-color: lightgray;
|
||||
margin-bottom: -2px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
WIKI\:SOURCE > ul.tabs > li.tab.selected {
|
||||
z-index: 2;
|
||||
background-color: white;
|
||||
font-weight: bold;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
WIKI\:SOURCE > ul.tabs > li.pane {
|
||||
border: 2px solid gray;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
background-color: white;
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
WIKI\:SOURCE > ul.tabs > li.pane {
|
||||
display:none;
|
||||
}
|
||||
|
||||
WIKI\:SOURCE > ul.tabs > li.pane.selected {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display:block;
|
||||
}
|
||||
WIKI\:SOURCE > ul.tabs > li.pane.source {
|
||||
font-size: .8em !important;
|
||||
}
|
||||
//////////////////
|
||||
@@ -1,51 +0,0 @@
|
||||
(function(){
|
||||
var HTML_TEMPLATE =
|
||||
'<!DOCTYPE HTML>\n' +
|
||||
'<html xmlns:ng="http://angularjs.org">\n' +
|
||||
' <head>\n' +
|
||||
' <script type="text/javascript"\n' +
|
||||
' src="http://angularjs.org/ng/js/angular-debug.js" ng:autobind></script>\n' +
|
||||
' </head>\n' +
|
||||
' <body>\n' +
|
||||
'_HTML_SOURCE_\n' +
|
||||
' </body>\n' +
|
||||
'</html>';
|
||||
|
||||
angular.widget('WIKI:SOURCE', function(element){
|
||||
this.descend(true);
|
||||
var html = element.text();
|
||||
element.show();
|
||||
var tabs = angular.element(
|
||||
'<ul class="tabs">' +
|
||||
'<li class="tab selected" to="angular"><angular/></li>' +
|
||||
'<li class="tab" to="plain">plain</li>' +
|
||||
'<li class="tab" to="source">source</li>' +
|
||||
'<li class="pane selected angular">' + html + '</li>' +
|
||||
'<li class="pane plain" ng:non-bindable>' + html + '</li>' +
|
||||
'<li class="pane source" ng:non-bindable><pre class="brush: js; html-script: true"></pre></li>' +
|
||||
'</ul>');
|
||||
var pre = tabs.
|
||||
find('>li.source>pre').
|
||||
text(HTML_TEMPLATE.replace('_HTML_SOURCE_', html));
|
||||
var color = element.attr('color') || 'white';
|
||||
element.html('');
|
||||
element.append(tabs);
|
||||
element.find('>ul.tabs>li.pane').css('background-color', color);
|
||||
var script = (html.match(/<script[^\>]*>([\s\S]*)<\/script>/) || [])[1] || '';
|
||||
try {
|
||||
eval(script);
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
}
|
||||
return function(element){
|
||||
element.find('>ul.tabs>li.tab').click(function(){
|
||||
if ($(this).is(".selected")) return;
|
||||
element.
|
||||
find('>ul.tabs>li.selected').
|
||||
add(this).
|
||||
add(element.find('>ul>li.pane.' + angular.element(this).attr('to'))).
|
||||
toggleClass('selected');
|
||||
});
|
||||
};
|
||||
});
|
||||
})();
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
/usr/local/bin/node docs/collect.js
|
||||
node docs/specs.js --noColor && node docs/collect.js
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
server: http://localhost:9876
|
||||
|
||||
load:
|
||||
- lib/jasmine-1.0.1/jasmine.js
|
||||
- lib/jasmine-jstd-adapter/JasmineAdapter.js
|
||||
- lib/jquery/jquery-1.4.2.js
|
||||
- test/jquery_remove.js
|
||||
- src/Angular.js
|
||||
- src/JSON.js
|
||||
- src/*.js
|
||||
- example/personalLog/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
- src/scenario/*.js
|
||||
- test/angular-mocks.js
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
- example/personalLog/test/*.js
|
||||
|
||||
exclude:
|
||||
- test/jquery_alias.js
|
||||
- src/angular.prefix
|
||||
- src/angular.suffix
|
||||
- src/angular-bootstrap.js
|
||||
- src/scenario/angular-bootstrap.js
|
||||
- src/AngularPublic.js
|
||||
|
||||
plugin:
|
||||
- name: "coverage"
|
||||
jar: "lib/jstestdriver/coverage.jar"
|
||||
module: "com.google.jstestdriver.coverage.CoverageModule"
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
var fs = require('fs');
|
||||
var sys = require('sys');
|
||||
var path = require('path');
|
||||
|
||||
var filename = __dirname + '/jasmine.js';
|
||||
global.window = {
|
||||
setTimeout: setTimeout,
|
||||
clearTimeout: clearTimeout,
|
||||
setInterval: setInterval,
|
||||
clearInterval: clearInterval
|
||||
};
|
||||
var src = fs.readFileSync(filename);
|
||||
var jasmine = process.compile(src + '\njasmine;', filename);
|
||||
delete global.window;
|
||||
|
||||
function noop(){}
|
||||
|
||||
jasmine.executeSpecsInFolder = function(folder, done, isVerbose, showColors, matcher){
|
||||
var log = [];
|
||||
var columnCounter = 0;
|
||||
var start = 0;
|
||||
var elapsed = 0;
|
||||
var verbose = isVerbose || false;
|
||||
var fileMatcher = new RegExp(matcher || "\.js$");
|
||||
var colors = showColors || false;
|
||||
var specs = jasmine.getAllSpecFiles(folder, fileMatcher);
|
||||
|
||||
var ansi = {
|
||||
green: '\033[32m',
|
||||
red: '\033[31m',
|
||||
yellow: '\033[33m',
|
||||
none: '\033[0m'
|
||||
};
|
||||
|
||||
for (var i = 0, len = specs.length; i < len; ++i){
|
||||
var filename = specs[i];
|
||||
require(filename.replace(/\.*$/, ""));
|
||||
}
|
||||
|
||||
var jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.reporter = {
|
||||
log: function(str){
|
||||
},
|
||||
|
||||
reportSpecStarting: function(runner) {
|
||||
},
|
||||
|
||||
reportRunnerStarting: function(runner) {
|
||||
sys.puts('Started');
|
||||
start = Number(new Date);
|
||||
},
|
||||
|
||||
reportSuiteResults: function(suite) {
|
||||
var specResults = suite.results();
|
||||
var path = [];
|
||||
while(suite) {
|
||||
path.unshift(suite.description);
|
||||
suite = suite.parentSuite;
|
||||
}
|
||||
var description = path.join(' ');
|
||||
|
||||
if (verbose)
|
||||
log.push('Spec ' + description);
|
||||
|
||||
specResults.items_.forEach(function(spec){
|
||||
if (spec.failedCount > 0 && spec.description) {
|
||||
if (!verbose)
|
||||
log.push(description);
|
||||
log.push(' it ' + spec.description);
|
||||
spec.items_.forEach(function(result){
|
||||
log.push(' ' + result.trace.stack + '\n');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
reportSpecResults: function(spec) {
|
||||
var result = spec.results();
|
||||
var msg = '';
|
||||
if (result.passed())
|
||||
{
|
||||
msg = (colors) ? (ansi.green + '.' + ansi.none) : '.';
|
||||
// } else if (result.skipped) { TODO: Research why "result.skipped" returns false when "xit" is called on a spec?
|
||||
// msg = (colors) ? (ansi.yellow + '*' + ansi.none) : '*';
|
||||
} else {
|
||||
msg = (colors) ? (ansi.red + 'F' + ansi.none) : 'F';
|
||||
}
|
||||
sys.print(msg);
|
||||
if (columnCounter++ < 50) return;
|
||||
columnCounter = 0;
|
||||
sys.print('\n');
|
||||
},
|
||||
|
||||
|
||||
reportRunnerResults: function(runner) {
|
||||
elapsed = (Number(new Date) - start) / 1000;
|
||||
sys.puts('\n');
|
||||
log.forEach(function(log){
|
||||
sys.puts(log);
|
||||
});
|
||||
sys.puts('Finished in ' + elapsed + ' seconds');
|
||||
|
||||
var summary = jasmine.printRunnerResults(runner);
|
||||
if(colors)
|
||||
{
|
||||
if(runner.results().failedCount === 0 )
|
||||
sys.puts(ansi.green + summary + ansi.none);
|
||||
else
|
||||
sys.puts(ansi.red + summary + ansi.none);
|
||||
} else {
|
||||
sys.puts(summary);
|
||||
}
|
||||
(done||noop)(runner, log);
|
||||
}
|
||||
};
|
||||
jasmineEnv.execute();
|
||||
};
|
||||
|
||||
jasmine.getAllSpecFiles = function(dir, matcher){
|
||||
var specs = [];
|
||||
|
||||
if (fs.statSync(dir).isFile() && dir.match(matcher)) {
|
||||
specs.push(dir);
|
||||
} else {
|
||||
var files = fs.readdirSync(dir);
|
||||
for (var i = 0, len = files.length; i < len; ++i){
|
||||
var filename = dir + '/' + files[i];
|
||||
if (fs.statSync(filename).isFile() && filename.match(matcher)){
|
||||
specs.push(filename);
|
||||
}else if (fs.statSync(filename).isDirectory()){
|
||||
var subfiles = this.getAllSpecFiles(filename, matcher);
|
||||
subfiles.forEach(function(result){
|
||||
specs.push(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return specs;
|
||||
};
|
||||
|
||||
jasmine.printRunnerResults = function(runner){
|
||||
var results = runner.results();
|
||||
var suites = runner.suites();
|
||||
var msg = '';
|
||||
msg += suites.length + ' test' + ((suites.length === 1) ? '' : 's') + ', ';
|
||||
msg += results.totalCount + ' assertion' + ((results.totalCount === 1) ? '' : 's') + ', ';
|
||||
msg += results.failedCount + ' failure' + ((results.failedCount === 1) ? '' : 's') + '\n';
|
||||
return msg;
|
||||
};
|
||||
|
||||
function now(){
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
jasmine.asyncSpecWait = function(){
|
||||
var wait = jasmine.asyncSpecWait;
|
||||
wait.start = now();
|
||||
wait.done = false;
|
||||
(function innerWait(){
|
||||
waits(10);
|
||||
runs(function() {
|
||||
if (wait.start + wait.timeout < now()) {
|
||||
expect('timeout waiting for spec').toBeNull();
|
||||
} else if (wait.done) {
|
||||
wait.done = false;
|
||||
} else {
|
||||
innerWait();
|
||||
}
|
||||
});
|
||||
})();
|
||||
};
|
||||
jasmine.asyncSpecWait.timeout = 4 * 1000;
|
||||
jasmine.asyncSpecDone = function(){
|
||||
jasmine.asyncSpecWait.done = true;
|
||||
};
|
||||
|
||||
for ( var key in jasmine) {
|
||||
exports[key] = jasmine[key];
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
var fs = require('fs');
|
||||
|
||||
var filename = __dirname + '/showdown-0.9.js';
|
||||
var src = fs.readFileSync(filename);
|
||||
var Showdown = process.compile(src + '\nShowdown;', filename);
|
||||
exports.Showdown = Showdown;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>
|
||||
<body>
|
||||
<span ng:init='x = {d:3}; x1 = {bar:[x,5]}; x1.bar[0].d = 4'>
|
||||
<input name="x1.bar[0].d" type="text"></input>
|
||||
<input name="x.d" type="text"></input>
|
||||
<span> {{x1}} -- {{x1.bar[0].d}}</span>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>
|
||||
<head>
|
||||
<body>
|
||||
<select name='selection0' style="display:block;">
|
||||
<option ng:repeat='value in ["FOO","BAR"]'">{{value}}</option>
|
||||
</select>
|
||||
{{selection0}} <-- FOO should be shown here
|
||||
|
||||
<hr/>
|
||||
|
||||
<select ng:init="selection1='ignore'" name='selection1' style="display:block;">
|
||||
<option ng:repeat='value in ["FOO","BAR"]' ng:bind-attr="{selected:'{{value==\'BAR\'}}'}">{{value}}</option>
|
||||
</select>
|
||||
{{selection1}} <-- BAR should be shown here
|
||||
|
||||
<hr/>
|
||||
|
||||
<select ng:init="selection2=1" name="selection2" style="display:block;">
|
||||
<option value="{{$index}}" ng:repeat="opt in ['zero', 'one']">{{opt}}</option>
|
||||
</select>
|
||||
{{selection2}} <-- 1 should be shown here
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>
|
||||
<body>
|
||||
<textarea name="html" rows="10" cols="100"></textarea>
|
||||
<div>{{html|html}}</div>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 20000 --config jsTestDriver-coverage.conf
|
||||
@@ -1 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 20000
|
||||
|
||||
+516
-107
@@ -4,27 +4,29 @@ if (typeof document.getAttribute == $undefined)
|
||||
document.getAttribute = function() {};
|
||||
|
||||
/**
|
||||
* @ngdoc
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.lowercase
|
||||
* @function
|
||||
*
|
||||
* @description Converts string to lowercase
|
||||
* @param {string} value
|
||||
* @param {string} string String to be lowercased.
|
||||
* @returns {string} Lowercased string.
|
||||
*/
|
||||
var lowercase = function (value){ return isString(value) ? value.toLowerCase() : value; };
|
||||
var lowercase = function (string){ return isString(string) ? string.toLowerCase() : string; };
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc
|
||||
* @name angular#uppercase
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.uppercase
|
||||
* @function
|
||||
*
|
||||
* @description Converts string to uppercase.
|
||||
* @param {string} value
|
||||
* @param {string} string String to be uppercased.
|
||||
* @returns {string} Uppercased string.
|
||||
*/
|
||||
var uppercase = function (value){ return isString(value) ? value.toUpperCase() : value; };
|
||||
var uppercase = function (string){ return isString(string) ? string.toUpperCase() : string; };
|
||||
|
||||
|
||||
var manualLowercase = function (s) {
|
||||
@@ -51,6 +53,9 @@ function fromCharCode(code) { return String.fromCharCode(code); }
|
||||
var _undefined = undefined,
|
||||
_null = null,
|
||||
$$element = '$element',
|
||||
$$update = '$update',
|
||||
$$scope = '$scope',
|
||||
$$validate = '$validate',
|
||||
$angular = 'angular',
|
||||
$array = 'array',
|
||||
$boolean = 'boolean',
|
||||
@@ -67,6 +72,8 @@ var _undefined = undefined,
|
||||
$number = 'number',
|
||||
$object = 'object',
|
||||
$string = 'string',
|
||||
$value = 'value',
|
||||
$selected = 'selected',
|
||||
$undefined = 'undefined',
|
||||
NG_EXCEPTION = 'ng-exception',
|
||||
NG_VALIDATION_ERROR = 'ng-validation-error',
|
||||
@@ -75,6 +82,7 @@ var _undefined = undefined,
|
||||
PRIORITY_WATCH = -1000,
|
||||
PRIORITY_LAST = 99999,
|
||||
PRIORITY = {'FIRST': PRIORITY_FIRST, 'LAST': PRIORITY_LAST, 'WATCH':PRIORITY_WATCH},
|
||||
Error = window.Error,
|
||||
jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy
|
||||
_ = window['_'],
|
||||
/** holds major version number for IE or NaN for real browsers */
|
||||
@@ -84,105 +92,51 @@ var _undefined = undefined,
|
||||
push = Array.prototype.push,
|
||||
error = window[$console] ? bind(window[$console], window[$console]['error'] || noop) : noop,
|
||||
|
||||
/**
|
||||
* @name angular
|
||||
* @namespace The exported angular namespace.
|
||||
*/
|
||||
angular = window[$angular] || (window[$angular] = {}),
|
||||
angularTextMarkup = extensionMap(angular, 'markup'),
|
||||
angularAttrMarkup = extensionMap(angular, 'attrMarkup'),
|
||||
/** @name angular.directive */
|
||||
angularDirective = extensionMap(angular, 'directive'),
|
||||
/** @name angular.widget */
|
||||
angularWidget = extensionMap(angular, 'widget', lowercase),
|
||||
/** @name angular.validator */
|
||||
angularValidator = extensionMap(angular, 'validator'),
|
||||
|
||||
|
||||
/**
|
||||
* @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}, {@link angular.filter.json}, {@link angular.filter.number},
|
||||
* and {@link angular.filter.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.
|
||||
* * `$element` — The DOM element containing the binding. This allows the filter to manipulate
|
||||
* the DOM in addition to transforming the input.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* //TODO this example current doesn't show up anywhere because the overview template doesn't
|
||||
* // render it.
|
||||
*
|
||||
* 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).
|
||||
|
||||
<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>
|
||||
<span ng:non-bindable="true">{{"hello"|reverse}}</span>: {{"hello"|reverse}}<br>
|
||||
<span ng:non-bindable="true">{{"hello"|reverse:true}}</span>: {{"hello"|reverse:true}}<br>
|
||||
<span ng:non-bindable="true">{{"hello"|reverse:true:"blue"}}</span>:
|
||||
{{"hello"|reverse:true:"blue"}}
|
||||
|
||||
* //TODO: I completely dropped a mention of using the other option (setter method), it's
|
||||
* confusing to have two ways to do the same thing. I just wonder if we should prefer using the
|
||||
* setter way over direct assignment because in the future we might want to be able to intercept
|
||||
* filter registrations for some reason.
|
||||
*/
|
||||
/** @name angular.fileter */
|
||||
angularFilter = extensionMap(angular, 'filter'),
|
||||
/** @name angular.formatter */
|
||||
angularFormatter = extensionMap(angular, 'formatter'),
|
||||
/** @name angular.service */
|
||||
angularService = extensionMap(angular, 'service'),
|
||||
angularCallbacks = extensionMap(angular, 'callbacks'),
|
||||
nodeName,
|
||||
rngScript = /^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/;
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.foreach
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Invokes the `iterator` function once for each item in `obj` collection. The collection can either
|
||||
* be an object or an array. The `iterator` function is invoked with `iterator(value, key)`, where
|
||||
* `value` is the value of an object property or an array element and `key` is the object property
|
||||
* key or array element index. Optionally, `context` can be specified for the iterator function.
|
||||
*
|
||||
<pre>
|
||||
var values = {name: 'misko', gender: 'male'};
|
||||
var log = [];
|
||||
angular.foreach(values, function(value, key){
|
||||
this.push(key + ': ' + value);
|
||||
}, log);
|
||||
expect(log).toEqual(['name: misko', 'gender:male']);
|
||||
</pre>
|
||||
*
|
||||
* @param {Object|Array} obj Object to iterate over.
|
||||
* @param {function()} iterator Iterator function.
|
||||
* @param {Object} context Object to become context (`this`) for the iterator function.
|
||||
* @returns {Objet|Array} Reference to `obj`.
|
||||
*/
|
||||
function foreach(obj, iterator, context) {
|
||||
var key;
|
||||
if (obj) {
|
||||
@@ -216,6 +170,31 @@ function foreachSorted(obj, iterator, context) {
|
||||
}
|
||||
|
||||
|
||||
function formatError(arg) {
|
||||
if (arg instanceof Error) {
|
||||
if (arg.stack) {
|
||||
arg = arg.stack;
|
||||
} else if (arg.sourceURL) {
|
||||
arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.extend
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Extends the destination object `dst` by copying all of the properties from the `src` objects to
|
||||
* `dst`. You can specify multiple `src` objects.
|
||||
*
|
||||
* @param {Object} dst The destination object.
|
||||
* @param {...Object} src The source object(s).
|
||||
*/
|
||||
function extend(dst) {
|
||||
foreach(arguments, function(obj){
|
||||
if (obj !== dst) {
|
||||
@@ -227,18 +206,64 @@ function extend(dst) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
function inherit(parent, extra) {
|
||||
return extend(new (extend(function(){}, {prototype:parent}))(), extra);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.noop
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Empty function that performs no operation whatsoever. This function is useful when writing code
|
||||
* in the functional style.
|
||||
<pre>
|
||||
function foo(callback) {
|
||||
var result = calculateResult();
|
||||
(callback || angular.noop)(result);
|
||||
}
|
||||
</pre>
|
||||
*/
|
||||
function noop() {}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.identity
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* A function that does nothing except for returning its first argument. This function is useful
|
||||
* when writing code in the functional style.
|
||||
*
|
||||
<pre>
|
||||
function transformer(transformationFn, value) {
|
||||
return (transformationFn || identity)(value);
|
||||
};
|
||||
</pre>
|
||||
*/
|
||||
function identity($) {return $;}
|
||||
|
||||
|
||||
function valueFn(value) {return function(){ return value; };}
|
||||
|
||||
|
||||
function extensionMap(angular, name, transform) {
|
||||
var extPoint;
|
||||
return angular[name] || (extPoint = angular[name] = function (name, fn, prop){
|
||||
name = (transform || identity)(name);
|
||||
if (isDefined(fn)) {
|
||||
if (isDefined(extPoint[name])) {
|
||||
foreach(extPoint[name], function(property, key) {
|
||||
if (key.charAt(0) == '$' && isUndefined(fn[key]))
|
||||
fn[key] = property;
|
||||
});
|
||||
}
|
||||
extPoint[name] = extend(fn, prop || {});
|
||||
}
|
||||
return extPoint[name];
|
||||
@@ -258,13 +283,130 @@ function jqLiteWrap(element) {
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.isUndefined
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if a reference is undefined.
|
||||
*
|
||||
* @param {*} value Reference to check.
|
||||
* @returns {boolean} True if `value` is undefined.
|
||||
*/
|
||||
function isUndefined(value){ return typeof value == $undefined; }
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.isDefined
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if a reference is defined.
|
||||
*
|
||||
* @param {*} value Reference to check.
|
||||
* @returns {boolean} True if `value` is defined.
|
||||
*/
|
||||
function isDefined(value){ return typeof value != $undefined; }
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.isObject
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if a reference is an `Object`. Unlike in JavaScript `null`s are not considered to be
|
||||
* objects.
|
||||
*
|
||||
* @param {*} value Reference to check.
|
||||
* @returns {boolean} True if `value` is an `Object` but not `null`.
|
||||
*/
|
||||
function isObject(value){ return value!=_null && typeof value == $object;}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.isString
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if a reference is a `String`.
|
||||
*
|
||||
* @param {*} value Reference to check.
|
||||
* @returns {boolean} True if `value` is a `String`.
|
||||
*/
|
||||
function isString(value){ return typeof value == $string;}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.isNumber
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if a reference is a `Number`.
|
||||
*
|
||||
* @param {*} value Reference to check.
|
||||
* @returns {boolean} True if `value` is a `Number`.
|
||||
*/
|
||||
function isNumber(value){ return typeof value == $number;}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.isDate
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if value is a date.
|
||||
*
|
||||
* @param {*} value Reference to check.
|
||||
* @returns {boolean} True if `value` is a `Date`.
|
||||
*/
|
||||
function isDate(value){ return value instanceof Date; }
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.isArray
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if a reference is an `Array`.
|
||||
*
|
||||
* @param {*} value Reference to check.
|
||||
* @returns {boolean} True if `value` is an `Array`.
|
||||
*/
|
||||
function isArray(value) { return value instanceof Array; }
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.isFunction
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if a reference is a `Function`.
|
||||
*
|
||||
* @param {*} value Reference to check.
|
||||
* @returns {boolean} True if `value` is a `Function`.
|
||||
*/
|
||||
function isFunction(value){ return typeof value == $function;}
|
||||
|
||||
|
||||
function isBoolean(value) { return typeof value == $boolean;}
|
||||
function isTextNode(node) { return nodeName(node) == '#text'; }
|
||||
function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }
|
||||
function isElement(node) {
|
||||
@@ -317,6 +459,27 @@ function map(obj, iterator, context) {
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.Object.size
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Determines the number of elements in an array or number of properties of an object.
|
||||
*
|
||||
* Note: this function is used to augment the Object type in angular expressions. See
|
||||
* {@link angular.Object} for more info.
|
||||
*
|
||||
* @param {Object|Array} obj Object or array to inspect.
|
||||
* @returns {number} The size of `obj` or `0` if `obj` is not an object or array.
|
||||
*
|
||||
* @example
|
||||
* Number of items in array: {{ [1,2].$size() }}<br/>
|
||||
* Number of items in object: {{ {a:1, b:2, c:3}.$size() }}<br/>
|
||||
*/
|
||||
function size(obj) {
|
||||
var size = 0;
|
||||
if (obj) {
|
||||
@@ -356,19 +519,40 @@ function isLeafNode (node) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies stuff.
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.Object.copy
|
||||
* @function
|
||||
*
|
||||
* If destination is not provided and source is an object or an array, a copy is created & returned,
|
||||
* otherwise the source is returned.
|
||||
* @description
|
||||
* Creates a deep copy of `source`.
|
||||
*
|
||||
* If destination is provided, all of its properties will be deleted and if source is an object or
|
||||
* an array, all of its members will be copied into the destination object. Finally the destination
|
||||
* is returned just for kicks.
|
||||
* If `destination` is not provided and `source` is an object or an array, a copy is created &
|
||||
* returned, otherwise the `source` is returned.
|
||||
*
|
||||
* @param {*} source The source to be used during copy.
|
||||
* Can be any type including primitives, null and undefined.
|
||||
* @param {(Object|Array)=} destination Optional destination into which the source is copied
|
||||
* @returns {*}
|
||||
* If `destination` is provided, all of its properties will be deleted.
|
||||
*
|
||||
* If `source` is an object or an array, all of its members will be copied into the `destination`
|
||||
* object.
|
||||
*
|
||||
* Note: this function is used to augment the Object type in angular expressions. See
|
||||
* {@link angular.Object} for more info.
|
||||
*
|
||||
* @param {*} source The source to be used to make a copy.
|
||||
* Can be any type including primitives, `null` and `undefined`.
|
||||
* @param {(Object|Array)=} destination Optional destination into which the source is copied.
|
||||
* @returns {*} The copy or updated `destination` if `destination` was specified.
|
||||
*
|
||||
* @example
|
||||
Salutation: <input type="text" name="master.salutation" value="Hello" /><br/>
|
||||
Name: <input type="text" name="master.name" value="world"/><br/>
|
||||
<button ng:click="form = master.$copy()">copy</button>
|
||||
<hr/>
|
||||
|
||||
Master is <span ng:hide="master.$equals(form)">NOT</span> same as form.
|
||||
|
||||
<pre>master={{master}}</pre>
|
||||
<pre>form={{form}}</pre>
|
||||
*/
|
||||
function copy(source, destination){
|
||||
if (!destination) {
|
||||
@@ -376,7 +560,7 @@ function copy(source, destination){
|
||||
if (source) {
|
||||
if (isArray(source)) {
|
||||
destination = copy(source, []);
|
||||
} else if (source instanceof Date) {
|
||||
} else if (isDate(source)) {
|
||||
destination = new Date(source.getTime());
|
||||
} else if (isObject(source)) {
|
||||
destination = copy(source, {});
|
||||
@@ -402,6 +586,42 @@ function copy(source, destination){
|
||||
return destination;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.Object.equals
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Determines if two objects or value are equivalent.
|
||||
*
|
||||
* To be equivalent, they must pass `==` comparison or be of the same type and have all their
|
||||
* properties pass `==` comparison.
|
||||
*
|
||||
* Supports values types, arrays and objects.
|
||||
*
|
||||
* For objects `function` properties and properties that start with `$` are not considered during
|
||||
* comparisons.
|
||||
*
|
||||
* Note: this function is used to augment the Object type in angular expressions. See
|
||||
* {@link angular.Object} for more info.
|
||||
*
|
||||
* @param {*} o1 Object or value to compare.
|
||||
* @param {*} o2 Object or value to compare.
|
||||
* @returns {boolean} True if arguments are equal.
|
||||
*
|
||||
* @example
|
||||
Salutation: <input type="text" name="master.salutation" value="Hello" /><br/>
|
||||
Name: <input type="text" name="master.name" value="world"/><br/>
|
||||
<button ng:click="form = master.$copy()">copy</button>
|
||||
<hr/>
|
||||
|
||||
Master is <span ng:hide="master.$equals(form)">NOT</span> same as form.
|
||||
|
||||
<pre>master={{master}}</pre>
|
||||
<pre>form={{form}}</pre>
|
||||
*/
|
||||
function equals(o1, o2) {
|
||||
if (o1 == o2) return true;
|
||||
var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
|
||||
@@ -465,6 +685,23 @@ function concat(array1, array2, index) {
|
||||
return array1.concat(slice.call(array2, index, array2.length));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.bind
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Returns function which calls function `fn` bound to `self` (`self` becomes the `this` for `fn`).
|
||||
* Optional `args` can be supplied which are prebound to the function, also known as
|
||||
* [function currying](http://en.wikipedia.org/wiki/Currying).
|
||||
*
|
||||
* @param {Object} self Context in which `fn` should be evaluated in.
|
||||
* @param {function()} fn Function to be bound.
|
||||
* @param {...*} args Optional arguments to be prebound to the `fn` function call.
|
||||
* @returns {function()} Function that wraps the `fn` with all the specified bindings.
|
||||
*/
|
||||
function bind(self, fn) {
|
||||
var curryArgs = arguments.length > 2 ? slice.call(arguments, 2, arguments.length) : [];
|
||||
if (typeof fn == $function) {
|
||||
@@ -474,7 +711,7 @@ function bind(self, fn) {
|
||||
return arguments.length ? fn.apply(self, arguments) : fn.call(self);
|
||||
};
|
||||
} else {
|
||||
// in IE, native methods ore not functions and so they can not be bound (but they don't need to be)
|
||||
// in IE, native methods are not functions and so they can not be bound (but they don't need to be)
|
||||
return fn;
|
||||
}
|
||||
}
|
||||
@@ -502,10 +739,31 @@ function merge(src, dst) {
|
||||
}
|
||||
}
|
||||
|
||||
function compile(element, existingScope) {
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.compile
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Compiles a piece of HTML or DOM into a {@link angular.scope scope} object.
|
||||
<pre>
|
||||
var scope1 = angular.compile(window.document);
|
||||
scope1.$init();
|
||||
|
||||
var scope2 = angular.compile('<div ng:click="clicked = true">click me</div>');
|
||||
scope2.$init();
|
||||
</pre>
|
||||
*
|
||||
* @param {string|DOMElement} element Element to compile.
|
||||
* @param {Object=} parentScope Scope to become the parent scope of the newly compiled scope.
|
||||
* @returns {Object} Compiled scope object.
|
||||
*/
|
||||
function compile(element, parentScope) {
|
||||
var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget),
|
||||
$element = jqLite(element);
|
||||
return compiler.compile($element)($element, existingScope);
|
||||
return compiler.compile($element)($element, parentScope);
|
||||
}
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
@@ -533,6 +791,157 @@ function toKeyValue(obj) {
|
||||
return parts.length ? parts.join('&') : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:autobind
|
||||
* @element script
|
||||
*
|
||||
* @TODO ng:autobind is not a directive!! it should be documented as bootstrap parameter in a
|
||||
* separate bootstrap section.
|
||||
* @TODO rename to ng:autobind to ng:autoboot
|
||||
*
|
||||
* @description
|
||||
* This section explains how to bootstrap your application with angular using either the angular
|
||||
* javascript file.
|
||||
*
|
||||
*
|
||||
* ## The angular distribution
|
||||
* Note that there are two versions of the angular javascript file that you can use:
|
||||
*
|
||||
* * `angular.js` - the development version - this file is unobfuscated, uncompressed, and thus
|
||||
* human-readable and useful when developing your angular applications.
|
||||
* * `angular.min.js` - the production version - this is a minified and obfuscated version of
|
||||
* `angular.js`. You want to use this version when you want to load a smaller but functionally
|
||||
* equivalent version of the code in your application. We use the Closure compiler to create this
|
||||
* file.
|
||||
*
|
||||
*
|
||||
* ## Auto-bootstrap with `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 `http://code.angularjs.org/angular-x.x.x.min.js` code and uses
|
||||
* the special `ng:autobind` attribute, like in this snippet of HTML:
|
||||
*
|
||||
* <pre>
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<script type="text/javascript" src="http://code.angularjs.org/angular-0.9.3.min.js"
|
||||
ng:autobind></script>
|
||||
</head>
|
||||
<body>
|
||||
Hello {{'world'}}!
|
||||
</body>
|
||||
</html>
|
||||
* </pre>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* ## Auto-bootstrap with `#autobind`
|
||||
* In rare cases when you can't define the `ng` namespace before the script tag (e.g. in some CMS
|
||||
* systems, etc), it is possible to auto-bootstrap angular by appending `#autobind` to the script
|
||||
* src URL, like in this snippet:
|
||||
*
|
||||
* <pre>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript"
|
||||
src="http://code.angularjs.org/angular-0.9.3.min.js#autobind"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div xmlns:ng="http://angularjs.org">
|
||||
Hello {{'world'}}!
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
* </pre>
|
||||
*
|
||||
* In this case it's the `#autobind` URL fragment that tells angular to auto-bootstrap.
|
||||
*
|
||||
*
|
||||
* ## Filename Restrictions for Auto-bootstrap
|
||||
* In order for us to find the auto-bootstrap script attribute or URL fragment, the value of the
|
||||
* `script` `src` attribute that loads angular script must match one of these naming
|
||||
* conventions:
|
||||
*
|
||||
* - `angular.js`
|
||||
* - `angular-min.js`
|
||||
* - `angular-x.x.x.js`
|
||||
* - `angular-x.x.x.min.js`
|
||||
* - `angular-x.x.x-xxxxxxxx.js` (dev snapshot)
|
||||
* - `angular-x.x.x-xxxxxxxx.min.js` (dev snapshot)
|
||||
* - `angular-bootstrap.js` (used for development of angular)
|
||||
*
|
||||
* Optionally, any of the filename format above can be prepended with relative or absolute URL that
|
||||
* ends with `/`.
|
||||
*
|
||||
*
|
||||
* ## Manual Bootstrap
|
||||
* Using auto-bootstrap is a handy way to start using <angular/>, but advanced users who want more
|
||||
* control over the initialization process might prefer to use manual bootstrap instead.
|
||||
*
|
||||
* The best way to get started with manual bootstraping 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">
|
||||
<head>
|
||||
<script type="text/javascript" src="http://code.angularjs.org/angular-0.9.3.min.js"
|
||||
ng:autobind></script>
|
||||
<script type="text/javascript">
|
||||
(function(window, previousOnLoad){
|
||||
window.onload = function(){
|
||||
try { (previousOnLoad||angular.noop)(); } catch(e) {}
|
||||
angular.compile(window.document).$init();
|
||||
};
|
||||
})(window, window.onload);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Hello {{'World'}}!
|
||||
</body>
|
||||
</html>
|
||||
* </pre>
|
||||
*
|
||||
* This is the sequence that your code should follow if you're bootstrapping angular on your own:
|
||||
*
|
||||
* * 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 xmlsn tag
|
||||
* to your page, create an alias, and set it to your unique domain:
|
||||
*
|
||||
* <pre>
|
||||
* <html xmlns:ng="http://angularjs.org" 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.
|
||||
*
|
||||
*/
|
||||
function angularInit(config){
|
||||
if (config.autobind) {
|
||||
// TODO default to the source of angular.js
|
||||
|
||||
+11
-1
@@ -1,4 +1,13 @@
|
||||
var browserSingleton;
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$browser
|
||||
* @requires $log
|
||||
*
|
||||
* @description
|
||||
* Represents the browser.
|
||||
*/
|
||||
angularService('$browser', function($log){
|
||||
if (!browserSingleton) {
|
||||
browserSingleton = new Browser(
|
||||
@@ -6,7 +15,8 @@ angularService('$browser', function($log){
|
||||
jqLite(window.document),
|
||||
jqLite(window.document.getElementsByTagName('head')[0]),
|
||||
XHR,
|
||||
$log);
|
||||
$log,
|
||||
window.setTimeout);
|
||||
browserSingleton.startPoller(50, function(delay, fn){setTimeout(delay,fn);});
|
||||
browserSingleton.bind();
|
||||
}
|
||||
|
||||
+190
-41
@@ -8,7 +8,7 @@ var XHR = window.XMLHttpRequest || function () {
|
||||
throw new Error("This browser does not support XMLHttpRequest.");
|
||||
};
|
||||
|
||||
function Browser(location, document, head, XHR, $log) {
|
||||
function Browser(location, document, head, XHR, $log, setTimeout) {
|
||||
var self = this;
|
||||
self.isMock = false;
|
||||
|
||||
@@ -19,7 +19,43 @@ function Browser(location, document, head, XHR, $log) {
|
||||
var outstandingRequestCount = 0;
|
||||
var outstandingRequestCallbacks = [];
|
||||
|
||||
self.xhr = function(method, url, post, callback){
|
||||
|
||||
/**
|
||||
* Executes the `fn` function (supports currying) and decrements the `outstandingRequestCallbacks`
|
||||
* counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
|
||||
*/
|
||||
function completeOutstandingRequest(fn) {
|
||||
try {
|
||||
fn.apply(null, slice.call(arguments, 1));
|
||||
} finally {
|
||||
outstandingRequestCount--;
|
||||
if (outstandingRequestCount === 0) {
|
||||
while(outstandingRequestCallbacks.length) {
|
||||
try {
|
||||
outstandingRequestCallbacks.pop()();
|
||||
} catch (e) {
|
||||
$log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#xhr
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @param {string} method Requested method (get|post|put|delete|head|json)
|
||||
* @param {string} url Requested url
|
||||
* @param {string=} post Post data to send
|
||||
* @param {function(number, string)} callback Function that will be called on response
|
||||
*
|
||||
* @description
|
||||
* Send ajax request
|
||||
*/
|
||||
self.xhr = function(method, url, post, callback) {
|
||||
if (isFunction(post)) {
|
||||
callback = post;
|
||||
post = _null;
|
||||
@@ -44,26 +80,22 @@ function Browser(location, document, head, XHR, $log) {
|
||||
outstandingRequestCount ++;
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
try {
|
||||
callback(xhr.status || 200, xhr.responseText);
|
||||
} finally {
|
||||
outstandingRequestCount--;
|
||||
if (outstandingRequestCount === 0) {
|
||||
while(outstandingRequestCallbacks.length) {
|
||||
try {
|
||||
outstandingRequestCallbacks.pop()();
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
completeOutstandingRequest(callback, xhr.status || 200, xhr.responseText);
|
||||
}
|
||||
};
|
||||
xhr.send(post || '');
|
||||
}
|
||||
};
|
||||
|
||||
self.notifyWhenNoOutstandingRequests = function(callback){
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#notifyWhenNoOutstandingRequests
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @param {function()} callback Function that will be called when no outstanding request
|
||||
*/
|
||||
self.notifyWhenNoOutstandingRequests = function(callback) {
|
||||
if (outstandingRequestCount === 0) {
|
||||
callback();
|
||||
} else {
|
||||
@@ -75,27 +107,51 @@ function Browser(location, document, head, XHR, $log) {
|
||||
// Poll Watcher API
|
||||
//////////////////////////////////////////////////////////////
|
||||
var pollFns = [];
|
||||
function poll(){
|
||||
foreach(pollFns, function(pollFn){ pollFn(); });
|
||||
}
|
||||
self.poll = poll;
|
||||
|
||||
/**
|
||||
* Adds a function to the list of functions that poller periodically executes
|
||||
* @return {Function} the added function
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#poll
|
||||
* @methodOf angular.service.$browser
|
||||
*/
|
||||
self.addPollFn = function(/**Function*/fn){
|
||||
self.poll = function() {
|
||||
foreach(pollFns, function(pollFn){ pollFn(); });
|
||||
};
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#addPollFn
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @param {function()} fn Poll function to add
|
||||
*
|
||||
* @description
|
||||
* Adds a function to the list of functions that poller periodically executes
|
||||
*
|
||||
* @returns {function()} the added function
|
||||
*/
|
||||
self.addPollFn = function(fn) {
|
||||
pollFns.push(fn);
|
||||
return fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configures the poller to run in the specified intervals, using the specified setTimeout fn and
|
||||
* kicks it off.
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#startPoller
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @param {number} interval How often should browser call poll functions (ms)
|
||||
* @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
|
||||
*
|
||||
* @description
|
||||
* Configures the poller to run in the specified intervals, using the specified
|
||||
* setTimeout fn and kicks it off.
|
||||
*/
|
||||
self.startPoller = function(/**number*/interval, /**Function*/setTimeout){
|
||||
self.startPoller = function(interval, setTimeout) {
|
||||
(function check(){
|
||||
poll();
|
||||
self.poll();
|
||||
setTimeout(check, interval);
|
||||
})();
|
||||
};
|
||||
@@ -103,15 +159,39 @@ function Browser(location, document, head, XHR, $log) {
|
||||
//////////////////////////////////////////////////////////////
|
||||
// URL API
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#setUrl
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @param {string} url New url
|
||||
*
|
||||
* @description
|
||||
* Sets browser's url
|
||||
*/
|
||||
self.setUrl = function(url) {
|
||||
var existingURL = location.href;
|
||||
if (!existingURL.match(/#/)) existingURL += '#';
|
||||
if (!url.match(/#/)) url += '#';
|
||||
location.href = url;
|
||||
};
|
||||
self.getUrl = function() {
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#getUrl
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @description
|
||||
* Get current browser's url
|
||||
*
|
||||
* @returns {string} Browser's url
|
||||
*/
|
||||
self.getUrl = function() {
|
||||
return location.href;
|
||||
};
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Cookies API
|
||||
@@ -121,19 +201,28 @@ function Browser(location, document, head, XHR, $log) {
|
||||
var lastCookieString = '';
|
||||
|
||||
/**
|
||||
* The cookies method provides a 'private' low level access to browser cookies. It is not meant to
|
||||
* be used directly, use the $cookie service instead.
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#cookies
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @param {string=} name Cookie name
|
||||
* @param {string=} value Cokkie value
|
||||
*
|
||||
* @description
|
||||
* The cookies method provides a 'private' low level access to browser cookies.
|
||||
* It is not meant to be used directly, use the $cookie service instead.
|
||||
*
|
||||
* The return values vary depending on the arguments that the method was called with as follows:
|
||||
* <ul><li>
|
||||
* cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it
|
||||
* </li><li>
|
||||
* cookies(name, value) -> set name to value, if value is undefined delete the cookie
|
||||
* </li><li>
|
||||
* cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)
|
||||
* </li></ul>
|
||||
* <ul>
|
||||
* <li>cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it</li>
|
||||
* <li>cookies(name, value) -> set name to value, if value is undefined delete the cookie</li>
|
||||
* <li>cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @returns {Object} Hash of all cookies (if called without any parameter)
|
||||
*/
|
||||
self.cookies = function (/**string*/name, /**string*/value){
|
||||
self.cookies = function (name, value) {
|
||||
var cookieLength, cookieArray, i, keyValue;
|
||||
|
||||
if (name) {
|
||||
@@ -171,11 +260,55 @@ function Browser(location, document, head, XHR, $log) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc
|
||||
* @name angular.service.$browser#defer
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @description
|
||||
* Executes a fn asynchroniously via `setTimeout(fn, 0)`.
|
||||
*
|
||||
* Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
|
||||
* `setTimeout` in tests, the fns are queued in an array, which can be programaticaly flushed via
|
||||
* `$browser.defer.flush()`.
|
||||
*
|
||||
* @param {function()} fn A function, who's execution should be defered.
|
||||
*/
|
||||
self.defer = function(fn) {
|
||||
outstandingRequestCount++;
|
||||
setTimeout(function() { completeOutstandingRequest(fn); }, 0);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Misc API
|
||||
//////////////////////////////////////////////////////////////
|
||||
var hoverListener = noop;
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#hover
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @description
|
||||
* Set hover listener.
|
||||
*
|
||||
* @param {function(Object, boolean)} listener Function that will be called when hover event
|
||||
* occurs.
|
||||
*/
|
||||
self.hover = function(listener) { hoverListener = listener; };
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#bind
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @description
|
||||
* Register hover function to real browser
|
||||
*/
|
||||
self.bind = function() {
|
||||
document.bind("mouseover", function(event){
|
||||
hoverListener(jqLite(msie ? event.srcElement : event.target), true);
|
||||
@@ -189,9 +322,16 @@ function Browser(location, document, head, XHR, $log) {
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#addCss
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @param {string} url Url to css file
|
||||
* @description
|
||||
* Adds a stylesheet tag to the head.
|
||||
*/
|
||||
self.addCss = function(/**string*/url) {
|
||||
self.addCss = function(url) {
|
||||
var link = jqLite(rawDocument.createElement('link'));
|
||||
link.attr('rel', 'stylesheet');
|
||||
link.attr('type', 'text/css');
|
||||
@@ -201,9 +341,18 @@ function Browser(location, document, head, XHR, $log) {
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#addJs
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @param {string} url Url to js file
|
||||
* @param {string=} dom_id Optional id for the script tag
|
||||
*
|
||||
* @description
|
||||
* Adds a script tag to the head.
|
||||
*/
|
||||
self.addJs = function(/**string*/url, /**string*/dom_id) {
|
||||
self.addJs = function(url, dom_id) {
|
||||
var script = jqLite(rawDocument.createElement('script'));
|
||||
script.attr('type', 'text/javascript');
|
||||
script.attr('src', url);
|
||||
|
||||
@@ -30,6 +30,7 @@ Template.prototype = {
|
||||
if (this.newScope) {
|
||||
childScope = createScope(scope);
|
||||
scope.$onEval(childScope.$eval);
|
||||
element.data($$scope, childScope);
|
||||
}
|
||||
foreach(this.inits, function(fn) {
|
||||
queue.push(function() {
|
||||
@@ -68,6 +69,17 @@ Template.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Function walks up the element chain looking for the scope associated with the give element.
|
||||
*/
|
||||
function retrieveScope(element) {
|
||||
var scope;
|
||||
while (element && !(scope = element.data($$scope))) {
|
||||
element = element.parent();
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
//Compiler
|
||||
//////////////////////////////////
|
||||
@@ -97,6 +109,7 @@ Compiler.prototype = {
|
||||
element = jqLite(element);
|
||||
var scope = parentScope && parentScope.$eval ?
|
||||
parentScope : createScope(parentScope);
|
||||
element.data($$scope, scope);
|
||||
return extend(scope, {
|
||||
$element:element,
|
||||
$init: function() {
|
||||
@@ -109,6 +122,58 @@ Compiler.prototype = {
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:eval-order
|
||||
*
|
||||
* @description
|
||||
* Normally the view is updated from top to bottom. This usually is
|
||||
* not a problem, but under some circumstances the values for data
|
||||
* is not available until after the full view is computed. If such
|
||||
* values are needed before they are computed the order of
|
||||
* evaluation can be change using ng:eval-order
|
||||
*
|
||||
* @element ANY
|
||||
* @param {integer|string=} [priority=0] priority integer, or FIRST, LAST constant
|
||||
*
|
||||
* @exampleDescription
|
||||
* try changing the invoice and see that the Total will lag in evaluation
|
||||
* @example
|
||||
<div>TOTAL: without ng:eval-order {{ items.$sum('total') | currency }}</div>
|
||||
<div ng:eval-order='LAST'>TOTAL: with ng:eval-order {{ items.$sum('total') | currency }}</div>
|
||||
<table ng:init="items=[{qty:1, cost:9.99, desc:'gadget'}]">
|
||||
<tr>
|
||||
<td>QTY</td>
|
||||
<td>Description</td>
|
||||
<td>Cost</td>
|
||||
<td>Total</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr ng:repeat="item in items">
|
||||
<td><input name="item.qty"/></td>
|
||||
<td><input name="item.desc"/></td>
|
||||
<td><input name="item.cost"/></td>
|
||||
<td>{{item.total = item.qty * item.cost | currency}}</td>
|
||||
<td><a href="" ng:click="items.$remove(item)">X</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><a href="" ng:click="items.$add()">add</a></td>
|
||||
<td>{{ items.$sum('total') | currency }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:format', function(){
|
||||
expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99');
|
||||
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$9.99');
|
||||
input('item.qty').enter('2');
|
||||
expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99');
|
||||
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$19.98');
|
||||
});
|
||||
*/
|
||||
|
||||
templatize: function(element, elementIndex, priority){
|
||||
var self = this,
|
||||
widget,
|
||||
|
||||
+19
-7
@@ -1,9 +1,20 @@
|
||||
/**
|
||||
* Create an inject method
|
||||
* @param providerScope provider's "this"
|
||||
* @param providers a function(name) which returns provider function
|
||||
* @param cache place where instances are saved for reuse
|
||||
* @returns {Function}
|
||||
* @ngdoc function
|
||||
* @name angular.injector
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Creates an inject function that can be used for dependency injection.
|
||||
*
|
||||
* @param {Object=} [providerScope={}] provider's `this`
|
||||
* @param {Object.<string, function()>=} [providers=angular.service] Map of provider (factory)
|
||||
* function.
|
||||
* @param {Object.<string, function()>=} [cache={}] Place where instances are saved for reuse. Can
|
||||
* also be used to override services speciafied by `providers` (useful in tests).
|
||||
* @returns {function()} Injector function.
|
||||
*
|
||||
* @TODO These docs need a lot of work. Specifically the returned function should be described in
|
||||
* great detail + we need to provide some examples.
|
||||
*/
|
||||
function createInjector(providerScope, providers, cache) {
|
||||
providers = providers || angularService;
|
||||
@@ -19,8 +30,9 @@ function createInjector(providerScope, providers, cache) {
|
||||
* string: return an instance for the injection key.
|
||||
* array of keys: returns an array of instances.
|
||||
* function: look at $inject property of function to determine instances
|
||||
* and then call the function with instances and scope. Any
|
||||
* additional arguments are passed on to function.
|
||||
* and then call the function with instances and `scope`. Any
|
||||
* additional arguments (`args`) are appended to the function
|
||||
* arguments.
|
||||
* object: initialize eager providers and publish them the ones with publish here.
|
||||
* none: same as object but use providerScope as place to publish.
|
||||
*/
|
||||
|
||||
+49
-15
@@ -1,11 +1,36 @@
|
||||
var array = [].constructor;
|
||||
|
||||
function toJson(obj, pretty){
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.toJson
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Serializes the input into a JSON formated string.
|
||||
*
|
||||
* @param {Object|Array|Date|string|number} obj Input to jsonify.
|
||||
* @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
|
||||
* @returns {string} Jsonified string representing `obj`.
|
||||
*/
|
||||
function toJson(obj, pretty) {
|
||||
var buf = [];
|
||||
toJsonArray(buf, obj, pretty ? "\n " : _null, []);
|
||||
return buf.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.fromJson
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Deserializes a string in the JSON format.
|
||||
*
|
||||
* @param {string} json JSON string to deserialize.
|
||||
* @returns {Object|Array|Date|string|number} Deserialized thingy.
|
||||
*/
|
||||
function fromJson(json) {
|
||||
if (!json) return json;
|
||||
try {
|
||||
@@ -22,40 +47,49 @@ function fromJson(json) {
|
||||
angular['toJson'] = toJson;
|
||||
angular['fromJson'] = fromJson;
|
||||
|
||||
function toJsonArray(buf, obj, pretty, stack){
|
||||
if (typeof obj == "object") {
|
||||
function toJsonArray(buf, obj, pretty, stack) {
|
||||
if (isObject(obj)) {
|
||||
if (obj === window) {
|
||||
buf.push('WINDOW');
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj === document) {
|
||||
buf.push('DOCUMENT');
|
||||
return;
|
||||
}
|
||||
|
||||
if (includes(stack, obj)) {
|
||||
buf.push("RECURSION");
|
||||
buf.push('RECURSION');
|
||||
return;
|
||||
}
|
||||
stack.push(obj);
|
||||
}
|
||||
var type = typeof obj;
|
||||
if (obj === _null) {
|
||||
buf.push($null);
|
||||
} else if (obj instanceof RegExp) {
|
||||
buf.push(angular['String']['quoteUnicode'](obj.toString()));
|
||||
} else if (type === $function) {
|
||||
} else if (isFunction(obj)) {
|
||||
return;
|
||||
} else if (type === $boolean) {
|
||||
} else if (isBoolean(obj)) {
|
||||
buf.push('' + obj);
|
||||
} else if (type === $number) {
|
||||
} else if (isNumber(obj)) {
|
||||
if (isNaN(obj)) {
|
||||
buf.push($null);
|
||||
} else {
|
||||
buf.push('' + obj);
|
||||
}
|
||||
} else if (type === $string) {
|
||||
} else if (isString(obj)) {
|
||||
return buf.push(angular['String']['quoteUnicode'](obj));
|
||||
} else if (type === $object) {
|
||||
if (obj instanceof Array) {
|
||||
} else if (isObject(obj)) {
|
||||
if (isArray(obj)) {
|
||||
buf.push("[");
|
||||
var len = obj.length;
|
||||
var sep = false;
|
||||
for(var i=0; i<len; i++) {
|
||||
var item = obj[i];
|
||||
if (sep) buf.push(",");
|
||||
if (!(item instanceof RegExp) && (typeof item == $function || typeof item == $undefined)) {
|
||||
if (!(item instanceof RegExp) && (isFunction(item) || isUndefined(item))) {
|
||||
buf.push($null);
|
||||
} else {
|
||||
toJsonArray(buf, item, pretty, stack);
|
||||
@@ -63,7 +97,7 @@ function toJsonArray(buf, obj, pretty, stack){
|
||||
sep = true;
|
||||
}
|
||||
buf.push("]");
|
||||
} else if (obj instanceof Date) {
|
||||
} else if (isDate(obj)) {
|
||||
buf.push(angular['String']['quoteUnicode'](angular['Date']['toString'](obj)));
|
||||
} else {
|
||||
buf.push("{");
|
||||
@@ -72,7 +106,7 @@ function toJsonArray(buf, obj, pretty, stack){
|
||||
var childPretty = pretty ? pretty + " " : false;
|
||||
var keys = [];
|
||||
for(var k in obj) {
|
||||
if (k.indexOf('$') === 0 || obj[k] === _undefined)
|
||||
if (obj[k] === _undefined)
|
||||
continue;
|
||||
keys.push(k);
|
||||
}
|
||||
@@ -94,7 +128,7 @@ function toJsonArray(buf, obj, pretty, stack){
|
||||
buf.push("}");
|
||||
}
|
||||
}
|
||||
if (typeof obj == $object) {
|
||||
if (isObject(obj)) {
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
+402
-30
@@ -66,7 +66,7 @@ function getterFn(path){
|
||||
code += 'if(!s) return s;\n' +
|
||||
'l=s;\n' +
|
||||
's=s' + key + ';\n' +
|
||||
'if(typeof s=="function") s = function(){ return l'+key+'.apply(l, arguments); };\n';
|
||||
'if(typeof s=="function" && !(s instanceof RegExp)) s = function(){ return l'+key+'.apply(l, arguments); };\n';
|
||||
if (key.charAt(1) == '$') {
|
||||
// special code for super-imposed functions
|
||||
var name = key.substr(2);
|
||||
@@ -101,25 +101,262 @@ function expressionCompile(exp){
|
||||
}
|
||||
|
||||
function errorHandlerFor(element, error) {
|
||||
elementError(element, NG_EXCEPTION, isDefined(error) ? toJson(error) : error);
|
||||
elementError(element, NG_EXCEPTION, isDefined(error) ? formatError(error) : error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc overview
|
||||
* @name angular.scope
|
||||
*
|
||||
* @description
|
||||
* Scope is a JavaScript object and the execution context for expressions. You can think about
|
||||
* scopes as JavaScript objects that have extra APIs for registering watchers. A scope is the model
|
||||
* in the model-view-controller design pattern.
|
||||
*
|
||||
* A few other characteristics of scopes:
|
||||
*
|
||||
* - Scopes can be nested. A scope (prototypically) inherits properties from its parent scope.
|
||||
* - Scopes can be attached (bound) to the HTML DOM tree (the view).
|
||||
* - A scope {@link angular.scope.$become becomes} `this` for a controller.
|
||||
* - Scope's {@link angular.scope.$eval $eval} is used to update its view.
|
||||
* - Scopes can {@link angular.scope.$watch watch} properties and fire events.
|
||||
*
|
||||
* # Basic Operations
|
||||
* Scopes can be created by calling {@link angular.scope() angular.scope()} or by compiling HTML.
|
||||
*
|
||||
* {@link angular.widget Widgets} and data bindings register listeners on the current scope to get
|
||||
* notified of changes to the scope state. When notified, these listeners push the updated state
|
||||
* through to the DOM.
|
||||
*
|
||||
* Here is a simple scope snippet to show how you can interact with the scope.
|
||||
* <pre>
|
||||
var scope = angular.scope();
|
||||
scope.salutation = 'Hello';
|
||||
scope.name = 'World';
|
||||
|
||||
expect(scope.greeting).toEqual(undefined);
|
||||
|
||||
scope.$watch('name', function(){
|
||||
this.greeting = this.salutation + ' ' + this.name + '!';
|
||||
});
|
||||
|
||||
expect(scope.greeting).toEqual('Hello World!');
|
||||
scope.name = 'Misko';
|
||||
// scope.$eval() will propagate the change to listeners
|
||||
expect(scope.greeting).toEqual('Hello World!');
|
||||
|
||||
scope.$eval();
|
||||
expect(scope.greeting).toEqual('Hello Misko!');
|
||||
* </pre>
|
||||
*
|
||||
* # Inheritance
|
||||
* A scope can inherit from a parent scope, as in this example:
|
||||
* <pre>
|
||||
var parent = angular.scope();
|
||||
var child = angular.scope(parent);
|
||||
|
||||
parent.salutation = "Hello";
|
||||
child.name = "World";
|
||||
expect(child.salutation).toEqual('Hello');
|
||||
|
||||
child.salutation = "Welcome";
|
||||
expect(child.salutation).toEqual('Welcome');
|
||||
expect(parent.salutation).toEqual('Hello');
|
||||
* </pre>
|
||||
*
|
||||
* # Dependency Injection
|
||||
* Scope also acts as a simple dependency injection framework.
|
||||
*
|
||||
* **TODO**: more info needed
|
||||
*
|
||||
* # When scopes are evaluated
|
||||
* Anyone can update a scope by calling its {@link angular.scope.$eval $eval()} method. By default
|
||||
* angular widgets listen to user change events (e.g. the user enters text into text field), copy
|
||||
* the data from the widget to the scope (the MVC model), and then call the `$eval()` method on the
|
||||
* root scope to update dependents. This creates a spreadsheet-like behavior: the bound views update
|
||||
* immediately as the user types into the text field.
|
||||
*
|
||||
* Similarly, when a request to fetch data from a server is made and the response comes back, the
|
||||
* data is written into the model and then $eval() is called to push updates through to the view and
|
||||
* any other dependents.
|
||||
*
|
||||
* Because a change in the model that's triggered either by user input or by server response calls
|
||||
* `$eval()`, it is unnecessary to call `$eval()` from within your controller. The only time when
|
||||
* calling `$eval()` is needed, is when implementing a custom widget or service.
|
||||
*
|
||||
* Because scopes are inherited, the child scope `$eval()` overrides the parent `$eval()` method.
|
||||
* So to update the whole page you need to call `$eval()` on the root scope as `$root.$eval()`.
|
||||
*
|
||||
* Note: A widget that creates scopes (i.e. {@link angular.widget.@ng:repeat ng:repeat}) is
|
||||
* responsible for forwarding `$eval()` calls from the parent to those child scopes. That way,
|
||||
* calling $eval() on the root scope will update the whole page.
|
||||
*
|
||||
*
|
||||
* @TODO THESE PARAMS AND RETURNS ARE NOT RENDERED IN THE TEMPLATE!! FIX THAT!
|
||||
* @param {Object} parent The scope that should become the parent for the newly created scope.
|
||||
* @param {Object.<string, function()>=} providers Map of service factory which need to be provided
|
||||
* for the current scope. Usually {@link angular.service}.
|
||||
* @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
|
||||
* append/override services provided by `providers`.
|
||||
* @returns {Object} Newly created scope.
|
||||
*
|
||||
*
|
||||
* @exampleDescription
|
||||
* This example demonstrates scope inheritance and property overriding.
|
||||
*
|
||||
* In this example, the root scope encompasses the whole HTML DOM tree. This scope has `salutation`,
|
||||
* `name`, and `names` properties. The {@link angular.widget@ng:repeat ng:repeat} creates a child
|
||||
* scope, one for each element in the names array. The repeater also assigns $index and name into
|
||||
* the child scope.
|
||||
*
|
||||
* Notice that:
|
||||
*
|
||||
* - While the name is set in the child scope it does not change the name defined in the root scope.
|
||||
* - The child scope inherits the salutation property from the root scope.
|
||||
* - The $index property does not leak from the child scope to the root scope.
|
||||
*
|
||||
* @example
|
||||
<ul ng:init="salutation='Hello'; name='Misko'; names=['World', 'Earth']">
|
||||
<li ng:repeat="name in names">
|
||||
{{$index}}: {{salutation}} {{name}}!
|
||||
</li>
|
||||
</ul>
|
||||
<pre>
|
||||
$index={{$index}}
|
||||
salutation={{salutation}}
|
||||
name={{name}}</pre>
|
||||
|
||||
@scenario
|
||||
it('should inherit the salutation property and override the name property', function() {
|
||||
expect(using('.doc-example-live').repeater('li').row(0)).
|
||||
toEqual(['0', 'Hello', 'World']);
|
||||
expect(using('.doc-example-live').repeater('li').row(1)).
|
||||
toEqual(['1', 'Hello', 'Earth']);
|
||||
expect(using('.doc-example-live').element('pre').text()).
|
||||
toBe('$index=\nsalutation=Hello\nname=Misko');
|
||||
});
|
||||
|
||||
*/
|
||||
function createScope(parent, providers, instanceCache) {
|
||||
function Parent(){}
|
||||
parent = Parent.prototype = (parent || {});
|
||||
var instance = new Parent();
|
||||
var evalLists = {sorted:[]};
|
||||
var postList = [], postHash = {}, postId = 0;
|
||||
|
||||
extend(instance, {
|
||||
'this': instance,
|
||||
$id: (scopeId++),
|
||||
$parent: parent,
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.scope.$bind
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Binds a function `fn` to the current scope. See: {@link angular.bind}.
|
||||
|
||||
<pre>
|
||||
var scope = angular.scope();
|
||||
var fn = scope.$bind(function(){
|
||||
return this;
|
||||
});
|
||||
expect(fn()).toEqual(scope);
|
||||
</pre>
|
||||
*
|
||||
* @param {function()} fn Function to be bound.
|
||||
*/
|
||||
$bind: bind(instance, bind, instance),
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.scope.$get
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Returns the value for `property_chain` on the current scope. Unlike in JavaScript, if there
|
||||
* are any `undefined` intermediary properties, `undefined` is returned instead of throwing an
|
||||
* exception.
|
||||
*
|
||||
<pre>
|
||||
var scope = angular.scope();
|
||||
expect(scope.$get('person.name')).toEqual(undefined);
|
||||
scope.person = {};
|
||||
expect(scope.$get('person.name')).toEqual(undefined);
|
||||
scope.person.name = 'misko';
|
||||
expect(scope.$get('person.name')).toEqual('misko');
|
||||
</pre>
|
||||
*
|
||||
* @param {string} property_chain String representing name of a scope property. Optionally
|
||||
* properties can be chained with `.` (dot), e.g. `'person.name.first'`
|
||||
* @returns {*} Value for the (nested) property.
|
||||
*/
|
||||
$get: bind(instance, getter, instance),
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.scope.$set
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Assigns a value to a property of the current scope specified via `property_chain`. Unlike in
|
||||
* JavaScript, if there are any `undefined` intermediary properties, empty objects are created
|
||||
* and assigned in to them instead of throwing an exception.
|
||||
*
|
||||
<pre>
|
||||
var scope = angular.scope();
|
||||
expect(scope.person).toEqual(undefined);
|
||||
scope.$set('person.name', 'misko');
|
||||
expect(scope.person).toEqual({name:'misko'});
|
||||
expect(scope.person.name).toEqual('misko');
|
||||
</pre>
|
||||
*
|
||||
* @param {string} property_chain String representing name of a scope property. Optionally
|
||||
* properties can be chained with `.` (dot), e.g. `'person.name.first'`
|
||||
* @param {*} value Value to assign to the scope property.
|
||||
*/
|
||||
$set: bind(instance, setter, instance),
|
||||
|
||||
$eval: function $eval(exp) {
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.scope.$eval
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Without the `exp` parameter triggers an eval cycle, for this scope and it's child scopes.
|
||||
*
|
||||
* With the `exp` parameter, compiles the expression to a function and calls it with `this` set
|
||||
* to the current scope and returns the result.
|
||||
*
|
||||
* # Example
|
||||
<pre>
|
||||
var scope = angular.scope();
|
||||
scope.a = 1;
|
||||
scope.b = 2;
|
||||
|
||||
expect(scope.$eval('a+b')).toEqual(3);
|
||||
expect(scope.$eval(function(){ return this.a + this.b; })).toEqual(3);
|
||||
|
||||
scope.$onEval('sum = a+b');
|
||||
expect(scope.sum).toEqual(undefined);
|
||||
scope.$eval();
|
||||
expect(scope.sum).toEqual(3);
|
||||
</pre>
|
||||
*
|
||||
* @param {(string|function())=} exp An angular expression to be compiled to a function or a js
|
||||
* function.
|
||||
*
|
||||
* @returns {*} The result of calling compiled `exp` with `this` set to the current scope.
|
||||
*/
|
||||
$eval: function(exp) {
|
||||
var type = typeof exp;
|
||||
var i, iSize;
|
||||
var j, jSize;
|
||||
@@ -133,11 +370,6 @@ function createScope(parent, providers, instanceCache) {
|
||||
instance.$tryEval(queue[j].fn, queue[j].handler);
|
||||
}
|
||||
}
|
||||
while(postList.length) {
|
||||
fn = postList.shift();
|
||||
delete postHash[fn.$postEvalId];
|
||||
instance.$tryEval(fn);
|
||||
}
|
||||
} else if (type === $function) {
|
||||
return exp.call(instance);
|
||||
} else if (type === 'string') {
|
||||
@@ -145,6 +377,44 @@ function createScope(parent, providers, instanceCache) {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.scope.$tryEval
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Evaluates the expression in the context of the current scope just like
|
||||
* {@link angular.scope.$eval()} with expression parameter, but also wraps it in a try/catch
|
||||
* block.
|
||||
*
|
||||
* If exception is thrown then `exceptionHandler` is used to handle the exception.
|
||||
*
|
||||
* # Example
|
||||
<pre>
|
||||
var scope = angular.scope();
|
||||
scope.error = function(){ throw 'myerror'; };
|
||||
scope.$exceptionHandler = function(e) {this.lastException = e; };
|
||||
|
||||
expect(scope.$eval('error()'));
|
||||
expect(scope.lastException).toEqual('myerror');
|
||||
this.lastException = null;
|
||||
|
||||
expect(scope.$eval('error()'), function(e) {this.lastException = e; });
|
||||
expect(scope.lastException).toEqual('myerror');
|
||||
|
||||
var body = angular.element(window.document.body);
|
||||
expect(scope.$eval('error()'), body);
|
||||
expect(body.attr('ng-exception')).toEqual('"myerror"');
|
||||
expect(body.hasClass('ng-exception')).toEqual(true);
|
||||
</pre>
|
||||
*
|
||||
* @param {string|function()} expression Angular expression to evaluate.
|
||||
* @param {function()|DOMElement} exceptionHandler Function to be called or DOMElement to be
|
||||
* decorated.
|
||||
* @returns {*} The result of `expression` evaluation.
|
||||
*/
|
||||
$tryEval: function (expression, exceptionHandler) {
|
||||
var type = typeof expression;
|
||||
try {
|
||||
@@ -165,14 +435,58 @@ function createScope(parent, providers, instanceCache) {
|
||||
}
|
||||
},
|
||||
|
||||
$watch: function(watchExp, listener, exceptionHandler) {
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.scope.$watch
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Registers `listener` as a callback to be executed every time the `watchExp` changes. Be aware
|
||||
* that callback gets, by default, called upon registration, this can be prevented via the
|
||||
* `initRun` parameter.
|
||||
*
|
||||
* # Example
|
||||
<pre>
|
||||
var scope = angular.scope();
|
||||
scope.name = 'misko';
|
||||
scope.counter = 0;
|
||||
|
||||
expect(scope.counter).toEqual(0);
|
||||
scope.$watch('name', 'counter = counter + 1');
|
||||
expect(scope.counter).toEqual(1);
|
||||
|
||||
scope.$eval();
|
||||
expect(scope.counter).toEqual(1);
|
||||
|
||||
scope.name = 'adam';
|
||||
scope.$eval();
|
||||
expect(scope.counter).toEqual(2);
|
||||
</pre>
|
||||
*
|
||||
* @param {function()|string} watchExp Expression that should be evaluated and checked for
|
||||
* change during each eval cycle. Can be an angular string expression or a function.
|
||||
* @param {function()|string} listener Function (or angular string expression) that gets called
|
||||
* every time the value of the `watchExp` changes. The function will be called with two
|
||||
* parameters, `newValue` and `oldValue`.
|
||||
* @param {(function()|DOMElement)=} [exceptionHanlder=angular.service.$exceptionHandler] Handler
|
||||
* that gets called when `watchExp` or `listener` throws an exception. If a DOMElement is
|
||||
* specified as handler, the element gets decorated by angular with the information about the
|
||||
* exception.
|
||||
* @param {boolean=} [initRun=true] Flag that prevents the first execution of the listener upon
|
||||
* registration.
|
||||
*
|
||||
*/
|
||||
$watch: function(watchExp, listener, exceptionHandler, initRun) {
|
||||
var watch = expressionCompile(watchExp),
|
||||
last;
|
||||
last = watch.call(instance);
|
||||
listener = expressionCompile(listener);
|
||||
function watcher(){
|
||||
function watcher(firstRun){
|
||||
var value = watch.call(instance),
|
||||
// we have to save the value because listener can call ourselves => inf loop
|
||||
lastValue = last;
|
||||
if (last !== value) {
|
||||
if (firstRun || lastValue !== value) {
|
||||
last = value;
|
||||
instance.$tryEval(function(){
|
||||
return listener.call(instance, value, lastValue);
|
||||
@@ -180,9 +494,36 @@ function createScope(parent, providers, instanceCache) {
|
||||
}
|
||||
}
|
||||
instance.$onEval(PRIORITY_WATCH, watcher);
|
||||
watcher();
|
||||
if (isUndefined(initRun)) initRun = true;
|
||||
if (initRun) watcher(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.scope.$onEval
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Evaluates the `expr` expression in the context of the current scope during each
|
||||
* {@link angular.scope.$eval eval cycle}.
|
||||
*
|
||||
* # Example
|
||||
<pre>
|
||||
var scope = angular.scope();
|
||||
scope.counter = 0;
|
||||
scope.$onEval('counter = counter + 1');
|
||||
expect(scope.counter).toEqual(0);
|
||||
scope.$eval();
|
||||
expect(scope.counter).toEqual(1);
|
||||
</pre>
|
||||
*
|
||||
* @param {number} [priority=0] Execution priority. Lower priority numbers get executed first.
|
||||
* @param {string|function()} expr Angular expression or function to be executed.
|
||||
* @param {(function()|DOMElement)=} [exceptionHandler=angular.service.$exceptionHandler] Handler
|
||||
* function to call or DOM element to decorate when an exception occurs.
|
||||
*
|
||||
*/
|
||||
$onEval: function(priority, expr, exceptionHandler){
|
||||
if (!isNumber(priority)) {
|
||||
exceptionHandler = expr;
|
||||
@@ -202,20 +543,35 @@ function createScope(parent, providers, instanceCache) {
|
||||
});
|
||||
},
|
||||
|
||||
$postEval: function(expr) {
|
||||
if (expr) {
|
||||
var fn = expressionCompile(expr);
|
||||
var id = fn.$postEvalId;
|
||||
if (!id) {
|
||||
id = '$' + instance.$id + "_" + (postId++);
|
||||
fn.$postEvalId = id;
|
||||
}
|
||||
if (!postHash[id]) {
|
||||
postList.push(postHash[id] = fn);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.scope.$become
|
||||
* @function
|
||||
* @deprecated This method will be removed before 1.0
|
||||
*
|
||||
* @description
|
||||
* Modifies the scope to act like an instance of the given class by:
|
||||
*
|
||||
* - copying the class's prototype methods
|
||||
* - applying the class's initialization function to the scope instance (without using the new
|
||||
* operator)
|
||||
*
|
||||
* That makes the scope be a `this` for the given class's methods — effectively an instance of
|
||||
* the given class with additional (scope) stuff. A scope can later `$become` another class.
|
||||
*
|
||||
* `$become` gets used to make the current scope act like an instance of a controller class.
|
||||
* This allows for use of a controller class in two ways.
|
||||
*
|
||||
* - as an ordinary JavaScript class for standalone testing, instantiated using the new
|
||||
* operator, with no attached view.
|
||||
* - as a controller for an angular model stored in a scope, "instantiated" by
|
||||
* `scope.$become(ControllerClass)`.
|
||||
*
|
||||
* Either way, the controller's methods refer to the model variables like `this.name`. When
|
||||
* stored in a scope, the model supports data binding. When bound to a view, {{name}} in the
|
||||
* HTML template refers to the same variable.
|
||||
*/
|
||||
$become: function(Class) {
|
||||
if (isFunction(Class)) {
|
||||
instance.constructor = Class;
|
||||
@@ -231,9 +587,25 @@ function createScope(parent, providers, instanceCache) {
|
||||
}
|
||||
},
|
||||
|
||||
$new: function(Class) {
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.scope.$new
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Creates a new {@link angular.scope scope}, that:
|
||||
*
|
||||
* - is a child of the current scope
|
||||
* - will {@link angular.scope.$become $become} of type specified via `constructor`
|
||||
*
|
||||
* @param {function()} constructor Constructor function of the type the new scope should assume.
|
||||
* @returns {Object} The newly created child scope.
|
||||
*
|
||||
*/
|
||||
$new: function(constructor) {
|
||||
var child = createScope(instance);
|
||||
child.$become.apply(instance, concat([Class], arguments, 1));
|
||||
child.$become.apply(instance, concat([constructor], arguments, 1));
|
||||
instance.$onEval(child.$eval);
|
||||
return child;
|
||||
}
|
||||
|
||||
+511
-12
@@ -4,13 +4,33 @@ var angularGlobal = {
|
||||
var type = typeof obj;
|
||||
if (type == $object) {
|
||||
if (obj instanceof Array) return $array;
|
||||
if (obj instanceof Date) return $date;
|
||||
if (isDate(obj)) return $date;
|
||||
if (obj.nodeType == 1) return $element;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc overview
|
||||
* @name angular.Object
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Utility functions for manipulation with JavaScript objects.
|
||||
*
|
||||
* These functions are exposed in two ways:
|
||||
*
|
||||
* - **in angular expressions**: the functions are bound to all objects and augment the Object
|
||||
* type. The names of these methods are prefixed with `$` character to minimize naming collisions.
|
||||
* To call a method, invoke the function without the first argument, e.g, `myObject.$foo(param2)`.
|
||||
*
|
||||
* - **in JavaScript code**: the functions don't augment the Object type and must be invoked as
|
||||
* functions of `angular.Object` as `angular.Object.foo(myObject, param2)`.
|
||||
*
|
||||
*/
|
||||
var angularCollection = {
|
||||
'copy': copy,
|
||||
'size': size,
|
||||
@@ -19,8 +39,121 @@ var angularCollection = {
|
||||
var angularObject = {
|
||||
'extend': extend
|
||||
};
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc overview
|
||||
* @name angular.Array
|
||||
*
|
||||
* @description
|
||||
* Utility functions for manipulation with JavaScript Array objects.
|
||||
*
|
||||
* These functions are exposed in two ways:
|
||||
*
|
||||
* - **in angular expressions**: the functions are bound to the Array objects and augment the Array
|
||||
* type as array methods. The names of these methods are prefixed with `$` character to minimize
|
||||
* naming collisions. To call a method, invoke `myArrayObject.$foo(params)`.
|
||||
*
|
||||
* - **in JavaScript code**: the functions don't augment the Array type and must be invoked as
|
||||
* functions of `angular.Array` as `angular.Array.foo(myArrayObject, params)`.
|
||||
*
|
||||
*/
|
||||
var angularArray = {
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.Array.indexOf
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Determines the index of `value` in `array`.
|
||||
*
|
||||
* Note: this function is used to augment the Array type in angular expressions. See
|
||||
* {@link angular.Array} for more info.
|
||||
*
|
||||
* @param {Array} array Array to search.
|
||||
* @param {*} value Value to search for.
|
||||
* @returns {number} The position of the element in `array`. The position is 0-based. `-1` is returned if the value can't be found.
|
||||
*
|
||||
* @example
|
||||
<div ng:init="books = ['Moby Dick', 'Great Gatsby', 'Romeo and Juliet']"></div>
|
||||
<input name='bookName' value='Romeo and Juliet'> <br>
|
||||
Index of '{{bookName}}' in the list {{books}} is <em>{{books.$indexOf(bookName)}}</em>.
|
||||
|
||||
@scenario
|
||||
it('should correctly calculate the initial index', function() {
|
||||
expect(binding('books.$indexOf(bookName)')).toBe('2');
|
||||
});
|
||||
|
||||
it('should recalculate', function() {
|
||||
input('bookName').enter('foo');
|
||||
expect(binding('books.$indexOf(bookName)')).toBe('-1');
|
||||
|
||||
input('bookName').enter('Moby Dick');
|
||||
expect(binding('books.$indexOf(bookName)')).toBe('0');
|
||||
});
|
||||
*/
|
||||
'indexOf': indexOf,
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.Array.sum
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* This function calculates the sum of all numbers in `array`. If the `expressions` is supplied,
|
||||
* it is evaluated once for each element in `array` and then the sum of these values is returned.
|
||||
*
|
||||
* Note: this function is used to augment the Array type in angular expressions. See
|
||||
* {@link angular.Array} for more info.
|
||||
*
|
||||
* @param {Array} array The source array.
|
||||
* @param {(string|function())=} expression Angular expression or a function to be evaluated for each
|
||||
* element in `array`. The array element becomes the `this` during the evaluation.
|
||||
* @returns {number} Sum of items in the array.
|
||||
*
|
||||
* @example
|
||||
<table ng:init="invoice= {items:[{qty:10, description:'gadget', cost:9.95}]}">
|
||||
<tr><th>Qty</th><th>Description</th><th>Cost</th><th>Total</th><th></th></tr>
|
||||
<tr ng:repeat="item in invoice.items">
|
||||
<td><input name="item.qty" value="1" size="4" ng:required ng:validate="integer"></td>
|
||||
<td><input name="item.description"></td>
|
||||
<td><input name="item.cost" value="0.00" ng:required ng:validate="number" size="6"></td>
|
||||
<td>{{item.qty * item.cost | currency}}</td>
|
||||
<td>[<a href ng:click="invoice.items.$remove(item)">X</a>]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href ng:click="invoice.items.$add()">add item</a></td>
|
||||
<td></td>
|
||||
<td>Total:</td>
|
||||
<td>{{invoice.items.$sum('qty*cost') | currency}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@scenario
|
||||
//TODO: these specs are lame because I had to work around issues #164 and #167
|
||||
it('should initialize and calculate the totals', function() {
|
||||
expect(repeater('.doc-example-live table tr', 'item in invoice.items').count()).toBe(3);
|
||||
expect(repeater('.doc-example-live table tr', 'item in invoice.items').row(1)).
|
||||
toEqual(['$99.50']);
|
||||
expect(binding("invoice.items.$sum('qty*cost')")).toBe('$99.50');
|
||||
expect(binding("invoice.items.$sum('qty*cost')")).toBe('$99.50');
|
||||
});
|
||||
|
||||
it('should add an entry and recalculate', function() {
|
||||
element('.doc-example a:contains("add item")').click();
|
||||
using('.doc-example-live tr:nth-child(3)').input('item.qty').enter('20');
|
||||
using('.doc-example-live tr:nth-child(3)').input('item.cost').enter('100');
|
||||
|
||||
expect(repeater('.doc-example-live table tr', 'item in invoice.items').row(2)).
|
||||
toEqual(['$2,000.00']);
|
||||
expect(binding("invoice.items.$sum('qty*cost')")).toBe('$2,099.50');
|
||||
});
|
||||
*/
|
||||
'sum':function(array, expression) {
|
||||
var fn = angular['Function']['compile'](expression);
|
||||
var sum = 0;
|
||||
@@ -32,12 +165,140 @@ var angularArray = {
|
||||
}
|
||||
return sum;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.Array.remove
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Modifies `array` by removing an element from it. The element will be looked up using the
|
||||
* {@link angular.Array.indexOf indexOf} function on the `array` and only the first instance of
|
||||
* the element will be removed.
|
||||
*
|
||||
* Note: this function is used to augment the Array type in angular expressions. See
|
||||
* {@link angular.Array} for more info.
|
||||
*
|
||||
* @param {Array} array Array from which an element should be removed.
|
||||
* @param {*} value Element to be removed.
|
||||
* @returns {*} The removed element.
|
||||
*
|
||||
* @example
|
||||
<ul ng:init="tasks=['Learn Angular', 'Read Documentation',
|
||||
'Check out demos', 'Build cool applications']">
|
||||
<li ng:repeat="task in tasks">
|
||||
{{task}} [<a href="" ng:click="tasks.$remove(task)">X</a>]
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
tasks = {{tasks}}
|
||||
|
||||
@scenario
|
||||
it('should initialize the task list with for tasks', function() {
|
||||
expect(repeater('.doc-example ul li', 'task in tasks').count()).toBe(4);
|
||||
expect(repeater('.doc-example ul li', 'task in tasks').column('task')).
|
||||
toEqual(['Learn Angular', 'Read Documentation', 'Check out demos',
|
||||
'Build cool applications']);
|
||||
});
|
||||
|
||||
it('should initialize the task list with for tasks', function() {
|
||||
element('.doc-example ul li a:contains("X"):first').click();
|
||||
expect(repeater('.doc-example ul li', 'task in tasks').count()).toBe(3);
|
||||
|
||||
element('.doc-example ul li a:contains("X"):last').click();
|
||||
expect(repeater('.doc-example ul li', 'task in tasks').count()).toBe(2);
|
||||
|
||||
expect(repeater('.doc-example ul li', 'task in tasks').column('task')).
|
||||
toEqual(['Read Documentation', 'Check out demos']);
|
||||
});
|
||||
*/
|
||||
'remove':function(array, value) {
|
||||
var index = indexOf(array, value);
|
||||
if (index >=0)
|
||||
array.splice(index, 1);
|
||||
return value;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.Array.filter
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Selects a subset of items from `array` and returns it as a new array.
|
||||
*
|
||||
* Note: this function is used to augment the Array type in angular expressions. See
|
||||
* {@link angular.Array} for more info.
|
||||
*
|
||||
* @param {Array} array The source array.
|
||||
* @param {string|Object|function()} expression The predicate to be used for selecting items from
|
||||
* `array`.
|
||||
*
|
||||
* Can be one of:
|
||||
*
|
||||
* - `string`: Predicate that results in a substring match using the value of `expression`
|
||||
* string. All strings or objects with string properties in `array` that contain this string
|
||||
* will be returned. The predicate can be negated by prefixing the string with `!`.
|
||||
*
|
||||
* - `Object`: A pattern object can be used to filter specific properties on objects contained
|
||||
* by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
|
||||
* which have property `name` containing "M" and property `phone` containing "1". A special
|
||||
* property name `$` can be used (as in `{$:"text"}`) to accept a match against any
|
||||
* property of the object. That's equivalent to the simple substring match with a `string`
|
||||
* as described above.
|
||||
*
|
||||
* - `function`: A predicate function can be used to write arbitrary filters. The function is
|
||||
* called for each element of `array`. The final result is an array of those elements that
|
||||
* the predicate returned true for.
|
||||
*
|
||||
* @example
|
||||
<div ng:init="friends = [{name:'John', phone:'555-1276'},
|
||||
{name:'Mary', phone:'800-BIG-MARY'},
|
||||
{name:'Mike', phone:'555-4321'},
|
||||
{name:'Adam', phone:'555-5678'},
|
||||
{name:'Julie', phone:'555-8765'}]"></div>
|
||||
|
||||
Search: <input name="searchText"/>
|
||||
<table id="searchTextResults">
|
||||
<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>
|
||||
<hr>
|
||||
Any: <input name="search.$"/> <br>
|
||||
Name only <input name="search.name"/><br>
|
||||
Phone only <input name="search.phone"/><br>
|
||||
<table id="searchObjResults">
|
||||
<tr><th>Name</th><th>Phone</th><tr>
|
||||
<tr ng:repeat="friend in friends.$filter(search)">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<tr>
|
||||
</table>
|
||||
|
||||
@scenario
|
||||
it('should search across all fields when filtering with a string', function() {
|
||||
input('searchText').enter('m');
|
||||
expect(repeater('#searchTextResults tr', 'friend in friends').column('name')).
|
||||
toEqual(['Mary', 'Mike', 'Adam']);
|
||||
|
||||
input('searchText').enter('76');
|
||||
expect(repeater('#searchTextResults tr', 'friend in friends').column('name')).
|
||||
toEqual(['John', 'Julie']);
|
||||
});
|
||||
|
||||
it('should search in specific fields when filtering with a predicate object', function() {
|
||||
input('search.$').enter('i');
|
||||
expect(repeater('#searchObjResults tr', 'friend in friends').column('name')).
|
||||
toEqual(['Mary', 'Mike', 'Julie']);
|
||||
});
|
||||
*/
|
||||
'filter':function(array, expression) {
|
||||
var predicates = [];
|
||||
predicates.check = function(value) {
|
||||
@@ -117,10 +378,124 @@ var angularArray = {
|
||||
}
|
||||
return filtered;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.Array.add
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* `add` is a function similar to JavaScript's `Array#push` method, in that it appends a new
|
||||
* element to an array, but it differs in that the value being added is optional and defaults to
|
||||
* an emty object.
|
||||
*
|
||||
* Note: this function is used to augment the Array type in angular expressions. See
|
||||
* {@link angular.Array} for more info.
|
||||
*
|
||||
* @param {Array} array The array expand.
|
||||
* @param {*=} [value={}] The value to be added.
|
||||
* @returns {Array} The expanded array.
|
||||
*
|
||||
* @exampleDescription
|
||||
* This example shows how an initially empty array can be filled with objects created from user
|
||||
* input via the `$add` method.
|
||||
*
|
||||
* @example
|
||||
[<a href="" ng:click="people.$add()">add empty</a>]
|
||||
[<a href="" ng:click="people.$add({name:'John', sex:'male'})">add 'John'</a>]
|
||||
[<a href="" ng:click="people.$add({name:'Mary', sex:'female'})">add 'Mary'</a>]
|
||||
|
||||
<ul ng:init="people=[]">
|
||||
<li ng:repeat="person in people">
|
||||
<input name="person.name">
|
||||
<select name="person.sex">
|
||||
<option value="">--chose one--</option>
|
||||
<option>male</option>
|
||||
<option>female</option>
|
||||
</select>
|
||||
[<a href="" ng:click="people.$remove(person)">X</a>]
|
||||
</li>
|
||||
</ul>
|
||||
<pre>people = {{people}}</pre>
|
||||
|
||||
@scenario
|
||||
beforeEach(function() {
|
||||
expect(binding('people')).toBe('people = []');
|
||||
});
|
||||
|
||||
it('should create an empty record when "add empty" is clicked', function() {
|
||||
element('.doc-example a:contains("add empty")').click();
|
||||
expect(binding('people')).toBe('people = [{\n "name":"",\n "sex":null}]');
|
||||
});
|
||||
|
||||
it('should create a "John" record when "add \'John\'" is clicked', function() {
|
||||
element('.doc-example a:contains("add \'John\'")').click();
|
||||
expect(binding('people')).toBe('people = [{\n "name":"John",\n "sex":"male"}]');
|
||||
});
|
||||
|
||||
it('should create a "Mary" record when "add \'Mary\'" is clicked', function() {
|
||||
element('.doc-example a:contains("add \'Mary\'")').click();
|
||||
expect(binding('people')).toBe('people = [{\n "name":"Mary",\n "sex":"female"}]');
|
||||
});
|
||||
|
||||
it('should delete a record when "X" is clicked', function() {
|
||||
element('.doc-example a:contains("add empty")').click();
|
||||
element('.doc-example li a:contains("X"):first').click();
|
||||
expect(binding('people')).toBe('people = []');
|
||||
});
|
||||
*/
|
||||
'add':function(array, value) {
|
||||
array.push(isUndefined(value)? {} : value);
|
||||
return array;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.Array.count
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Determines the number of elements in an array. Optionally it will count only those elements
|
||||
* for which the `condition` evaluets to `true`.
|
||||
*
|
||||
* Note: this function is used to augment the Array type in angular expressions. See
|
||||
* {@link angular.Array} for more info.
|
||||
*
|
||||
* @param {Array} array The array to count elements in.
|
||||
* @param {(function()|string)=} condition A function to be evaluated or angular expression to be
|
||||
* compiled and evaluated. The element that is currently being iterated over, is exposed to
|
||||
* the `condition` as `this`.
|
||||
* @returns {number} Number of elements in the array (for which the condition evaluates to true).
|
||||
*
|
||||
* @example
|
||||
<pre ng:init="items = [{name:'knife', points:1},
|
||||
{name:'fork', points:3},
|
||||
{name:'spoon', points:1}]"></pre>
|
||||
<ul>
|
||||
<li ng:repeat="item in items">
|
||||
{{item.name}}: points=
|
||||
<input type="text" name="item.points"/> <!-- id="item{{$index}} -->
|
||||
</li>
|
||||
</ul>
|
||||
<p>Number of items which have one point: <em>{{ items.$count('points==1') }}</em></p>
|
||||
<p>Number of items which have more than one point: <em>{{items.$count('points>1')}}</em></p>
|
||||
|
||||
@scenario
|
||||
it('should calculate counts', function() {
|
||||
expect(binding('items.$count(\'points==1\')')).toEqual(2);
|
||||
expect(binding('items.$count(\'points>1\')')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should recalculate when updated', function() {
|
||||
using('.doc-example li:first-child').input('item.points').enter('23');
|
||||
expect(binding('items.$count(\'points==1\')')).toEqual(1);
|
||||
expect(binding('items.$count(\'points>1\')')).toEqual(2);
|
||||
});
|
||||
*/
|
||||
'count':function(array, condition) {
|
||||
if (!condition) return array.length;
|
||||
var fn = angular['Function']['compile'](condition), count = 0;
|
||||
@@ -131,6 +506,86 @@ var angularArray = {
|
||||
});
|
||||
return count;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.Array.orderBy
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Orders `array` by the `expression` predicate.
|
||||
*
|
||||
* Note: this function is used to augment the Array type in angular expressions. See
|
||||
* {@link angular.Array} for more info.
|
||||
*
|
||||
* @param {Array} array The array to sort.
|
||||
* @param {function()|string|Array.<(function()|string)>} expression A predicate to be used by the
|
||||
* comparator to determine the order of elements.
|
||||
*
|
||||
* Can be one of:
|
||||
*
|
||||
* - `function`: JavaScript's Array#sort comparator function
|
||||
* - `string`: angular expression which evaluates to an object to order by, such as 'name' to
|
||||
* sort by a property called 'name'. Optionally prefixed with `+` or `-` to control ascending
|
||||
* or descending sort order (e.g. +name or -name).
|
||||
* - `Array`: array of function or string predicates, such that a first predicate in the array
|
||||
* is used for sorting, but when the items are equivalent next predicate is used.
|
||||
*
|
||||
* @param {boolean=} reverse Reverse the order the array.
|
||||
* @returns {Array} Sorted copy of the source array.
|
||||
*
|
||||
* @example
|
||||
<div ng:init="friends = [{name:'John', phone:'555-1212', age:10},
|
||||
{name:'Mary', phone:'555-9876', age:19},
|
||||
{name:'Mike', phone:'555-4321', age:21},
|
||||
{name:'Adam', phone:'555-5678', age:35},
|
||||
{name:'Julie', phone:'555-8765', age:29}]"></div>
|
||||
|
||||
<pre>Sorting predicate = {{predicate}}</pre>
|
||||
<hr/>
|
||||
<table ng:init="predicate='-age'">
|
||||
<tr>
|
||||
<th><a href="" ng:click="predicate = 'name'">Name</a>
|
||||
(<a href ng:click="predicate = '-name'">^</a>)</th>
|
||||
<th><a href="" ng:click="predicate = 'phone'">Phone</a>
|
||||
(<a href ng:click="predicate = '-phone'">^</a>)</th>
|
||||
<th><a href="" ng:click="predicate = 'age'">Age</a>
|
||||
(<a href ng:click="predicate = '-age'">^</a>)</th>
|
||||
<tr>
|
||||
<tr ng:repeat="friend in friends.$orderBy(predicate)">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<td>{{friend.age}}</td>
|
||||
<tr>
|
||||
</table>
|
||||
|
||||
@scenario
|
||||
it('should be reverse ordered by aged', function() {
|
||||
expect(binding('predicate')).toBe('Sorting predicate = -age');
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.age')).
|
||||
toEqual(['35', '29', '21', '19', '10']);
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
|
||||
});
|
||||
|
||||
it('should reorder the table when user selects different predicate', function() {
|
||||
element('.doc-example a:contains("Name")').click();
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.age')).
|
||||
toEqual(['35', '10', '29', '19', '21']);
|
||||
|
||||
element('.doc-example a:contains("Phone")+a:contains("^")').click();
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.phone')).
|
||||
toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
|
||||
});
|
||||
*/
|
||||
//TODO: WTH is descend param for and how/when it should be used, how is it affected by +/- in
|
||||
// predicate? the code below is impossible to read and specs are not very good.
|
||||
'orderBy':function(array, expression, descend) {
|
||||
expression = isArray(expression) ? expression: [expression];
|
||||
expression = map(expression, function($){
|
||||
@@ -173,10 +628,52 @@ var angularArray = {
|
||||
return t1 < t2 ? -1 : 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.Array.limitTo
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Creates a new array containing only the first, or last `limit` number of elements of the
|
||||
* source `array`.
|
||||
*
|
||||
* Note: this function is used to augment the Array type in angular expressions. See
|
||||
* {@link angular.Array} for more info.
|
||||
*
|
||||
* @param {Array} array Source array to be limited.
|
||||
* @param {string|Number} limit The length of the returned array. If the number is positive, the
|
||||
* first `limit` items from the source array will be copied, if the number is negative, the
|
||||
* last `limit` items will be copied.
|
||||
* @returns {Array} New array of length `limit`.
|
||||
*
|
||||
*/
|
||||
limitTo: function(array, limit) {
|
||||
limit = parseInt(limit, 10);
|
||||
var out = [],
|
||||
i, n;
|
||||
|
||||
if (limit > 0) {
|
||||
i = 0;
|
||||
n = limit;
|
||||
} else {
|
||||
i = array.length + limit;
|
||||
n = array.length;
|
||||
}
|
||||
|
||||
for (; i<n; i++) {
|
||||
out.push(array[i]);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
var R_ISO8061_STR = /^(\d{4})-(\d\d)-(\d\d)(?:T(\d\d)(?:\:(\d\d)(?:\:(\d\d)(?:\.(\d{3}))?)?)?Z)?$/;
|
||||
|
||||
var angularString = {
|
||||
'quote':function(string) {
|
||||
return '"' + string.replace(/\\/g, '\\\\').
|
||||
@@ -210,11 +707,10 @@ var angularString = {
|
||||
*/
|
||||
'toDate':function(string){
|
||||
var match;
|
||||
if (typeof string == 'string' &&
|
||||
(match = string.match(/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/))){
|
||||
if (isString(string) && (match = string.match(R_ISO8061_STR))){
|
||||
var date = new Date(0);
|
||||
date.setUTCFullYear(match[1], match[2] - 1, match[3]);
|
||||
date.setUTCHours(match[4], match[5], match[6], 0);
|
||||
date.setUTCHours(match[4]||0, match[5]||0, match[6]||0, match[7]||0);
|
||||
return date;
|
||||
}
|
||||
return string;
|
||||
@@ -223,14 +719,17 @@ var angularString = {
|
||||
|
||||
var angularDate = {
|
||||
'toString':function(date){
|
||||
function pad(n) { return n < 10 ? "0" + n : n; }
|
||||
return !date ? date :
|
||||
date.getUTCFullYear() + '-' +
|
||||
pad(date.getUTCMonth() + 1) + '-' +
|
||||
pad(date.getUTCDate()) + 'T' +
|
||||
pad(date.getUTCHours()) + ':' +
|
||||
pad(date.getUTCMinutes()) + ':' +
|
||||
pad(date.getUTCSeconds()) + 'Z' ;
|
||||
return !date ?
|
||||
date :
|
||||
date.toISOString ?
|
||||
date.toISOString() :
|
||||
padNumber(date.getUTCFullYear(), 4) + '-' +
|
||||
padNumber(date.getUTCMonth() + 1, 2) + '-' +
|
||||
padNumber(date.getUTCDate(), 2) + 'T' +
|
||||
padNumber(date.getUTCHours(), 2) + ':' +
|
||||
padNumber(date.getUTCMinutes(), 2) + ':' +
|
||||
padNumber(date.getUTCSeconds(), 2) + '.' +
|
||||
padNumber(date.getUTCMilliseconds(), 3) + 'Z';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+529
-67
@@ -1,9 +1,99 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:init
|
||||
*
|
||||
* @description
|
||||
* `ng:init` attribute allows the for initialization tasks to be executed
|
||||
* before the template enters execution mode during bootstrap.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
*
|
||||
* @example
|
||||
<div ng:init="greeting='Hello'; person='World'">
|
||||
{{greeting}} {{person}}!
|
||||
</div>
|
||||
*
|
||||
* @scenario
|
||||
it('should check greeting', function(){
|
||||
expect(binding('greeting')).toBe('Hello');
|
||||
expect(binding('person')).toBe('World');
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:init", function(expression){
|
||||
return function(element){
|
||||
this.$tryEval(expression, element);
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:controller
|
||||
*
|
||||
* @description
|
||||
* To support the Model-View-Controller design pattern, it is possible
|
||||
* to assign behavior to a scope through `ng:controller`. The scope is
|
||||
* the MVC model. The HTML (with data bindings) is the MVC view.
|
||||
* The `ng:controller` directive specifies the MVC controller class
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
*
|
||||
* @example
|
||||
<script type="text/javascript">
|
||||
function SettingsController() {
|
||||
this.name = "John Smith";
|
||||
this.contacts = [
|
||||
{type:'phone', value:'408 555 1212'},
|
||||
{type:'email', value:'john.smith@example.org'} ];
|
||||
}
|
||||
SettingsController.prototype = {
|
||||
greet: function(){
|
||||
alert(this.name);
|
||||
},
|
||||
addContact: function(){
|
||||
this.contacts.push({type:'email', value:'yourname@example.org'});
|
||||
},
|
||||
removeContact: function(contactToRemove) {
|
||||
angular.Array.remove(this.contacts, contactToRemove);
|
||||
},
|
||||
clearContact: function(contact) {
|
||||
contact.type = 'phone';
|
||||
contact.value = '';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="SettingsController">
|
||||
Name: <input type="text" name="name"/>
|
||||
[ <a href="" ng:click="greet()">greet</a> ]<br/>
|
||||
Contact:
|
||||
<ul>
|
||||
<li ng:repeat="contact in contacts">
|
||||
<select name="contact.type">
|
||||
<option>phone</option>
|
||||
<option>email</option>
|
||||
</select>
|
||||
<input type="text" name="contact.value"/>
|
||||
[ <a href="" ng:click="clearContact(contact)">clear</a>
|
||||
| <a href="" ng:click="removeContact(contact)">X</a> ]
|
||||
</li>
|
||||
<li>[ <a href="" ng:click="addContact()">add</a> ]</li>
|
||||
</ul>
|
||||
</div>
|
||||
*
|
||||
* @scenario
|
||||
it('should check controller', function(){
|
||||
expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
|
||||
expect(element('.doc-example-live li[ng\\:repeat-index="0"] input').val()).toBe('408 555 1212');
|
||||
expect(element('.doc-example-live li[ng\\:repeat-index="1"] input').val()).toBe('john.smith@example.org');
|
||||
element('.doc-example-live li:first a:contains("clear")').click();
|
||||
expect(element('.doc-example-live li:first input').val()).toBe('');
|
||||
element('.doc-example-live li:last a:contains("add")').click();
|
||||
expect(element('.doc-example-live li[ng\\:repeat-index="2"] input').val()).toBe('yourname@example.org');
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:controller", function(expression){
|
||||
this.scope(true);
|
||||
return function(element){
|
||||
@@ -16,12 +106,77 @@ angularDirective("ng:controller", function(expression){
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:eval
|
||||
*
|
||||
* @description
|
||||
* The `ng:eval` allows you to execute a binding which has side effects
|
||||
* without displaying the result to the user.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* Notice that `{{` `obj.multiplied = obj.a * obj.b` `}}` has a side effect of assigning
|
||||
* a value to `obj.multiplied` and displaying the result to the user. Sometimes,
|
||||
* however, it is desirable to execute a side effect without showing the value to
|
||||
* the user. In such a case `ng:eval` allows you to execute code without updating
|
||||
* the display.
|
||||
*
|
||||
* @example
|
||||
* <input name="obj.a" value="6" >
|
||||
* * <input name="obj.b" value="2">
|
||||
* = {{obj.multiplied = obj.a * obj.b}} <br>
|
||||
* <span ng:eval="obj.divide = obj.a / obj.b"></span>
|
||||
* <span ng:eval="obj.updateCount = 1 + (obj.updateCount||0)"></span>
|
||||
* <tt>obj.divide = {{obj.divide}}</tt><br/>
|
||||
* <tt>obj.updateCount = {{obj.updateCount}}</tt>
|
||||
*
|
||||
* @scenario
|
||||
it('should check eval', function(){
|
||||
expect(binding('obj.divide')).toBe('3');
|
||||
expect(binding('obj.updateCount')).toBe('2');
|
||||
input('obj.a').enter('12');
|
||||
expect(binding('obj.divide')).toBe('6');
|
||||
expect(binding('obj.updateCount')).toBe('3');
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:eval", function(expression){
|
||||
return function(element){
|
||||
this.$onEval(expression, element);
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:bind
|
||||
*
|
||||
* @description
|
||||
* The `ng:bind` attribute asks <angular/> to replace the text content of this
|
||||
* HTML element with the value of the given expression and kept it up to
|
||||
* date when the expression's value changes. Usually you just write
|
||||
* {{expression}} and let <angular/> compile it into
|
||||
* `<span ng:bind="expression"></span>` at bootstrap time.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* Try it here: enter text in text box and watch the greeting change.
|
||||
* @example
|
||||
* Enter name: <input type="text" name="name" value="Whirled">. <br>
|
||||
* Hello <span ng:bind="name" />!
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:bind', function(){
|
||||
expect(using('.doc-example-live').binding('name')).toBe('Whirled');
|
||||
using('.doc-example-live').input('name').enter('world');
|
||||
expect(using('.doc-example-live').binding('name')).toBe('world');
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:bind", function(expression, element){
|
||||
element.addClass('ng-binding');
|
||||
return function(element) {
|
||||
@@ -31,7 +186,7 @@ angularDirective("ng:bind", function(expression, element){
|
||||
oldElement = this.hasOwnProperty($$element) ? this.$element : _undefined;
|
||||
this.$element = element;
|
||||
value = this.$tryEval(expression, function(e){
|
||||
error = toJson(e);
|
||||
error = formatError(e);
|
||||
});
|
||||
this.$element = oldElement;
|
||||
// If we are HTML than save the raw HTML data so that we don't
|
||||
@@ -98,6 +253,39 @@ function compileBindTemplate(template){
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:bind-template
|
||||
*
|
||||
* @description
|
||||
* The `ng:bind-template` attribute specifies that the element
|
||||
* text should be replaced with the template in ng:bind-template.
|
||||
* Unlike ng:bind the ng:bind-template can contain multiple `{{` `}}`
|
||||
* expressions. (This is required since some HTML elements
|
||||
* can not have SPAN elements such as TITLE, or OPTION to name a few.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string} template of form
|
||||
* <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* Try it here: enter text in text box and watch the greeting change.
|
||||
* @example
|
||||
Salutation: <input type="text" name="salutation" value="Hello"><br/>
|
||||
Name: <input type="text" name="name" value="World"><br/>
|
||||
<pre ng:bind-template="{{salutation}} {{name}}!"></pre>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:bind', function(){
|
||||
expect(using('.doc-example-live').binding('{{salutation}} {{name}}')).
|
||||
toBe('Hello World!');
|
||||
using('.doc-example-live').input('salutation').enter('Greetings');
|
||||
using('.doc-example-live').input('name').enter('user');
|
||||
expect(using('.doc-example-live').binding('{{salutation}} {{name}}')).
|
||||
toBe('Greetings user!');
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:bind-template", function(expression, element){
|
||||
element.addClass('ng-binding');
|
||||
var templateFn = compileBindTemplate(expression);
|
||||
@@ -116,95 +304,116 @@ angularDirective("ng:bind-template", function(expression, element){
|
||||
var REMOVE_ATTRIBUTES = {
|
||||
'disabled':'disabled',
|
||||
'readonly':'readOnly',
|
||||
'checked':'checked'
|
||||
'checked':'checked',
|
||||
'selected':'selected'
|
||||
};
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:bind-attr
|
||||
*
|
||||
* @description
|
||||
* The `ng:bind-attr` attribute specifies that the element attributes
|
||||
* which should be replaced by the expression in it. Unlike `ng:bind`
|
||||
* the `ng:bind-attr` contains a JSON key value pairs representing
|
||||
* which attributes need to be changed. You don’t usually write the
|
||||
* `ng:bind-attr` in the HTML since embedding
|
||||
* <tt ng:non-bindable>{{expression}}</tt> into the
|
||||
* attribute directly is the preferred way. The attributes get
|
||||
* translated into `<span ng:bind-attr="{attr:expression}"/>` at
|
||||
* bootstrap time.
|
||||
*
|
||||
* This HTML snippet is preferred way of working with `ng:bind-attr`
|
||||
* <pre>
|
||||
* <a href="http://www.google.com/search?q={{query}}">Google</a>
|
||||
* </pre>
|
||||
*
|
||||
* The above gets translated to bellow during bootstrap time.
|
||||
* <pre>
|
||||
* <a ng:bind-attr='{"href":"http://www.google.com/search?q={{query}}"}'>Google</a>
|
||||
* </pre>
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string} attribute_json a JSON key-value pairs representing
|
||||
* the attributes to replace. Each key matches the attribute
|
||||
* which needs to be replaced. Each value is a text template of
|
||||
* the attribute with embedded
|
||||
* <tt ng:non-bindable>{{expression}}</tt>s. Any number of
|
||||
* key-value pairs can be specified.
|
||||
*
|
||||
* @exampleDescription
|
||||
* Try it here: enter text in text box and click Google.
|
||||
* @example
|
||||
Google for:
|
||||
<input type="text" name="query" value="AngularJS"/>
|
||||
<a href="http://www.google.com/search?q={{query}}">Google</a>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:bind-attr', function(){
|
||||
expect(using('.doc-example-live').element('a').attr('href')).
|
||||
toBe('http://www.google.com/search?q=AngularJS');
|
||||
using('.doc-example-live').input('query').enter('google');
|
||||
expect(using('.doc-example-live').element('a').attr('href')).
|
||||
toBe('http://www.google.com/search?q=google');
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:bind-attr", function(expression){
|
||||
return function(element){
|
||||
var lastValue = {};
|
||||
var updateFn = element.parent().data('$update');
|
||||
var updateFn = element.data($$update) || noop;
|
||||
this.$onEval(function(){
|
||||
var values = this.$eval(expression);
|
||||
var values = this.$eval(expression),
|
||||
dirty = noop;
|
||||
for(var key in values) {
|
||||
var value = compileBindTemplate(values[key]).call(this, element),
|
||||
specialName = REMOVE_ATTRIBUTES[lowercase(key)];
|
||||
if (lastValue[key] !== value) {
|
||||
lastValue[key] = value;
|
||||
if (specialName) {
|
||||
if (element[specialName] = toBoolean(value)) {
|
||||
element.attr(specialName, value);
|
||||
if (toBoolean(value)) {
|
||||
element.attr(specialName, specialName);
|
||||
element.attr('ng-' + specialName, value);
|
||||
} else {
|
||||
element.removeAttr(key);
|
||||
element.removeAttr(specialName);
|
||||
element.removeAttr('ng-' + specialName);
|
||||
}
|
||||
(element.data('$validate')||noop)();
|
||||
(element.data($$validate)||noop)();
|
||||
} else {
|
||||
element.attr(key, value);
|
||||
}
|
||||
this.$postEval(updateFn);
|
||||
dirty = updateFn;
|
||||
}
|
||||
}
|
||||
dirty();
|
||||
}, element);
|
||||
};
|
||||
});
|
||||
|
||||
angularWidget("@ng:non-bindable", noop);
|
||||
|
||||
angularWidget("@ng:repeat", function(expression, element){
|
||||
element.removeAttr('ng:repeat');
|
||||
element.replaceWith(this.comment("ng:repeat: " + expression));
|
||||
var template = this.compile(element);
|
||||
return function(reference){
|
||||
var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
|
||||
lhs, rhs, valueIdent, keyIdent;
|
||||
if (! match) {
|
||||
throw "Expected ng:repeat in form of 'item in collection' but got '" +
|
||||
expression + "'.";
|
||||
}
|
||||
lhs = match[1];
|
||||
rhs = match[2];
|
||||
match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);
|
||||
if (!match) {
|
||||
throw "'item' in 'item in collection' should be identifier or (key, value) but got '" +
|
||||
keyValue + "'.";
|
||||
}
|
||||
valueIdent = match[3] || match[1];
|
||||
keyIdent = match[2];
|
||||
|
||||
var children = [], currentScope = this;
|
||||
this.$onEval(function(){
|
||||
var index = 0, childCount = children.length, childScope, lastElement = reference,
|
||||
collection = this.$tryEval(rhs, reference), is_array = isArray(collection);
|
||||
for ( var key in collection) {
|
||||
if (!is_array || collection.hasOwnProperty(key)) {
|
||||
if (index < childCount) {
|
||||
// reuse existing child
|
||||
childScope = children[index];
|
||||
childScope[valueIdent] = collection[key];
|
||||
if (keyIdent) childScope[keyIdent] = key;
|
||||
} else {
|
||||
// grow children
|
||||
childScope = template(quickClone(element), createScope(currentScope));
|
||||
childScope[valueIdent] = collection[key];
|
||||
if (keyIdent) childScope[keyIdent] = key;
|
||||
lastElement.after(childScope.$element);
|
||||
childScope.$index = index;
|
||||
childScope.$element.attr('ng:repeat-index', index);
|
||||
childScope.$init();
|
||||
children.push(childScope);
|
||||
}
|
||||
childScope.$eval();
|
||||
lastElement = childScope.$element;
|
||||
index ++;
|
||||
}
|
||||
}
|
||||
// shrink children
|
||||
while(children.length > index) {
|
||||
children.pop().$element.remove();
|
||||
}
|
||||
}, reference);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:click
|
||||
*
|
||||
* @description
|
||||
* The ng:click allows you to specify custom behavior when
|
||||
* element is clicked.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval upon click.
|
||||
*
|
||||
* @example
|
||||
<button ng:click="count = count + 1" ng:init="count=0">
|
||||
Increment
|
||||
</button>
|
||||
count: {{count}}
|
||||
* @scenario
|
||||
it('should check ng:click', function(){
|
||||
expect(binding('count')).toBe('0');
|
||||
element('.doc-example-live :button').click();
|
||||
expect(binding('count')).toBe('1');
|
||||
});
|
||||
*/
|
||||
/*
|
||||
* A directive that allows creation of custom onclick handlers that are defined as angular
|
||||
* expressions and are compiled and executed within the current scope.
|
||||
@@ -225,6 +434,36 @@ angularDirective("ng:click", function(expression, element){
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:submit
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* @element form
|
||||
* @param {expression} expression to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
* <form ng:submit="list.push(text);text='';" ng:init="list=[]">
|
||||
* Enter text and hit enter:
|
||||
* <input type="text" name="text" value="hello"/>
|
||||
* </form>
|
||||
* <pre>list={{list}}</pre>
|
||||
* @scenario
|
||||
it('should check ng:submit', function(){
|
||||
expect(binding('list')).toBe('list=[]');
|
||||
element('.doc-example-live form input').click();
|
||||
this.addFutureAction('submit from', function($window, $document, done) {
|
||||
$window.angular.element(
|
||||
$document.elements('.doc-example-live form')).
|
||||
trigger('submit');
|
||||
done();
|
||||
});
|
||||
expect(binding('list')).toBe('list=["hello"]');
|
||||
});
|
||||
*/
|
||||
/**
|
||||
* Enables binding angular expressions to onsubmit events.
|
||||
*
|
||||
@@ -243,6 +482,34 @@ angularDirective("ng:submit", function(expression, element) {
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:watch
|
||||
*
|
||||
* @description
|
||||
* The `ng:watch` allows you watch a variable and then execute
|
||||
* an evaluation on variable change.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* Notice that the counter is incremented
|
||||
* every time you change the text.
|
||||
* @example
|
||||
<div ng:init="counter=0" ng:watch="name: counter = counter+1">
|
||||
<input type="text" name="name" value="hello"><br/>
|
||||
Change counter: {{counter}} Name: {{name}}
|
||||
</div>
|
||||
* @scenario
|
||||
it('should check ng:watch', function(){
|
||||
expect(using('.doc-example-live').binding('counter')).toBe('2');
|
||||
using('.doc-example-live').input('name').enter('abc');
|
||||
expect(using('.doc-example-live').binding('counter')).toBe('3');
|
||||
});
|
||||
*/
|
||||
//TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular)
|
||||
angularDirective("ng:watch", function(expression, element){
|
||||
return function(element){
|
||||
var self = this;
|
||||
@@ -271,10 +538,145 @@ function ngClass(selector) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:class
|
||||
*
|
||||
* @description
|
||||
* The `ng:class` allows you to set CSS class on HTML element
|
||||
* conditionally.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
<input type="button" value="set" ng:click="myVar='ng-input-indicator-wait'">
|
||||
<input type="button" value="clear" ng:click="myVar=''">
|
||||
<br>
|
||||
<span ng:class="myVar">Sample Text </span>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:class', function(){
|
||||
expect(element('.doc-example-live span').attr('className')).not().
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
|
||||
using('.doc-example-live').element(':button:first').click();
|
||||
|
||||
expect(element('.doc-example-live span').attr('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
|
||||
using('.doc-example-live').element(':button:last').click();
|
||||
|
||||
expect(element('.doc-example-live span').attr('className')).not().
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:class", ngClass(function(){return true;}));
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:class-odd
|
||||
*
|
||||
* @description
|
||||
* The `ng:class-odd` and `ng:class-even` works exactly as
|
||||
* `ng:class`, except it works in conjunction with `ng:repeat`
|
||||
* and takes affect only on odd (even) rows.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval. Must be inside
|
||||
* `ng:repeat`.
|
||||
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
<ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']">
|
||||
<li ng:repeat="name in names">
|
||||
<span ng:class-odd="'ng-format-negative'"
|
||||
ng:class-even="'ng-input-indicator-wait'">
|
||||
{{name}}
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:class-odd and ng:class-even', function(){
|
||||
expect(element('.doc-example-live li:first span').attr('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
expect(element('.doc-example-live li:last span').attr('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:class-even
|
||||
*
|
||||
* @description
|
||||
* The `ng:class-odd` and `ng:class-even` works exactly as
|
||||
* `ng:class`, except it works in conjunction with `ng:repeat`
|
||||
* and takes affect only on odd (even) rows.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval. Must be inside
|
||||
* `ng:repeat`.
|
||||
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
<ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']">
|
||||
<li ng:repeat="name in names">
|
||||
<span ng:class-odd="'ng-format-negative'"
|
||||
ng:class-even="'ng-input-indicator-wait'">
|
||||
{{name}}
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:class-odd and ng:class-even', function(){
|
||||
expect(element('.doc-example-live li:first span').attr('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
expect(element('.doc-example-live li:last span').attr('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:show
|
||||
*
|
||||
* @description
|
||||
* The `ng:show` and `ng:hide` allows you to show or hide a portion
|
||||
* of the HTML conditionally.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression if truthy then the element is
|
||||
* shown or hidden respectively.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
Click me: <input type="checkbox" name="checked"><br/>
|
||||
Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/>
|
||||
Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:show / ng:hide', function(){
|
||||
expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
|
||||
|
||||
input('checked').check();
|
||||
|
||||
expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:show", function(expression, element){
|
||||
return function(element){
|
||||
this.$onEval(function(){
|
||||
@@ -283,6 +685,36 @@ angularDirective("ng:show", function(expression, element){
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:hide
|
||||
*
|
||||
* @description
|
||||
* The `ng:show` and `ng:hide` allows you to show or hide a portion
|
||||
* of the HTML conditionally.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression if truthy then the element is
|
||||
* shown or hidden respectively.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
Click me: <input type="checkbox" name="checked"><br/>
|
||||
Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/>
|
||||
Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:show / ng:hide', function(){
|
||||
expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
|
||||
|
||||
input('checked').check();
|
||||
|
||||
expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:hide", function(expression, element){
|
||||
return function(element){
|
||||
this.$onEval(function(){
|
||||
@@ -291,6 +723,36 @@ angularDirective("ng:hide", function(expression, element){
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:style
|
||||
*
|
||||
* @description
|
||||
* The ng:style allows you to set CSS style on an HTML element conditionally.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression which evals to an object whes key's are
|
||||
* CSS style names and values are coresponding values for those
|
||||
* CSS keys.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
<input type="button" value="set" ng:click="myStyle={color:'red'}">
|
||||
<input type="button" value="clear" ng:click="myStyle={}">
|
||||
<br/>
|
||||
<span ng:style="myStyle">Sample Text</span>
|
||||
<pre>myStyle={{myStyle}}</pre>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:style', function(){
|
||||
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
|
||||
element('.doc-example-live :button[value=set]').click();
|
||||
expect(element('.doc-example-live span').css('color')).toBe('red');
|
||||
element('.doc-example-live :button[value=clear]').click();
|
||||
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
|
||||
});
|
||||
*/
|
||||
angularDirective("ng:style", function(expression, element){
|
||||
return function(element){
|
||||
var resetStyle = getStyle(element);
|
||||
|
||||
+52
-24
@@ -1,4 +1,5 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.currency
|
||||
* @function
|
||||
@@ -23,8 +24,8 @@
|
||||
it('should update', function(){
|
||||
input('amount').enter('-1234');
|
||||
expect(binding('amount | currency')).toBe('$-1,234.00');
|
||||
// TODO: implement
|
||||
// expect(binding('amount')).toHaveColor('red'); //what about toHaveCssClass instead?
|
||||
expect(element('.doc-example-live .ng-binding').attr('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
});
|
||||
*/
|
||||
angularFilter.currency = function(amount){
|
||||
@@ -33,6 +34,7 @@ angularFilter.currency = function(amount){
|
||||
};
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.number
|
||||
* @function
|
||||
@@ -42,22 +44,28 @@ angularFilter.currency = function(amount){
|
||||
*
|
||||
* If the input is not a number empty string is returned.
|
||||
*
|
||||
* @param {(number|string)} number Number to format.
|
||||
* @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to. Default 2.
|
||||
* @param {number|string} number Number to format.
|
||||
* @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
|
||||
* @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
|
||||
*
|
||||
* @example
|
||||
<span ng:non-bindable>{{1234.56789 | number}}</span>: {{1234.56789 | number}}<br/>
|
||||
<span ng:non-bindable>{{1234.56789 | number:0}}</span>: {{1234.56789 | number:0}}<br/>
|
||||
<span ng:non-bindable>{{1234.56789 | number:2}}</span>: {{1234.56789 | number:2}}<br/>
|
||||
<span ng:non-bindable>{{-1234.56789 | number:4}}</span>: {{-1234.56789 | number:4}}
|
||||
*
|
||||
Enter number: <input name='val' value='1234.56789' /><br/>
|
||||
Default formatting: {{val | number}}<br/>
|
||||
No fractions: {{val | number:0}}<br/>
|
||||
Negative number: {{-val | number:4}}
|
||||
|
||||
* @scenario
|
||||
it('should format numbers', function(){
|
||||
expect(binding('1234.56789 | number')).toBe('1,234.57');
|
||||
expect(binding('1234.56789 | number:0')).toBe('1,235');
|
||||
expect(binding('1234.56789 | number:2')).toBe('1,234.57');
|
||||
expect(binding('-1234.56789 | number:4')).toBe('-1,234.5679');
|
||||
expect(binding('val | number')).toBe('1,234.57');
|
||||
expect(binding('val | number:0')).toBe('1,235');
|
||||
expect(binding('-val | number:4')).toBe('-1,234.5679');
|
||||
});
|
||||
|
||||
it('should update', function(){
|
||||
input('val').enter('3374.333');
|
||||
expect(binding('val | number')).toBe('3,374.33');
|
||||
expect(binding('val | number:0')).toBe('3,374');
|
||||
expect(binding('-val | number:4')).toBe('-3,374.3330');
|
||||
});
|
||||
*/
|
||||
angularFilter.number = function(number, fractionSize){
|
||||
@@ -142,6 +150,7 @@ var NUMBER_STRING = /^\d+$/;
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.date
|
||||
* @function
|
||||
@@ -168,7 +177,8 @@ var NUMBER_STRING = /^\d+$/;
|
||||
* * `'a'`: am/pm marker
|
||||
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200‒1200)
|
||||
*
|
||||
* @param {(Date|number|string)} date Date to format either as Date object or milliseconds.
|
||||
* @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
|
||||
* number) or ISO 8601 extended datetime string (yyyy-MM-ddTHH:mm:ss.SSSZ).
|
||||
* @param {string=} format Formatting rules. If not specified, Date#toLocaleDateString is used.
|
||||
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
|
||||
*
|
||||
@@ -188,13 +198,19 @@ var NUMBER_STRING = /^\d+$/;
|
||||
*
|
||||
*/
|
||||
angularFilter.date = function(date, format) {
|
||||
if (isString(date) && NUMBER_STRING.test(date)) {
|
||||
date = parseInt(date, 10);
|
||||
if (isString(date)) {
|
||||
if (NUMBER_STRING.test(date)) {
|
||||
date = parseInt(date, 10);
|
||||
} else {
|
||||
date = angularString.toDate(date);
|
||||
}
|
||||
}
|
||||
|
||||
if (isNumber(date)) {
|
||||
date = new Date(date);
|
||||
} else if (!(date instanceof Date)) {
|
||||
}
|
||||
|
||||
if (!isDate(date)) {
|
||||
return date;
|
||||
}
|
||||
|
||||
@@ -216,6 +232,7 @@ angularFilter.date = function(date, format) {
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.json
|
||||
* @function
|
||||
@@ -231,12 +248,19 @@ angularFilter.date = function(date, format) {
|
||||
*
|
||||
* @css ng-monospace Always applied to the encapsulating element.
|
||||
*
|
||||
* @example
|
||||
<span ng:non-bindable>{{ {a:1, b:[]} | json }}</span>: <pre>{{ {a:1, b:[]} | json }}</pre>
|
||||
* @example:
|
||||
<input type="text" name="objTxt" value="{a:1, b:[]}"
|
||||
ng:eval="obj = $eval(objTxt)"/>
|
||||
<pre>{{ obj | json }}</pre>
|
||||
*
|
||||
* @scenario
|
||||
it('should jsonify filtered objects', function() {
|
||||
expect(binding('{{ {a:1, b:[]} | json')).toBe('{\n "a":1,\n "b":[]}');
|
||||
expect(binding('obj | json')).toBe('{\n "a":1,\n "b":[]}');
|
||||
});
|
||||
|
||||
it('should update', function() {
|
||||
input('objTxt').enter('[1, 2, 3]');
|
||||
expect(binding('obj | json')).toBe('[1,2,3]');
|
||||
});
|
||||
*
|
||||
*/
|
||||
@@ -247,6 +271,7 @@ angularFilter.json = function(object) {
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.lowercase
|
||||
* @function
|
||||
@@ -257,6 +282,7 @@ angularFilter.lowercase = lowercase;
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.uppercase
|
||||
* @function
|
||||
@@ -267,6 +293,7 @@ angularFilter.uppercase = uppercase;
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.html
|
||||
* @function
|
||||
@@ -343,7 +370,7 @@ snippet</p></textarea>
|
||||
});
|
||||
|
||||
it('should update', function(){
|
||||
textarea('snippet').enter('new <b>text</b>');
|
||||
input('snippet').enter('new <b>text</b>');
|
||||
expect(using('#html-filter').binding('snippet | html')).toBe('new <b>text</b>');
|
||||
expect(using('#escaped-html').binding('snippet')).toBe("new <b>text</b>");
|
||||
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).toBe('new <b>text</b>');
|
||||
@@ -355,6 +382,7 @@ angularFilter.html = function(html, option){
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.linky
|
||||
* @function
|
||||
@@ -394,8 +422,8 @@ and one more: ftp://127.0.0.1/.</textarea>
|
||||
<td><div ng:bind="snippet"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
*
|
||||
* @scenario
|
||||
|
||||
@scenario
|
||||
it('should linkify the snippet with urls', function(){
|
||||
expect(using('#linky-filter').binding('snippet | linky')).
|
||||
toBe('Pretty text with some links:\n' +
|
||||
@@ -415,7 +443,7 @@ and one more: ftp://127.0.0.1/.</textarea>
|
||||
});
|
||||
|
||||
it('should update', function(){
|
||||
textarea('snippet').enter('new http://link.');
|
||||
input('snippet').enter('new http://link.');
|
||||
expect(using('#linky-filter').binding('snippet | linky')).
|
||||
toBe('new <a href="http://link">http://link</a>.');
|
||||
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
|
||||
|
||||
@@ -6,8 +6,79 @@ function toString(obj) {
|
||||
var NUMBER = /^\s*[-+]?\d*(\.\d*)?\s*$/;
|
||||
|
||||
angularFormatter.noop = formatter(identity, identity);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc formatter
|
||||
* @name angular.formatter.json
|
||||
*
|
||||
* @description
|
||||
* Formats the user input as JSON text.
|
||||
*
|
||||
* @returns {string} A JSON string representation of the model.
|
||||
*
|
||||
* @example
|
||||
* <div ng:init="data={name:'misko', project:'angular'}">
|
||||
* <input type="text" size='50' name="data" ng:format="json"/>
|
||||
* <pre>data={{data}}</pre>
|
||||
* </div>
|
||||
*
|
||||
* @scenario
|
||||
* it('should format json', function(){
|
||||
* expect(binding('data')).toEqual('data={\n \"name\":\"misko\",\n \"project\":\"angular\"}');
|
||||
* input('data').enter('{}');
|
||||
* expect(binding('data')).toEqual('data={\n }');
|
||||
* });
|
||||
*/
|
||||
angularFormatter.json = formatter(toJson, fromJson);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc formatter
|
||||
* @name angular.formatter.boolean
|
||||
*
|
||||
* @description
|
||||
* Use boolean formatter if you wish to store the data as boolean.
|
||||
*
|
||||
* @returns {boolean} Converts to `true` unless user enters (blank), `f`, `false`, `0`, `no`, `[]`.
|
||||
*
|
||||
* @example
|
||||
* Enter truthy text:
|
||||
* <input type="text" name="value" ng:format="boolean" value="no"/>
|
||||
* <input type="checkbox" name="value"/>
|
||||
* <pre>value={{value}}</pre>
|
||||
*
|
||||
* @scenario
|
||||
* it('should format boolean', function(){
|
||||
* expect(binding('value')).toEqual('value=false');
|
||||
* input('value').enter('truthy');
|
||||
* expect(binding('value')).toEqual('value=true');
|
||||
* });
|
||||
*/
|
||||
angularFormatter['boolean'] = formatter(toString, toBoolean);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc formatter
|
||||
* @name angular.formatter.number
|
||||
*
|
||||
* @description
|
||||
* Use number formatter if you wish to convert the user entered string to a number.
|
||||
*
|
||||
* @returns {number} Number from the parsed string.
|
||||
*
|
||||
* @example
|
||||
* Enter valid number:
|
||||
* <input type="text" name="value" ng:format="number" value="1234"/>
|
||||
* <pre>value={{value}}</pre>
|
||||
*
|
||||
* @scenario
|
||||
* it('should format numbers', function(){
|
||||
* expect(binding('value')).toEqual('value=1234');
|
||||
* input('value').enter('5678');
|
||||
* expect(binding('value')).toEqual('value=5678');
|
||||
* });
|
||||
*/
|
||||
angularFormatter.number = formatter(toString, function(obj){
|
||||
if (obj == _null || NUMBER.exec(obj)) {
|
||||
return obj===_null || obj === '' ? _null : 1*obj;
|
||||
@@ -16,6 +87,32 @@ angularFormatter.number = formatter(toString, function(obj){
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc formatter
|
||||
* @name angular.formatter.list
|
||||
*
|
||||
* @description
|
||||
* Use list formatter if you wish to convert the user entered string to an array.
|
||||
*
|
||||
* @returns {Array} Array parsed from the entered string.
|
||||
*
|
||||
* @example
|
||||
* Enter a list of items:
|
||||
* <input type="text" name="value" ng:format="list" value=" chair ,, table"/>
|
||||
* <input type="text" name="value" ng:format="list"/>
|
||||
* <pre>value={{value}}</pre>
|
||||
*
|
||||
* @scenario
|
||||
* it('should format lists', function(){
|
||||
* expect(binding('value')).toEqual('value=["chair","table"]');
|
||||
* this.addFutureAction('change to XYZ', function($window, $document, done){
|
||||
* $document.elements('.doc-example :input:last').val(',,a,b,').trigger('change');
|
||||
* done();
|
||||
* });
|
||||
* expect(binding('value')).toEqual('value=["a","b"]');
|
||||
* });
|
||||
*/
|
||||
angularFormatter.list = formatter(
|
||||
function(obj) { return obj ? obj.join(", ") : obj; },
|
||||
function(value) {
|
||||
@@ -28,6 +125,32 @@ angularFormatter.list = formatter(
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc formatter
|
||||
* @name angular.formatter.trim
|
||||
*
|
||||
* @description
|
||||
* Use trim formatter if you wish to trim extra spaces in user text.
|
||||
*
|
||||
* @returns {String} Trim excess leading and trailing space.
|
||||
*
|
||||
* @example
|
||||
* Enter text with leading/trailing spaces:
|
||||
* <input type="text" name="value" ng:format="trim" value=" book "/>
|
||||
* <input type="text" name="value" ng:format="trim"/>
|
||||
* <pre>value={{value|json}}</pre>
|
||||
*
|
||||
* @scenario
|
||||
* it('should format trim', function(){
|
||||
* expect(binding('value')).toEqual('value="book"');
|
||||
* this.addFutureAction('change to XYZ', function($window, $document, done){
|
||||
* $document.elements('.doc-example :input:last').val(' text ').trigger('change');
|
||||
* done();
|
||||
* });
|
||||
* expect(binding('value')).toEqual('value="text"');
|
||||
* });
|
||||
*/
|
||||
angularFormatter.trim = formatter(
|
||||
function(obj) { return obj ? trim("" + obj) : ""; }
|
||||
);
|
||||
|
||||
@@ -68,6 +68,59 @@ angularTextMarkup('OPTION', function(text, textNode, parentElement){
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:href
|
||||
*
|
||||
* @description
|
||||
* Using <angular/> markup like {{hash}} in an href attribute makes
|
||||
* the page open to a wrong URL, ff the user clicks that link before
|
||||
* angular has a chance to replace the {{hash}} with actual URL, the
|
||||
* link will be broken and will most likely return a 404 error.
|
||||
* The `ng:href` solves this problem by placing the `href` in the
|
||||
* `ng:` namespace.
|
||||
*
|
||||
* The buggy way to write it:
|
||||
* <pre>
|
||||
* <a href="http://www.gravatar.com/avatar/{{hash}}"/>
|
||||
* </pre>
|
||||
*
|
||||
* The correct way to write it:
|
||||
* <pre>
|
||||
* <a ng:href="http://www.gravatar.com/avatar/{{hash}}"/>
|
||||
* </pre>
|
||||
*
|
||||
* @element ANY
|
||||
* @param {template} template any string which can contain `{{}}` markup.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:src
|
||||
*
|
||||
* @description
|
||||
* Using <angular/> markup like `{{hash}}` in a `src` attribute doesn't
|
||||
* work right: The browser will fetch from the URL with the literal
|
||||
* text `{{hash}}` until <angular/> replaces the expression inside
|
||||
* `{{hash}}`. The `ng:src` attribute solves this problem by placing
|
||||
* the `src` attribute in the `ng:` namespace.
|
||||
*
|
||||
* The buggy way to write it:
|
||||
* <pre>
|
||||
* <img src="http://www.gravatar.com/avatar/{{hash}}"/>
|
||||
* </pre>
|
||||
*
|
||||
* The correct way to write it:
|
||||
* <pre>
|
||||
* <img ng:src="http://www.gravatar.com/avatar/{{hash}}"/>
|
||||
* </pre>
|
||||
*
|
||||
* @element ANY
|
||||
* @param {template} template any string which can contain `{{}}` markup.
|
||||
*/
|
||||
|
||||
var NG_BIND_ATTR = 'ng:bind-attr';
|
||||
var SPECIAL_ATTRS = {'ng:src': 'src', 'ng:href': 'href'};
|
||||
angularAttrMarkup('{{}}', function(value, name, element){
|
||||
|
||||
+100
-186
@@ -9,7 +9,7 @@ var OPERATORS = {
|
||||
'/':function(self, a,b){return a/b;},
|
||||
'%':function(self, a,b){return a%b;},
|
||||
'^':function(self, a,b){return a^b;},
|
||||
'=':function(self, a,b){return setter(self, a, b);},
|
||||
'=':noop,
|
||||
'==':function(self, a,b){return a==b;},
|
||||
'!=':function(self, a,b){return a!=b;},
|
||||
'<':function(self, a,b){return a<b;},
|
||||
@@ -26,13 +26,13 @@ var OPERATORS = {
|
||||
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
|
||||
|
||||
function lex(text, parseStringsForObjects){
|
||||
var dateParseLength = parseStringsForObjects ? 20 : -1,
|
||||
var dateParseLength = parseStringsForObjects ? 24 : -1,
|
||||
tokens = [],
|
||||
token,
|
||||
index = 0,
|
||||
json = [],
|
||||
ch,
|
||||
lastCh = ':'; // can start regexp
|
||||
lastCh = ':';
|
||||
|
||||
while (index < text.length) {
|
||||
ch = text.charAt(index);
|
||||
@@ -40,8 +40,6 @@ function lex(text, parseStringsForObjects){
|
||||
readString(ch);
|
||||
} else if (isNumber(ch) || is('.') && isNumber(peek())) {
|
||||
readNumber();
|
||||
} else if ( was('({[:,;') && is('/') ) {
|
||||
readRegexp();
|
||||
} else if (isIdent(ch)) {
|
||||
readIdent();
|
||||
if (was('{,') && json[0]=='{' &&
|
||||
@@ -67,15 +65,15 @@ function lex(text, parseStringsForObjects){
|
||||
tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
|
||||
index += 1;
|
||||
} else {
|
||||
throw "Lexer Error: Unexpected next character [" +
|
||||
text.substring(index) +
|
||||
"] in expression '" + text +
|
||||
"' at column '" + (index+1) + "'.";
|
||||
throwError("Unexpected next character ", index, index+1);
|
||||
}
|
||||
}
|
||||
lastCh = ch;
|
||||
}
|
||||
return tokens;
|
||||
|
||||
|
||||
//////////////////////////////////////////////
|
||||
|
||||
function is(chars) {
|
||||
return chars.indexOf(ch) != -1;
|
||||
@@ -100,136 +98,69 @@ function lex(text, parseStringsForObjects){
|
||||
'A' <= ch && ch <= 'Z' ||
|
||||
'_' == ch || ch == '$';
|
||||
}
|
||||
function isExpOperator(ch) {
|
||||
return ch == '-' || ch == '+';
|
||||
function throwError(error, start, end) {
|
||||
end = end || index;
|
||||
throw Error("Lexer Error: " + error + " at column" +
|
||||
(isDefined(start) ?
|
||||
"s " + start + "-" + index + " [" + text.substring(start, end) + "]" :
|
||||
" " + end) +
|
||||
" in expression [" + text + "].");
|
||||
}
|
||||
function readNumber() {
|
||||
var number = "";
|
||||
|
||||
function consume(regexp, processToken, errorMsg) {
|
||||
var match = text.substr(index).match(regexp);
|
||||
var token = {index: index};
|
||||
var start = index;
|
||||
while (index < text.length) {
|
||||
var ch = text.charAt(index);
|
||||
if (ch == '.' || isNumber(ch)) {
|
||||
number += ch;
|
||||
} else {
|
||||
var peekCh = peek();
|
||||
if (ch == 'E' && isExpOperator(peekCh)) {
|
||||
number += ch;
|
||||
} else if (isExpOperator(ch) &&
|
||||
peekCh && isNumber(peekCh) &&
|
||||
number.charAt(number.length - 1) == 'E') {
|
||||
number += ch;
|
||||
} else if (isExpOperator(ch) &&
|
||||
(!peekCh || !isNumber(peekCh)) &&
|
||||
number.charAt(number.length - 1) == 'E') {
|
||||
throw 'Lexer found invalid exponential value "' + text + '"';
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
number = 1 * number;
|
||||
tokens.push({index:start, text:number, json:true,
|
||||
fn:function(){return number;}});
|
||||
}
|
||||
function readIdent() {
|
||||
var ident = "";
|
||||
var start = index;
|
||||
while (index < text.length) {
|
||||
var ch = text.charAt(index);
|
||||
if (ch == '.' || isIdent(ch) || isNumber(ch)) {
|
||||
ident += ch;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
var fn = OPERATORS[ident];
|
||||
if (!fn) {
|
||||
fn = getterFn(ident);
|
||||
fn.isAssignable = ident;
|
||||
}
|
||||
tokens.push({index:start, text:ident, fn:fn, json: OPERATORS[ident]});
|
||||
if (!match) throwError(errorMsg);
|
||||
index += match[0].length;
|
||||
processToken(token, token.text = match[0], start);
|
||||
tokens.push(token);
|
||||
}
|
||||
|
||||
function readString(quote) {
|
||||
var start = index;
|
||||
index++;
|
||||
var string = "";
|
||||
var rawString = quote;
|
||||
var escape = false;
|
||||
while (index < text.length) {
|
||||
var ch = text.charAt(index);
|
||||
rawString += ch;
|
||||
if (escape) {
|
||||
if (ch == 'u') {
|
||||
var hex = text.substring(index + 1, index + 5);
|
||||
if (!hex.match(/[\da-f]{4}/i))
|
||||
throw "Lexer Error: Invalid unicode escape [\\u" +
|
||||
hex + "] starting at column '" +
|
||||
start + "' in expression '" + text + "'.";
|
||||
index += 4;
|
||||
string += String.fromCharCode(parseInt(hex, 16));
|
||||
} else {
|
||||
var rep = ESCAPE[ch];
|
||||
if (rep) {
|
||||
string += rep;
|
||||
} else {
|
||||
string += ch;
|
||||
}
|
||||
}
|
||||
escape = false;
|
||||
} else if (ch == '\\') {
|
||||
escape = true;
|
||||
} else if (ch == quote) {
|
||||
index++;
|
||||
tokens.push({index:start, text:rawString, string:string, json:true,
|
||||
fn:function(){
|
||||
return (string.length == dateParseLength) ?
|
||||
angular['String']['toDate'](string) : string;
|
||||
}});
|
||||
return;
|
||||
} else {
|
||||
string += ch;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
throw "Lexer Error: Unterminated quote [" +
|
||||
text.substring(start) + "] starting at column '" +
|
||||
(start+1) + "' in expression '" + text + "'.";
|
||||
function readNumber() {
|
||||
consume(/^(\d+)?(\.\d+)?([eE][+-]?\d+)?/, function(token, number){
|
||||
token.text = number = 1 * number;
|
||||
token.json = true;
|
||||
token.fn = valueFn(number);
|
||||
}, "Not a valid number");
|
||||
}
|
||||
function readRegexp(quote) {
|
||||
var start = index;
|
||||
index++;
|
||||
var regexp = "";
|
||||
var escape = false;
|
||||
while (index < text.length) {
|
||||
var ch = text.charAt(index);
|
||||
if (escape) {
|
||||
regexp += ch;
|
||||
escape = false;
|
||||
} else if (ch === '\\') {
|
||||
regexp += ch;
|
||||
escape = true;
|
||||
} else if (ch === '/') {
|
||||
index++;
|
||||
var flags = "";
|
||||
if (isIdent(text.charAt(index))) {
|
||||
readIdent();
|
||||
flags = tokens.pop().text;
|
||||
}
|
||||
var compiledRegexp = new RegExp(regexp, flags);
|
||||
tokens.push({index:start, text:regexp, flags:flags,
|
||||
fn:function(){return compiledRegexp;}});
|
||||
return;
|
||||
} else {
|
||||
regexp += ch;
|
||||
|
||||
function readIdent() {
|
||||
consume(/^[\w_\$][\w_\$\d]*(\.[\w_\$][\w_\$\d]*)*/, function(token, ident){
|
||||
fn = OPERATORS[ident];
|
||||
if (!fn) {
|
||||
fn = getterFn(ident);
|
||||
fn.isAssignable = ident;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
throw "Lexer Error: Unterminated RegExp [" +
|
||||
text.substring(start) + "] starting at column '" +
|
||||
(start+1) + "' in expression '" + text + "'.";
|
||||
token.fn = OPERATORS[ident]||extend(getterFn(ident), {
|
||||
assign:function(self, value){
|
||||
return setter(self, ident, value);
|
||||
}
|
||||
});
|
||||
token.json = OPERATORS[ident];
|
||||
});
|
||||
}
|
||||
|
||||
function readString(quote) {
|
||||
consume(/^(('(\\'|[^'])*')|("(\\"|[^"])*"))/, function(token, rawString, start){
|
||||
var hasError;
|
||||
var string = token.string = rawString.substr(1, rawString.length - 2).
|
||||
replace(/(\\u(.?.?.?.?))|(\\(.))/g,
|
||||
function(match, wholeUnicode, unicode, wholeEscape, escape){
|
||||
if (unicode && !unicode.match(/[\da-fA-F]{4}/))
|
||||
hasError = hasError || bind(null, throwError, "Invalid unicode escape [\\u" + unicode + "]", start);
|
||||
return unicode ?
|
||||
String.fromCharCode(parseInt(unicode, 16)) :
|
||||
ESCAPE[escape] || escape;
|
||||
});
|
||||
(hasError||noop)();
|
||||
token.json = true;
|
||||
token.fn = function(){
|
||||
return (string.length == dateParseLength) ?
|
||||
angular['String']['toDate'](string) :
|
||||
string;
|
||||
};
|
||||
}, "Unterminated string");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,21 +175,21 @@ function parser(text, json){
|
||||
statements: statements,
|
||||
validator: validator,
|
||||
filter: filter,
|
||||
//TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular)
|
||||
watch: watch
|
||||
};
|
||||
|
||||
///////////////////////////////////
|
||||
|
||||
function error(msg, token) {
|
||||
throw "Token '" + token.text +
|
||||
"' is " + msg + " at column='" +
|
||||
(token.index + 1) + "' of expression '" +
|
||||
text + "' starting at '" + text.substring(token.index) + "'.";
|
||||
function throwError(msg, token) {
|
||||
throw Error("Parse Error: Token '" + token.text +
|
||||
"' " + msg + " at column " +
|
||||
(token.index + 1) + " of expression [" +
|
||||
text + "] starting at [" + text.substring(token.index) + "].");
|
||||
}
|
||||
|
||||
function peekToken() {
|
||||
if (tokens.length === 0)
|
||||
throw "Unexpected end of expression: " + text;
|
||||
throw Error("Unexpected end of expression: " + text);
|
||||
return tokens[0];
|
||||
}
|
||||
|
||||
@@ -279,10 +210,7 @@ function parser(text, json){
|
||||
if (token) {
|
||||
if (json && !token.json) {
|
||||
index = token.index;
|
||||
throw "Expression at column='" +
|
||||
token.index + "' of expression '" +
|
||||
text + "' starting at '" + text.substring(token.index) +
|
||||
"' is not valid json.";
|
||||
throwError("is not valid json", token);
|
||||
}
|
||||
tokens.shift();
|
||||
this.currentToken = token;
|
||||
@@ -293,11 +221,7 @@ function parser(text, json){
|
||||
|
||||
function consume(e1){
|
||||
if (!expect(e1)) {
|
||||
var token = peek();
|
||||
throw "Expecting '" + e1 + "' at column '" +
|
||||
(token.index+1) + "' in '" +
|
||||
text + "' got '" +
|
||||
text.substring(token.index) + "'.";
|
||||
throwError("is unexpected, expecting [" + e1 + "]", peek());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,8 +243,7 @@ function parser(text, json){
|
||||
|
||||
function assertAllConsumed(){
|
||||
if (tokens.length !== 0) {
|
||||
throw "Did not understand '" + text.substring(tokens[0].index) +
|
||||
"' while evaluating '" + text + "'.";
|
||||
throwError("is extra token not part of expression", tokens[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,31 +309,22 @@ function parser(text, json){
|
||||
}
|
||||
|
||||
function expression(){
|
||||
return throwStmt();
|
||||
}
|
||||
|
||||
function throwStmt(){
|
||||
if (expect('throw')) {
|
||||
var throwExp = assignment();
|
||||
return function (self) {
|
||||
throw throwExp(self);
|
||||
};
|
||||
} else {
|
||||
return assignment();
|
||||
}
|
||||
return assignment();
|
||||
}
|
||||
|
||||
function assignment(){
|
||||
var left = logicalOR();
|
||||
var right;
|
||||
var token;
|
||||
if (token = expect('=')) {
|
||||
if (!left.isAssignable) {
|
||||
throw "Left hand side '" +
|
||||
text.substring(0, token.index) + "' of assignment '" +
|
||||
text.substring(token.index) + "' is not assignable.";
|
||||
if (!left.assign) {
|
||||
throwError("implies assignment but [" +
|
||||
text.substring(0, token.index) + "] can not be assigned to", token);
|
||||
}
|
||||
var ident = function(){return left.isAssignable;};
|
||||
return binaryFn(ident, token.fn, logicalOR());
|
||||
right = logicalOR();
|
||||
return function(self){
|
||||
return left.assign(self, right(self));
|
||||
};
|
||||
} else {
|
||||
return left;
|
||||
}
|
||||
@@ -497,8 +411,7 @@ function parser(text, json){
|
||||
instance = instance[key];
|
||||
}
|
||||
if (typeof instance != $function) {
|
||||
throw "Function '" + token.text + "' at column '" +
|
||||
(token.index+1) + "' in '" + text + "' is not defined.";
|
||||
throwError("should be a function", token);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
@@ -517,7 +430,7 @@ function parser(text, json){
|
||||
var token = expect();
|
||||
primary = token.fn;
|
||||
if (!primary) {
|
||||
error("not a primary expression", token);
|
||||
throwError("not a primary expression", token);
|
||||
}
|
||||
}
|
||||
var next;
|
||||
@@ -529,7 +442,7 @@ function parser(text, json){
|
||||
} else if (next.text === '.') {
|
||||
primary = fieldAccess(primary);
|
||||
} else {
|
||||
throw "IMPOSSIBLE";
|
||||
throwError("IMPOSSIBLE");
|
||||
}
|
||||
}
|
||||
return primary;
|
||||
@@ -538,28 +451,28 @@ function parser(text, json){
|
||||
function fieldAccess(object) {
|
||||
var field = expect().text;
|
||||
var getter = getterFn(field);
|
||||
var fn = function (self){
|
||||
return extend(function (self){
|
||||
return getter(object(self));
|
||||
};
|
||||
fn.isAssignable = field;
|
||||
return fn;
|
||||
}, {
|
||||
assign:function(self, value){
|
||||
return setter(object(self), field, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function objectIndex(obj) {
|
||||
var indexFn = expression();
|
||||
consume(']');
|
||||
if (expect('=')) {
|
||||
var rhs = expression();
|
||||
return function (self){
|
||||
return obj(self)[indexFn(self)] = rhs(self);
|
||||
};
|
||||
} else {
|
||||
return function (self){
|
||||
return extend(
|
||||
function (self){
|
||||
var o = obj(self);
|
||||
var i = indexFn(self);
|
||||
return (o) ? o[i] : _undefined;
|
||||
};
|
||||
}
|
||||
}, {
|
||||
assign:function(self, value){
|
||||
return obj(self)[indexFn(self)] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function functionCall(fn) {
|
||||
@@ -624,6 +537,7 @@ function parser(text, json){
|
||||
};
|
||||
}
|
||||
|
||||
//TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular)
|
||||
function watch () {
|
||||
var decl = [];
|
||||
while(hasTokens()) {
|
||||
|
||||
+66
-71
@@ -17,40 +17,42 @@
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var START_TAG_REGEXP = /^<\s*([\w:]+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
|
||||
END_TAG_REGEXP = /^<\s*\/\s*([\w:]+)[^>]*>/,
|
||||
ATTR_REGEXP = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g,
|
||||
ATTR_REGEXP = /(\w+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
|
||||
BEGIN_TAG_REGEXP = /^</,
|
||||
BEGING_END_TAGE_REGEXP = /^<\s*\//,
|
||||
COMMENT_REGEXP = /<!--(.*?)-->/g,
|
||||
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g;
|
||||
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
|
||||
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/,
|
||||
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)
|
||||
|
||||
// Empty Elements - HTML 4.01
|
||||
var emptyElements = makeMap("area,base,basefont,br,col,hr,img,input,isindex,link,param");
|
||||
var emptyElements = makeMap("area,br,col,hr,img");
|
||||
|
||||
// Block Elements - HTML 4.01
|
||||
var blockElements = makeMap("address,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,"+
|
||||
"form,hr,ins,isindex,li,map,menu,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
|
||||
var blockElements = makeMap("address,blockquote,center,dd,del,dir,div,dl,dt,"+
|
||||
"hr,ins,li,map,menu,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
|
||||
|
||||
// Inline Elements - HTML 4.01
|
||||
var inlineElements = makeMap("a,abbr,acronym,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,img,"+
|
||||
"input,ins,kbd,label,map,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
|
||||
|
||||
var inlineElements = makeMap("a,abbr,acronym,b,bdo,big,br,cite,code,del,dfn,em,font,i,img,"+
|
||||
"ins,kbd,label,map,q,s,samp,small,span,strike,strong,sub,sup,tt,u,var");
|
||||
// Elements that you can, intentionally, leave open
|
||||
// (and which close themselves)
|
||||
var closeSelfElements = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
|
||||
|
||||
// Attributes that have their values filled in disabled="disabled"
|
||||
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
|
||||
|
||||
var closeSelfElements = makeMap("colgroup,dd,dt,li,p,td,tfoot,th,thead,tr");
|
||||
// Special Elements (can contain anything)
|
||||
var specialElements = makeMap("script,style");
|
||||
|
||||
var validElements = extend({}, emptyElements, blockElements, inlineElements, closeSelfElements);
|
||||
var validAttrs = extend({}, fillAttrs, makeMap(
|
||||
'abbr,align,alink,alt,archive,axis,background,bgcolor,border,cellpadding,cellspacing,cite,class,classid,clear,code,codebase,'+
|
||||
'codetype,color,cols,colspan,content,coords,data,dir,face,for,headers,height,href,hreflang,hspace,id,label,lang,language,'+
|
||||
'link,longdesc,marginheight,marginwidth,maxlength,media,method,name,nowrap,profile,prompt,rel,rev,rows,rowspan,rules,scheme,'+
|
||||
'scope,scrolling,shape,size,span,src,standby,start,summary,tabindex,target,text,title,type,usemap,valign,value,valuetype,'+
|
||||
'vlink,vspace,width'));
|
||||
|
||||
//see: http://www.w3.org/TR/html4/index/attributes.html
|
||||
//Attributes that have their values filled in disabled="disabled"
|
||||
var fillAttrs = makeMap("compact,ismap,nohref,nowrap");
|
||||
//Attributes that have href and hence need to be sanitized
|
||||
var uriAttrs = makeMap("background,href,longdesc,src,usemap");
|
||||
var validAttrs = extend({}, fillAttrs, uriAttrs, makeMap(
|
||||
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
|
||||
'color,cols,colspan,coords,dir,face,headers,height,hreflang,hspace,'+
|
||||
'lang,language,rel,rev,rows,rowspan,rules,'+
|
||||
'scope,scrolling,shape,span,start,summary,target,title,type,'+
|
||||
'valign,value,vspace,width'));
|
||||
|
||||
/**
|
||||
* @example
|
||||
@@ -64,7 +66,7 @@ var validAttrs = extend({}, fillAttrs, makeMap(
|
||||
* @param {string} html string
|
||||
* @param {object} handler
|
||||
*/
|
||||
var htmlParser = function( html, handler ) {
|
||||
function htmlParser( html, handler ) {
|
||||
var index, chars, match, stack = [], last = html;
|
||||
stack.last = function(){ return stack[ stack.length - 1 ]; };
|
||||
|
||||
@@ -112,8 +114,7 @@ var htmlParser = function( html, handler ) {
|
||||
var text = index < 0 ? html : html.substring( 0, index );
|
||||
html = index < 0 ? "" : html.substring( index );
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
handler.chars( decodeEntities(text) );
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -122,8 +123,7 @@ var htmlParser = function( html, handler ) {
|
||||
replace(COMMENT_REGEXP, "$1").
|
||||
replace(CDATA_REGEXP, "$1");
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
handler.chars( decodeEntities(text) );
|
||||
|
||||
return "";
|
||||
});
|
||||
@@ -157,21 +157,18 @@ var htmlParser = function( html, handler ) {
|
||||
if ( !unary )
|
||||
stack.push( tagName );
|
||||
|
||||
if ( handler.start ) {
|
||||
var attrs = {};
|
||||
var attrs = {};
|
||||
|
||||
rest.replace(ATTR_REGEXP, function(match, name) {
|
||||
var value = arguments[2] ? arguments[2] :
|
||||
arguments[3] ? arguments[3] :
|
||||
arguments[4] ? arguments[4] :
|
||||
fillAttrs[name] ? name : "";
|
||||
rest.replace(ATTR_REGEXP, function(match, name) {
|
||||
var value = arguments[2] ? arguments[2] :
|
||||
arguments[3] ? arguments[3] :
|
||||
arguments[4] ? arguments[4] :
|
||||
fillAttrs[name] ? name : "";
|
||||
|
||||
attrs[name] = value; //value.replace(/(^|[^\\])"/g, '$1\\\"') //"
|
||||
});
|
||||
attrs[name] = decodeEntities(value); //value.replace(/(^|[^\\])"/g, '$1\\\"') //"
|
||||
});
|
||||
|
||||
if ( handler.start )
|
||||
handler.start( tagName, attrs, unary );
|
||||
}
|
||||
handler.start( tagName, attrs, unary );
|
||||
}
|
||||
|
||||
function parseEndTag( tag, tagName ) {
|
||||
@@ -186,14 +183,13 @@ var htmlParser = function( html, handler ) {
|
||||
if ( pos >= 0 ) {
|
||||
// Close all the open elements, up the stack
|
||||
for ( i = stack.length - 1; i >= pos; i-- )
|
||||
if ( handler.end )
|
||||
handler.end( stack[ i ] );
|
||||
handler.end( stack[ i ] );
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param str 'key1,key2,...'
|
||||
@@ -206,28 +202,32 @@ function makeMap(str){
|
||||
return obj;
|
||||
}
|
||||
|
||||
/*
|
||||
* For attack vectors see: http://ha.ckers.org/xss.html
|
||||
*/
|
||||
var JAVASCRIPT_URL = /^javascript:/i,
|
||||
NBSP_REGEXP = / /gim,
|
||||
HEX_ENTITY_REGEXP = /&#x([\da-f]*);?/igm,
|
||||
DEC_ENTITY_REGEXP = /&#(\d+);?/igm,
|
||||
CHAR_REGEXP = /[\w:]/gm,
|
||||
HEX_DECODE = function(match, code){return fromCharCode(parseInt(code,16));},
|
||||
DEC_DECODE = function(match, code){return fromCharCode(code);};
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns true if url decodes to something which starts with 'javascript:' hence unsafe
|
||||
* decodes all entities into regular string
|
||||
* @param value
|
||||
* @returns {string} A string with decoded entities.
|
||||
*/
|
||||
function isJavaScriptUrl(url) {
|
||||
var chars = [];
|
||||
url.replace(NBSP_REGEXP, '').
|
||||
replace(HEX_ENTITY_REGEXP, HEX_DECODE).
|
||||
replace(DEC_ENTITY_REGEXP, DEC_DECODE).
|
||||
// Remove all non \w: characters, unfurtunetly value.replace(/[\w:]/,'') can be defeated using \u0000
|
||||
replace(CHAR_REGEXP, function(ch){chars.push(ch);});
|
||||
return JAVASCRIPT_URL.test(lowercase(chars.join('')));
|
||||
var hiddenPre=document.createElement("pre");
|
||||
function decodeEntities(value) {
|
||||
hiddenPre.innerHTML=value.replace(/</g,"<");
|
||||
return hiddenPre.innerText || hiddenPre.textContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes all potentially dangerous characters, so that the
|
||||
* resulting string can be safely inserted into attribute or
|
||||
* element text.
|
||||
* @param value
|
||||
* @returns escaped text
|
||||
*/
|
||||
function encodeEntities(value) {
|
||||
return value.
|
||||
replace(/&/g, '&').
|
||||
replace(NON_ALPHANUMERIC_REGEXP, function(value){
|
||||
return '&#' + value.charCodeAt(0) + ';';
|
||||
}).
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,18 +249,16 @@ function htmlSanitizeWriter(buf){
|
||||
if (!ignore && specialElements[tag]) {
|
||||
ignore = tag;
|
||||
}
|
||||
if (!ignore && validElements[tag]) {
|
||||
if (!ignore && validElements[tag] == true) {
|
||||
out('<');
|
||||
out(tag);
|
||||
foreach(attrs, function(value, key){
|
||||
if (validAttrs[lowercase(key)] && !isJavaScriptUrl(value)) {
|
||||
var lkey=lowercase(key);
|
||||
if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
|
||||
out(' ');
|
||||
out(key);
|
||||
out('="');
|
||||
out(value.
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>').
|
||||
replace(/\"/g,'"'));
|
||||
out(encodeEntities(value));
|
||||
out('"');
|
||||
}
|
||||
});
|
||||
@@ -269,7 +267,7 @@ function htmlSanitizeWriter(buf){
|
||||
},
|
||||
end: function(tag){
|
||||
tag = lowercase(tag);
|
||||
if (!ignore && validElements[tag]) {
|
||||
if (!ignore && validElements[tag] == true) {
|
||||
out('</');
|
||||
out(tag);
|
||||
out('>');
|
||||
@@ -280,10 +278,7 @@ function htmlSanitizeWriter(buf){
|
||||
},
|
||||
chars: function(chars){
|
||||
if (!ignore) {
|
||||
out(chars.
|
||||
replace(/&(\w+[&;\W])?/g, function(match, entity){return entity?match:'&';}).
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>'));
|
||||
out(encodeEntities(chars));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -294,3 +294,31 @@ function browserTrigger(element, type) {
|
||||
};
|
||||
})(_jQuery.fn);
|
||||
|
||||
/**
|
||||
* Finds all bindings with the substring match of name and returns an
|
||||
* array of their values.
|
||||
*
|
||||
* @param {string} name The name to match
|
||||
* @return {Array.<string>} String of binding values
|
||||
*/
|
||||
_jQuery.fn.bindings = function(name) {
|
||||
function contains(text, value) {
|
||||
return value instanceof RegExp ?
|
||||
value.test(text) :
|
||||
text && text.indexOf(value) >= 0;
|
||||
}
|
||||
var result = [];
|
||||
this.find('.ng-binding:visible').each(function() {
|
||||
var element = new _jQuery(this);
|
||||
if (!angular.isDefined(name) ||
|
||||
contains(element.attr('ng:bind'), name) ||
|
||||
contains(element.attr('ng:bind-template'), name)) {
|
||||
if (element.is('input, textarea')) {
|
||||
result.push(element.val());
|
||||
} else {
|
||||
result.push(element.html());
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -111,6 +111,7 @@ angular.scenario.SpecRunner.prototype.addFutureAction = function(name, behavior,
|
||||
$document.elements = function(selector) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
selector = (self.selector || '') + ' ' + (selector || '');
|
||||
selector = _jQuery.trim(selector) || '*';
|
||||
angular.foreach(args, function(value, index) {
|
||||
selector = selector.replace('$' + (index + 1), value);
|
||||
});
|
||||
|
||||
+28
-76
@@ -152,30 +152,16 @@ angular.scenario.dsl('using', function() {
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* binding(name) returns the value of a binding
|
||||
* binding(name) returns the value of the first matching binding
|
||||
*/
|
||||
angular.scenario.dsl('binding', function() {
|
||||
function contains(text, value) {
|
||||
return value instanceof RegExp ?
|
||||
value.test(text) :
|
||||
text && text.indexOf(value) >= 0;
|
||||
}
|
||||
return function(name) {
|
||||
return this.addFutureAction("select binding '" + name + "'", function($window, $document, done) {
|
||||
var elements = $document.elements('.ng-binding');
|
||||
for ( var i = 0; i < elements.length; i++) {
|
||||
var element = new elements.init(elements[i]);
|
||||
if (contains(element.attr('ng:bind'), name) ||
|
||||
contains(element.attr('ng:bind-template'), name)) {
|
||||
if (element.is('input, textarea')) {
|
||||
done(null, element.val());
|
||||
} else {
|
||||
done(null, element.html());
|
||||
}
|
||||
return;
|
||||
}
|
||||
var values = $document.elements().bindings(name);
|
||||
if (!values.length) {
|
||||
return done("Binding selector '" + name + "' did not match.");
|
||||
}
|
||||
done("Binding selector '" + name + "' did not match.");
|
||||
done(null, values[0]);
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -191,7 +177,7 @@ angular.scenario.dsl('input', function() {
|
||||
|
||||
chain.enter = function(value) {
|
||||
return this.addFutureAction("input '" + this.name + "' enter '" + value + "'", function($window, $document, done) {
|
||||
var input = $document.elements('input[name="$1"]', this.name);
|
||||
var input = $document.elements(':input[name="$1"]', this.name);
|
||||
input.val(value);
|
||||
input.trigger('change');
|
||||
done();
|
||||
@@ -200,7 +186,7 @@ angular.scenario.dsl('input', function() {
|
||||
|
||||
chain.check = function() {
|
||||
return this.addFutureAction("checkbox '" + this.name + "' toggle", function($window, $document, done) {
|
||||
var input = $document.elements('input:checkbox[name="$1"]', this.name);
|
||||
var input = $document.elements(':checkbox[name="$1"]', this.name);
|
||||
input.trigger('click');
|
||||
done();
|
||||
});
|
||||
@@ -209,7 +195,7 @@ angular.scenario.dsl('input', function() {
|
||||
chain.select = function(value) {
|
||||
return this.addFutureAction("radio button '" + this.name + "' toggle '" + value + "'", function($window, $document, done) {
|
||||
var input = $document.
|
||||
elements('input:radio[name$="@$1"][value="$2"]', this.name, value);
|
||||
elements(':radio[name$="@$1"][value="$2"]', this.name, value);
|
||||
input.trigger('click');
|
||||
done();
|
||||
});
|
||||
@@ -222,29 +208,6 @@ angular.scenario.dsl('input', function() {
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* textarea(name).enter(value) enters value in the text area with specified name
|
||||
*/
|
||||
angular.scenario.dsl('textarea', function() {
|
||||
var chain = {};
|
||||
|
||||
chain.enter = function(value) {
|
||||
return this.addFutureAction("textarea '" + this.name + "' enter '" + value + "'", function($window, $document, done) {
|
||||
var textarea = $document.elements('textarea[name="$1"]', this.name);
|
||||
textarea.val(value);
|
||||
textarea.trigger('change');
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
return function(name) {
|
||||
this.name = name;
|
||||
return chain;
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* repeater('#products table', 'Product List').count() number of rows
|
||||
@@ -266,16 +229,7 @@ angular.scenario.dsl('repeater', function() {
|
||||
|
||||
chain.column = function(binding) {
|
||||
return this.addFutureAction("repeater '" + this.label + "' column '" + binding + "'", function($window, $document, done) {
|
||||
var values = [];
|
||||
$document.elements().each(function() {
|
||||
_jQuery(this).find(':visible').each(function() {
|
||||
var element = _jQuery(this);
|
||||
if (element.attr('ng:bind') === binding) {
|
||||
values.push(element.text());
|
||||
}
|
||||
});
|
||||
});
|
||||
done(null, values);
|
||||
done(null, $document.elements().bindings(binding));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -285,13 +239,7 @@ angular.scenario.dsl('repeater', function() {
|
||||
var matches = $document.elements().slice(index, index + 1);
|
||||
if (!matches.length)
|
||||
return done('row ' + index + ' out of bounds');
|
||||
_jQuery(matches[0]).find(':visible').each(function() {
|
||||
var element = _jQuery(this);
|
||||
if (angular.isDefined(element.attr('ng:bind'))) {
|
||||
values.push(element.text());
|
||||
}
|
||||
});
|
||||
done(null, values);
|
||||
done(null, matches.bindings());
|
||||
});
|
||||
};
|
||||
|
||||
@@ -338,13 +286,14 @@ angular.scenario.dsl('select', function() {
|
||||
* Usage:
|
||||
* element(selector, label).count() get the number of elements that match selector
|
||||
* element(selector, label).click() clicks an element
|
||||
* element(selector, label).attr(name) gets the value of an attribute
|
||||
* element(selector, label).attr(name, value) sets the value of an attribute
|
||||
* element(selector, label).val() gets the value (as defined by jQuery)
|
||||
* element(selector, label).val(value) sets the value (as defined by jQuery)
|
||||
* element(selector, label).query(fn) executes fn(selectedElements, done)
|
||||
* element(selector, label).{method}() gets the value (as defined by jQuery, ex. val)
|
||||
* element(selector, label).{method}(value) sets the value (as defined by jQuery, ex. val)
|
||||
* element(selector, label).{method}(key) gets the value (as defined by jQuery, ex. attr)
|
||||
* element(selector, label).{method}(key, value) sets the value (as defined by jQuery, ex. attr)
|
||||
*/
|
||||
angular.scenario.dsl('element', function() {
|
||||
var KEY_VALUE_METHODS = ['attr', 'css'];
|
||||
var VALUE_METHODS = [
|
||||
'val', 'text', 'html', 'height', 'innerHeight', 'outerHeight', 'width',
|
||||
'innerWidth', 'outerWidth', 'position', 'scrollLeft', 'scrollTop', 'offset'
|
||||
@@ -376,22 +325,25 @@ angular.scenario.dsl('element', function() {
|
||||
});
|
||||
};
|
||||
|
||||
chain.attr = function(name, value) {
|
||||
var futureName = "element '" + this.label + "' get attribute '" + name + "'";
|
||||
if (angular.isDefined(value)) {
|
||||
futureName = "element '" + this.label + "' set attribute '" + name + "' to " + "'" + value + "'";
|
||||
}
|
||||
return this.addFutureAction(futureName, function($window, $document, done) {
|
||||
done(null, $document.elements().attr(name, value));
|
||||
});
|
||||
};
|
||||
|
||||
chain.query = function(fn) {
|
||||
return this.addFutureAction('element ' + this.label + ' custom query', function($window, $document, done) {
|
||||
fn.call(this, $document.elements(), done);
|
||||
});
|
||||
};
|
||||
|
||||
angular.foreach(KEY_VALUE_METHODS, function(methodName) {
|
||||
chain[methodName] = function(name, value) {
|
||||
var futureName = "element '" + this.label + "' get " + methodName + " '" + name + "'";
|
||||
if (angular.isDefined(value)) {
|
||||
futureName = "element '" + this.label + "' set " + methodName + " '" + name + "' to " + "'" + value + "'";
|
||||
}
|
||||
return this.addFutureAction(futureName, function($window, $document, done) {
|
||||
var element = $document.elements();
|
||||
done(null, element[methodName].call(element, name, value));
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
angular.foreach(VALUE_METHODS, function(methodName) {
|
||||
chain[methodName] = function(value) {
|
||||
var futureName = "element '" + this.label + "' " + methodName;
|
||||
|
||||
+570
-32
@@ -8,11 +8,67 @@ function angularServiceInject(name, fn, inject, eager) {
|
||||
angularService(name, fn, {$inject:inject, $creation:eager});
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$window
|
||||
*
|
||||
* @description
|
||||
* Is reference to the browser's <b>window</b> object. While <b>window</b>
|
||||
* is globally available in JavaScript, it causes testability problems, because
|
||||
* it is a global variable. In <b><angular/></b> we always refer to it through the
|
||||
* $window service, so it may be overriden, removed or mocked for testing.
|
||||
*
|
||||
* All expressions are evaluated with respect to current scope so they don't
|
||||
* suffer from window globality.
|
||||
*
|
||||
* @example
|
||||
<input ng:init="greeting='Hello World!'" type="text" name="greeting" />
|
||||
<button ng:click="$window.alert(greeting)">ALERT</button>
|
||||
*/
|
||||
angularServiceInject("$window", bind(window, identity, window), [], EAGER_PUBLISHED);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$document
|
||||
* @requires $window
|
||||
*
|
||||
* @description
|
||||
* Reference to the browser window.document, but wrapped into angular.element().
|
||||
*/
|
||||
angularServiceInject("$document", function(window){
|
||||
return jqLite(window.document);
|
||||
}, ['$window'], EAGER_PUBLISHED);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$location
|
||||
* @requires $browser
|
||||
*
|
||||
* @property {string} href
|
||||
* @property {string} protocol
|
||||
* @property {string} host
|
||||
* @property {number} port
|
||||
* @property {string} path
|
||||
* @property {Object.<string|boolean>} search
|
||||
* @property {string} hash
|
||||
* @property {string} hashPath
|
||||
* @property {Object.<string|boolean>} hashSearch
|
||||
*
|
||||
* @description
|
||||
* Parses the browser location url and makes it available to your application.
|
||||
* Any changes to the url are reflected into $location service and changes to
|
||||
* $location are reflected to url.
|
||||
* Notice that using browser's forward/back buttons changes the $location.
|
||||
*
|
||||
* @example
|
||||
<a href="#">clear hash</a> |
|
||||
<a href="#myPath?name=misko">test hash</a><br/>
|
||||
<input type='text' name="$location.hash"/>
|
||||
<pre>$location = {{$location}}</pre>
|
||||
*/
|
||||
angularServiceInject("$location", function(browser) {
|
||||
var scope = this,
|
||||
location = {toString:toString, update:update, updateHash: updateHash},
|
||||
@@ -39,6 +95,12 @@ angularServiceInject("$location", function(browser) {
|
||||
// PUBLIC METHODS
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#update
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* Update location object
|
||||
* Does not immediately update the browser
|
||||
* Browser is updated at the end of $eval()
|
||||
@@ -69,7 +131,13 @@ angularServiceInject("$location", function(browser) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update location hash
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#updateHash
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* Update location hash part
|
||||
* @see update()
|
||||
*
|
||||
* @example
|
||||
@@ -99,9 +167,13 @@ angularServiceInject("$location", function(browser) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#toString
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* Returns string representation - href
|
||||
*
|
||||
* @return {string} Location's href property
|
||||
*/
|
||||
function toString() {
|
||||
updateLocation();
|
||||
@@ -176,7 +248,9 @@ angularServiceInject("$location", function(browser) {
|
||||
*/
|
||||
function composeHash(loc) {
|
||||
var hashSearch = toKeyValue(loc.hashSearch);
|
||||
return escape(loc.hashPath) + (hashSearch ? '?' + hashSearch : '');
|
||||
//TODO: temporary fix for issue #158
|
||||
return escape(loc.hashPath).replace(/%21/gi, '!').replace(/%3A/gi, ':').replace(/%24/gi, '$') +
|
||||
(hashSearch ? '?' + hashSearch : '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,23 +298,127 @@ angularServiceInject("$location", function(browser) {
|
||||
}, ['$browser'], EAGER_PUBLISHED);
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$log
|
||||
* @requires $window
|
||||
*
|
||||
* @description
|
||||
* Is simple service for logging. Default implementation writes the message
|
||||
* into the browser's console (if present).
|
||||
*
|
||||
* This is useful for debugging.
|
||||
*
|
||||
* @example
|
||||
<p>Reload this page with open console, enter text and hit the log button...</p>
|
||||
Message:
|
||||
<input type="text" name="message" value="Hello World!"/>
|
||||
<button ng:click="$log.log(message)">log</button>
|
||||
<button ng:click="$log.warn(message)">warn</button>
|
||||
<button ng:click="$log.info(message)">info</button>
|
||||
<button ng:click="$log.error(message)">error</button>
|
||||
*/
|
||||
angularServiceInject("$log", function($window){
|
||||
var console = $window.console || {log: noop, warn: noop, info: noop, error: noop},
|
||||
log = console.log || noop;
|
||||
return {
|
||||
log: bind(console, log),
|
||||
warn: bind(console, console.warn || log),
|
||||
info: bind(console, console.info || log),
|
||||
error: bind(console, console.error || log)
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$log#log
|
||||
* @methodOf angular.service.$log
|
||||
*
|
||||
* @description
|
||||
* Write a log message
|
||||
*/
|
||||
log: consoleLog('log'),
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$log#warn
|
||||
* @methodOf angular.service.$log
|
||||
*
|
||||
* @description
|
||||
* Write a warning message
|
||||
*/
|
||||
warn: consoleLog('warn'),
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$log#info
|
||||
* @methodOf angular.service.$log
|
||||
*
|
||||
* @description
|
||||
* Write an information message
|
||||
*/
|
||||
info: consoleLog('info'),
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$log#error
|
||||
* @methodOf angular.service.$log
|
||||
*
|
||||
* @description
|
||||
* Write an error message
|
||||
*/
|
||||
error: consoleLog('error')
|
||||
};
|
||||
|
||||
function consoleLog(type) {
|
||||
var console = $window.console || {};
|
||||
var logFn = console[type] || console.log || noop;
|
||||
if (logFn.apply) {
|
||||
return function(){
|
||||
var args = [];
|
||||
foreach(arguments, function(arg){
|
||||
args.push(formatError(arg));
|
||||
});
|
||||
return logFn.apply(console, args);
|
||||
};
|
||||
} else {
|
||||
// we are IE, in which case there is nothing we can do
|
||||
return logFn;
|
||||
}
|
||||
}
|
||||
}, ['$window'], EAGER_PUBLISHED);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$exceptionHandler
|
||||
* @requires $log
|
||||
*
|
||||
* @description
|
||||
* Any uncaught exception in <angular/> is delegated to this service.
|
||||
* The default implementation simply delegates to $log.error which logs it into
|
||||
* the browser console.
|
||||
*
|
||||
* When unit testing it is useful to have uncaught exceptions propagate
|
||||
* to the test so the test will fail rather than silently log the exception
|
||||
* to the browser console. For this purpose you can override this service with
|
||||
* a simple rethrow.
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$exceptionHandler', function($log){
|
||||
return function(e) {
|
||||
$log.error(e);
|
||||
};
|
||||
}, ['$log'], EAGER_PUBLISHED);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$hover
|
||||
* @requires $browser
|
||||
* @requires $document
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject("$hover", function(browser, document) {
|
||||
var tooltip, self = this, error, width = 300, arrowWidth = 10, body = jqLite(document[0].body);
|
||||
browser.hover(function(element, show){
|
||||
@@ -287,9 +465,16 @@ angularServiceInject("$hover", function(browser, document) {
|
||||
});
|
||||
}, ['$browser', '$document'], EAGER);
|
||||
|
||||
|
||||
/* Keeps references to all invalid widgets found during validation. Can be queried to find if there
|
||||
* are invalid widgets currently displayed
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$invalidWidgets
|
||||
*
|
||||
* @description
|
||||
* Keeps references to all invalid widgets found during validation.
|
||||
* Can be queried to find whether there are any invalid widgets currently displayed.
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject("$invalidWidgets", function(){
|
||||
var invalidWidgets = [];
|
||||
@@ -373,7 +558,60 @@ function switchRouteMatcher(on, when, dstName) {
|
||||
return match ? dst : _null;
|
||||
}
|
||||
|
||||
angularServiceInject('$route', function(location){
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$route
|
||||
* @requires $location
|
||||
*
|
||||
* @property {Object} current Name of the current route
|
||||
* @property {Array.<Object>} routes List of configured routes
|
||||
*
|
||||
* @description
|
||||
* Watches $location.hashPath and tries to map the hash to an existing route
|
||||
* definition. It is used for deep-linking URLs to controllers and views (HTML partials).
|
||||
*
|
||||
* $route is typically used in conjunction with ng:include widget.
|
||||
*
|
||||
* @example
|
||||
<p>
|
||||
This example shows how changing the URL hash causes the <tt>$route</tt>
|
||||
to match a route against the URL, and the <tt>[[ng:include]]</tt> pulls in the partial.
|
||||
Try changing the URL in the input box to see changes.
|
||||
</p>
|
||||
|
||||
<script>
|
||||
angular.service('myApp', function($route) {
|
||||
$route.when('/Book/:bookId', {template:'rsrc/book.html', controller:BookCntl});
|
||||
$route.when('/Book/:bookId/ch/:chapterId', {template:'rsrc/chapter.html', controller:ChapterCntl});
|
||||
$route.onChange(function() {
|
||||
$route.current.scope.params = $route.current.params;
|
||||
});
|
||||
}, {$inject: ['$route']});
|
||||
|
||||
function BookCntl() {
|
||||
this.name = "BookCntl";
|
||||
}
|
||||
|
||||
function ChapterCntl() {
|
||||
this.name = "ChapterCntl";
|
||||
}
|
||||
</script>
|
||||
|
||||
Chose:
|
||||
<a href="#/Book/Moby">Moby</a> |
|
||||
<a href="#/Book/Moby/ch/1">Moby: Ch1</a> |
|
||||
<a href="#/Book/Gatsby">Gatsby</a> |
|
||||
<a href="#/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a><br/>
|
||||
<input type="text" name="$location.hashPath" size="80" />
|
||||
<pre>$location={{$location}}</pre>
|
||||
<pre>$route.current.template={{$route.current.template}}</pre>
|
||||
<pre>$route.current.params={{$route.current.params}}</pre>
|
||||
<pre>$route.current.scope.name={{$route.current.scope.name}}</pre>
|
||||
<hr/>
|
||||
<ng:include src="$route.current.template" scope="$route.current.scope"/>
|
||||
*/
|
||||
angularServiceInject('$route', function(location) {
|
||||
var routes = {},
|
||||
onChange = [],
|
||||
matcher = switchRouteMatcher,
|
||||
@@ -381,8 +619,35 @@ angularServiceInject('$route', function(location){
|
||||
dirty = 0,
|
||||
$route = {
|
||||
routes: routes,
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$route#onChange
|
||||
* @methodOf angular.service.$route
|
||||
*
|
||||
* @param {function()} fn Function that will be called on route change
|
||||
*
|
||||
* @description
|
||||
* Register a handler function that will be called when route changes
|
||||
*/
|
||||
onChange: bind(onChange, onChange.push),
|
||||
when:function (path, params){
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$route#when
|
||||
* @methodOf angular.service.$route
|
||||
*
|
||||
* @param {string} path Route path (matched against $location.hash)
|
||||
* @param {Object} params Mapping information to be assigned to `$route.current` on route
|
||||
* match.
|
||||
* @returns {Object} route object
|
||||
*
|
||||
* @description
|
||||
* Add new route
|
||||
*/
|
||||
when:function (path, params) {
|
||||
if (angular.isUndefined(path)) return routes;
|
||||
var route = routes[path];
|
||||
if (!route) route = routes[path] = {};
|
||||
@@ -415,6 +680,18 @@ angularServiceInject('$route', function(location){
|
||||
return $route;
|
||||
}, ['$location'], EAGER_PUBLISHED);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$xhr
|
||||
* @requires $browser
|
||||
* @requires $xhr.error
|
||||
* @requires $log
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$xhr', function($browser, $error, $log){
|
||||
var self = this;
|
||||
return function(method, url, post, callback){
|
||||
@@ -446,12 +723,34 @@ angularServiceInject('$xhr', function($browser, $error, $log){
|
||||
};
|
||||
}, ['$browser', '$xhr.error', '$log']);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$xhr.error
|
||||
* @requires $log
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$xhr.error', function($log){
|
||||
return function(request, response){
|
||||
$log.error('ERROR: XHR: ' + request.url, request, response);
|
||||
};
|
||||
}, ['$log']);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$xhr.bulk
|
||||
* @requires $xhr
|
||||
* @requires $xhr.error
|
||||
* @requires $log
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
|
||||
var requests = [],
|
||||
scope = this;
|
||||
@@ -502,7 +801,51 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
|
||||
return bulkXHR;
|
||||
}, ['$xhr', '$xhr.error', '$log']);
|
||||
|
||||
angularServiceInject('$xhr.cache', function($xhr){
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$defer
|
||||
* @requires $browser
|
||||
* @requires $log
|
||||
*
|
||||
* @description
|
||||
* Delegates to {@link angular.service.$browser.defer $browser.defer}, but wraps the `fn` function
|
||||
* into a try/catch block and delegates any exceptions to
|
||||
* {@link angular.services.$exceptionHandler $exceptionHandler} service.
|
||||
*
|
||||
* In tests you can use `$browser.defer.flush()` to flush the queue of deferred functions.
|
||||
*
|
||||
* @param {function()} fn A function, who's execution should be deferred.
|
||||
*/
|
||||
angularServiceInject('$defer', function($browser, $exceptionHandler) {
|
||||
var scope = this;
|
||||
|
||||
return function(fn) {
|
||||
$browser.defer(function() {
|
||||
try {
|
||||
fn();
|
||||
} catch(e) {
|
||||
$exceptionHandler(e);
|
||||
} finally {
|
||||
scope.$eval();
|
||||
}
|
||||
});
|
||||
};
|
||||
}, ['$browser', '$exceptionHandler']);
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$xhr.cache
|
||||
* @requires $xhr
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$xhr.cache', function($xhr, $defer){
|
||||
var inflight = {}, self = this;
|
||||
function cache(method, url, post, callback, verifyCache){
|
||||
if (isFunction(post)) {
|
||||
@@ -510,9 +853,9 @@ angularServiceInject('$xhr.cache', function($xhr){
|
||||
post = _null;
|
||||
}
|
||||
if (method == 'GET') {
|
||||
var data;
|
||||
if (data = cache.data[url]) {
|
||||
callback(200, copy(data.value));
|
||||
var data, dataCached;
|
||||
if (dataCached = cache.data[url]) {
|
||||
$defer(function() { callback(200, copy(dataCached.value)); });
|
||||
if (!verifyCache)
|
||||
return;
|
||||
}
|
||||
@@ -544,20 +887,173 @@ angularServiceInject('$xhr.cache', function($xhr){
|
||||
cache.data = {};
|
||||
cache.delegate = $xhr;
|
||||
return cache;
|
||||
}, ['$xhr.bulk']);
|
||||
}, ['$xhr.bulk', '$defer']);
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.service.$resource
|
||||
* @requires $xhr
|
||||
*
|
||||
* @description
|
||||
* Is a factory which creates a resource object which lets you interact with
|
||||
* <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer" target="_blank">RESTful</a>
|
||||
* server-side data sources.
|
||||
* Resource object has action methods which provide high-level behaviors without
|
||||
* the need to interact with the low level $xhr or XMLHttpRequest().
|
||||
*
|
||||
* <pre>
|
||||
// Define CreditCard class
|
||||
var CreditCard = $resource('/user/:userId/card/:cardId',
|
||||
{userId:123, cardId:'@id'}, {
|
||||
charge: {method:'POST', params:{charge:true}}
|
||||
});
|
||||
|
||||
// We can retrieve a collection from the server
|
||||
var cards = CreditCard.query();
|
||||
// GET: /user/123/card
|
||||
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
||||
|
||||
var card = cards[0];
|
||||
// each item is an instance of CreditCard
|
||||
expect(card instanceof CreditCard).toEqual(true);
|
||||
card.name = "J. Smith";
|
||||
// non GET methods are mapped onto the instances
|
||||
card.$save();
|
||||
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
||||
|
||||
// our custom method is mapped as well.
|
||||
card.$charge({amount:9.99});
|
||||
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
||||
|
||||
// we can create an instance as well
|
||||
var newCard = new CreditCard({number:'0123'});
|
||||
newCard.name = "Mike Smith";
|
||||
newCard.$save();
|
||||
// POST: /user/123/card {number:'0123', name:'Mike Smith'}
|
||||
// server returns: {id:789, number:'01234', name: 'Mike Smith'};
|
||||
expect(newCard.id).toEqual(789);
|
||||
* </pre>
|
||||
*
|
||||
* The object returned from this function execution is a resource "class" which has "static" method
|
||||
* for each action in the definition.
|
||||
*
|
||||
* Calling these methods invoke `$xhr` on the `url` template with the given `method` and `params`.
|
||||
* When the data is returned from the server then the object is an instance of the resource type and
|
||||
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
|
||||
* operations (create, read, update, delete) on server-side data.
|
||||
|
||||
<pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
var user = User.get({userId:123}, function(){
|
||||
user.abc = true;
|
||||
user.$save();
|
||||
});
|
||||
</pre>
|
||||
*
|
||||
* It's worth noting that the callback for `get`, `query` and other method gets passed in the
|
||||
* response that came from the server, so one could rewrite the above example as:
|
||||
*
|
||||
<pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
User.get({userId:123}, function(u){
|
||||
u.abc = true;
|
||||
u.$save();
|
||||
});
|
||||
</pre>
|
||||
*
|
||||
*
|
||||
* @param {string} url A parameterized URL template with parameters prefixed by `:` as in
|
||||
* `/user/:username`.
|
||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
||||
* `actions` methods.
|
||||
* @param {Object.<Object>=} actions Map of actions available for the resource.
|
||||
*
|
||||
* Each resource comes preconfigured with `get`, `save`, `query`, `remove`, and `delete` to
|
||||
* mimic the RESTful philosophy.
|
||||
*
|
||||
* To create your own actions, pass in a map keyed on action names (e.g. `'charge'`) with
|
||||
* elements consisting of these properties:
|
||||
*
|
||||
* - `{string} method`: Request method type. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
|
||||
* and [`JSON`](http://en.wikipedia.org/wiki/JSON#JSONP) (also known as JSONP).
|
||||
* - `{Object=} params`: Set of pre-bound parameters for the action.
|
||||
* - `{boolean=} isArray`: If true then the returned object for this action is an array, see the
|
||||
* pre-binding section.
|
||||
* - `{boolean=} verifyCache`: If true then items returned from cache, are double checked by
|
||||
* running the query again and updating the resource asynchroniously.
|
||||
*
|
||||
* Each service comes preconfigured with the following overridable actions:
|
||||
* <pre>
|
||||
* { 'get': {method:'GET'},
|
||||
'save': {method:'POST'},
|
||||
'query': {method:'GET', isArray:true},
|
||||
'remove': {method:'DELETE'},
|
||||
'delete': {method:'DELETE'} };
|
||||
* </pre>
|
||||
*
|
||||
* @returns {Object} A resource "class".
|
||||
*
|
||||
* @example
|
||||
<script>
|
||||
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});
|
||||
}
|
||||
};
|
||||
BuzzController.$inject = ['$resource'];
|
||||
</script>
|
||||
|
||||
<div ng:controller="BuzzController">
|
||||
<input name="userId" value="googlebuzz"/>
|
||||
<button ng:click="fetch()">fetch</button>
|
||||
<hr/>
|
||||
<div 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 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>
|
||||
*/
|
||||
angularServiceInject('$resource', function($xhr){
|
||||
var resource = new ResourceFactory($xhr);
|
||||
return bind(resource, resource.route);
|
||||
}, ['$xhr.cache']);
|
||||
|
||||
|
||||
/**
|
||||
* $cookies service provides read/write access to the browser cookies. Currently only session
|
||||
* cookies are supported.
|
||||
*
|
||||
* Only a simple Object is exposed and by adding or removing properties to/from this object, new
|
||||
* cookies are created or deleted from the browser at the end of the current eval.
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$cookies
|
||||
* @requires $browser
|
||||
*
|
||||
* @description
|
||||
* Provides read/write access to browser's cookies.
|
||||
*
|
||||
* Only a simple Object is exposed and by adding or removing properties to/from
|
||||
* this object, new cookies are created/deleted at the end of current $eval.
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$cookies', function($browser) {
|
||||
var rootScope = this,
|
||||
@@ -630,23 +1126,65 @@ angularServiceInject('$cookies', function($browser) {
|
||||
}
|
||||
}, ['$browser'], EAGER_PUBLISHED);
|
||||
|
||||
|
||||
/**
|
||||
* $cookieStore provides a key-value (string-object) storage that is backed by session cookies.
|
||||
* Objects put or retrieved from this storage are automatically serialized or deserialized.
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$cookieStore
|
||||
* @requires $cookies
|
||||
*
|
||||
* @description
|
||||
* Provides a key-value (string-object) storage, that is backed by session cookies.
|
||||
* Objects put or retrieved from this storage are automatically serialized or
|
||||
* deserialized by angular's toJson/fromJson.
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$cookieStore', function($store) {
|
||||
|
||||
return {
|
||||
get: function(/**string*/key) {
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$cookieStore#get
|
||||
* @methodOf angular.service.$cookieStore
|
||||
*
|
||||
* @description
|
||||
* Returns the value of given cookie key
|
||||
*
|
||||
* @param {string} key Id to use for lookup.
|
||||
* @returns {Object} Deserialized cookie value.
|
||||
*/
|
||||
get: function(key) {
|
||||
return fromJson($store[key]);
|
||||
},
|
||||
|
||||
put: function(/**string*/key, /**Object*/value) {
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$cookieStore#put
|
||||
* @methodOf angular.service.$cookieStore
|
||||
*
|
||||
* @description
|
||||
* Sets a value for given cookie key
|
||||
*
|
||||
* @param {string} key Id for the `value`.
|
||||
* @param {Object} value Value to be stored.
|
||||
*/
|
||||
put: function(key, value) {
|
||||
$store[key] = toJson(value);
|
||||
},
|
||||
|
||||
remove: function(/**string*/key) {
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$cookieStore#remove
|
||||
* @methodOf angular.service.$cookieStore
|
||||
*
|
||||
* @description
|
||||
* Remove given cookie
|
||||
*
|
||||
* @param {string} key Id of the key-value pair to delete.
|
||||
*/
|
||||
remove: function(key) {
|
||||
delete $store[key];
|
||||
}
|
||||
};
|
||||
|
||||
+285
-10
@@ -1,6 +1,33 @@
|
||||
foreach({
|
||||
extend(angularValidator, {
|
||||
'noop': function() { return _null; },
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc validator
|
||||
* @name angular.validator.regexp
|
||||
* @description
|
||||
* Use regexp validator to restrict the input to any Regular Expression.
|
||||
*
|
||||
* @param {string} value value to validate
|
||||
* @param {regexp} expression regular expression.
|
||||
* @css ng-validation-error
|
||||
*
|
||||
* @example
|
||||
* <script> var ssn = /^\d\d\d-\d\d-\d\d\d\d$/; </script>
|
||||
* Enter valid SSN:
|
||||
* <input name="ssn" value="123-45-6789" ng:validate="regexp:$window.ssn" >
|
||||
*
|
||||
* @scenario
|
||||
* it('should invalidate non ssn', function(){
|
||||
* var textBox = element('.doc-example :input');
|
||||
* expect(textBox.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* expect(textBox.val()).toEqual('123-45-6789');
|
||||
*
|
||||
* input('ssn').enter('123-45-67890');
|
||||
* expect(textBox.attr('className')).toMatch(/ng-validation-error/);
|
||||
* });
|
||||
*
|
||||
*/
|
||||
'regexp': function(value, regexp, msg) {
|
||||
if (!value.match(regexp)) {
|
||||
return msg ||
|
||||
@@ -10,6 +37,44 @@ foreach({
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc validator
|
||||
* @name angular.validator.number
|
||||
* @description
|
||||
* Use number validator to restrict the input to numbers with an
|
||||
* optional range. (See integer for whole numbers validator).
|
||||
*
|
||||
* @param {string} value value to validate
|
||||
* @param {int=} [min=MIN_INT] minimum value.
|
||||
* @param {int=} [max=MAX_INT] maximum value.
|
||||
* @css ng-validation-error
|
||||
*
|
||||
* @example
|
||||
* Enter number: <input name="n1" ng:validate="number" > <br>
|
||||
* Enter number greater than 10: <input name="n2" ng:validate="number:10" > <br>
|
||||
* Enter number between 100 and 200: <input name="n3" ng:validate="number:100:200" > <br>
|
||||
*
|
||||
* @scenario
|
||||
* it('should invalidate number', function(){
|
||||
* var n1 = element('.doc-example :input[name=n1]');
|
||||
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* input('n1').enter('1.x');
|
||||
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
*
|
||||
* var n2 = element('.doc-example :input[name=n2]');
|
||||
* expect(n2.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* input('n2').enter('9');
|
||||
* expect(n2.attr('className')).toMatch(/ng-validation-error/);
|
||||
*
|
||||
* var n3 = element('.doc-example :input[name=n3]');
|
||||
* expect(n3.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* input('n3').enter('201');
|
||||
* expect(n3.attr('className')).toMatch(/ng-validation-error/);
|
||||
*
|
||||
* });
|
||||
*
|
||||
*/
|
||||
'number': function(value, min, max) {
|
||||
var num = 1 * value;
|
||||
if (num == value) {
|
||||
@@ -25,6 +90,43 @@ foreach({
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc validator
|
||||
* @name angular.validator.integer
|
||||
* @description
|
||||
* Use number validator to restrict the input to integers with an
|
||||
* optional range. (See integer for whole numbers validator).
|
||||
*
|
||||
* @param {string} value value to validate
|
||||
* @param {int=} [min=MIN_INT] minimum value.
|
||||
* @param {int=} [max=MAX_INT] maximum value.
|
||||
* @css ng-validation-error
|
||||
*
|
||||
* @example
|
||||
* Enter integer: <input name="n1" ng:validate="integer" > <br>
|
||||
* Enter integer equal or greater than 10: <input name="n2" ng:validate="integer:10" > <br>
|
||||
* Enter integer between 100 and 200 (inclusive): <input name="n3" ng:validate="integer:100:200" > <br>
|
||||
*
|
||||
* @scenario
|
||||
* it('should invalidate integer', function(){
|
||||
* var n1 = element('.doc-example :input[name=n1]');
|
||||
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* input('n1').enter('1.1');
|
||||
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
*
|
||||
* var n2 = element('.doc-example :input[name=n2]');
|
||||
* expect(n2.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* input('n2').enter('10.1');
|
||||
* expect(n2.attr('className')).toMatch(/ng-validation-error/);
|
||||
*
|
||||
* var n3 = element('.doc-example :input[name=n3]');
|
||||
* expect(n3.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* input('n3').enter('100.1');
|
||||
* expect(n3.attr('className')).toMatch(/ng-validation-error/);
|
||||
*
|
||||
* });
|
||||
*/
|
||||
'integer': function(value, min, max) {
|
||||
var numberError = angularValidator['number'](value, min, max);
|
||||
if (numberError) return numberError;
|
||||
@@ -34,6 +136,30 @@ foreach({
|
||||
return _null;
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc validator
|
||||
* @name angular.validator.date
|
||||
* @description
|
||||
* Use date validator to restrict the user input to a valid date
|
||||
* in format in format MM/DD/YYYY.
|
||||
*
|
||||
* @param {string} value value to validate
|
||||
* @css ng-validation-error
|
||||
*
|
||||
* @example
|
||||
* Enter valid date:
|
||||
* <input name="text" value="1/1/2009" ng:validate="date" >
|
||||
*
|
||||
* @scenario
|
||||
* it('should invalidate date', function(){
|
||||
* var n1 = element('.doc-example :input');
|
||||
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* input('text').enter('123/123/123');
|
||||
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
* });
|
||||
*
|
||||
*/
|
||||
'date': function(value) {
|
||||
var fields = /^(\d\d?)\/(\d\d?)\/(\d\d\d\d)$/.exec(value);
|
||||
var date = fields ? new Date(fields[3], fields[1]-1, fields[2]) : 0;
|
||||
@@ -44,13 +170,29 @@ foreach({
|
||||
_null : "Value is not a date. (Expecting format: 12/31/2009).";
|
||||
},
|
||||
|
||||
'ssn': function(value) {
|
||||
if (value.match(/^\d\d\d-\d\d-\d\d\d\d$/)) {
|
||||
return _null;
|
||||
}
|
||||
return "SSN needs to be in 999-99-9999 format.";
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc validator
|
||||
* @name angular.validator.email
|
||||
* @description
|
||||
* Use email validator if you wist to restrict the user input to a valid email.
|
||||
*
|
||||
* @param {string} value value to validate
|
||||
* @css ng-validation-error
|
||||
*
|
||||
* @example
|
||||
* Enter valid email:
|
||||
* <input name="text" ng:validate="email" value="me@example.com">
|
||||
*
|
||||
* @scenario
|
||||
* it('should invalidate email', function(){
|
||||
* var n1 = element('.doc-example :input');
|
||||
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* input('text').enter('a@b.c');
|
||||
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
* });
|
||||
*
|
||||
*/
|
||||
'email': function(value) {
|
||||
if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) {
|
||||
return _null;
|
||||
@@ -58,6 +200,29 @@ foreach({
|
||||
return "Email needs to be in username@host.com format.";
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc validator
|
||||
* @name angular.validator.phone
|
||||
* @description
|
||||
* Use phone validator to restrict the input phone numbers.
|
||||
*
|
||||
* @param {string} value value to validate
|
||||
* @css ng-validation-error
|
||||
*
|
||||
* @example
|
||||
* Enter valid phone number:
|
||||
* <input name="text" value="1(234)567-8901" ng:validate="phone" >
|
||||
*
|
||||
* @scenario
|
||||
* it('should invalidate phone', function(){
|
||||
* var n1 = element('.doc-example :input');
|
||||
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* input('text').enter('+12345678');
|
||||
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
* });
|
||||
*
|
||||
*/
|
||||
'phone': function(value) {
|
||||
if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) {
|
||||
return _null;
|
||||
@@ -68,6 +233,29 @@ foreach({
|
||||
return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly.";
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc validator
|
||||
* @name angular.validator.url
|
||||
* @description
|
||||
* Use phone validator to restrict the input URLs.
|
||||
*
|
||||
* @param {string} value value to validate
|
||||
* @css ng-validation-error
|
||||
*
|
||||
* @example
|
||||
* Enter valid phone number:
|
||||
* <input name="text" value="http://example.com/abc.html" size="40" ng:validate="url" >
|
||||
*
|
||||
* @scenario
|
||||
* it('should invalidate url', function(){
|
||||
* var n1 = element('.doc-example :input');
|
||||
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* input('text').enter('abc://server/path');
|
||||
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
* });
|
||||
*
|
||||
*/
|
||||
'url': function(value) {
|
||||
if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) {
|
||||
return _null;
|
||||
@@ -75,6 +263,30 @@ foreach({
|
||||
return "URL needs to be in http://server[:port]/path format.";
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc validator
|
||||
* @name angular.validator.json
|
||||
* @description
|
||||
* Use json validator if you wish to restrict the user input to a valid JSON.
|
||||
*
|
||||
* @param {string} value value to validate
|
||||
* @css ng-validation-error
|
||||
*
|
||||
* @example
|
||||
* <textarea name="json" cols="60" rows="5" ng:validate="json">
|
||||
* {name:'abc'}
|
||||
* </textarea>
|
||||
*
|
||||
* @scenario
|
||||
* it('should invalidate json', function(){
|
||||
* var n1 = element('.doc-example :input');
|
||||
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
* input('json').enter('{name}');
|
||||
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
* });
|
||||
*
|
||||
*/
|
||||
'json': function(value) {
|
||||
try {
|
||||
fromJson(value);
|
||||
@@ -84,6 +296,69 @@ foreach({
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc validator
|
||||
* @name angular.validator.asynchronous
|
||||
* @description
|
||||
* Use asynchronous validator if the validation can not be computed
|
||||
* immediately, but is provided through a callback. The widget
|
||||
* automatically shows a spinning indicator while the validity of
|
||||
* the widget is computed. This validator caches the result.
|
||||
*
|
||||
* @param {string} value value to validate
|
||||
* @param {function(inputToValidate,validationDone)} validate function to call to validate the state
|
||||
* of the input.
|
||||
* @param {function(data)=} [update=noop] function to call when state of the
|
||||
* validator changes
|
||||
*
|
||||
* @paramDescription
|
||||
* The `validate` function (specified by you) is called as
|
||||
* `validate(inputToValidate, validationDone)`:
|
||||
*
|
||||
* * `inputToValidate`: value of the input box.
|
||||
* * `validationDone`: `function(error, data){...}`
|
||||
* * `error`: error text to display if validation fails
|
||||
* * `data`: data object to pass to update function
|
||||
*
|
||||
* The `update` function is optionally specified by you and is
|
||||
* called by <angular/> on input change. Since the
|
||||
* asynchronous validator caches the results, the update
|
||||
* function can be called without a call to `validate`
|
||||
* function. The function is called as `update(data)`:
|
||||
*
|
||||
* * `data`: data object as passed from validate function
|
||||
*
|
||||
* @css ng-input-indicator-wait, ng-validation-error
|
||||
*
|
||||
* @example
|
||||
* <script>
|
||||
* function myValidator(inputToValidate, validationDone) {
|
||||
* setTimeout(function(){
|
||||
* validationDone(inputToValidate.length % 2);
|
||||
* }, 500);
|
||||
* }
|
||||
* </script>
|
||||
* This input is validated asynchronously:
|
||||
* <input name="text" ng:validate="asynchronous:$window.myValidator">
|
||||
*
|
||||
* @scenario
|
||||
* it('should change color in delayed way', function(){
|
||||
* var textBox = element('.doc-example :input');
|
||||
* expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/);
|
||||
* expect(textBox.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
*
|
||||
* input('text').enter('X');
|
||||
* expect(textBox.attr('className')).toMatch(/ng-input-indicator-wait/);
|
||||
*
|
||||
* pause(.6);
|
||||
*
|
||||
* expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/);
|
||||
* expect(textBox.attr('className')).toMatch(/ng-validation-error/);
|
||||
*
|
||||
* });
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* cache is attached to the element
|
||||
* cache: {
|
||||
@@ -120,7 +395,7 @@ foreach({
|
||||
element.removeClass('ng-input-indicator-wait');
|
||||
scope.$invalidWidgets.markValid(element);
|
||||
}
|
||||
element.data('$validate')();
|
||||
element.data($$validate)();
|
||||
scope.$root.$eval();
|
||||
});
|
||||
} else if (inputState.inFlight) {
|
||||
@@ -132,4 +407,4 @@ foreach({
|
||||
return inputState.error;
|
||||
}
|
||||
|
||||
}, function(v,k) {angularValidator[k] = v;});
|
||||
});
|
||||
|
||||
+564
-28
@@ -1,5 +1,135 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc widget
|
||||
* @name angular.widget.HTML
|
||||
*
|
||||
* @description
|
||||
* The most common widgets you will use will be in the from of the
|
||||
* standard HTML set. These widgets are bound using the name attribute
|
||||
* to an expression. In addition they can have `ng:validate`, `ng:required`,
|
||||
* `ng:format`, `ng:change` attribute to further control their behavior.
|
||||
*
|
||||
* @usageContent
|
||||
* see example below for usage
|
||||
*
|
||||
* <input type="text|checkbox|..." ... />
|
||||
* <textarea ... />
|
||||
* <select ...>
|
||||
* <option>...</option>
|
||||
* </select>
|
||||
*
|
||||
* @example
|
||||
<table style="font-size:.9em;">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Format</th>
|
||||
<th>HTML</th>
|
||||
<th>UI</th>
|
||||
<th ng:non-bindable>{{input#}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>text</th>
|
||||
<td>String</td>
|
||||
<td><tt><input type="text" name="input1"></tt></td>
|
||||
<td><input type="text" name="input1" size="4"></td>
|
||||
<td><tt>{{input1|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>textarea</th>
|
||||
<td>String</td>
|
||||
<td><tt><textarea name="input2"></textarea></tt></td>
|
||||
<td><textarea name="input2" cols='6'></textarea></td>
|
||||
<td><tt>{{input2|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>radio</th>
|
||||
<td>String</td>
|
||||
<td><tt>
|
||||
<input type="radio" name="input3" value="A"><br>
|
||||
<input type="radio" name="input3" value="B">
|
||||
</tt></td>
|
||||
<td>
|
||||
<input type="radio" name="input3" value="A">
|
||||
<input type="radio" name="input3" value="B">
|
||||
</td>
|
||||
<td><tt>{{input3|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>checkbox</th>
|
||||
<td>Boolean</td>
|
||||
<td><tt><input type="checkbox" name="input4" value="checked"></tt></td>
|
||||
<td><input type="checkbox" name="input4" value="checked"></td>
|
||||
<td><tt>{{input4|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>pulldown</th>
|
||||
<td>String</td>
|
||||
<td><tt>
|
||||
<select name="input5"><br>
|
||||
<option value="c">C</option><br>
|
||||
<option value="d">D</option><br>
|
||||
</select><br>
|
||||
</tt></td>
|
||||
<td>
|
||||
<select name="input5">
|
||||
<option value="c">C</option>
|
||||
<option value="d">D</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><tt>{{input5|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>multiselect</th>
|
||||
<td>Array</td>
|
||||
<td><tt>
|
||||
<select name="input6" multiple size="4"><br>
|
||||
<option value="e">E</option><br>
|
||||
<option value="f">F</option><br>
|
||||
</select><br>
|
||||
</tt></td>
|
||||
<td>
|
||||
<select name="input6" multiple size="4">
|
||||
<option value="e">E</option>
|
||||
<option value="f">F</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><tt>{{input6|json}}</tt></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
* @scenario
|
||||
* it('should exercise text', function(){
|
||||
* input('input1').enter('Carlos');
|
||||
* expect(binding('input1')).toEqual('"Carlos"');
|
||||
* });
|
||||
* it('should exercise textarea', function(){
|
||||
* input('input2').enter('Carlos');
|
||||
* expect(binding('input2')).toEqual('"Carlos"');
|
||||
* });
|
||||
* it('should exercise radio', function(){
|
||||
* expect(binding('input3')).toEqual('null');
|
||||
* input('input3').select('A');
|
||||
* expect(binding('input3')).toEqual('"A"');
|
||||
* input('input3').select('B');
|
||||
* expect(binding('input3')).toEqual('"B"');
|
||||
* });
|
||||
* it('should exercise checkbox', function(){
|
||||
* expect(binding('input4')).toEqual('false');
|
||||
* input('input4').check();
|
||||
* expect(binding('input4')).toEqual('true');
|
||||
* });
|
||||
* it('should exercise pulldown', function(){
|
||||
* expect(binding('input5')).toEqual('"c"');
|
||||
* select('input5').option('d');
|
||||
* expect(binding('input5')).toEqual('"d"');
|
||||
* });
|
||||
* it('should exercise multiselect', function(){
|
||||
* expect(binding('input6')).toEqual('[]');
|
||||
* select('input6').options('e');
|
||||
* expect(binding('input6')).toEqual('["e"]');
|
||||
* select('input6').options('e', 'f');
|
||||
* expect(binding('input6')).toEqual('["e","f"]');
|
||||
* });
|
||||
*/
|
||||
|
||||
function modelAccessor(scope, element) {
|
||||
@@ -36,6 +166,101 @@ function compileValidator(expr) {
|
||||
return parser(expr).validator()();
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc widget
|
||||
* @name angular.widget.@ng:validate
|
||||
*
|
||||
* @description
|
||||
* The `ng:validate` attribute widget validates the user input. If the input does not pass
|
||||
* validation, the `ng-validation-error` CSS class and the `ng:error` attribute are set on the input
|
||||
* element. Check out {@link angular.validator validators} to find out more.
|
||||
*
|
||||
* @param {string} validator The name of a built-in or custom {@link angular.validator validator} to
|
||||
* to be used.
|
||||
*
|
||||
* @element INPUT
|
||||
* @css ng-validation-error
|
||||
*
|
||||
* @exampleDescription
|
||||
* This example shows how the input element becomes red when it contains invalid input. Correct
|
||||
* the input to make the error disappear.
|
||||
*
|
||||
* @example
|
||||
I don't validate:
|
||||
<input type="text" name="value" value="NotANumber"><br/>
|
||||
|
||||
I need an integer or nothing:
|
||||
<input type="text" name="value" ng:validate="integer"><br/>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:validate', function(){
|
||||
expect(element('.doc-example-live :input:last').attr('className')).
|
||||
toMatch(/ng-validation-error/);
|
||||
|
||||
input('value').enter('123');
|
||||
expect(element('.doc-example-live :input:last').attr('className')).
|
||||
not().toMatch(/ng-validation-error/);
|
||||
});
|
||||
*/
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc widget
|
||||
* @name angular.widget.@ng:required
|
||||
*
|
||||
* @description
|
||||
* The `ng:required` attribute widget validates that the user input is present. It is a special case
|
||||
* of the {@link angular.widget.@ng:validate ng:validate} attribute widget.
|
||||
*
|
||||
* @element INPUT
|
||||
* @css ng-validation-error
|
||||
*
|
||||
* @exampleDescription
|
||||
* This example shows how the input element becomes red when it contains invalid input. Correct
|
||||
* the input to make the error disappear.
|
||||
*
|
||||
* @example
|
||||
I cannot be blank: <input type="text" name="value" ng:required><br/>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:required', function(){
|
||||
expect(element('.doc-example-live :input').attr('className')).toMatch(/ng-validation-error/);
|
||||
input('value').enter('123');
|
||||
expect(element('.doc-example-live :input').attr('className')).not().toMatch(/ng-validation-error/);
|
||||
});
|
||||
*/
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc widget
|
||||
* @name angular.widget.@ng:format
|
||||
*
|
||||
* @description
|
||||
* The `ng:format` attribute widget formats stored data to user-readable text and parses the text
|
||||
* back to the stored form. You might find this useful for example if you collect user input in a
|
||||
* text field but need to store the data in the model as a list. Check out
|
||||
* {@link angular.formatter formatters} to learn more.
|
||||
*
|
||||
* @param {string} formatter The name of the built-in or custom {@link angular.formatter formatter}
|
||||
* to be used.
|
||||
*
|
||||
* @element INPUT
|
||||
*
|
||||
* @exampleDescription
|
||||
* This example shows how the user input is converted from a string and internally represented as an
|
||||
* array.
|
||||
*
|
||||
* @example
|
||||
Enter a comma separated list of items:
|
||||
<input type="text" name="list" ng:format="list" value="table, chairs, plate">
|
||||
<pre>list={{list}}</pre>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:format', function(){
|
||||
expect(binding('list')).toBe('list=["table","chairs","plate"]');
|
||||
input('list').enter(',,, a ,,,');
|
||||
expect(binding('list')).toBe('list=["a"]');
|
||||
});
|
||||
*/
|
||||
function valueAccessor(scope, element) {
|
||||
var validatorName = element.attr('ng:validate') || NOOP,
|
||||
validator = compileValidator(validatorName),
|
||||
@@ -57,7 +282,7 @@ function valueAccessor(scope, element) {
|
||||
required = requiredExpr === '';
|
||||
}
|
||||
|
||||
element.data('$validate', validate);
|
||||
element.data($$validate, validate);
|
||||
return {
|
||||
get: function(){
|
||||
if (lastError)
|
||||
@@ -166,6 +391,7 @@ var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initW
|
||||
// 'file': fileWidget???
|
||||
};
|
||||
|
||||
|
||||
function initWidgetValue(initValue) {
|
||||
return function (model, view) {
|
||||
var value = view.get();
|
||||
@@ -191,6 +417,40 @@ function radioInit(model, view, element) {
|
||||
view.set(modelValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:change
|
||||
*
|
||||
* @description
|
||||
* The directive executes an expression whenever the input widget changes.
|
||||
*
|
||||
* @element INPUT
|
||||
* @param {expression} expression to execute.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
<div ng:init="checkboxCount=0; textCount=0"></div>
|
||||
<input type="text" name="text" ng:change="textCount = 1 + textCount">
|
||||
changeCount {{textCount}}<br/>
|
||||
<input type="checkbox" name="checkbox" ng:change="checkboxCount = 1 + checkboxCount">
|
||||
changeCount {{checkboxCount}}<br/>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:change', function(){
|
||||
expect(binding('textCount')).toBe('0');
|
||||
expect(binding('checkboxCount')).toBe('0');
|
||||
|
||||
using('.doc-example-live').input('text').enter('abc');
|
||||
expect(binding('textCount')).toBe('1');
|
||||
expect(binding('checkboxCount')).toBe('0');
|
||||
|
||||
|
||||
using('.doc-example-live').input('checkbox').check();
|
||||
expect(binding('textCount')).toBe('1');
|
||||
expect(binding('checkboxCount')).toBe('1');
|
||||
});
|
||||
*/
|
||||
function inputWidget(events, modelAccessor, viewAccessor, initFn) {
|
||||
return function(element) {
|
||||
var scope = this,
|
||||
@@ -202,18 +462,13 @@ function inputWidget(events, modelAccessor, viewAccessor, initFn) {
|
||||
this.$eval(element.attr('ng:init')||'');
|
||||
// Don't register a handler if we are a button (noopAccessor) and there is no action
|
||||
if (action || modelAccessor !== noopAccessor) {
|
||||
element.bind(events, function(event){
|
||||
element.bind(events, function (){
|
||||
model.set(view.get());
|
||||
lastValue = model.get();
|
||||
scope.$tryEval(action, element);
|
||||
scope.$root.$eval();
|
||||
});
|
||||
}
|
||||
function updateView(){
|
||||
view.set(lastValue = model.get());
|
||||
}
|
||||
updateView();
|
||||
element.data('$update', updateView);
|
||||
scope.$watch(model.get, function(value){
|
||||
if (lastValue !== value) {
|
||||
view.set(lastValue = value);
|
||||
@@ -235,29 +490,93 @@ angularWidget('select', function(element){
|
||||
return inputWidgetSelector.call(this, element);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Consider this:
|
||||
* <select name="selection">
|
||||
* <option ng:repeat="x in [1,2]">{{x}}</option>
|
||||
* </select>
|
||||
*
|
||||
* The issue is that the select gets evaluated before option is unrolled.
|
||||
* This means that the selection is undefined, but the browser
|
||||
* default behavior is to show the top selection in the list.
|
||||
* To fix that we register a $update function on the select element
|
||||
* and the option creation then calls the $update function when it is
|
||||
* unrolled. The $update function then calls this update function, which
|
||||
* then tries to determine if the model is unassigned, and if so it tries to
|
||||
* chose one of the options from the list.
|
||||
*/
|
||||
angularWidget('option', function(){
|
||||
this.descend(true);
|
||||
this.directives(true);
|
||||
return function(element) {
|
||||
this.$postEval(element.parent().data('$update'));
|
||||
var select = element.parent();
|
||||
var scope = retrieveScope(select);
|
||||
var model = modelFormattedAccessor(scope, select);
|
||||
var view = valueAccessor(scope, select);
|
||||
var option = element;
|
||||
var lastValue = option.attr($value);
|
||||
var lastSelected = option.attr('ng-' + $selected);
|
||||
element.data($$update, function(){
|
||||
var value = option.attr($value);
|
||||
var selected = option.attr('ng-' + $selected);
|
||||
var modelValue = model.get();
|
||||
if (lastSelected != selected || lastValue != value) {
|
||||
lastSelected = selected;
|
||||
lastValue = value;
|
||||
if (selected || modelValue == _null || modelValue == _undefined)
|
||||
model.set(value);
|
||||
if (value == modelValue) {
|
||||
view.set(lastValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
/*ng:doc
|
||||
* @type widget
|
||||
* @name ng:include
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc widget
|
||||
* @name angular.widget.ng:include
|
||||
*
|
||||
* @description
|
||||
* Include external HTML fragment.
|
||||
*
|
||||
* Keep in mind that Same Origin Policy applies to included resources
|
||||
* (e.g. ng:include won't work for file:// access).
|
||||
*
|
||||
* @param {string} src expression evaluating to URL.
|
||||
* @param {Scope=} [scope=new_child_scope] expression evaluating to angular.scope
|
||||
* @param {string=} onload Expression to evaluate when a new partial is loaded.
|
||||
*
|
||||
* @example
|
||||
* <select name="url">
|
||||
* <option value="angular.filter.date.html">date filter</option>
|
||||
* <option value="angular.filter.html.html">html filter</option>
|
||||
* <option value="">(blank)</option>
|
||||
* </select>
|
||||
* <tt>url = <a href="{{url}}">{{url}}</a></tt>
|
||||
* <hr/>
|
||||
* <ng:include src="url"></ng:include>
|
||||
*
|
||||
* @scenario
|
||||
* it('should load date filter', function(){
|
||||
* expect(element('.doc-example ng\\:include').text()).toMatch(/angular\.filter\.date/);
|
||||
* });
|
||||
* it('should change to hmtl filter', function(){
|
||||
* select('url').option('angular.filter.html.html');
|
||||
* expect(element('.doc-example ng\\:include').text()).toMatch(/angular\.filter\.html/);
|
||||
* });
|
||||
* it('should change to blank', function(){
|
||||
* select('url').option('(blank)');
|
||||
* expect(element('.doc-example ng\\:include').text()).toEqual('');
|
||||
* });
|
||||
*/
|
||||
angularWidget('ng:include', function(element){
|
||||
var compiler = this,
|
||||
srcExp = element.attr("src"),
|
||||
scopeExp = element.attr("scope") || '';
|
||||
scopeExp = element.attr("scope") || '',
|
||||
onloadExp = element[0].getAttribute('onload') || ''; //workaround for jquery bug #7537
|
||||
if (element[0]['ng:compiled']) {
|
||||
this.descend(true);
|
||||
this.directives(true);
|
||||
@@ -282,13 +601,15 @@ angularWidget('ng:include', function(element){
|
||||
});
|
||||
this.$watch(function(){return changeCounter;}, function(){
|
||||
var src = this.$eval(srcExp),
|
||||
useScope = this.$eval(scopeExp);
|
||||
useScope = this.$eval(scopeExp);
|
||||
|
||||
if (src) {
|
||||
xhr('GET', src, function(code, response){
|
||||
element.html(response);
|
||||
childScope = useScope || createScope(scope);
|
||||
compiler.compile(element)(element, childScope);
|
||||
childScope.$init();
|
||||
scope.$eval(onloadExp);
|
||||
});
|
||||
} else {
|
||||
childScope = null;
|
||||
@@ -299,6 +620,56 @@ angularWidget('ng:include', function(element){
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc widget
|
||||
* @name angular.widget.ng:switch
|
||||
*
|
||||
* @description
|
||||
* Conditionally change the DOM structure.
|
||||
*
|
||||
* @usageContent
|
||||
* <any ng:switch-when="matchValue1">...</any>
|
||||
* <any ng:switch-when="matchValue2">...</any>
|
||||
* ...
|
||||
* <any ng:switch-default>...</any>
|
||||
*
|
||||
* @param {*} on expression to match against <tt>ng:switch-when</tt>.
|
||||
* @paramDescription
|
||||
* On child elments add:
|
||||
*
|
||||
* * `ng:switch-when`: the case statement to match against. If match then this
|
||||
* case will be displayed.
|
||||
* * `ng:switch-default`: the default case when no other casses match.
|
||||
*
|
||||
* @example
|
||||
<select name="switch">
|
||||
<option>settings</option>
|
||||
<option>home</option>
|
||||
<option>other</option>
|
||||
</select>
|
||||
<tt>switch={{switch}}</tt>
|
||||
</hr>
|
||||
<ng:switch on="switch" >
|
||||
<div ng:switch-when="settings">Settings Div</div>
|
||||
<span ng:switch-when="home">Home Span</span>
|
||||
<span ng:switch-default>default</span>
|
||||
</ng:switch>
|
||||
</code>
|
||||
*
|
||||
* @scenario
|
||||
* it('should start in settings', function(){
|
||||
* expect(element('.doc-example ng\\:switch').text()).toEqual('Settings Div');
|
||||
* });
|
||||
* it('should change to home', function(){
|
||||
* select('switch').option('home');
|
||||
* expect(element('.doc-example ng\\:switch').text()).toEqual('Home Span');
|
||||
* });
|
||||
* it('should select deafault', function(){
|
||||
* select('switch').option('other');
|
||||
* expect(element('.doc-example ng\\:switch').text()).toEqual('default');
|
||||
* });
|
||||
*/
|
||||
var ngSwitch = angularWidget('ng:switch', function (element){
|
||||
var compiler = this,
|
||||
watchExpr = element.attr("on"),
|
||||
@@ -308,21 +679,26 @@ var ngSwitch = angularWidget('ng:switch', function (element){
|
||||
changeExpr = element.attr('change') || '',
|
||||
cases = [];
|
||||
if (!usingFn) throw "Using expression '" + usingExpr + "' unknown.";
|
||||
if (!watchExpr) throw "Missing 'on' attribute.";
|
||||
eachNode(element, function(caseElement){
|
||||
var when = caseElement.attr('ng:switch-when');
|
||||
if (when) {
|
||||
cases.push({
|
||||
when: function(scope, value){
|
||||
var args = [value, when];
|
||||
foreach(usingExprParams, function(arg){
|
||||
args.push(arg);
|
||||
});
|
||||
return usingFn.apply(scope, args);
|
||||
},
|
||||
var switchCase = {
|
||||
change: changeExpr,
|
||||
element: caseElement,
|
||||
template: compiler.compile(caseElement)
|
||||
});
|
||||
};
|
||||
if (isString(when)) {
|
||||
switchCase.when = function(scope, value){
|
||||
var args = [value, when];
|
||||
foreach(usingExprParams, function(arg){
|
||||
args.push(arg);
|
||||
});
|
||||
return usingFn.apply(scope, args);
|
||||
};
|
||||
cases.unshift(switchCase);
|
||||
} else if (isString(caseElement.attr('ng:switch-default'))) {
|
||||
switchCase.when = valueFn(true);
|
||||
cases.push(switchCase);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -335,10 +711,12 @@ var ngSwitch = angularWidget('ng:switch', function (element){
|
||||
return function(element){
|
||||
var scope = this, childScope;
|
||||
this.$watch(watchExpr, function(value){
|
||||
var found = false;
|
||||
element.html('');
|
||||
childScope = createScope(scope);
|
||||
foreach(cases, function(switchCase){
|
||||
if (switchCase.when(childScope, value)) {
|
||||
if (!found && switchCase.when(childScope, value)) {
|
||||
found = true;
|
||||
var caseElement = quickClone(switchCase.element);
|
||||
element.append(caseElement);
|
||||
childScope.$tryEval(switchCase.change, element);
|
||||
@@ -353,7 +731,7 @@ var ngSwitch = angularWidget('ng:switch', function (element){
|
||||
};
|
||||
}, {
|
||||
equals: function(on, when) {
|
||||
return on == when;
|
||||
return ''+on == when;
|
||||
},
|
||||
route: switchRouteMatcher
|
||||
});
|
||||
@@ -367,7 +745,7 @@ var ngSwitch = angularWidget('ng:switch', function (element){
|
||||
* changing the location or causing page reloads, e.g.:
|
||||
* <a href="" ng:click="model.$save()">Save</a>
|
||||
*/
|
||||
angular.widget('a', function() {
|
||||
angularWidget('a', function() {
|
||||
this.descend(true);
|
||||
this.directives(true);
|
||||
|
||||
@@ -378,4 +756,162 @@ angular.widget('a', function() {
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc widget
|
||||
* @name angular.widget.@ng:repeat
|
||||
*
|
||||
* @description
|
||||
* `ng:repeat` instantiates a template once per item from a collection. The collection is enumerated
|
||||
* with `ng:repeat-index` attribute starting from 0. Each template instance gets its own scope where
|
||||
* the given loop variable is set to the current collection item and `$index` is set to the item
|
||||
* index or key.
|
||||
*
|
||||
* There are special properties exposed on the local scope of each template instance:
|
||||
*
|
||||
* * `$index` – `{number}` – iterator offset of the repeated element (0..length-1)
|
||||
* * `$position` – {string} – position of the repeated element in the iterator. One of: `'first'`,
|
||||
* `'middle'` or `'last'`.
|
||||
*
|
||||
* NOTE: `ng:repeat` looks like a directive, but is actually an attribute widget.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string} repeat_expression The expression indicating how to enumerate a collection. Two
|
||||
* formats are currently supported:
|
||||
*
|
||||
* * `variable in expression` – where variable is the user defined loop variable and `expression`
|
||||
* is a scope expression giving the collection to enumerate.
|
||||
*
|
||||
* For example: `track in cd.tracks`.
|
||||
* * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
|
||||
* and `expression` is the scope expression giving the collection to enumerate.
|
||||
*
|
||||
* For example: `(name, age) in {'adam':10, 'amalie':12}`.
|
||||
*
|
||||
* @exampleDescription
|
||||
* This example initializes the scope to a list of names and
|
||||
* than uses `ng:repeat` to display every person.
|
||||
* @example
|
||||
<div ng:init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
|
||||
I have {{friends.length}} friends. They are:
|
||||
<ul>
|
||||
<li ng:repeat="friend in friends">
|
||||
[{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
* @scenario
|
||||
it('should check ng:repeat', function(){
|
||||
var r = using('.doc-example-live').repeater('ul li');
|
||||
expect(r.count()).toBe(2);
|
||||
expect(r.row(0)).toEqual(["1","John","25"]);
|
||||
expect(r.row(1)).toEqual(["2","Mary","28"]);
|
||||
});
|
||||
*/
|
||||
angularWidget("@ng:repeat", function(expression, element){
|
||||
element.removeAttr('ng:repeat');
|
||||
element.replaceWith(this.comment("ng:repeat: " + expression));
|
||||
var template = this.compile(element);
|
||||
return function(reference){
|
||||
var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
|
||||
lhs, rhs, valueIdent, keyIdent;
|
||||
if (! match) {
|
||||
throw Error("Expected ng:repeat in form of 'item in collection' but got '" +
|
||||
expression + "'.");
|
||||
}
|
||||
lhs = match[1];
|
||||
rhs = match[2];
|
||||
match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);
|
||||
if (!match) {
|
||||
throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
|
||||
keyValue + "'.");
|
||||
}
|
||||
valueIdent = match[3] || match[1];
|
||||
keyIdent = match[2];
|
||||
|
||||
var children = [], currentScope = this;
|
||||
this.$onEval(function(){
|
||||
var index = 0,
|
||||
childCount = children.length,
|
||||
lastElement = reference,
|
||||
collection = this.$tryEval(rhs, reference),
|
||||
is_array = isArray(collection),
|
||||
collectionLength = 0,
|
||||
childScope,
|
||||
key;
|
||||
|
||||
if (is_array) {
|
||||
collectionLength = collection.length;
|
||||
} else {
|
||||
for (key in collection)
|
||||
if (collection.hasOwnProperty(key))
|
||||
collectionLength++;
|
||||
}
|
||||
|
||||
for (key in collection) {
|
||||
if (!is_array || collection.hasOwnProperty(key)) {
|
||||
if (index < childCount) {
|
||||
// reuse existing child
|
||||
childScope = children[index];
|
||||
childScope[valueIdent] = collection[key];
|
||||
if (keyIdent) childScope[keyIdent] = key;
|
||||
} else {
|
||||
// grow children
|
||||
childScope = template(quickClone(element), createScope(currentScope));
|
||||
childScope[valueIdent] = collection[key];
|
||||
if (keyIdent) childScope[keyIdent] = key;
|
||||
lastElement.after(childScope.$element);
|
||||
childScope.$index = index;
|
||||
childScope.$position = index == 0 ?
|
||||
'first' :
|
||||
(index == collectionLength - 1 ? 'last' : 'middle');
|
||||
childScope.$element.attr('ng:repeat-index', index);
|
||||
childScope.$init();
|
||||
children.push(childScope);
|
||||
}
|
||||
childScope.$eval();
|
||||
lastElement = childScope.$element;
|
||||
index ++;
|
||||
}
|
||||
}
|
||||
// shrink children
|
||||
while(children.length > index) {
|
||||
children.pop().$element.remove();
|
||||
}
|
||||
}, reference);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc widget
|
||||
* @name angular.widget.@ng:non-bindable
|
||||
*
|
||||
* @description
|
||||
* Sometimes it is necessary to write code which looks like bindings but which should be left alone
|
||||
* by angular. Use `ng:non-bindable` to make angular ignore a chunk of HTML.
|
||||
*
|
||||
* NOTE: `ng:non-bindable` looks like a directive, but is actually an attribute widget.
|
||||
*
|
||||
* @element ANY
|
||||
*
|
||||
* @exampleDescription
|
||||
* In this example there are two location where a siple binding (`{{}}`) is present, but the one
|
||||
* wrapped in `ng:non-bindable` is left alone.
|
||||
*
|
||||
* @example
|
||||
<div>Normal: {{1 + 2}}</div>
|
||||
<div ng:non-bindable>Ignored: {{1 + 2}}</div>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:non-bindable', function(){
|
||||
expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
|
||||
expect(using('.doc-example-live').element('div:last').text()).
|
||||
toMatch(/1 \+ 2/);
|
||||
});
|
||||
*/
|
||||
angularWidget("@ng:non-bindable", noop);
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
tests=$1
|
||||
if [[ $tests = "" ]]; then
|
||||
tests="all"
|
||||
fi
|
||||
|
||||
java -Xmx1g -jar lib/jstestdriver/JsTestDriver.jar --config jsTestDriver-coverage.conf --testOutput=tmp/lcov --tests "$tests"
|
||||
+88
-12
@@ -10,6 +10,7 @@ describe('Angular', function(){
|
||||
scope.$init();
|
||||
scope.$eval();
|
||||
expect(onUpdateView).wasCalled();
|
||||
dealoc(scope);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,8 +27,8 @@ describe("copy", function(){
|
||||
it("should return same object", function (){
|
||||
var obj = {};
|
||||
var arr = [];
|
||||
assertSame(obj, copy({}, obj));
|
||||
assertSame(arr, copy([], arr));
|
||||
expect(copy({}, obj)).toBe(obj);
|
||||
expect(copy([], arr)).toBe(arr);
|
||||
});
|
||||
|
||||
it("should copy Date", function(){
|
||||
@@ -40,26 +41,26 @@ describe("copy", function(){
|
||||
it("should copy array", function(){
|
||||
var src = [1, {name:"value"}];
|
||||
var dst = [{key:"v"}];
|
||||
assertSame(dst, copy(src, dst));
|
||||
assertEquals([1, {name:"value"}], dst);
|
||||
assertEquals({name:"value"}, dst[1]);
|
||||
assertNotSame(src[1], dst[1]);
|
||||
expect(copy(src, dst)).toBe(dst);
|
||||
expect(dst).toEqual([1, {name:"value"}]);
|
||||
expect(dst[1]).toEqual({name:"value"});
|
||||
expect(dst[1]).not.toBe(src[1]);
|
||||
});
|
||||
|
||||
it('should copy empty array', function() {
|
||||
var src = [];
|
||||
var dst = [{key: "v"}];
|
||||
assertEquals([], copy(src, dst));
|
||||
assertEquals([], dst);
|
||||
expect(copy(src, dst)).toEqual([]);
|
||||
expect(dst).toEqual([]);
|
||||
});
|
||||
|
||||
it("should copy object", function(){
|
||||
var src = {a:{name:"value"}};
|
||||
var dst = {b:{key:"v"}};
|
||||
assertSame(dst, copy(src, dst));
|
||||
assertEquals({a:{name:"value"}}, dst);
|
||||
assertEquals(src.a, dst.a);
|
||||
assertNotSame(src.a, dst.a);
|
||||
expect(copy(src, dst)).toBe(dst);
|
||||
expect(dst).toEqual({a:{name:"value"}});
|
||||
expect(dst.a).toEqual(src.a);
|
||||
expect(dst.a).not.toBe(src.a);
|
||||
});
|
||||
|
||||
it("should copy primitives", function(){
|
||||
@@ -255,6 +256,20 @@ describe('angularJsConfig', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should extract angular autobind config from the script hashpath attributes', function() {
|
||||
var doc = { getElementsByTagName: function(tagName) {
|
||||
expect(lowercase(tagName)).toEqual('script');
|
||||
return [{nodeName: 'SCRIPT',
|
||||
src: 'angularjs/angular.js#autobind'}];
|
||||
}};
|
||||
|
||||
expect(angularJsConfig(doc)).toEqual({base_url: 'angularjs/',
|
||||
autobind: true,
|
||||
ie_compat: 'angularjs/angular-ie-compat.js',
|
||||
ie_compat_id: 'ng-ie-compat'});
|
||||
});
|
||||
|
||||
|
||||
it("should default to versioned ie-compat file if angular file is versioned", function() {
|
||||
var doc = { getElementsByTagName: function(tagName) {
|
||||
expect(lowercase(tagName)).toEqual('script');
|
||||
@@ -280,3 +295,64 @@ describe('angularJsConfig', function() {
|
||||
ie_compat_id: 'ng-ie-compat'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('extensionMap', function() {
|
||||
it('should preserve $ properties on override', function() {
|
||||
var extension = extensionMap({}, 'fake');
|
||||
extension('first', {$one: true, $two: true});
|
||||
var result = extension('first', {$one: false, $three: true});
|
||||
|
||||
expect(result.$one).toBeFalsy();
|
||||
expect(result.$two).toBeTruthy();
|
||||
expect(result.$three).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not preserve non-angular properties', function() {
|
||||
var extension = extensionMap({}, 'fake');
|
||||
extension('first', {two: true});
|
||||
var result = extension('first', {$one: false, $three: true});
|
||||
|
||||
expect(result.two).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('angular service', function() {
|
||||
it('should override services', function() {
|
||||
var scope = createScope();
|
||||
angular.service('fake', function() { return 'old'; });
|
||||
angular.service('fake', function() { return 'new'; });
|
||||
|
||||
expect(scope.$inject('fake')).toEqual('new');
|
||||
});
|
||||
|
||||
it('should preserve $ properties on override', function() {
|
||||
angular.service('fake', {$one: true}, {$two: true});
|
||||
var result = angular.service('fake', {$third: true});
|
||||
|
||||
expect(result.$one).toBeTruthy();
|
||||
expect(result.$two).toBeTruthy();
|
||||
expect(result.$third).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not preserve non-angular properties on override', function() {
|
||||
angular.service('fake', {one: true}, {two: true});
|
||||
var result = angular.service('fake', {third: true});
|
||||
|
||||
expect(result.one).not.toBeDefined();
|
||||
expect(result.two).not.toBeDefined();
|
||||
expect(result.third).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDate', function() {
|
||||
it('should return true for Date object', function() {
|
||||
expect(isDate(new Date())).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non Date objects', function() {
|
||||
expect(isDate([])).toBe(false);
|
||||
expect(isDate('')).toBe(false);
|
||||
expect(isDate(23)).toBe(false);
|
||||
expect(isDate({})).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
+52
-4
@@ -115,6 +115,36 @@ describe('api', function(){
|
||||
});
|
||||
|
||||
|
||||
describe('limit', function() {
|
||||
var items;
|
||||
|
||||
beforeEach(function() {
|
||||
items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||
});
|
||||
|
||||
|
||||
it('should return the first X items when X is positive', function() {
|
||||
expect(angular.Array.limitTo(items, 3)).toEqual(['a', 'b', 'c']);
|
||||
expect(angular.Array.limitTo(items, '3')).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
|
||||
it('should return the last X items when X is negative', function() {
|
||||
expect(angular.Array.limitTo(items, -3)).toEqual(['f', 'g', 'h']);
|
||||
expect(angular.Array.limitTo(items, '-3')).toEqual(['f', 'g', 'h']);
|
||||
});
|
||||
|
||||
|
||||
it('should return an empty array when X cannot be parsed', function() {
|
||||
expect(angular.Array.limitTo(items, 'bogus')).toEqual([]);
|
||||
expect(angular.Array.limitTo(items, 'null')).toEqual([]);
|
||||
expect(angular.Array.limitTo(items, 'undefined')).toEqual([]);
|
||||
expect(angular.Array.limitTo(items, null)).toEqual([]);
|
||||
expect(angular.Array.limitTo(items, undefined)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Add', function(){
|
||||
var add = angular.Array.add;
|
||||
assertJsonEquals([{}, "a"], add(add([]),"a"));
|
||||
@@ -185,18 +215,36 @@ describe('api', function(){
|
||||
it('DateToUTC', function(){
|
||||
var date = new Date("Sep 10 2003 13:02:03 GMT");
|
||||
assertEquals("date", angular.Object.typeOf(date));
|
||||
assertEquals("2003-09-10T13:02:03Z", angular.Date.toString(date));
|
||||
assertEquals("2003-09-10T13:02:03.000Z", angular.Date.toString(date));
|
||||
assertEquals(date.getTime(), angular.String.toDate(angular.Date.toString(date)).getTime());
|
||||
});
|
||||
|
||||
it('UTCtoDate', function(){
|
||||
expect(angular.String.toDate("2003-09-10T13:02:03Z")).toEqual(new Date("Sep 10 2003 13:02:03 GMT"));
|
||||
//full ISO8061
|
||||
expect(angular.String.toDate("2003-09-10T13:02:03.000Z")).
|
||||
toEqual(new Date("Sep 10 2003 13:02:03 GMT"));
|
||||
|
||||
//no millis
|
||||
expect(angular.String.toDate("2003-09-10T13:02:03Z")).
|
||||
toEqual(new Date("Sep 10 2003 13:02:03 GMT"));
|
||||
|
||||
//no seconds
|
||||
expect(angular.String.toDate("2003-09-10T13:02Z")).
|
||||
toEqual(new Date("Sep 10 2003 13:02:00 GMT"));
|
||||
|
||||
//no minutes
|
||||
expect(angular.String.toDate("2003-09-10T13Z")).
|
||||
toEqual(new Date("Sep 10 2003 13:00:00 GMT"));
|
||||
|
||||
//no time
|
||||
expect(angular.String.toDate("2003-09-10")).
|
||||
toEqual(new Date("Sep 10 2003 00:00:00 GMT"));
|
||||
});
|
||||
|
||||
it('StringFromUTC', function(){
|
||||
var date = angular.String.toDate("2003-09-10T13:02:03Z");
|
||||
var date = angular.String.toDate("2003-09-10T13:02:03.000Z");
|
||||
assertEquals("date", angular.Object.typeOf(date));
|
||||
assertEquals("2003-09-10T13:02:03Z", angular.Date.toString(date));
|
||||
assertEquals("2003-09-10T13:02:03.000Z", angular.Date.toString(date));
|
||||
assertEquals("str", angular.String.toDate("str"));
|
||||
});
|
||||
|
||||
|
||||
+11
-11
@@ -5,6 +5,7 @@ BinderTest.prototype.setUp = function(){
|
||||
|
||||
this.compile = function(html, initialScope, parent) {
|
||||
var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
|
||||
if (self.element) dealoc(self.element);
|
||||
var element = self.element = jqLite(html);
|
||||
var scope = compiler.compile(element)(element);
|
||||
|
||||
@@ -276,15 +277,15 @@ BinderTest.prototype.testIfTextBindingThrowsErrorDecorateTheSpan = function(){
|
||||
a.scope.$eval();
|
||||
var span = childNode(doc, 0);
|
||||
assertTrue(span.hasClass('ng-exception'));
|
||||
assertEquals('ErrorMsg1', fromJson(span.text()));
|
||||
assertEquals('"ErrorMsg1"', span.attr('ng-exception'));
|
||||
assertTrue(!!span.text().match(/ErrorMsg1/));
|
||||
assertTrue(!!span.attr('ng-exception').match(/ErrorMsg1/));
|
||||
|
||||
a.scope.$set('error.throw', function(){throw "MyError";});
|
||||
a.scope.$eval();
|
||||
span = childNode(doc, 0);
|
||||
assertTrue(span.hasClass('ng-exception'));
|
||||
assertTrue(span.text(), span.text().match('MyError') !== null);
|
||||
assertEquals('"MyError"', span.attr('ng-exception'));
|
||||
assertEquals('MyError', span.attr('ng-exception'));
|
||||
|
||||
a.scope.$set('error.throw', function(){return "ok";});
|
||||
a.scope.$eval();
|
||||
@@ -438,13 +439,12 @@ BinderTest.prototype.testActionOnAHrefThrowsError = function(){
|
||||
var model = {books:[]};
|
||||
var c = this.compile('<a ng:click="action()">Add Phone</a>', model);
|
||||
c.scope.action = function(){
|
||||
throw {a:'abc', b:2};
|
||||
throw new Error('MyError');
|
||||
};
|
||||
var input = c.node;
|
||||
browserTrigger(input, 'click');
|
||||
var error = fromJson(input.attr('ng-exception'));
|
||||
assertEquals("abc", error.a);
|
||||
assertEquals(2, error.b);
|
||||
var error = input.attr('ng-exception');
|
||||
assertTrue(!!error.match(/MyError/));
|
||||
assertTrue("should have an error class", input.hasClass('ng-exception'));
|
||||
|
||||
// TODO: I think that exception should never get cleared so this portion of test makes no sense
|
||||
@@ -607,13 +607,13 @@ BinderTest.prototype.testItShouldListenOnRightScope = function() {
|
||||
'<ul ng:init="counter=0; gCounter=0" ng:watch="w:counter=counter+1">' +
|
||||
'<li ng:repeat="n in [1,2,4]" ng:watch="w:counter=counter+1;w:$root.gCounter=$root.gCounter+n"/></ul>');
|
||||
c.scope.$eval();
|
||||
assertEquals(0, c.scope.$get("counter"));
|
||||
assertEquals(0, c.scope.$get("gCounter"));
|
||||
assertEquals(1, c.scope.$get("counter"));
|
||||
assertEquals(7, c.scope.$get("gCounter"));
|
||||
|
||||
c.scope.$set("w", "something");
|
||||
c.scope.$eval();
|
||||
assertEquals(1, c.scope.$get("counter"));
|
||||
assertEquals(7, c.scope.$get("gCounter"));
|
||||
assertEquals(2, c.scope.$get("counter"));
|
||||
assertEquals(14, c.scope.$get("gCounter"));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testItShouldRepeatOnHashes = function() {
|
||||
|
||||
+37
-2
@@ -1,8 +1,21 @@
|
||||
describe('browser', function(){
|
||||
|
||||
var browser, location, head, xhr;
|
||||
var browser, location, head, xhr, setTimeoutQueue;
|
||||
|
||||
function fakeSetTimeout(fn) {
|
||||
setTimeoutQueue.push(fn);
|
||||
}
|
||||
|
||||
fakeSetTimeout.flush = function() {
|
||||
foreach(setTimeoutQueue, function(fn) {
|
||||
fn();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
beforeEach(function(){
|
||||
setTimeoutQueue = [];
|
||||
|
||||
location = {href:"http://server", hash:""};
|
||||
head = {
|
||||
scripts: [],
|
||||
@@ -14,7 +27,7 @@ describe('browser', function(){
|
||||
this.open = noop;
|
||||
this.setRequestHeader = noop;
|
||||
this.send = noop;
|
||||
});
|
||||
}, undefined, fakeSetTimeout);
|
||||
});
|
||||
|
||||
it('should contain cookie cruncher', function() {
|
||||
@@ -59,6 +72,28 @@ describe('browser', function(){
|
||||
});
|
||||
|
||||
|
||||
describe('defer', function() {
|
||||
it('should execute fn asynchroniously via setTimeout', function() {
|
||||
var counter = 0;
|
||||
browser.defer(function() {counter++;});
|
||||
expect(counter).toBe(0);
|
||||
|
||||
fakeSetTimeout.flush();
|
||||
expect(counter).toBe(1);
|
||||
});
|
||||
|
||||
|
||||
it('should update outstandingRequests counter', function() {
|
||||
var callback = jasmine.createSpy('callback');
|
||||
browser.defer(callback);
|
||||
expect(callback).not.wasCalled();
|
||||
|
||||
fakeSetTimeout.flush();
|
||||
expect(callback).wasCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('cookies', function() {
|
||||
|
||||
function deleteAllCookies() {
|
||||
|
||||
+17
-11
@@ -1,5 +1,5 @@
|
||||
describe('compiler', function(){
|
||||
var compiler, markup, directives, widgets, compile, log;
|
||||
var compiler, markup, directives, widgets, compile, log, scope;
|
||||
|
||||
beforeEach(function(){
|
||||
log = "";
|
||||
@@ -14,7 +14,8 @@ describe('compiler', function(){
|
||||
watch: function(expression, element){
|
||||
return function() {
|
||||
this.$watch(expression, function(val){
|
||||
log += ":" + val;
|
||||
if (val)
|
||||
log += ":" + val;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -31,6 +32,10 @@ describe('compiler', function(){
|
||||
return scope;
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
it('should recognize a directive', function(){
|
||||
var e = jqLite('<div directive="expr" ignore="me"></div>');
|
||||
@@ -43,7 +48,8 @@ describe('compiler', function(){
|
||||
};
|
||||
};
|
||||
var template = compiler.compile(e);
|
||||
var init = template(e).$init;
|
||||
scope = template(e);
|
||||
var init = scope.$init;
|
||||
expect(log).toEqual("found");
|
||||
init();
|
||||
expect(e.hasClass('ng-directive')).toEqual(true);
|
||||
@@ -51,12 +57,12 @@ describe('compiler', function(){
|
||||
});
|
||||
|
||||
it('should recurse to children', function(){
|
||||
var scope = compile('<div><span hello="misko"/></div>');
|
||||
scope = compile('<div><span hello="misko"/></div>');
|
||||
expect(log).toEqual("hello misko");
|
||||
});
|
||||
|
||||
it('should watch scope', function(){
|
||||
var scope = compile('<span watch="name"/>');
|
||||
scope = compile('<span watch="name"/>');
|
||||
expect(log).toEqual("");
|
||||
scope.$eval();
|
||||
scope.$set('name', 'misko');
|
||||
@@ -70,7 +76,7 @@ describe('compiler', function(){
|
||||
|
||||
it('should prevent descend', function(){
|
||||
directives.stop = function(){ this.descend(false); };
|
||||
var scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>');
|
||||
scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>');
|
||||
expect(log).toEqual("hello misko");
|
||||
});
|
||||
|
||||
@@ -86,7 +92,7 @@ describe('compiler', function(){
|
||||
});
|
||||
};
|
||||
};
|
||||
var scope = compile('before<span duplicate="expr">x</span>after');
|
||||
scope = compile('before<span duplicate="expr">x</span>after');
|
||||
expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span>after</div>');
|
||||
scope.$eval();
|
||||
expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span><span>x</span>after</div>');
|
||||
@@ -102,7 +108,7 @@ describe('compiler', function(){
|
||||
textNode[0].nodeValue = 'replaced';
|
||||
}
|
||||
});
|
||||
var scope = compile('before<span>middle</span>after');
|
||||
scope = compile('before<span>middle</span>after');
|
||||
expect(sortedHtml(scope.$element[0], true)).toEqual('<div>before<span class="ng-directive" hello="middle">replaced</span>after</div>');
|
||||
expect(log).toEqual("hello middle");
|
||||
});
|
||||
@@ -115,7 +121,7 @@ describe('compiler', function(){
|
||||
log += 'init';
|
||||
};
|
||||
};
|
||||
var scope = compile('<ng:button>push me</ng:button>');
|
||||
scope = compile('<ng:button>push me</ng:button>');
|
||||
expect(lowercase(scope.$element[0].innerHTML)).toEqual('<div>button</div>');
|
||||
expect(log).toEqual('init');
|
||||
});
|
||||
@@ -134,7 +140,7 @@ describe('compiler', function(){
|
||||
if (text == '{{1+2}}')
|
||||
parent.text('3');
|
||||
});
|
||||
var scope = compile('<div><h1>ignore me</h1></div>');
|
||||
scope = compile('<div><h1>ignore me</h1></div>');
|
||||
expect(scope.$element.text()).toEqual('3');
|
||||
});
|
||||
|
||||
@@ -157,7 +163,7 @@ describe('compiler', function(){
|
||||
textNode.remove();
|
||||
}
|
||||
});
|
||||
var scope = compile('A---B---C===D');
|
||||
scope = compile('A---B---C===D');
|
||||
expect(sortedHtml(scope.$element)).toEqual('<div>A<hr></hr>B<hr></hr>C<p></p>D</div>');
|
||||
});
|
||||
|
||||
|
||||
+75
-61
@@ -1,91 +1,100 @@
|
||||
describe('filter', function(){
|
||||
describe('filter', function() {
|
||||
|
||||
var filter = angular.filter;
|
||||
|
||||
it('should called the filter when evaluating expression', function() {
|
||||
var scope = createScope();
|
||||
filter.fakeFilter = function(){};
|
||||
spyOn(filter, 'fakeFilter');
|
||||
|
||||
scope.$eval('10|fakeFilter');
|
||||
expect(filter.fakeFilter).toHaveBeenCalledWith(10);
|
||||
delete filter['fakeFilter'];
|
||||
});
|
||||
|
||||
it('should call filter on scope context', function() {
|
||||
var scope = createScope();
|
||||
scope.name = 'misko';
|
||||
filter.fakeFilter = function() {
|
||||
expect(this.name).toEqual('misko');
|
||||
};
|
||||
spyOn(filter, 'fakeFilter').andCallThrough();
|
||||
|
||||
scope.$eval('10|fakeFilter');
|
||||
expect(filter.fakeFilter).toHaveBeenCalled();
|
||||
delete filter['fakeFilter'];
|
||||
});
|
||||
|
||||
describe('Currency', function(){
|
||||
it('should do basic filter', function(){
|
||||
describe('currency', function() {
|
||||
it('should do basic filter', function() {
|
||||
var html = jqLite('<span/>');
|
||||
var context = {$element:html};
|
||||
var currency = bind(context, filter.currency);
|
||||
|
||||
assertEquals(currency(0), '$0.00');
|
||||
assertEquals(html.hasClass('ng-format-negative'), false);
|
||||
assertEquals(currency(-999), '$-999.00');
|
||||
assertEquals(html.hasClass('ng-format-negative'), true);
|
||||
assertEquals(currency(1234.5678), '$1,234.57');
|
||||
assertEquals(html.hasClass('ng-format-negative'), false);
|
||||
expect(currency(0)).toEqual('$0.00');
|
||||
expect(html.hasClass('ng-format-negative')).toBeFalsy();
|
||||
expect(currency(-999)).toEqual('$-999.00');
|
||||
expect(html.hasClass('ng-format-negative')).toBeTruthy();
|
||||
expect(currency(1234.5678)).toEqual('$1,234.57');
|
||||
expect(html.hasClass('ng-format-negative')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('FilterThisIsContext', function(){
|
||||
it('should do basic filter', function(){
|
||||
expectAsserts(1);
|
||||
var scope = createScope();
|
||||
scope.name = 'misko';
|
||||
filter.testFn = function () {
|
||||
assertEquals('scope not equal', 'misko', this.name);
|
||||
};
|
||||
scope.$eval("0|testFn");
|
||||
delete angular.filter['testFn'];
|
||||
});
|
||||
});
|
||||
|
||||
describe('NumberFormat', function(){
|
||||
it('should do basic filter', function(){
|
||||
|
||||
describe('number', function() {
|
||||
it('should do basic filter', function() {
|
||||
var context = {jqElement:jqLite('<span/>')};
|
||||
var number = bind(context, filter.number);
|
||||
|
||||
assertEquals('0', number(0, 0));
|
||||
assertEquals('0.00', number(0));
|
||||
assertEquals('-999.00', number(-999));
|
||||
assertEquals('1,234.57', number(1234.5678));
|
||||
assertEquals('', number(Number.NaN));
|
||||
assertEquals('1,234.57', number("1234.5678"));
|
||||
assertEquals("", number(1/0));
|
||||
expect(number(0, 0)).toEqual('0');
|
||||
expect(number(0)).toEqual('0.00');
|
||||
expect(number(-999)).toEqual('-999.00');
|
||||
expect(number(1234.5678)).toEqual('1,234.57');
|
||||
expect(number(Number.NaN)).toEqual('');
|
||||
expect(number("1234.5678")).toEqual('1,234.57');
|
||||
expect(number(1/0)).toEqual("");
|
||||
});
|
||||
});
|
||||
|
||||
describe('Json', function () {
|
||||
it('should do basic filter', function(){
|
||||
assertEquals(toJson({a:"b"}, true), filter.json.call({$element:jqLite('<div></div>')}, {a:"b"}));
|
||||
describe('json', function () {
|
||||
it('should do basic filter', function() {
|
||||
expect(filter.json.call({$element:jqLite('<div></div>')}, {a:"b"})).toEqual(toJson({a:"b"}, true));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Lowercase', function() {
|
||||
it('should do basic filter', function(){
|
||||
assertEquals('abc', filter.lowercase('AbC'));
|
||||
assertEquals(null, filter.lowercase(null));
|
||||
describe('lowercase', function() {
|
||||
it('should do basic filter', function() {
|
||||
expect(filter.lowercase('AbC')).toEqual('abc');
|
||||
expect(filter.lowercase(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Uppercase', function() {
|
||||
it('should do basic filter', function(){
|
||||
assertEquals('ABC', filter.uppercase('AbC'));
|
||||
assertEquals(null, filter.uppercase(null));
|
||||
describe('uppercase', function() {
|
||||
it('should do basic filter', function() {
|
||||
expect(filter.uppercase('AbC')).toEqual('ABC');
|
||||
expect(filter.uppercase(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Html', function() {
|
||||
it('should do basic filter', function(){
|
||||
describe('html', function() {
|
||||
it('should do basic filter', function() {
|
||||
var html = filter.html("a<b>c</b>d");
|
||||
expect(html instanceof HTML).toBeTruthy();
|
||||
expect(html.html).toEqual("a<b>c</b>d");
|
||||
});
|
||||
});
|
||||
|
||||
describe('Linky', function() {
|
||||
describe('linky', function() {
|
||||
var linky = filter.linky;
|
||||
it('should do basic filter', function(){
|
||||
assertEquals(
|
||||
'<a href="http://ab/">http://ab/</a> ' +
|
||||
'(<a href="http://a/">http://a/</a>) ' +
|
||||
'<<a href="http://a/">http://a/</a>> ' +
|
||||
'<a href="http://1.2/v:~-123">http://1.2/v:~-123</a>. c',
|
||||
linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c").html);
|
||||
assertEquals(undefined, linky(undefined));
|
||||
it('should do basic filter', function() {
|
||||
expect(linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c").html).
|
||||
toEqual('<a href="http://ab/">http://ab/</a> ' +
|
||||
'(<a href="http://a/">http://a/</a>) ' +
|
||||
'<<a href="http://a/">http://a/</a>> ' +
|
||||
'<a href="http://1.2/v:~-123">http://1.2/v:~-123</a>. c');
|
||||
expect(linky(undefined)).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle mailto:', function(){
|
||||
it('should handle mailto:', function() {
|
||||
expect(linky("mailto:me@example.com").html).toEqual('<a href="mailto:me@example.com">me@example.com</a>');
|
||||
expect(linky("me@example.com").html).toEqual('<a href="mailto:me@example.com">me@example.com</a>');
|
||||
expect(linky("send email to me@example.com, but").html).
|
||||
@@ -95,13 +104,12 @@ describe('filter', function(){
|
||||
|
||||
describe('date', function(){
|
||||
|
||||
var morning = new TzDate(+5, '2010-09-03T12:05:08Z'); //7am
|
||||
var noon = new TzDate(+5, '2010-09-03T17:05:08Z'); //12pm
|
||||
var midnight = new TzDate(+5, '2010-09-03T05:05:08Z'); //12am
|
||||
|
||||
var morning = new TzDate(+5, '2010-09-03T12:05:08.000Z'); //7am
|
||||
var noon = new TzDate(+5, '2010-09-03T17:05:08.000Z'); //12pm
|
||||
var midnight = new TzDate(+5, '2010-09-03T05:05:08.000Z'); //12am
|
||||
|
||||
it('should ignore falsy inputs', function() {
|
||||
expect(filter.date(null)).toEqual(null);
|
||||
expect(filter.date(null)).toBeNull();
|
||||
expect(filter.date('')).toEqual('');
|
||||
});
|
||||
|
||||
@@ -115,7 +123,7 @@ describe('filter', function(){
|
||||
expect(filter.date(noon.getTime() + "")).toEqual(noon.toLocaleDateString());
|
||||
});
|
||||
|
||||
it('should accept format', function() {
|
||||
it('should accept various format strings', function() {
|
||||
expect(filter.date(morning, "yy-MM-dd HH:mm:ss")).
|
||||
toEqual('10-09-03 07:05:08');
|
||||
|
||||
@@ -128,5 +136,11 @@ describe('filter', function(){
|
||||
expect(filter.date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=12:05:08pm0500');
|
||||
});
|
||||
|
||||
it('should be able to parse ISO 8601 dates/times using', function() {
|
||||
var isoString = '2010-09-03T05:05:08.872Z';
|
||||
expect(filter.date(isoString)).
|
||||
toEqual(angular.String.toDate(isoString).toLocaleDateString());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+85
-72
@@ -1,107 +1,107 @@
|
||||
describe('json', function(){
|
||||
it('should parse Primitives', function() {
|
||||
assertEquals("null", toJson(0/0));
|
||||
assertEquals("null", toJson(null));
|
||||
assertEquals("true", toJson(true));
|
||||
assertEquals("false", toJson(false));
|
||||
assertEquals("123.45", toJson(123.45));
|
||||
assertEquals('"abc"', toJson("abc"));
|
||||
assertEquals('"a \\t \\n \\r b \\\\"', toJson("a \t \n \r b \\"));
|
||||
it('should serialize primitives', function() {
|
||||
expect(toJson(0/0)).toEqual('null');
|
||||
expect(toJson(null)).toEqual('null');
|
||||
expect(toJson(true)).toEqual('true');
|
||||
expect(toJson(false)).toEqual('false');
|
||||
expect(toJson(123.45)).toEqual("123.45");
|
||||
expect(toJson("abc")).toEqual('"abc"');
|
||||
expect(toJson("a \t \n \r b \\")).toEqual('"a \\t \\n \\r b \\\\"');
|
||||
});
|
||||
|
||||
it('should parse Escaping', function() {
|
||||
assertEquals("\"7\\\\\\\"7\"", toJson("7\\\"7"));
|
||||
it('should serialize strings with escaped characters', function() {
|
||||
expect(toJson("7\\\"7")).toEqual("\"7\\\\\\\"7\"");
|
||||
});
|
||||
|
||||
it('should parse Objects', function() {
|
||||
assertEquals('{"a":1,"b":2}', toJson({a:1,b:2}));
|
||||
assertEquals('{"a":{"b":2}}', toJson({a:{b:2}}));
|
||||
assertEquals('{"a":{"b":{"c":0}}}', toJson({a:{b:{c:0}}}));
|
||||
assertEquals('{"a":{"b":null}}', toJson({a:{b:0/0}}));
|
||||
it('should serialize objects', function() {
|
||||
expect(toJson({a:1,b:2})).toEqual('{"a":1,"b":2}');
|
||||
expect(toJson({a:{b:2}})).toEqual('{"a":{"b":2}}');
|
||||
expect(toJson({a:{b:{c:0}}})).toEqual('{"a":{"b":{"c":0}}}');
|
||||
expect(toJson({a:{b:0/0}})).toEqual('{"a":{"b":null}}');
|
||||
});
|
||||
|
||||
it('should parse ObjectPretty', function() {
|
||||
assertEquals('{\n "a":1,\n "b":2}', toJson({a:1,b:2}, true));
|
||||
assertEquals('{\n "a":{\n "b":2}}', toJson({a:{b:2}}, true));
|
||||
it('should format objects pretty', function() {
|
||||
expect(toJson({a:1,b:2}, true)).toEqual('{\n "a":1,\n "b":2}');
|
||||
expect(toJson({a:{b:2}}, true)).toEqual('{\n "a":{\n "b":2}}');
|
||||
});
|
||||
|
||||
it('should parse Array', function() {
|
||||
assertEquals('[]', toJson([]));
|
||||
assertEquals('[1,"b"]', toJson([1,"b"]));
|
||||
it('should serialize array', function() {
|
||||
expect(toJson([])).toEqual('[]');
|
||||
expect(toJson([1,"b"])).toEqual('[1,"b"]');
|
||||
});
|
||||
|
||||
it('should parse RegExp', function() {
|
||||
assertEquals('"/foo/"', toJson(/foo/));
|
||||
assertEquals('[1,"/foo/"]', toJson([1,new RegExp("foo")]));
|
||||
it('should serialize RegExp', function() {
|
||||
expect(toJson(/foo/)).toEqual('"/foo/"');
|
||||
expect(toJson([1,new RegExp("foo")])).toEqual('[1,"/foo/"]');
|
||||
});
|
||||
|
||||
it('should parse IgnoreFunctions', function() {
|
||||
assertEquals('[null,1]', toJson([function(){},1]));
|
||||
assertEquals('{}', toJson({a:function(){}}));
|
||||
it('should ignore functions', function() {
|
||||
expect(toJson([function(){},1])).toEqual('[null,1]');
|
||||
expect(toJson({a:function(){}})).toEqual('{}');
|
||||
});
|
||||
|
||||
it('should parse ParseNull', function() {
|
||||
assertNull(fromJson("null"));
|
||||
it('should parse null', function() {
|
||||
expect(fromJson("null")).toBeNull();
|
||||
});
|
||||
|
||||
it('should parse ParseBoolean', function() {
|
||||
assertTrue(fromJson("true"));
|
||||
assertFalse(fromJson("false"));
|
||||
it('should parse boolean', function() {
|
||||
expect(fromJson("true")).toBeTruthy();
|
||||
expect(fromJson("false")).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should parse $$isIgnored', function() {
|
||||
assertEquals("{}", toJson({$$:0}));
|
||||
});
|
||||
|
||||
it('should parse ArrayWithEmptyItems', function() {
|
||||
it('should serialize array with empty items', function() {
|
||||
var a = [];
|
||||
a[1] = "X";
|
||||
assertEquals('[null,"X"]', toJson(a));
|
||||
expect(toJson(a)).toEqual('[null,"X"]');
|
||||
});
|
||||
|
||||
it('should parse ItShouldEscapeUnicode', function() {
|
||||
assertEquals(1, "\u00a0".length);
|
||||
assertEquals(8, toJson("\u00a0").length);
|
||||
assertEquals(1, fromJson(toJson("\u00a0")).length);
|
||||
it('should escape unicode', function() {
|
||||
expect("\u00a0".length).toEqual(1);
|
||||
expect(toJson("\u00a0").length).toEqual(8);
|
||||
expect(fromJson(toJson("\u00a0")).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should parse ItShouldUTCDates', function() {
|
||||
var date = angular.String.toDate("2009-10-09T01:02:03Z");
|
||||
assertEquals('"2009-10-09T01:02:03Z"', toJson(date));
|
||||
assertEquals(date.getTime(),
|
||||
fromJson('"2009-10-09T01:02:03Z"').getTime());
|
||||
it('should serialize UTC dates', function() {
|
||||
var date = angular.String.toDate("2009-10-09T01:02:03.027Z");
|
||||
expect(toJson(date)).toEqual('"2009-10-09T01:02:03.027Z"');
|
||||
expect(fromJson('"2009-10-09T01:02:03.027Z"').getTime()).toEqual(date.getTime());
|
||||
});
|
||||
|
||||
it('should parse ItShouldPreventRecursion', function() {
|
||||
it('should prevent recursion', function() {
|
||||
var obj = {a:'b'};
|
||||
obj.recursion = obj;
|
||||
assertEquals('{"a":"b","recursion":RECURSION}', angular.toJson(obj));
|
||||
expect(angular.toJson(obj)).toEqual('{"a":"b","recursion":RECURSION}');
|
||||
});
|
||||
|
||||
it('should parse ItShouldIgnore$Properties', function() {
|
||||
var scope = createScope();
|
||||
scope.a = 'a';
|
||||
scope['$b'] = '$b';
|
||||
scope.c = 'c';
|
||||
expect(angular.toJson(scope)).toEqual('{"a":"a","c":"c","this":RECURSION}');
|
||||
it('should serialize $ properties', function() {
|
||||
var obj = {$a: 'a'};
|
||||
expect(angular.toJson(obj)).toEqual('{"$a":"a"}');
|
||||
});
|
||||
|
||||
it('should parse ItShouldSerializeInheritedProperties', function() {
|
||||
var scope = createScope({p:'p'});
|
||||
scope.a = 'a';
|
||||
expect(angular.toJson(scope)).toEqual('{"a":"a","p":"p","this":RECURSION}');
|
||||
it('should serialize inherited properties', function() {
|
||||
var obj = inherit({p:'p'});
|
||||
obj.a = 'a';
|
||||
expect(angular.toJson(obj)).toEqual('{"a":"a","p":"p"}');
|
||||
});
|
||||
|
||||
it('should parse ItShouldSerializeSameObjectsMultipleTimes', function() {
|
||||
it('should serialize same objects multiple times', function() {
|
||||
var obj = {a:'b'};
|
||||
assertEquals('{"A":{"a":"b"},"B":{"a":"b"}}', angular.toJson({A:obj, B:obj}));
|
||||
expect(angular.toJson({A:obj, B:obj})).toEqual('{"A":{"a":"b"},"B":{"a":"b"}}');
|
||||
});
|
||||
|
||||
it('should parse ItShouldNotSerializeUndefinedValues', function() {
|
||||
assertEquals('{}', angular.toJson({A:undefined}));
|
||||
it('should not serialize undefined values', function() {
|
||||
expect(angular.toJson({A:undefined})).toEqual('{}');
|
||||
});
|
||||
|
||||
it('should not serialize $window object', function() {
|
||||
expect(toJson(window)).toEqual('WINDOW');
|
||||
});
|
||||
|
||||
it('should not serialize $document object', function() {
|
||||
expect(toJson(document)).toEqual('DOCUMENT');
|
||||
});
|
||||
|
||||
it('should parse ItShouldParseFloats', function() {
|
||||
it('should parse floats', function() {
|
||||
expect(fromJson("{value:2.55, name:'misko'}")).toEqual({value:2.55, name:'misko'});
|
||||
});
|
||||
|
||||
@@ -109,35 +109,48 @@ describe('json', function(){
|
||||
expect(fromJson("{neg:-2.55, pos:+.3, a:[-2, +.1, -.2, +.3]}")).toEqual({neg:-2.55, pos:+.3, a:[-2, +.1, -.2, +.3]});
|
||||
});
|
||||
|
||||
it('should parse exponents', function() {
|
||||
expect(fromJson("{exp:1.2E10}")).toEqual({exp:1.2E10});
|
||||
expect(fromJson("{exp:1.2E-10}")).toEqual({exp:1.2E-10});
|
||||
expect(fromJson("{exp:1.2e+10}")).toEqual({exp:1.2E10});
|
||||
expect(fromJson("{exp:1.2e-10}")).toEqual({exp:1.2E-10});
|
||||
});
|
||||
|
||||
describe('security', function(){
|
||||
it('should not allow naked expressions', function(){
|
||||
expect(function(){fromJson('1+2');}).toThrow("Did not understand '+2' while evaluating '1+2'.");
|
||||
expect(function(){fromJson('1+2');}).
|
||||
toThrow(new Error("Parse Error: Token '+' is extra token not part of expression at column 2 of expression [1+2] starting at [+2]."));
|
||||
});
|
||||
|
||||
it('should not allow naked expressions group', function(){
|
||||
expect(function(){fromJson('(1+2)');}).toThrow("Expression at column='0' of expression '(1+2)' starting at '(1+2)' is not valid json.");
|
||||
expect(function(){fromJson('(1+2)');}).
|
||||
toThrow(new Error("Parse Error: Token '(' is not valid json at column 1 of expression [(1+2)] starting at [(1+2)]."));
|
||||
});
|
||||
|
||||
it('should not allow expressions in objects', function(){
|
||||
expect(function(){fromJson('{a:abc()}');}).toThrow("Expression at column='3' of expression '{a:abc()}' starting at 'abc()}' is not valid json.");
|
||||
expect(function(){fromJson('{a:abc()}');}).
|
||||
toThrow(new Error("Parse Error: Token 'abc' is not valid json at column 4 of expression [{a:abc()}] starting at [abc()}]."));
|
||||
});
|
||||
|
||||
it('should not allow expressions in arrays', function(){
|
||||
expect(function(){fromJson('[1+2]');}).toThrow("Expression at column='2' of expression '[1+2]' starting at '+2]' is not valid json.");
|
||||
expect(function(){fromJson('[1+2]');}).
|
||||
toThrow(new Error("Parse Error: Token '+' is not valid json at column 3 of expression [[1+2]] starting at [+2]]."));
|
||||
});
|
||||
|
||||
it('should not allow vars', function(){
|
||||
expect(function(){fromJson('[1, x]');}).toThrow("Expression at column='4' of expression '[1, x]' starting at 'x]' is not valid json.");
|
||||
expect(function(){fromJson('[1, x]');}).
|
||||
toThrow(new Error("Parse Error: Token 'x' is not valid json at column 5 of expression [[1, x]] starting at [x]]."));
|
||||
});
|
||||
|
||||
it('should not allow dereference', function(){
|
||||
expect(function(){fromJson('["".constructor]');}).toThrow("Expression at column='3' of expression '[\"\".constructor]' starting at '.constructor]' is not valid json.");
|
||||
expect(function(){fromJson('["".constructor]');}).
|
||||
toThrow(new Error("Parse Error: Token '.' is not valid json at column 4 of expression [[\"\".constructor]] starting at [.constructor]]."));
|
||||
});
|
||||
|
||||
it('should not allow expressions ofter valid json', function(){
|
||||
expect(function(){fromJson('[].constructor');}).toThrow("Expression at column='2' of expression '[].constructor' starting at '.constructor' is not valid json.");
|
||||
expect(function(){fromJson('[].constructor');}).
|
||||
toThrow(new Error("Parse Error: Token '.' is not valid json at column 3 of expression [[].constructor] starting at [.constructor]."));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
+219
-288
@@ -1,146 +1,125 @@
|
||||
describe('parser', function(){
|
||||
describe('lexer', function(){
|
||||
it('should TokenizeAString', function(){
|
||||
describe('parser', function() {
|
||||
describe('lexer', function() {
|
||||
it('should tokenize a string', function() {
|
||||
var tokens = lex("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\"");
|
||||
var i = 0;
|
||||
assertEquals(tokens[i].index, 0);
|
||||
assertEquals(tokens[i].text, 'a.bc');
|
||||
expect(tokens[i].index).toEqual(0);
|
||||
expect(tokens[i].text).toEqual('a.bc');
|
||||
|
||||
i++;
|
||||
assertEquals(tokens[i].index, 4);
|
||||
assertEquals(tokens[i].text, '[');
|
||||
expect(tokens[i].index).toEqual(4);
|
||||
expect(tokens[i].text).toEqual('[');
|
||||
|
||||
i++;
|
||||
assertEquals(tokens[i].index, 5);
|
||||
assertEquals(tokens[i].text, 22);
|
||||
expect(tokens[i].index).toEqual(5);
|
||||
expect(tokens[i].text).toEqual(22);
|
||||
|
||||
i++;
|
||||
assertEquals(tokens[i].index, 7);
|
||||
assertEquals(tokens[i].text, ']');
|
||||
expect(tokens[i].index).toEqual(7);
|
||||
expect(tokens[i].text).toEqual(']');
|
||||
|
||||
i++;
|
||||
assertEquals(tokens[i].index, 8);
|
||||
assertEquals(tokens[i].text, '+');
|
||||
expect(tokens[i].index).toEqual(8);
|
||||
expect(tokens[i].text).toEqual('+');
|
||||
|
||||
i++;
|
||||
assertEquals(tokens[i].index, 9);
|
||||
assertEquals(tokens[i].text, 1.3);
|
||||
expect(tokens[i].index).toEqual(9);
|
||||
expect(tokens[i].text).toEqual(1.3);
|
||||
|
||||
i++;
|
||||
assertEquals(tokens[i].index, 12);
|
||||
assertEquals(tokens[i].text, '|');
|
||||
expect(tokens[i].index).toEqual(12);
|
||||
expect(tokens[i].text).toEqual('|');
|
||||
|
||||
i++;
|
||||
assertEquals(tokens[i].index, 13);
|
||||
assertEquals(tokens[i].text, 'f');
|
||||
expect(tokens[i].index).toEqual(13);
|
||||
expect(tokens[i].text).toEqual('f');
|
||||
|
||||
i++;
|
||||
assertEquals(tokens[i].index, 14);
|
||||
assertEquals(tokens[i].text, ':');
|
||||
expect(tokens[i].index).toEqual(14);
|
||||
expect(tokens[i].text).toEqual(':');
|
||||
|
||||
i++;
|
||||
assertEquals(tokens[i].index, 15);
|
||||
assertEquals(tokens[i].string, "a'c");
|
||||
expect(tokens[i].index).toEqual(15);
|
||||
expect(tokens[i].string).toEqual("a'c");
|
||||
|
||||
i++;
|
||||
assertEquals(tokens[i].index, 21);
|
||||
assertEquals(tokens[i].text, ':');
|
||||
expect(tokens[i].index).toEqual(21);
|
||||
expect(tokens[i].text).toEqual(':');
|
||||
|
||||
i++;
|
||||
assertEquals(tokens[i].index, 22);
|
||||
assertEquals(tokens[i].string, 'd"e');
|
||||
expect(tokens[i].index).toEqual(22);
|
||||
expect(tokens[i].string).toEqual('d"e');
|
||||
});
|
||||
|
||||
it('should TokenizeUndefined', function(){
|
||||
it('should tokenize undefined', function() {
|
||||
var tokens = lex("undefined");
|
||||
var i = 0;
|
||||
assertEquals(tokens[i].index, 0);
|
||||
assertEquals(tokens[i].text, 'undefined');
|
||||
assertEquals(undefined, tokens[i].fn());
|
||||
expect(tokens[i].index).toEqual(0);
|
||||
expect(tokens[i].text).toEqual('undefined');
|
||||
expect(undefined).toEqual(tokens[i].fn());
|
||||
});
|
||||
|
||||
|
||||
|
||||
it('should TokenizeRegExp', function(){
|
||||
var tokens = lex("/r 1/");
|
||||
var i = 0;
|
||||
assertEquals(tokens[i].index, 0);
|
||||
assertEquals(tokens[i].text, 'r 1');
|
||||
assertEquals("r 1".match(tokens[i].fn())[0], 'r 1');
|
||||
});
|
||||
|
||||
it('should QuotedString', function(){
|
||||
|
||||
it('should tokenize quoted string', function() {
|
||||
var str = "['\\'', \"\\\"\"]";
|
||||
var tokens = lex(str);
|
||||
|
||||
assertEquals(1, tokens[1].index);
|
||||
assertEquals("'", tokens[1].string);
|
||||
expect(tokens[1].index).toEqual(1);
|
||||
expect(tokens[1].string).toEqual("'");
|
||||
|
||||
assertEquals(7, tokens[3].index);
|
||||
assertEquals('"', tokens[3].string);
|
||||
expect(tokens[3].index).toEqual(7);
|
||||
expect(tokens[3].string).toEqual('"');
|
||||
});
|
||||
|
||||
it('should QuotedStringEscape', function(){
|
||||
it('should tokenize escaped quoted string', function() {
|
||||
var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
|
||||
var tokens = lex(str);
|
||||
|
||||
assertEquals('"\n\f\r\t\v\u00A0', tokens[0].string);
|
||||
expect(tokens[0].string).toEqual('"\n\f\r\t\v\u00A0');
|
||||
});
|
||||
|
||||
it('should TokenizeUnicode', function(){
|
||||
it('should tokenize unicode', function() {
|
||||
var tokens = lex('"\\u00A0"');
|
||||
assertEquals(1, tokens.length);
|
||||
assertEquals('\u00a0', tokens[0].string);
|
||||
expect(tokens.length).toEqual(1);
|
||||
expect(tokens[0].string).toEqual('\u00a0');
|
||||
});
|
||||
|
||||
it('should error when non terminated string', function(){
|
||||
expect(function(){
|
||||
lex('ignore "text');
|
||||
}).toThrow(new Error('Lexer Error: Unterminated string at column 7 in expression [ignore "text].'));
|
||||
});
|
||||
|
||||
it('should TokenizeRegExpWithOptions', function(){
|
||||
var tokens = lex("/r/g");
|
||||
var i = 0;
|
||||
assertEquals(tokens[i].index, 0);
|
||||
assertEquals(tokens[i].text, 'r');
|
||||
assertEquals(tokens[i].flags, 'g');
|
||||
assertEquals("rr".match(tokens[i].fn()).length, 2);
|
||||
it('should ignore whitespace', function() {
|
||||
var tokens = lex("a \t \n \r \u00A0 b");
|
||||
expect(tokens[0].text).toEqual('a');
|
||||
expect(tokens[1].text).toEqual('b');
|
||||
});
|
||||
|
||||
it('should TokenizeRegExpWithEscape', function(){
|
||||
var tokens = lex("/\\/\\d/");
|
||||
var i = 0;
|
||||
assertEquals(tokens[i].index, 0);
|
||||
assertEquals(tokens[i].text, '\\/\\d');
|
||||
assertEquals("/1".match(tokens[i].fn())[0], '/1');
|
||||
});
|
||||
|
||||
it('should IgnoreWhitespace', function(){
|
||||
var tokens = lex("a \t \n \r b");
|
||||
assertEquals(tokens[0].text, 'a');
|
||||
assertEquals(tokens[1].text, 'b');
|
||||
});
|
||||
|
||||
it('should Relation', function(){
|
||||
it('should tokenize relation', function() {
|
||||
var tokens = lex("! == != < > <= >=");
|
||||
assertEquals(tokens[0].text, '!');
|
||||
assertEquals(tokens[1].text, '==');
|
||||
assertEquals(tokens[2].text, '!=');
|
||||
assertEquals(tokens[3].text, '<');
|
||||
assertEquals(tokens[4].text, '>');
|
||||
assertEquals(tokens[5].text, '<=');
|
||||
assertEquals(tokens[6].text, '>=');
|
||||
expect(tokens[0].text).toEqual('!');
|
||||
expect(tokens[1].text).toEqual('==');
|
||||
expect(tokens[2].text).toEqual('!=');
|
||||
expect(tokens[3].text).toEqual('<');
|
||||
expect(tokens[4].text).toEqual('>');
|
||||
expect(tokens[5].text).toEqual('<=');
|
||||
expect(tokens[6].text).toEqual('>=');
|
||||
});
|
||||
|
||||
it('should Statements', function(){
|
||||
it('should tokenize statements', function() {
|
||||
var tokens = lex("a;b;");
|
||||
assertEquals(tokens[0].text, 'a');
|
||||
assertEquals(tokens[1].text, ';');
|
||||
assertEquals(tokens[2].text, 'b');
|
||||
assertEquals(tokens[3].text, ';');
|
||||
expect(tokens[0].text).toEqual('a');
|
||||
expect(tokens[1].text).toEqual(';');
|
||||
expect(tokens[2].text).toEqual('b');
|
||||
expect(tokens[3].text).toEqual(';');
|
||||
});
|
||||
|
||||
it('should Number', function(){
|
||||
it('should tokenize number', function() {
|
||||
var tokens = lex("0.5");
|
||||
expect(tokens[0].text).toEqual(0.5);
|
||||
});
|
||||
|
||||
it('should NegativeNumber', function(){
|
||||
it('should tokenize negative number', function() {
|
||||
var value = createScope().$eval("-0.5");
|
||||
expect(value).toEqual(-0.5);
|
||||
|
||||
@@ -148,7 +127,7 @@ describe('parser', function(){
|
||||
expect(value).toEqual({a:-0.5});
|
||||
});
|
||||
|
||||
it('should NumberExponent', function(){
|
||||
it('should tokenize number with exponent', function() {
|
||||
var tokens = lex("0.5E-10");
|
||||
expect(tokens[0].text).toEqual(0.5E-10);
|
||||
expect(createScope().$eval("0.5E-10")).toEqual(0.5E-10);
|
||||
@@ -157,308 +136,260 @@ describe('parser', function(){
|
||||
expect(tokens[0].text).toEqual(0.5E+10);
|
||||
});
|
||||
|
||||
it('should NumberExponentInvalid', function(){
|
||||
assertThrows('Lexer found invalid exponential value "0.5E-"', function(){
|
||||
lex("0.5E-");
|
||||
});
|
||||
assertThrows('Lexer found invalid exponential value "0.5E-A"', function(){
|
||||
lex("0.5E-A");
|
||||
});
|
||||
});
|
||||
|
||||
it('should NumberStartingWithDot', function(){
|
||||
it('should tokenize number starting with a dot', function() {
|
||||
var tokens = lex(".5");
|
||||
expect(tokens[0].text).toEqual(0.5);
|
||||
});
|
||||
|
||||
it('should throw error on invalid unicode', function(){
|
||||
assertThrows("Lexer Error: Invalid unicode escape [\\u1''b] starting at column '0' in expression ''\\u1''bla''.", function(){
|
||||
lex("'\\u1''bla'");
|
||||
});
|
||||
it('should throw error on invalid unicode', function() {
|
||||
expect(function() {
|
||||
lex("'\\u1xbla'");
|
||||
}).toThrow(new Error("Lexer Error: Invalid unicode escape [\\u1xbl] at columns 0-9 ['\\u1xbla'] in expression ['\\u1xbla']."));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var scope;
|
||||
beforeEach(function () {
|
||||
scope = createScope();
|
||||
});
|
||||
|
||||
it('should parse Expressions', function(){
|
||||
var scope = createScope();
|
||||
assertEquals(scope.$eval("-1"), -1);
|
||||
assertEquals(scope.$eval("1 + 2.5"), 3.5);
|
||||
assertEquals(scope.$eval("1 + -2.5"), -1.5);
|
||||
assertEquals(scope.$eval("1+2*3/4"), 1+2*3/4);
|
||||
assertEquals(scope.$eval("0--1+1.5"), 0- -1 + 1.5);
|
||||
assertEquals(scope.$eval("-0--1++2*-3/-4"), -0- -1+ +2*-3/-4);
|
||||
assertEquals(scope.$eval("1/2*3"), 1/2*3);
|
||||
it('should parse expressions', function() {
|
||||
expect(scope.$eval("-1")).toEqual(-1);
|
||||
expect(scope.$eval("1 + 2.5")).toEqual(3.5);
|
||||
expect(scope.$eval("1 + -2.5")).toEqual(-1.5);
|
||||
expect(scope.$eval("1+2*3/4")).toEqual(1+2*3/4);
|
||||
expect(scope.$eval("0--1+1.5")).toEqual(0- -1 + 1.5);
|
||||
expect(scope.$eval("-0--1++2*-3/-4")).toEqual(-0- -1+ +2*-3/-4);
|
||||
expect(scope.$eval("1/2*3")).toEqual(1/2*3);
|
||||
});
|
||||
|
||||
it('should parse Comparison', function(){
|
||||
var scope = createScope();
|
||||
assertEquals(scope.$eval("false"), false);
|
||||
assertEquals(scope.$eval("!true"), false);
|
||||
assertEquals(scope.$eval("1==1"), true);
|
||||
assertEquals(scope.$eval("1!=2"), true);
|
||||
assertEquals(scope.$eval("1<2"), true);
|
||||
assertEquals(scope.$eval("1<=1"), true);
|
||||
assertEquals(scope.$eval("1>2"), 1>2);
|
||||
assertEquals(scope.$eval("2>=1"), 2>=1);
|
||||
|
||||
assertEquals(true === 2<3, scope.$eval("true==2<3"));
|
||||
|
||||
it('should parse comparison', function() {
|
||||
expect(scope.$eval("false")).toBeFalsy();
|
||||
expect(scope.$eval("!true")).toBeFalsy();
|
||||
expect(scope.$eval("1==1")).toBeTruthy();
|
||||
expect(scope.$eval("1!=2")).toBeTruthy();
|
||||
expect(scope.$eval("1<2")).toBeTruthy();
|
||||
expect(scope.$eval("1<=1")).toBeTruthy();
|
||||
expect(scope.$eval("1>2")).toEqual(1>2);
|
||||
expect(scope.$eval("2>=1")).toEqual(2>=1);
|
||||
expect(scope.$eval("true==2<3")).toEqual(true === 2<3);
|
||||
});
|
||||
|
||||
it('should parse Logical', function(){
|
||||
var scope = createScope();
|
||||
assertEquals(scope.$eval("0&&2"), 0&&2);
|
||||
assertEquals(scope.$eval("0||2"), 0||2);
|
||||
assertEquals(scope.$eval("0||1&&2"), 0||1&&2);
|
||||
it('should parse logical', function() {
|
||||
expect(scope.$eval("0&&2")).toEqual(0&&2);
|
||||
expect(scope.$eval("0||2")).toEqual(0||2);
|
||||
expect(scope.$eval("0||1&&2")).toEqual(0||1&&2);
|
||||
});
|
||||
|
||||
it('should parse String', function(){
|
||||
var scope = createScope();
|
||||
assertEquals(scope.$eval("'a' + 'b c'"), "ab c");
|
||||
it('should parse string', function() {
|
||||
expect(scope.$eval("'a' + 'b c'")).toEqual("ab c");
|
||||
});
|
||||
|
||||
it('should parse Filters', function(){
|
||||
it('should parse filters', function(){
|
||||
angular.filter.substring = function(input, start, end) {
|
||||
return input.substring(start, end);
|
||||
};
|
||||
|
||||
angular.filter.upper = {_case:function(input) {
|
||||
angular.filter.upper = {_case: function(input) {
|
||||
return input.toUpperCase();
|
||||
}};
|
||||
var scope = createScope();
|
||||
try {
|
||||
|
||||
expect(function() {
|
||||
scope.$eval("1|nonExistant");
|
||||
fail();
|
||||
} catch (e) {
|
||||
assertEquals(e, "Function 'nonExistant' at column '3' in '1|nonExistant' is not defined.");
|
||||
}
|
||||
}).toThrow(new Error("Parse Error: Token 'nonExistant' should be a function at column 3 of expression [1|nonExistant] starting at [nonExistant]."));
|
||||
|
||||
scope.$set('offset', 3);
|
||||
assertEquals(scope.$eval("'abcd'|upper._case"), "ABCD");
|
||||
assertEquals(scope.$eval("'abcd'|substring:1:offset"), "bc");
|
||||
assertEquals(scope.$eval("'abcd'|substring:1:3|upper._case"), "BC");
|
||||
expect(scope.$eval("'abcd'|upper._case")).toEqual("ABCD");
|
||||
expect(scope.$eval("'abcd'|substring:1:offset")).toEqual("bc");
|
||||
expect(scope.$eval("'abcd'|substring:1:3|upper._case")).toEqual("BC");
|
||||
});
|
||||
|
||||
it('should parse ScopeAccess', function(){
|
||||
var scope = createScope();
|
||||
it('should access scope', function() {
|
||||
scope.$set('a', 123);
|
||||
scope.$set('b.c', 456);
|
||||
assertEquals(scope.$eval("a", scope), 123);
|
||||
assertEquals(scope.$eval("b.c", scope), 456);
|
||||
assertEquals(scope.$eval("x.y.z", scope), undefined);
|
||||
expect(scope.$eval("a", scope)).toEqual(123);
|
||||
expect(scope.$eval("b.c", scope)).toEqual(456);
|
||||
expect(scope.$eval("x.y.z", scope)).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should parse Grouping', function(){
|
||||
var scope = createScope();
|
||||
assertEquals(scope.$eval("(1+2)*3"), (1+2)*3);
|
||||
it('should evaluate grouped expressions', function() {
|
||||
expect(scope.$eval("(1+2)*3")).toEqual((1+2)*3);
|
||||
});
|
||||
|
||||
it('should parse Assignments', function(){
|
||||
var scope = createScope();
|
||||
assertEquals(scope.$eval("a=12"), 12);
|
||||
assertEquals(scope.$get("a"), 12);
|
||||
it('should evaluate assignments', function() {
|
||||
expect(scope.$eval("a=12")).toEqual(12);
|
||||
expect(scope.$get("a")).toEqual(12);
|
||||
|
||||
scope = createScope();
|
||||
assertEquals(scope.$eval("x.y.z=123;"), 123);
|
||||
assertEquals(scope.$get("x.y.z"), 123);
|
||||
expect(scope.$eval("x.y.z=123;")).toEqual(123);
|
||||
expect(scope.$get("x.y.z")).toEqual(123);
|
||||
|
||||
assertEquals(234, scope.$eval("a=123; b=234"));
|
||||
assertEquals(123, scope.$get("a"));
|
||||
assertEquals(234, scope.$get("b"));
|
||||
expect(scope.$eval("a=123; b=234")).toEqual(234);
|
||||
expect(scope.$get("a")).toEqual(123);
|
||||
expect(scope.$get("b")).toEqual(234);
|
||||
});
|
||||
|
||||
it('should parse FunctionCallsNoArgs', function(){
|
||||
var scope = createScope();
|
||||
it('should evaluate function call without arguments', function() {
|
||||
scope.$set('const', function(a,b){return 123;});
|
||||
assertEquals(scope.$eval("const()"), 123);
|
||||
expect(scope.$eval("const()")).toEqual(123);
|
||||
});
|
||||
|
||||
it('should parse FunctionCalls', function(){
|
||||
var scope = createScope();
|
||||
scope.$set('add', function(a,b){
|
||||
it('should evaluate function call with arguments', function() {
|
||||
scope.$set('add', function(a,b) {
|
||||
return a+b;
|
||||
});
|
||||
assertEquals(3, scope.$eval("add(1,2)"));
|
||||
expect(scope.$eval("add(1,2)")).toEqual(3);
|
||||
});
|
||||
|
||||
it('should parse CalculationBug', function(){
|
||||
var scope = createScope();
|
||||
it('should evaluate multiplication and division', function() {
|
||||
scope.$set('taxRate', 8);
|
||||
scope.$set('subTotal', 100);
|
||||
assertEquals(scope.$eval("taxRate / 100 * subTotal"), 8);
|
||||
assertEquals(scope.$eval("subTotal * taxRate / 100"), 8);
|
||||
expect(scope.$eval("taxRate / 100 * subTotal")).toEqual(8);
|
||||
expect(scope.$eval("subTotal * taxRate / 100")).toEqual(8);
|
||||
});
|
||||
|
||||
it('should parse Array', function(){
|
||||
var scope = createScope();
|
||||
assertEquals(scope.$eval("[]").length, 0);
|
||||
assertEquals(scope.$eval("[1, 2]").length, 2);
|
||||
assertEquals(scope.$eval("[1, 2]")[0], 1);
|
||||
assertEquals(scope.$eval("[1, 2]")[1], 2);
|
||||
it('should evaluate array', function() {
|
||||
expect(scope.$eval("[]").length).toEqual(0);
|
||||
expect(scope.$eval("[1, 2]").length).toEqual(2);
|
||||
expect(scope.$eval("[1, 2]")[0]).toEqual(1);
|
||||
expect(scope.$eval("[1, 2]")[1]).toEqual(2);
|
||||
});
|
||||
|
||||
it('should parse ArrayAccess', function(){
|
||||
var scope = createScope();
|
||||
assertEquals(scope.$eval("[1][0]"), 1);
|
||||
assertEquals(scope.$eval("[[1]][0][0]"), 1);
|
||||
assertEquals(scope.$eval("[].length"), 0);
|
||||
assertEquals(scope.$eval("[1, 2].length"), 2);
|
||||
it('should evaluate array access', function() {
|
||||
expect(scope.$eval("[1][0]")).toEqual(1);
|
||||
expect(scope.$eval("[[1]][0][0]")).toEqual(1);
|
||||
expect(scope.$eval("[].length")).toEqual(0);
|
||||
expect(scope.$eval("[1, 2].length")).toEqual(2);
|
||||
});
|
||||
|
||||
it('should parse Object', function(){
|
||||
var scope = createScope();
|
||||
assertEquals(toJson(scope.$eval("{}")), "{}");
|
||||
assertEquals(toJson(scope.$eval("{a:'b'}")), '{"a":"b"}');
|
||||
assertEquals(toJson(scope.$eval("{'a':'b'}")), '{"a":"b"}');
|
||||
assertEquals(toJson(scope.$eval("{\"a\":'b'}")), '{"a":"b"}');
|
||||
it('should evaluate object', function() {
|
||||
expect(toJson(scope.$eval("{}"))).toEqual("{}");
|
||||
expect(toJson(scope.$eval("{a:'b'}"))).toEqual('{"a":"b"}');
|
||||
expect(toJson(scope.$eval("{'a':'b'}"))).toEqual('{"a":"b"}');
|
||||
expect(toJson(scope.$eval("{\"a\":'b'}"))).toEqual('{"a":"b"}');
|
||||
});
|
||||
|
||||
it('should parse ObjectAccess', function(){
|
||||
var scope = createScope();
|
||||
assertEquals("WC", scope.$eval("{false:'WC', true:'CC'}[false]"));
|
||||
it('should evaluate object access', function() {
|
||||
expect(scope.$eval("{false:'WC', true:'CC'}[false]")).toEqual("WC");
|
||||
});
|
||||
|
||||
it('should parse JSON', function(){
|
||||
var scope = createScope();
|
||||
assertEquals(toJson(scope.$eval("[{}]")), "[{}]");
|
||||
assertEquals(toJson(scope.$eval("[{a:[]}, {b:1}]")), '[{"a":[]},{"b":1}]');
|
||||
it('should evaluate JSON', function() {
|
||||
expect(toJson(scope.$eval("[{}]"))).toEqual("[{}]");
|
||||
expect(toJson(scope.$eval("[{a:[]}, {b:1}]"))).toEqual('[{"a":[]},{"b":1}]');
|
||||
});
|
||||
|
||||
it('should parse MultippleStatements', function(){
|
||||
var scope = createScope();
|
||||
assertEquals(scope.$eval("a=1;b=3;a+b"), 4);
|
||||
assertEquals(scope.$eval(";;1;;"), 1);
|
||||
it('should evaluate multipple statements', function() {
|
||||
expect(scope.$eval("a=1;b=3;a+b")).toEqual(4);
|
||||
expect(scope.$eval(";;1;;")).toEqual(1);
|
||||
});
|
||||
|
||||
it('should parse ParseThrow', function(){
|
||||
expectAsserts(1);
|
||||
var scope = createScope();
|
||||
scope.$set('e', 'abc');
|
||||
try {
|
||||
scope.$eval("throw e");
|
||||
} catch(e) {
|
||||
assertEquals(e, 'abc');
|
||||
}
|
||||
});
|
||||
|
||||
it('should parse MethodsGetDispatchedWithCorrectThis', function(){
|
||||
var scope = createScope();
|
||||
var C = function (){
|
||||
this.a=123;
|
||||
it('should evaluate object methods in correct context (this)', function() {
|
||||
var C = function () {
|
||||
this.a = 123;
|
||||
};
|
||||
C.prototype.getA = function(){
|
||||
C.prototype.getA = function() {
|
||||
return this.a;
|
||||
};
|
||||
|
||||
scope.$set("obj", new C());
|
||||
assertEquals(123, scope.$eval("obj.getA()"));
|
||||
expect(scope.$eval("obj.getA()")).toEqual(123);
|
||||
});
|
||||
it('should parse MethodsArgumentsGetCorrectThis', function(){
|
||||
var scope = createScope();
|
||||
var C = function (){
|
||||
this.a=123;
|
||||
|
||||
it('should evaluate methods in correct context (this) in argument', function() {
|
||||
var C = function () {
|
||||
this.a = 123;
|
||||
};
|
||||
C.prototype.sum = function(value){
|
||||
C.prototype.sum = function(value) {
|
||||
return this.a + value;
|
||||
};
|
||||
C.prototype.getA = function(){
|
||||
C.prototype.getA = function() {
|
||||
return this.a;
|
||||
};
|
||||
|
||||
scope.$set("obj", new C());
|
||||
assertEquals(246, scope.$eval("obj.sum(obj.getA())"));
|
||||
expect(scope.$eval("obj.sum(obj.getA())")).toEqual(246);
|
||||
});
|
||||
|
||||
it('should parse ObjectPointsToScopeValue', function(){
|
||||
var scope = createScope();
|
||||
it('should evaluate objects on scope context', function() {
|
||||
scope.$set('a', "abc");
|
||||
assertEquals("abc", scope.$eval("{a:a}").a);
|
||||
expect(scope.$eval("{a:a}").a).toEqual("abc");
|
||||
});
|
||||
|
||||
it('should parse FieldAccess', function(){
|
||||
var scope = createScope();
|
||||
var fn = function(){
|
||||
return {name:'misko'};
|
||||
};
|
||||
scope.$set('a', fn);
|
||||
assertEquals("misko", scope.$eval("a().name"));
|
||||
it('should evaluate field access on function call result', function() {
|
||||
scope.$set('a', function() {
|
||||
return {name:'misko'};
|
||||
});
|
||||
expect(scope.$eval("a().name")).toEqual("misko");
|
||||
});
|
||||
|
||||
it('should parse ArrayIndexBug', function () {
|
||||
var scope = createScope();
|
||||
it('should evaluate field access after array access', function () {
|
||||
scope.$set('items', [{}, {name:'misko'}]);
|
||||
|
||||
assertEquals("misko", scope.$eval('items[1].name'));
|
||||
expect(scope.$eval('items[1].name')).toEqual("misko");
|
||||
});
|
||||
|
||||
it('should parse ArrayAssignment', function () {
|
||||
var scope = createScope();
|
||||
it('should evaluate array assignment', function() {
|
||||
scope.$set('items', []);
|
||||
|
||||
assertEquals("abc", scope.$eval('items[1] = "abc"'));
|
||||
assertEquals("abc", scope.$eval('items[1]'));
|
||||
expect(scope.$eval('items[1] = "abc"')).toEqual("abc");
|
||||
expect(scope.$eval('items[1]')).toEqual("abc");
|
||||
// Dont know how to make this work....
|
||||
// assertEquals("moby", scope.$eval('books[1] = "moby"'));
|
||||
// assertEquals("moby", scope.$eval('books[1]'));
|
||||
// expect(scope.$eval('books[1] = "moby"')).toEqual("moby");
|
||||
// expect(scope.$eval('books[1]')).toEqual("moby");
|
||||
});
|
||||
|
||||
it('should parse FiltersCanBeGrouped', function () {
|
||||
var scope = createScope({name:'MISKO'});
|
||||
assertEquals('misko', scope.$eval('n = (name|lowercase)'));
|
||||
assertEquals('misko', scope.$eval('n'));
|
||||
it('should evaluate grouped filters', function() {
|
||||
scope.name = 'MISKO';
|
||||
expect(scope.$eval('n = (name|lowercase)')).toEqual('misko');
|
||||
expect(scope.$eval('n')).toEqual('misko');
|
||||
});
|
||||
|
||||
it('should parse Remainder', function () {
|
||||
var scope = createScope();
|
||||
assertEquals(1, scope.$eval('1%2'));
|
||||
it('should evaluate remainder', function() {
|
||||
expect(scope.$eval('1%2')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should parse SumOfUndefinedIsNotUndefined', function () {
|
||||
var scope = createScope();
|
||||
assertEquals(1, scope.$eval('1+undefined'));
|
||||
assertEquals(1, scope.$eval('undefined+1'));
|
||||
it('should evaluate sum with undefined', function() {
|
||||
expect(scope.$eval('1+undefined')).toEqual(1);
|
||||
expect(scope.$eval('undefined+1')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should parse MissingThrowsError', function(){
|
||||
var scope = createScope();
|
||||
try {
|
||||
it('should throw exception on non-closed bracket', function() {
|
||||
expect(function() {
|
||||
scope.$eval('[].count(');
|
||||
fail();
|
||||
} catch (e) {
|
||||
assertEquals('Unexpected end of expression: [].count(', e);
|
||||
}
|
||||
}).toThrow('Unexpected end of expression: [].count(');
|
||||
});
|
||||
|
||||
it('should parse DoubleNegationBug', function (){
|
||||
var scope = createScope();
|
||||
assertEquals(true, scope.$eval('true'));
|
||||
assertEquals(false, scope.$eval('!true'));
|
||||
assertEquals(true, scope.$eval('!!true'));
|
||||
assertEquals('a', scope.$eval('{true:"a", false:"b"}[!!true]'));
|
||||
it('should evaluate double negation', function() {
|
||||
expect(scope.$eval('true')).toBeTruthy();
|
||||
expect(scope.$eval('!true')).toBeFalsy();
|
||||
expect(scope.$eval('!!true')).toBeTruthy();
|
||||
expect(scope.$eval('{true:"a", false:"b"}[!!true]')).toEqual('a');
|
||||
});
|
||||
|
||||
it('should parse NegationBug', function () {
|
||||
var scope = createScope();
|
||||
assertEquals(!false || true, scope.$eval("!false || true"));
|
||||
assertEquals(!11 == 10, scope.$eval("!11 == 10"));
|
||||
assertEquals(12/6/2, scope.$eval("12/6/2"));
|
||||
it('should evaluate negation', function() {
|
||||
expect(scope.$eval("!false || true")).toEqual(!false || true);
|
||||
expect(scope.$eval("!11 == 10")).toEqual(!11 == 10);
|
||||
expect(scope.$eval("12/6/2")).toEqual(12/6/2);
|
||||
});
|
||||
|
||||
it('should parse BugStringConfusesparser', function(){
|
||||
var scope = createScope();
|
||||
assertEquals('!', scope.$eval('suffix = "!"'));
|
||||
it('should evaluate exclamation mark', function() {
|
||||
expect(scope.$eval('suffix = "!"')).toEqual('!');
|
||||
});
|
||||
|
||||
it('should parse ParsingBug', function () {
|
||||
var scope = createScope();
|
||||
assertEquals({a: "-"}, scope.$eval("{a:'-'}"));
|
||||
it('should evaluate minus', function() {
|
||||
expect(scope.$eval("{a:'-'}")).toEqual({a: "-"});
|
||||
});
|
||||
|
||||
it('should parse Undefined', function () {
|
||||
var scope = createScope();
|
||||
assertEquals(undefined, scope.$eval("undefined"));
|
||||
assertEquals(undefined, scope.$eval("a=undefined"));
|
||||
assertEquals(undefined, scope.$get("a"));
|
||||
it('should evaluate undefined', function() {
|
||||
expect(scope.$eval("undefined")).not.toBeDefined();
|
||||
expect(scope.$eval("a=undefined")).not.toBeDefined();
|
||||
expect(scope.$get("a")).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should allow assignment after array dereference', function(){
|
||||
scope = angular.scope();
|
||||
scope.obj = [{}];
|
||||
scope.$eval('obj[0].name=1');
|
||||
expect(scope.obj.name).toBeUndefined();
|
||||
expect(scope.obj[0].name).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -169,6 +169,7 @@ describe("resource", function() {
|
||||
var person = Person.get({id:123});
|
||||
$browser.xhr.flush();
|
||||
expect(person.name).toEqual('misko');
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
it('should return the same object when verifying the cache', function(){
|
||||
@@ -183,11 +184,14 @@ describe("resource", function() {
|
||||
|
||||
$browser.xhr.expectGET('/Person/123').respond('[\n{\nname:\n"rob"\n}\n]');
|
||||
var person2 = Person.query({id:123});
|
||||
$browser.defer.flush();
|
||||
|
||||
expect(person2[0].name).toEqual('misko');
|
||||
var person2Cache = person2;
|
||||
$browser.xhr.flush();
|
||||
expect(person2Cache).toEqual(person2);
|
||||
expect(person2[0].name).toEqual('rob');
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
describe('failure mode', function(){
|
||||
|
||||
+58
-46
@@ -1,52 +1,64 @@
|
||||
describe("ScenarioSpec: Compilation", function(){
|
||||
it("should compile dom node and return scope", function(){
|
||||
var node = jqLite('<div ng:init="a=1">{{b=a+1}}</div>')[0];
|
||||
var scope = compile(node);
|
||||
scope.$init();
|
||||
expect(scope.a).toEqual(1);
|
||||
expect(scope.b).toEqual(2);
|
||||
var scope;
|
||||
|
||||
beforeEach(function(){
|
||||
scope = null;
|
||||
});
|
||||
|
||||
it("should compile jQuery node and return scope", function(){
|
||||
var scope = compile(jqLite('<div>{{a=123}}</div>')).$init();
|
||||
expect(jqLite(scope.$element).text()).toEqual('123');
|
||||
|
||||
afterEach(function(){
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
it("should compile text node and return scope", function(){
|
||||
var scope = compile('<div>{{a=123}}</div>').$init();
|
||||
expect(jqLite(scope.$element).text()).toEqual('123');
|
||||
|
||||
describe('compilation', function(){
|
||||
it("should compile dom node and return scope", function(){
|
||||
var node = jqLite('<div ng:init="a=1">{{b=a+1}}</div>')[0];
|
||||
scope = compile(node);
|
||||
scope.$init();
|
||||
expect(scope.a).toEqual(1);
|
||||
expect(scope.b).toEqual(2);
|
||||
});
|
||||
|
||||
it("should compile jQuery node and return scope", function(){
|
||||
scope = compile(jqLite('<div>{{a=123}}</div>')).$init();
|
||||
expect(jqLite(scope.$element).text()).toEqual('123');
|
||||
});
|
||||
|
||||
it("should compile text node and return scope", function(){
|
||||
scope = compile('<div>{{a=123}}</div>').$init();
|
||||
expect(jqLite(scope.$element).text()).toEqual('123');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ScenarioSpec: Scope", function(){
|
||||
it("should have set, get, eval, $init, updateView methods", function(){
|
||||
var scope = compile('<div>{{a}}</div>').$init();
|
||||
scope.$eval("$invalidWidgets.push({})");
|
||||
expect(scope.$set("a", 2)).toEqual(2);
|
||||
expect(scope.$get("a")).toEqual(2);
|
||||
expect(scope.$eval("a=3")).toEqual(3);
|
||||
scope.$eval();
|
||||
expect(jqLite(scope.$element).text()).toEqual('3');
|
||||
|
||||
describe('scope', function(){
|
||||
it("should have set, get, eval, $init, updateView methods", function(){
|
||||
scope = compile('<div>{{a}}</div>').$init();
|
||||
scope.$eval("$invalidWidgets.push({})");
|
||||
expect(scope.$set("a", 2)).toEqual(2);
|
||||
expect(scope.$get("a")).toEqual(2);
|
||||
expect(scope.$eval("a=3")).toEqual(3);
|
||||
scope.$eval();
|
||||
expect(jqLite(scope.$element).text()).toEqual('3');
|
||||
});
|
||||
|
||||
it("should have $ objects", function(){
|
||||
scope = compile('<div></div>', {$config: {a:"b"}});
|
||||
expect(scope.$get('$location')).toBeDefined();
|
||||
expect(scope.$get('$eval')).toBeDefined();
|
||||
expect(scope.$get('$config')).toBeDefined();
|
||||
expect(scope.$get('$config.a')).toEqual("b");
|
||||
});
|
||||
});
|
||||
|
||||
it("should have $ objects", function(){
|
||||
var scope = compile('<div></div>', {$config: {a:"b"}});
|
||||
expect(scope.$get('$location')).toBeDefined();
|
||||
expect(scope.$get('$eval')).toBeDefined();
|
||||
expect(scope.$get('$config')).toBeDefined();
|
||||
expect(scope.$get('$config.a')).toEqual("b");
|
||||
|
||||
describe("configuration", function(){
|
||||
it("should take location object", function(){
|
||||
var url = "http://server/#?book=moby";
|
||||
scope = compile("<div>{{$location}}</div>");
|
||||
var $location = scope.$location;
|
||||
var $browser = scope.$inject('$browser');
|
||||
expect($location.hashSearch.book).toBeUndefined();
|
||||
$browser.setUrl(url);
|
||||
$browser.poll();
|
||||
expect($location.hashSearch.book).toEqual('moby');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ScenarioSpec: configuration", function(){
|
||||
it("should take location object", function(){
|
||||
var url = "http://server/#?book=moby";
|
||||
var scope = compile("<div>{{$location}}</div>");
|
||||
var $location = scope.$location;
|
||||
var $browser = scope.$inject('$browser');
|
||||
expect($location.hashSearch.book).toBeUndefined();
|
||||
$browser.setUrl(url);
|
||||
$browser.poll();
|
||||
expect($location.hashSearch.book).toEqual('moby');
|
||||
});
|
||||
});
|
||||
});
|
||||
+44
-27
@@ -52,6 +52,11 @@ describe('scope/model', function(){
|
||||
model.$eval('name="works"');
|
||||
expect(model.name).toEqual('works');
|
||||
});
|
||||
|
||||
it('should not bind regexps', function(){
|
||||
model.exp = /abc/;
|
||||
expect(model.$eval('exp')).toEqual(model.exp);
|
||||
});
|
||||
|
||||
it('should do nothing on empty string and not update view', function(){
|
||||
var onEval = jasmine.createSpy('onEval');
|
||||
@@ -96,6 +101,40 @@ describe('scope/model', function(){
|
||||
model.$eval();
|
||||
expect(count).toEqual(1);
|
||||
});
|
||||
|
||||
it('should run listener upon registration by default', function() {
|
||||
var model = createScope();
|
||||
var count = 0,
|
||||
nameNewVal = 'crazy val 1',
|
||||
nameOldVal = 'crazy val 2';
|
||||
|
||||
model.$watch('name', function(newVal, oldVal){
|
||||
count ++;
|
||||
nameNewVal = newVal;
|
||||
nameOldVal = oldVal;
|
||||
});
|
||||
|
||||
expect(count).toBe(1);
|
||||
expect(nameNewVal).not.toBeDefined();
|
||||
expect(nameOldVal).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should not run listener upon registration if flag is passed in', function() {
|
||||
var model = createScope();
|
||||
var count = 0,
|
||||
nameNewVal = 'crazy val 1',
|
||||
nameOldVal = 'crazy val 2';
|
||||
|
||||
model.$watch('name', function(newVal, oldVal){
|
||||
count ++;
|
||||
nameNewVal = newVal;
|
||||
nameOldVal = oldVal;
|
||||
}, undefined, false);
|
||||
|
||||
expect(count).toBe(0);
|
||||
expect(nameNewVal).toBe('crazy val 1');
|
||||
expect(nameOldVal).toBe('crazy val 2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('$bind', function(){
|
||||
@@ -109,17 +148,17 @@ describe('scope/model', function(){
|
||||
describe('$tryEval', function(){
|
||||
it('should report error on element', function(){
|
||||
var scope = createScope();
|
||||
scope.$tryEval('throw "myerror";', function(error){
|
||||
scope.$tryEval(function(){throw "myError";}, function(error){
|
||||
scope.error = error;
|
||||
});
|
||||
expect(scope.error).toEqual('myerror');
|
||||
expect(scope.error).toEqual('myError');
|
||||
});
|
||||
|
||||
it('should report error on visible element', function(){
|
||||
var element = jqLite('<div></div>');
|
||||
var scope = createScope();
|
||||
scope.$tryEval('throw "myError"', element);
|
||||
expect(element.attr('ng-exception')).toEqual('"myError"'); // errors are jsonified
|
||||
scope.$tryEval(function(){throw "myError";}, element);
|
||||
expect(element.attr('ng-exception')).toEqual('myError');
|
||||
expect(element.hasClass('ng-exception')).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -129,7 +168,7 @@ describe('scope/model', function(){
|
||||
scope.$exceptionHandler = function(e){
|
||||
this.error = e;
|
||||
};
|
||||
scope.$tryEval('throw "myError"');
|
||||
scope.$tryEval(function(){throw "myError";});
|
||||
expect(scope.error).toEqual("myError");
|
||||
});
|
||||
});
|
||||
@@ -175,28 +214,6 @@ describe('scope/model', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('$postEval', function(){
|
||||
it('should eval function once and last', function(){
|
||||
var log = '';
|
||||
var scope = createScope();
|
||||
function onceOnly(){log+= '@';}
|
||||
scope.$onEval(function(){log+= '.';});
|
||||
scope.$postEval(function(){log+= '!';});
|
||||
scope.$postEval(onceOnly);
|
||||
scope.$postEval(onceOnly);
|
||||
scope.$postEval(); // ignore
|
||||
scope.$eval();
|
||||
expect(log).toEqual('.!@');
|
||||
scope.$eval();
|
||||
expect(log).toEqual('.!@.');
|
||||
|
||||
scope.$postEval(onceOnly);
|
||||
scope.$postEval(onceOnly);
|
||||
scope.$eval();
|
||||
expect(log).toEqual('.!@..@');
|
||||
});
|
||||
});
|
||||
|
||||
describe('$new', function(){
|
||||
it('should $new should create new child scope and $become controller', function(){
|
||||
var parent = createScope(null, {exampleService: function(){return 'Example Service';}});
|
||||
|
||||
@@ -69,12 +69,6 @@ ValidatorTest.prototype.testPhone = function() {
|
||||
assertEquals(null, angular.validator.phone("+421 0905 933 297"));
|
||||
};
|
||||
|
||||
ValidatorTest.prototype.testSSN = function() {
|
||||
var error = "SSN needs to be in 999-99-9999 format.";
|
||||
assertEquals(angular.validator.ssn("ab"), error);
|
||||
assertEquals(angular.validator.ssn("123-45-6789"), null);
|
||||
};
|
||||
|
||||
ValidatorTest.prototype.testURL = function() {
|
||||
var error = "URL needs to be in http://server[:port]/path format.";
|
||||
assertEquals(angular.validator.url("ab"), error);
|
||||
@@ -110,12 +104,6 @@ describe('Validator:asynchronous', function(){
|
||||
|
||||
afterEach(function(){
|
||||
if (self.$element) self.$element.remove();
|
||||
var oldCache = jqCache;
|
||||
jqCache = {};
|
||||
if (size(oldCache)) {
|
||||
dump(oldCache);
|
||||
}
|
||||
expect(size(oldCache)).toEqual(0);
|
||||
});
|
||||
|
||||
it('should make a request and show spinner', function(){
|
||||
|
||||
Vendored
+42
-5
@@ -113,6 +113,15 @@ function MockBrowser() {
|
||||
|
||||
self.cookieHash = {};
|
||||
self.lastCookieHash = {};
|
||||
self.deferredFns = [];
|
||||
|
||||
self.defer = function(fn) {
|
||||
self.deferredFns.push(fn);
|
||||
};
|
||||
|
||||
self.defer.flush = function() {
|
||||
while (self.deferredFns.length) self.deferredFns.shift()();
|
||||
};
|
||||
}
|
||||
MockBrowser.prototype = {
|
||||
|
||||
@@ -156,7 +165,6 @@ MockBrowser.prototype = {
|
||||
return this.cookieHash;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
angular.service('$browser', function(){
|
||||
@@ -195,12 +203,17 @@ angular.service('$browser', function(){
|
||||
function TzDate(offset, timestamp) {
|
||||
if (angular.isString(timestamp)) {
|
||||
var tsStr = timestamp;
|
||||
timestamp = angular.String.toDate(timestamp).getTime();
|
||||
|
||||
this.origDate = angular.String.toDate(timestamp);
|
||||
|
||||
timestamp = this.origDate.getTime();
|
||||
if (isNaN(timestamp))
|
||||
throw {
|
||||
name: "Illegal Argument",
|
||||
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
|
||||
};
|
||||
} else {
|
||||
this.origDate = new Date(timestamp);
|
||||
}
|
||||
|
||||
var localOffset = new Date(timestamp).getTimezoneOffset();
|
||||
@@ -243,10 +256,34 @@ function TzDate(offset, timestamp) {
|
||||
return offset * 60;
|
||||
};
|
||||
|
||||
this.getUTCFullYear = function() {
|
||||
return this.origDate.getUTCFullYear();
|
||||
};
|
||||
|
||||
this.getUTCMonth = function() {
|
||||
return this.origDate.getUTCMonth();
|
||||
};
|
||||
|
||||
this.getUTCDate = function() {
|
||||
return this.origDate.getUTCDate();
|
||||
};
|
||||
|
||||
this.getUTCHours = function() {
|
||||
return this.origDate.getUTCHours();
|
||||
};
|
||||
|
||||
this.getUTCMinutes = function() {
|
||||
return this.origDate.getUTCMinutes();
|
||||
};
|
||||
|
||||
this.getUTCSeconds = function() {
|
||||
return this.origDate.getUTCSeconds();
|
||||
};
|
||||
|
||||
|
||||
//hide all methods not implemented in this mock that the Date prototype exposes
|
||||
var unimplementedMethods = ['getDay', 'getMilliseconds', 'getTime', 'getUTCDate', 'getUTCDay',
|
||||
'getUTCFullYear', 'getUTCHours', 'getUTCMilliseconds', 'getUTCMinutes', 'getUTCMonth',
|
||||
'getUTCSeconds', 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
|
||||
var unimplementedMethods = ['getDay', 'getMilliseconds', 'getTime', 'getUTCDay',
|
||||
'getUTCMilliseconds', 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
|
||||
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
|
||||
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
|
||||
'setYear', 'toDateString', 'toJSON', 'toGMTString', 'toLocaleFormat', 'toLocaleString',
|
||||
|
||||
Vendored
+25
-3
@@ -10,8 +10,8 @@ describe('TzDate', function() {
|
||||
});
|
||||
|
||||
it('should take dateString as constructor argument', function() {
|
||||
expect(new TzDate(0, '1970-01-01T00:00:00Z').getTime()).toBe(0);
|
||||
expect(new TzDate(0, '2010-09-03T23:05:08Z').getTime()).toBe(1283555108000);
|
||||
expect(new TzDate(0, '1970-01-01T00:00:00.000Z').getTime()).toBe(0);
|
||||
expect(new TzDate(0, '2010-09-03T23:05:08.023Z').getTime()).toBe(1283555108023);
|
||||
});
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('TzDate', function() {
|
||||
|
||||
|
||||
it('should create a date representing new year in Bratislava', function() {
|
||||
var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
|
||||
var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00.000Z');
|
||||
expect(newYearInBratislava.getTimezoneOffset()).toBe(-60);
|
||||
expect(newYearInBratislava.getFullYear()).toBe(2010);
|
||||
expect(newYearInBratislava.getMonth()).toBe(0);
|
||||
@@ -96,4 +96,26 @@ describe('TzDate', function() {
|
||||
expect(newYearInBratislava.getHours()).toBe(0);
|
||||
expect(newYearInBratislava.getMinutes()).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('should delegate all the UTC methods to the original UTC Date object', function() {
|
||||
//from when created from string
|
||||
var date1 = new TzDate(-1, '2009-12-31T23:00:00.000Z');
|
||||
expect(date1.getUTCFullYear()).toBe(2009);
|
||||
expect(date1.getUTCMonth()).toBe(11);
|
||||
expect(date1.getUTCDate()).toBe(31);
|
||||
expect(date1.getUTCHours()).toBe(23);
|
||||
expect(date1.getUTCMinutes()).toBe(0);
|
||||
expect(date1.getUTCSeconds()).toBe(0);
|
||||
|
||||
|
||||
//from when created from millis
|
||||
var date2 = new TzDate(-1, angular.String.toDate('2009-12-31T23:00:00.000Z').getTime());
|
||||
expect(date2.getUTCFullYear()).toBe(2009);
|
||||
expect(date2.getUTCMonth()).toBe(11);
|
||||
expect(date2.getUTCDate()).toBe(31);
|
||||
expect(date2.getUTCHours()).toBe(23);
|
||||
expect(date2.getUTCMinutes()).toBe(0);
|
||||
expect(date2.getUTCSeconds()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
+4
-45
@@ -1,4 +1,4 @@
|
||||
describe("directives", function(){
|
||||
describe("directive", function(){
|
||||
|
||||
var compile, model, element;
|
||||
|
||||
@@ -13,8 +13,7 @@ describe("directives", function(){
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
if (model && model.$element) model.$element.remove();
|
||||
expect(size(jqCache)).toEqual(0);
|
||||
dealoc(model);
|
||||
});
|
||||
|
||||
it("should ng:init", function() {
|
||||
@@ -128,56 +127,16 @@ describe("directives", function(){
|
||||
expect(input.checked).toEqual(true);
|
||||
});
|
||||
|
||||
it('should ng:non-bindable', function(){
|
||||
var scope = compile('<div ng:non-bindable><span ng:bind="name"></span></div>');
|
||||
scope.$set('name', 'misko');
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('');
|
||||
});
|
||||
|
||||
it('should ng:repeat over array', function(){
|
||||
var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>');
|
||||
|
||||
Array.prototype.extraProperty = "should be ignored";
|
||||
scope.items = ['misko', 'shyam'];
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('misko;shyam;');
|
||||
delete Array.prototype.extraProperty;
|
||||
|
||||
scope.items = ['adam', 'kai', 'brad'];
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('adam;kai;brad;');
|
||||
|
||||
scope.items = ['brad'];
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('brad;');
|
||||
});
|
||||
|
||||
it('should ng:repeat over object', function(){
|
||||
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
|
||||
scope.$set('items', {misko:'swe', shyam:'set'});
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('misko:swe;shyam:set;');
|
||||
});
|
||||
|
||||
it('should error on wrong parsing of ng:repeat', function(){
|
||||
var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>');
|
||||
var log = "";
|
||||
log += element.attr('ng-exception') + ';';
|
||||
log += element.hasClass('ng-exception') + ';';
|
||||
expect(log).toEqual("\"Expected ng:repeat in form of 'item in collection' but got 'i dont parse'.\";true;");
|
||||
});
|
||||
|
||||
it('should ng:watch', function(){
|
||||
var scope = compile('<div ng:watch="i: count = count + 1" ng:init="count = 0">');
|
||||
scope.$eval();
|
||||
scope.$eval();
|
||||
expect(scope.$get('count')).toEqual(0);
|
||||
expect(scope.$get('count')).toEqual(1);
|
||||
|
||||
scope.$set('i', 0);
|
||||
scope.$eval();
|
||||
scope.$eval();
|
||||
expect(scope.$get('count')).toEqual(1);
|
||||
expect(scope.$get('count')).toEqual(2);
|
||||
});
|
||||
|
||||
describe('ng:click', function(){
|
||||
|
||||
+87
-89
@@ -14,8 +14,7 @@ describe("markups", function(){
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
if (element) element.remove();
|
||||
expect(size(jqCache)).toEqual(0);
|
||||
dealoc(element);
|
||||
});
|
||||
|
||||
it('should translate {{}} in text', function(){
|
||||
@@ -63,92 +62,91 @@ describe("markups", function(){
|
||||
compile('<a ng:href="{{url}}" rel="{{rel}}"></a>');
|
||||
expect(sortedHtml(element)).toEqual('<a ng:bind-attr="{"href":"{{url}}","rel":"{{rel}}"}"></a>');
|
||||
});
|
||||
|
||||
it('should Parse Text With No Bindings', function(){
|
||||
var parts = parseBindings("a");
|
||||
assertEquals(parts.length, 1);
|
||||
assertEquals(parts[0], "a");
|
||||
assertTrue(!binding(parts[0]));
|
||||
});
|
||||
|
||||
it('should Parse Empty Text', function(){
|
||||
var parts = parseBindings("");
|
||||
assertEquals(parts.length, 1);
|
||||
assertEquals(parts[0], "");
|
||||
assertTrue(!binding(parts[0]));
|
||||
});
|
||||
|
||||
it('should Parse Inner Binding', function(){
|
||||
var parts = parseBindings("a{{b}}C");
|
||||
assertEquals(parts.length, 3);
|
||||
assertEquals(parts[0], "a");
|
||||
assertTrue(!binding(parts[0]));
|
||||
assertEquals(parts[1], "{{b}}");
|
||||
assertEquals(binding(parts[1]), "b");
|
||||
assertEquals(parts[2], "C");
|
||||
assertTrue(!binding(parts[2]));
|
||||
});
|
||||
|
||||
it('should Parse Ending Binding', function(){
|
||||
var parts = parseBindings("a{{b}}");
|
||||
assertEquals(parts.length, 2);
|
||||
assertEquals(parts[0], "a");
|
||||
assertTrue(!binding(parts[0]));
|
||||
assertEquals(parts[1], "{{b}}");
|
||||
assertEquals(binding(parts[1]), "b");
|
||||
});
|
||||
|
||||
it('should Parse Begging Binding', function(){
|
||||
var parts = parseBindings("{{b}}c");
|
||||
assertEquals(parts.length, 2);
|
||||
assertEquals(parts[0], "{{b}}");
|
||||
assertEquals(binding(parts[0]), "b");
|
||||
assertEquals(parts[1], "c");
|
||||
assertTrue(!binding(parts[1]));
|
||||
});
|
||||
|
||||
it('should Parse Loan Binding', function(){
|
||||
var parts = parseBindings("{{b}}");
|
||||
assertEquals(parts.length, 1);
|
||||
assertEquals(parts[0], "{{b}}");
|
||||
assertEquals(binding(parts[0]), "b");
|
||||
});
|
||||
|
||||
it('should Parse Two Bindings', function(){
|
||||
var parts = parseBindings("{{b}}{{c}}");
|
||||
assertEquals(parts.length, 2);
|
||||
assertEquals(parts[0], "{{b}}");
|
||||
assertEquals(binding(parts[0]), "b");
|
||||
assertEquals(parts[1], "{{c}}");
|
||||
assertEquals(binding(parts[1]), "c");
|
||||
});
|
||||
|
||||
it('should Parse Two Bindings With Text In Middle', function(){
|
||||
var parts = parseBindings("{{b}}x{{c}}");
|
||||
assertEquals(parts.length, 3);
|
||||
assertEquals(parts[0], "{{b}}");
|
||||
assertEquals(binding(parts[0]), "b");
|
||||
assertEquals(parts[1], "x");
|
||||
assertTrue(!binding(parts[1]));
|
||||
assertEquals(parts[2], "{{c}}");
|
||||
assertEquals(binding(parts[2]), "c");
|
||||
});
|
||||
|
||||
it('should Parse Multiline', function(){
|
||||
var parts = parseBindings('"X\nY{{A\nB}}C\nD"');
|
||||
assertTrue(!!binding('{{A\nB}}'));
|
||||
assertEquals(parts.length, 3);
|
||||
assertEquals(parts[0], '"X\nY');
|
||||
assertEquals(parts[1], '{{A\nB}}');
|
||||
assertEquals(parts[2], 'C\nD"');
|
||||
});
|
||||
|
||||
it('should Has Binding', function(){
|
||||
assertTrue(hasBindings(parseBindings("{{a}}")));
|
||||
assertTrue(!hasBindings(parseBindings("a")));
|
||||
assertTrue(hasBindings(parseBindings("{{b}}x{{c}}")));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
var BindingMarkupTest = TestCase("BindingMarkupTest");
|
||||
|
||||
BindingMarkupTest.prototype.testParseTextWithNoBindings = function(){
|
||||
var parts = parseBindings("a");
|
||||
assertEquals(parts.length, 1);
|
||||
assertEquals(parts[0], "a");
|
||||
assertTrue(!binding(parts[0]));
|
||||
};
|
||||
|
||||
BindingMarkupTest.prototype.testParseEmptyText = function(){
|
||||
var parts = parseBindings("");
|
||||
assertEquals(parts.length, 1);
|
||||
assertEquals(parts[0], "");
|
||||
assertTrue(!binding(parts[0]));
|
||||
};
|
||||
|
||||
BindingMarkupTest.prototype.testParseInnerBinding = function(){
|
||||
var parts = parseBindings("a{{b}}c");
|
||||
assertEquals(parts.length, 3);
|
||||
assertEquals(parts[0], "a");
|
||||
assertTrue(!binding(parts[0]));
|
||||
assertEquals(parts[1], "{{b}}");
|
||||
assertEquals(binding(parts[1]), "b");
|
||||
assertEquals(parts[2], "c");
|
||||
assertTrue(!binding(parts[2]));
|
||||
};
|
||||
|
||||
BindingMarkupTest.prototype.testParseEndingBinding = function(){
|
||||
var parts = parseBindings("a{{b}}");
|
||||
assertEquals(parts.length, 2);
|
||||
assertEquals(parts[0], "a");
|
||||
assertTrue(!binding(parts[0]));
|
||||
assertEquals(parts[1], "{{b}}");
|
||||
assertEquals(binding(parts[1]), "b");
|
||||
};
|
||||
|
||||
BindingMarkupTest.prototype.testParseBeggingBinding = function(){
|
||||
var parts = parseBindings("{{b}}c");
|
||||
assertEquals(parts.length, 2);
|
||||
assertEquals(parts[0], "{{b}}");
|
||||
assertEquals(binding(parts[0]), "b");
|
||||
assertEquals(parts[1], "c");
|
||||
assertTrue(!binding(parts[1]));
|
||||
};
|
||||
|
||||
BindingMarkupTest.prototype.testParseLoanBinding = function(){
|
||||
var parts = parseBindings("{{b}}");
|
||||
assertEquals(parts.length, 1);
|
||||
assertEquals(parts[0], "{{b}}");
|
||||
assertEquals(binding(parts[0]), "b");
|
||||
};
|
||||
|
||||
BindingMarkupTest.prototype.testParseTwoBindings = function(){
|
||||
var parts = parseBindings("{{b}}{{c}}");
|
||||
assertEquals(parts.length, 2);
|
||||
assertEquals(parts[0], "{{b}}");
|
||||
assertEquals(binding(parts[0]), "b");
|
||||
assertEquals(parts[1], "{{c}}");
|
||||
assertEquals(binding(parts[1]), "c");
|
||||
};
|
||||
|
||||
BindingMarkupTest.prototype.testParseTwoBindingsWithTextInMiddle = function(){
|
||||
var parts = parseBindings("{{b}}x{{c}}");
|
||||
assertEquals(parts.length, 3);
|
||||
assertEquals(parts[0], "{{b}}");
|
||||
assertEquals(binding(parts[0]), "b");
|
||||
assertEquals(parts[1], "x");
|
||||
assertTrue(!binding(parts[1]));
|
||||
assertEquals(parts[2], "{{c}}");
|
||||
assertEquals(binding(parts[2]), "c");
|
||||
};
|
||||
|
||||
BindingMarkupTest.prototype.testParseMultiline = function(){
|
||||
var parts = parseBindings('"X\nY{{A\nB}}C\nD"');
|
||||
assertTrue(!!binding('{{A\nB}}'));
|
||||
assertEquals(parts.length, 3);
|
||||
assertEquals(parts[0], '"X\nY');
|
||||
assertEquals(parts[1], '{{A\nB}}');
|
||||
assertEquals(parts[2], 'C\nD"');
|
||||
};
|
||||
|
||||
BindingMarkupTest.prototype.testHasBinding = function(){
|
||||
assertTrue(hasBindings(parseBindings("{{a}}")));
|
||||
assertTrue(!hasBindings(parseBindings("a")));
|
||||
assertTrue(hasBindings(parseBindings("{{b}}x{{c}}")));
|
||||
};
|
||||
|
||||
+84
-20
@@ -6,7 +6,7 @@ describe('HTML', function(){
|
||||
|
||||
it('should echo html', function(){
|
||||
expectHTML('hello<b class="1\'23" align=\'""\'>world</b>.').
|
||||
toEqual('hello<b class="1\'23" align="""">world</b>.');
|
||||
toEqual('hello<b class="1\'23" align="""">world</b>.');
|
||||
});
|
||||
|
||||
it('should remove script', function(){
|
||||
@@ -33,7 +33,7 @@ describe('HTML', function(){
|
||||
expectHTML('a<SCRIPT>ev<script>evil</sCript>il</scrIpt>c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove unknown tag names', function(){
|
||||
it('should remove unknown names', function(){
|
||||
expectHTML('a<xxx><B>b</B></xxx>c').toEqual('a<b>b</b>c');
|
||||
});
|
||||
|
||||
@@ -49,16 +49,34 @@ describe('HTML', function(){
|
||||
expectHTML('a<my:hr/><my:div>b</my:div>c').toEqual('abc');
|
||||
});
|
||||
|
||||
it('should handle entities', function(){
|
||||
var everything = '<div rel="!@#$%^&*()_+-={}[]:";\'<>?,./`~ ħ">' +
|
||||
'!@#$%^&*()_+-={}[]:";\'<>?,./`~ ħ</div>';
|
||||
expectHTML(everything).toEqual(everything);
|
||||
});
|
||||
|
||||
it('should handle improper html', function(){
|
||||
expectHTML('< div id="</div>" alt=abc href=\'"\' >text< /div>').
|
||||
toEqual('<div id="</div>" alt="abc" href=""">text</div>');
|
||||
expectHTML('< div rel="</div>" alt=abc dir=\'"\' >text< /div>').
|
||||
toEqual('<div rel="</div>" alt="abc" dir=""">text</div>');
|
||||
});
|
||||
|
||||
it('should handle improper html2', function(){
|
||||
expectHTML('< div id="</div>" / >').
|
||||
toEqual('<div id="</div>"/>');
|
||||
expectHTML('< div rel="</div>" / >').
|
||||
toEqual('<div rel="</div>"/>');
|
||||
});
|
||||
|
||||
|
||||
it('should ignore back slash as escape', function(){
|
||||
expectHTML('<img alt="xxx\\" title="><script>....">').
|
||||
toEqual('<img alt="xxx\\" title="><script>...."/>');
|
||||
});
|
||||
|
||||
it('should ignore object attributes', function(){
|
||||
expectHTML('<a constructor="hola">:)</a>').
|
||||
toEqual('<a>:)</a>');
|
||||
expectHTML('<constructor constructor="hola">:)</constructor>').
|
||||
toEqual('');
|
||||
});
|
||||
|
||||
describe('htmlSanitizerWriter', function(){
|
||||
var writer, html;
|
||||
beforeEach(function(){
|
||||
@@ -68,12 +86,12 @@ describe('HTML', function(){
|
||||
|
||||
it('should write basic HTML', function(){
|
||||
writer.chars('before');
|
||||
writer.start('div', {id:'123'}, false);
|
||||
writer.start('div', {rel:'123'}, false);
|
||||
writer.chars('in');
|
||||
writer.end('div');
|
||||
writer.chars('after');
|
||||
|
||||
expect(html).toEqual('before<div id="123">in</div>after');
|
||||
expect(html).toEqual('before<div rel="123">in</div>after');
|
||||
});
|
||||
|
||||
it('should escape text nodes', function(){
|
||||
@@ -81,19 +99,14 @@ describe('HTML', function(){
|
||||
expect(html).toEqual('a<div>&</div>c');
|
||||
});
|
||||
|
||||
it('should not double escape entities', function(){
|
||||
writer.chars(' ><');
|
||||
expect(html).toEqual(' ><');
|
||||
});
|
||||
|
||||
it('should escape IE script', function(){
|
||||
writer.chars('&{}');
|
||||
expect(html).toEqual('&{}');
|
||||
writer.chars('&<>{}');
|
||||
expect(html).toEqual('&<>{}');
|
||||
});
|
||||
|
||||
it('should escape attributes', function(){
|
||||
writer.start('div', {id:'\"\'<>'});
|
||||
expect(html).toEqual('<div id=""\'<>">');
|
||||
writer.start('div', {rel:'!@#$%^&*()_+-={}[]:";\'<>?,./`~ \n\0\r\u0127'});
|
||||
expect(html).toEqual('<div rel="!@#$%^&*()_+-={}[]:";\'<>?,./`~ � ħ">');
|
||||
});
|
||||
|
||||
it('should ignore missformed elements', function(){
|
||||
@@ -105,12 +118,63 @@ describe('HTML', function(){
|
||||
writer.start('div', {unknown:""});
|
||||
expect(html).toEqual('<div>');
|
||||
});
|
||||
|
||||
describe('explicitly dissallow', function(){
|
||||
it('should not allow attributes', function(){
|
||||
writer.start('div', {id:'a', name:'a', style:'a'});
|
||||
expect(html).toEqual('<div>');
|
||||
});
|
||||
|
||||
it('should not allow tags', function(){
|
||||
function tag(name) {
|
||||
writer.start(name, {});
|
||||
writer.end(name);
|
||||
};
|
||||
tag('frameset');
|
||||
tag('frame');
|
||||
tag('form');
|
||||
tag('param');
|
||||
tag('object');
|
||||
tag('embed');
|
||||
tag('textarea');
|
||||
tag('input');
|
||||
tag('button');
|
||||
tag('option');
|
||||
tag('select');
|
||||
tag('script');
|
||||
tag('style');
|
||||
tag('link');
|
||||
tag('base');
|
||||
tag('basefont');
|
||||
expect(html).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isUri', function(){
|
||||
|
||||
function isUri(value) {
|
||||
return value.match(URI_REGEXP);
|
||||
}
|
||||
|
||||
it('should be URI', function(){
|
||||
expect(isUri('http://abc')).toBeTruthy();
|
||||
expect(isUri('https://abc')).toBeTruthy();
|
||||
expect(isUri('ftp://abc')).toBeTruthy();
|
||||
expect(isUri('mailto:me@example.com')).toBeTruthy();
|
||||
expect(isUri('#anchor')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not be UIR', function(){
|
||||
expect(isUri('')).toBeFalsy();
|
||||
expect(isUri('javascript:alert')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('javascript URL attribute', function(){
|
||||
beforeEach(function(){
|
||||
this.addMatchers({
|
||||
toBeValidUrl: function(){
|
||||
return !isJavaScriptUrl(this.actual);
|
||||
return URI_REGEXP.exec(this.actual);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -118,7 +182,7 @@ describe('HTML', function(){
|
||||
it('should ignore javascript:', function(){
|
||||
expect('JavaScript:abc').not.toBeValidUrl();
|
||||
expect(' \n Java\n Script:abc').not.toBeValidUrl();
|
||||
expect('JavaScript/my.js').toBeValidUrl();
|
||||
expect('http://JavaScript/my.js').toBeValidUrl();
|
||||
});
|
||||
|
||||
it('should ignore dec encoded javascript:', function(){
|
||||
|
||||
@@ -266,6 +266,26 @@ describe("angular.scenario.dsl", function() {
|
||||
expect(doc.find('div').attr('class')).toEqual('bam');
|
||||
});
|
||||
|
||||
it('should get css', function() {
|
||||
doc.append('<div id="test" style="border: 1px solid red"></div>');
|
||||
$root.dsl.element('#test').css('border');
|
||||
expect($root.futureResult).toMatch(/red/);
|
||||
});
|
||||
|
||||
it('should set css', function() {
|
||||
doc.append('<div id="test" style="border: 1px solid red"></div>');
|
||||
$root.dsl.element('#test').css('border', '1px solid green');
|
||||
expect(doc.find('#test').css('border')).toMatch(/green/);
|
||||
});
|
||||
|
||||
it('should add all jQuery key/value methods', function() {
|
||||
var METHODS = ['css', 'attr'];
|
||||
var chain = $root.dsl.element('input');
|
||||
angular.foreach(METHODS, function(name) {
|
||||
expect(angular.isFunction(chain[name])).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get val', function() {
|
||||
doc.append('<input value="bar">');
|
||||
$root.dsl.element('input').val();
|
||||
@@ -325,8 +345,10 @@ describe("angular.scenario.dsl", function() {
|
||||
beforeEach(function() {
|
||||
doc.append(
|
||||
'<ul>' +
|
||||
' <li ng:repeat-index="0"><span ng:bind="name">misko</span><span ng:bind="gender">male</span></li>' +
|
||||
' <li ng:repeat-index="1"><span ng:bind="name">felisa</span><span ng:bind="gender">female</span></li>' +
|
||||
' <li ng:repeat-index="0"><span ng:bind="name" class="ng-binding">misko</span>' +
|
||||
' <span ng:bind="test && gender" class="ng-binding">male</span></li>' +
|
||||
' <li ng:repeat-index="1"><span ng:bind="name" class="ng-binding">felisa</span>' +
|
||||
' <span ng:bind="gender | uppercase" class="ng-binding">female</span></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
chain = $root.dsl.repeater('ul li');
|
||||
@@ -509,7 +531,7 @@ describe("angular.scenario.dsl", function() {
|
||||
|
||||
it('should change value in textarea', function() {
|
||||
doc.append('<textarea name="test.textarea">something</textarea>');
|
||||
var chain = $root.dsl.textarea('test.textarea');
|
||||
var chain = $root.dsl.input('test.textarea');
|
||||
chain.enter('foo');
|
||||
expect(_jQuery('textarea[name="test.textarea"]').val()).toEqual('foo');
|
||||
});
|
||||
|
||||
+155
-7
@@ -17,8 +17,7 @@ describe("service", function(){
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
if (scope && scope.$element)
|
||||
scope.$element.remove();
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
|
||||
@@ -70,6 +69,42 @@ describe("service", function(){
|
||||
scope.$log.info();
|
||||
scope.$log.error();
|
||||
});
|
||||
|
||||
describe('Error', function(){
|
||||
var e, $log, $console, errorArgs;
|
||||
beforeEach(function(){
|
||||
e = new Error('');
|
||||
e.message = undefined;
|
||||
e.sourceURL = undefined;
|
||||
e.line = undefined;
|
||||
e.stack = undefined;
|
||||
|
||||
$console = angular.service('$log')({console:{error:function(){
|
||||
errorArgs = arguments;
|
||||
}}});
|
||||
});
|
||||
|
||||
it('should pass error if does not have trace', function(){
|
||||
$console.error('abc', e);
|
||||
expect(errorArgs).toEqual(['abc', e]);
|
||||
});
|
||||
|
||||
it('should print stack', function(){
|
||||
e.stack = 'stack';
|
||||
$console.error('abc', e);
|
||||
expect(errorArgs).toEqual(['abc', 'stack']);
|
||||
});
|
||||
|
||||
it('should print line', function(){
|
||||
e.message = 'message';
|
||||
e.sourceURL = 'sourceURL';
|
||||
e.line = '123';
|
||||
$console.error('abc', e);
|
||||
expect(errorArgs).toEqual(['abc', 'message\nsourceURL:123']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("$exceptionHandler", function(){
|
||||
@@ -166,7 +201,7 @@ describe("service", function(){
|
||||
});
|
||||
|
||||
it('should update hash before any processing', function(){
|
||||
var scope = compile('<div>');
|
||||
scope = compile('<div>');
|
||||
var log = '';
|
||||
scope.$watch('$location.hash', function(){
|
||||
log += this.$location.hashPath + ';';
|
||||
@@ -223,7 +258,7 @@ describe("service", function(){
|
||||
|
||||
describe("$invalidWidgets", function(){
|
||||
it("should count number of invalid widgets", function(){
|
||||
var scope = compile('<input name="price" ng:required ng:validate="number"></input>');
|
||||
scope = compile('<input name="price" ng:required ng:validate="number"></input>');
|
||||
jqLite(document.body).append(scope.$element);
|
||||
scope.$init();
|
||||
expect(scope.$invalidWidgets.length).toEqual(1);
|
||||
@@ -255,8 +290,8 @@ describe("service", function(){
|
||||
function BookChapter() {
|
||||
this.log = '<init>';
|
||||
}
|
||||
var scope = compile('<div></div>').$init();
|
||||
var $route = scope.$inject('$route');
|
||||
scope = compile('<div></div>').$init();
|
||||
$route = scope.$inject('$route');
|
||||
$route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template:'Chapter.html'});
|
||||
$route.when('/Blank');
|
||||
$route.onChange(function(){
|
||||
@@ -294,6 +329,76 @@ describe("service", function(){
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('$defer', function() {
|
||||
var $defer, $exceptionHandler;
|
||||
|
||||
beforeEach(function(){
|
||||
scope = createScope({}, angularService, {
|
||||
'$exceptionHandler': jasmine.createSpy('$exceptionHandler')
|
||||
});
|
||||
|
||||
$browser = scope.$inject('$browser');
|
||||
$defer = scope.$inject('$defer');
|
||||
$exceptionHandler = scope.$inject('$exceptionHandler');
|
||||
});
|
||||
|
||||
|
||||
it('should delegate functions to $browser.defer', function() {
|
||||
var counter = 0;
|
||||
$defer(function() { counter++; });
|
||||
|
||||
expect(counter).toBe(0);
|
||||
|
||||
$browser.defer.flush();
|
||||
expect(counter).toBe(1);
|
||||
|
||||
$browser.defer.flush(); //does nothing
|
||||
expect(counter).toBe(1);
|
||||
|
||||
expect($exceptionHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should delegate exception to the $exceptionHandler service', function() {
|
||||
$defer(function() {throw "Test Error";});
|
||||
expect($exceptionHandler).not.toHaveBeenCalled();
|
||||
|
||||
$browser.defer.flush();
|
||||
expect($exceptionHandler).toHaveBeenCalledWith("Test Error");
|
||||
});
|
||||
|
||||
|
||||
it('should call eval after each callback is executed', function() {
|
||||
var eval = this.spyOn(scope, '$eval').andCallThrough();
|
||||
|
||||
$defer(function() {});
|
||||
expect(eval).wasNotCalled();
|
||||
|
||||
$browser.defer.flush();
|
||||
expect(eval).wasCalled();
|
||||
|
||||
eval.reset(); //reset the spy;
|
||||
|
||||
$defer(function() {});
|
||||
$defer(function() {});
|
||||
$browser.defer.flush();
|
||||
expect(eval.callCount).toBe(2);
|
||||
});
|
||||
|
||||
|
||||
it('should call eval even if an exception is thrown in callback', function() {
|
||||
var eval = this.spyOn(scope, '$eval').andCallThrough();
|
||||
|
||||
$defer(function() {throw "Test Error"});
|
||||
expect(eval).wasNotCalled();
|
||||
|
||||
$browser.defer.flush();
|
||||
expect(eval).wasCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('$xhr', function(){
|
||||
var log;
|
||||
function callback(code, response) {
|
||||
@@ -391,12 +496,15 @@ describe("service", function(){
|
||||
$browserXhr.expectGET('/url').respond('first');
|
||||
cache('GET', '/url', null, callback);
|
||||
$browserXhr.flush();
|
||||
|
||||
$browserXhr.expectGET('/url').respond('ERROR');
|
||||
cache('GET', '/url', null, callback);
|
||||
$browser.defer.flush();
|
||||
$browserXhr.flush();
|
||||
expect(log).toEqual('"first";"first";');
|
||||
|
||||
cache('GET', '/url', null, callback, false);
|
||||
$browserXhr.flush();
|
||||
$browser.defer.flush();
|
||||
expect(log).toEqual('"first";"first";"first";');
|
||||
});
|
||||
|
||||
@@ -404,9 +512,12 @@ describe("service", function(){
|
||||
$browserXhr.expectGET('/url').respond('first');
|
||||
cache('GET', '/url', null, callback, true);
|
||||
$browserXhr.flush();
|
||||
|
||||
$browserXhr.expectGET('/url').respond('ERROR');
|
||||
cache('GET', '/url', null, callback, true);
|
||||
$browser.defer.flush();
|
||||
expect(log).toEqual('"first";"first";');
|
||||
|
||||
$browserXhr.flush();
|
||||
expect(log).toEqual('"first";"first";"ERROR";');
|
||||
});
|
||||
@@ -414,8 +525,11 @@ describe("service", function(){
|
||||
it('should serve requests from cache', function(){
|
||||
cache.data.url = {value:'123'};
|
||||
cache('GET', 'url', null, callback);
|
||||
$browser.defer.flush();
|
||||
expect(log).toEqual('"123";');
|
||||
|
||||
cache('GET', 'url', null, callback, false);
|
||||
$browser.defer.flush();
|
||||
expect(log).toEqual('"123";"123";');
|
||||
});
|
||||
|
||||
@@ -443,6 +557,40 @@ describe("service", function(){
|
||||
cache('POST', 'abc', {});
|
||||
expect(cache.data.url).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should call callback asynchronously for both cache hit and cache miss', function() {
|
||||
$browserXhr.expectGET('/url').respond('+');
|
||||
cache('GET', '/url', null, callback);
|
||||
expect(log).toEqual(''); //callback hasn't executed
|
||||
|
||||
$browserXhr.flush();
|
||||
expect(log).toEqual('"+";'); //callback has executed
|
||||
|
||||
cache('GET', '/url', null, callback);
|
||||
expect(log).toEqual('"+";'); //callback hasn't executed
|
||||
|
||||
$browser.defer.flush();
|
||||
expect(log).toEqual('"+";"+";'); //callback has executed
|
||||
});
|
||||
|
||||
it('should call eval after callbacks for both cache hit and cache miss execute', function() {
|
||||
var eval = this.spyOn(scope, '$eval').andCallThrough();
|
||||
|
||||
$browserXhr.expectGET('/url').respond('+');
|
||||
cache('GET', '/url', null, callback);
|
||||
expect(eval).wasNotCalled();
|
||||
|
||||
$browserXhr.flush();
|
||||
expect(eval).wasCalled();
|
||||
|
||||
eval.reset(); //reset the spy
|
||||
|
||||
cache('GET', '/url', null, callback);
|
||||
expect(eval).wasNotCalled();
|
||||
|
||||
$browser.defer.flush();
|
||||
expect(eval).wasCalled();
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -13,6 +13,8 @@ if (window.jstestdriver) {
|
||||
}
|
||||
|
||||
beforeEach(function(){
|
||||
// This is to reset parsers global cache of expressions.
|
||||
compileCache = {};
|
||||
this.addMatchers({
|
||||
toBeInvalid: function(){
|
||||
var element = jqLite(this.actual);
|
||||
@@ -50,6 +52,25 @@ beforeEach(function(){
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(clearJqCache);
|
||||
|
||||
function clearJqCache(){
|
||||
var count = 0;
|
||||
foreachSorted(jqCache, function(value, key){
|
||||
count ++;
|
||||
delete jqCache[key];
|
||||
foreach(value, function(value, key){
|
||||
if (value.$element)
|
||||
dump(key, sortedHtml(value.$element));
|
||||
else
|
||||
dump(key, toJson(value));
|
||||
});
|
||||
});
|
||||
if (count) {
|
||||
fail('Found jqCache references that were not deallocated!');
|
||||
}
|
||||
}
|
||||
|
||||
function nakedExpect(obj) {
|
||||
return expect(angular.fromJson(angular.toJson(obj)));
|
||||
}
|
||||
@@ -58,6 +79,11 @@ function childNode(element, index) {
|
||||
return jqLite(element[0].childNodes[index]);
|
||||
}
|
||||
|
||||
function dealoc(obj) {
|
||||
var element = (obj||{}).$element || obj;
|
||||
if (element && element.dealoc) element.dealoc();
|
||||
}
|
||||
|
||||
extend(angular, {
|
||||
'element': jqLite,
|
||||
'compile': compile,
|
||||
|
||||
+168
-7
@@ -11,12 +11,12 @@ describe("widget", function(){
|
||||
(before||noop).apply(scope);
|
||||
if (parent) parent.append(element);
|
||||
scope.$init();
|
||||
return scope;
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
if (element && element.dealoc) element.dealoc();
|
||||
expect(size(jqCache)).toEqual(0);
|
||||
dealoc(element);
|
||||
});
|
||||
|
||||
describe("input", function(){
|
||||
@@ -41,6 +41,13 @@ describe("widget", function(){
|
||||
expect(scope.$get('name')).toEqual('Kai');
|
||||
expect(scope.$get('count')).toEqual(2);
|
||||
});
|
||||
|
||||
it('should allow complex refernce binding', function(){
|
||||
compile('<div ng:init="obj={abc:{}}">'+
|
||||
'<input type="Text" name="obj[\'abc\'].name" value="Misko""/>'+
|
||||
'</div>');
|
||||
expect(scope.obj['abc'].name).toEqual('Misko');
|
||||
});
|
||||
|
||||
describe("ng:format", function(){
|
||||
|
||||
@@ -361,7 +368,7 @@ describe("widget", function(){
|
||||
'<option value="{{$index}}" ng:repeat="name in [\'A\', \'B\', \'C\']">{{name}}</option>' +
|
||||
'</select>');
|
||||
// childNodes[0] is repeater comment
|
||||
expect(scope.selection).toEqual(undefined);
|
||||
expect(scope.selection).toEqual(0);
|
||||
|
||||
browserTrigger(element[0].childNodes[2], 'change');
|
||||
expect(scope.selection).toEqual(1);
|
||||
@@ -397,6 +404,32 @@ describe("widget", function(){
|
||||
scope.$eval();
|
||||
expect(element[0].childNodes[1].selected).toEqual(true);
|
||||
});
|
||||
|
||||
it('should select default option on repeater', function(){
|
||||
compile(
|
||||
'<select name="selection">' +
|
||||
'<option ng:repeat="no in [1,2]">{{no}}</option>' +
|
||||
'</select>');
|
||||
expect(scope.selection).toEqual('1');
|
||||
});
|
||||
|
||||
it('should select selected option on repeater', function(){
|
||||
compile(
|
||||
'<select name="selection">' +
|
||||
'<option ng:repeat="no in [1,2]">{{no}}</option>' +
|
||||
'<option selected>ABC</option>' +
|
||||
'</select>');
|
||||
expect(scope.selection).toEqual('ABC');
|
||||
});
|
||||
|
||||
it('should select dynamically selected option on repeater', function(){
|
||||
compile(
|
||||
'<select name="selection">' +
|
||||
'<option ng:repeat="no in [1,2]" ng:bind-attr="{selected:\'{{no==2}}\'}">{{no}}</option>' +
|
||||
'</select>');
|
||||
expect(scope.selection).toEqual('2');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should support type="select-multiple"', function(){
|
||||
@@ -430,7 +463,11 @@ describe("widget", function(){
|
||||
|
||||
describe('ng:switch', function(){
|
||||
it('should switch on value change', function(){
|
||||
compile('<ng:switch on="select"><div ng:switch-when="1">first:{{name}}</div><div ng:switch-when="2">second:{{name}}</div></ng:switch>');
|
||||
compile('<ng:switch on="select">' +
|
||||
'<div ng:switch-when="1">first:{{name}}</div>' +
|
||||
'<div ng:switch-when="2">second:{{name}}</div>' +
|
||||
'<div ng:switch-when="true">true:{{name}}</div>' +
|
||||
'</ng:switch>');
|
||||
expect(element.html()).toEqual('');
|
||||
scope.select = 1;
|
||||
scope.$eval();
|
||||
@@ -444,13 +481,34 @@ describe("widget", function(){
|
||||
scope.name = 'misko';
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('second:misko');
|
||||
scope.select = true;
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('true:misko');
|
||||
});
|
||||
|
||||
it("should compare stringified versions", function(){
|
||||
var switchWidget = angular.widget('ng:switch');
|
||||
expect(switchWidget.equals(true, 'true')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should switch on switch-when-default', function(){
|
||||
compile('<ng:switch on="select">' +
|
||||
'<div ng:switch-when="1">one</div>' +
|
||||
'<div ng:switch-default>other</div>' +
|
||||
'</ng:switch>');
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('other');
|
||||
scope.select = 1;
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('one');
|
||||
});
|
||||
|
||||
it("should match urls", function(){
|
||||
var scope = angular.compile('<ng:switch on="url" using="route:params"><div ng:switch-when="/Book/:name">{{params.name}}</div></ng:switch>');
|
||||
scope.url = '/Book/Moby';
|
||||
scope.$init();
|
||||
expect(scope.$element.text()).toEqual('Moby');
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
it("should match sandwich ids", function(){
|
||||
@@ -459,13 +517,14 @@ describe("widget", function(){
|
||||
expect(match).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should call init on switch', function(){
|
||||
it('should call change on switch', function(){
|
||||
var scope = angular.compile('<ng:switch on="url" change="name=\'works\'"><div ng:switch-when="a">{{name}}</div></ng:switch>');
|
||||
var cleared = false;
|
||||
scope.url = 'a';
|
||||
scope.$init();
|
||||
expect(scope.name).toEqual(undefined);
|
||||
expect(scope.$element.text()).toEqual('works');
|
||||
dealoc(scope);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -478,7 +537,9 @@ describe("widget", function(){
|
||||
scope.url = 'myUrl';
|
||||
scope.$inject('$xhr.cache').data.myUrl = {value:'{{name}}'};
|
||||
scope.$init();
|
||||
scope.$inject('$browser').defer.flush();
|
||||
expect(element.text()).toEqual('misko');
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
it('should remove previously included text if a falsy value is bound to src', function() {
|
||||
@@ -489,6 +550,7 @@ describe("widget", function(){
|
||||
scope.url = 'myUrl';
|
||||
scope.$inject('$xhr.cache').data.myUrl = {value:'{{name}}'};
|
||||
scope.$init();
|
||||
scope.$inject('$browser').defer.flush();
|
||||
|
||||
expect(element.text()).toEqual('igor');
|
||||
|
||||
@@ -496,6 +558,7 @@ describe("widget", function(){
|
||||
scope.$eval();
|
||||
|
||||
expect(element.text()).toEqual('');
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
it('should allow this for scope', function(){
|
||||
@@ -504,9 +567,27 @@ describe("widget", function(){
|
||||
scope.url = 'myUrl';
|
||||
scope.$inject('$xhr.cache').data.myUrl = {value:'{{c=c+1}}'};
|
||||
scope.$init();
|
||||
// This should not be 4, but to fix this properly
|
||||
// we need to have real events on the scopes.
|
||||
scope.$inject('$browser').defer.flush();
|
||||
|
||||
// this one should really be just '1', but due to lack of real events things are not working
|
||||
// properly. see discussion at: http://is.gd/ighKk
|
||||
expect(element.text()).toEqual('4');
|
||||
dealoc(element);
|
||||
});
|
||||
|
||||
it('should evaluate onload expression when a partial is loaded', function() {
|
||||
var element = jqLite('<ng:include src="url" onload="loaded = true"></ng:include>');
|
||||
var scope = angular.compile(element);
|
||||
|
||||
expect(scope.loaded).not.toBeDefined();
|
||||
|
||||
scope.url = 'myUrl';
|
||||
scope.$inject('$xhr.cache').data.myUrl = {value:'my partial'};
|
||||
scope.$init();
|
||||
scope.$inject('$browser').defer.flush();
|
||||
expect(element.text()).toEqual('my partial');
|
||||
expect(scope.loaded).toBe(true);
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -544,5 +625,85 @@ describe("widget", function(){
|
||||
expect(document.location.href).toEqual(orgLocation);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('@ng:repeat', function() {
|
||||
|
||||
it('should ng:repeat over array', function(){
|
||||
var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>');
|
||||
|
||||
Array.prototype.extraProperty = "should be ignored";
|
||||
scope.items = ['misko', 'shyam'];
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('misko;shyam;');
|
||||
delete Array.prototype.extraProperty;
|
||||
|
||||
scope.items = ['adam', 'kai', 'brad'];
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('adam;kai;brad;');
|
||||
|
||||
scope.items = ['brad'];
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('brad;');
|
||||
});
|
||||
|
||||
it('should ng:repeat over object', function(){
|
||||
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
|
||||
scope.$set('items', {misko:'swe', shyam:'set'});
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('misko:swe;shyam:set;');
|
||||
});
|
||||
|
||||
it('should error on wrong parsing of ng:repeat', function(){
|
||||
var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>');
|
||||
var log = "";
|
||||
log += element.attr('ng-exception') + ';';
|
||||
log += element.hasClass('ng-exception') + ';';
|
||||
expect(log.match(/Expected ng:repeat in form of 'item in collection' but got 'i dont parse'./)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should expose iterator offset as $index when iterating over arrays', function() {
|
||||
var scope = compile('<ul><li ng:repeat="item in items" ' +
|
||||
'ng:bind="item + $index + \'|\'"></li></ul>');
|
||||
scope.items = ['misko', 'shyam', 'frodo'];
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('misko0|shyam1|frodo2|');
|
||||
});
|
||||
|
||||
it('should expose iterator offset as $index when iterating over objects', function() {
|
||||
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
|
||||
'ng:bind="key + \':\' + val + $index + \'|\'"></li></ul>');
|
||||
scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('misko:m0|shyam:s1|frodo:f2|');
|
||||
});
|
||||
|
||||
it('should expose iterator position as $position when iterating over arrays', function() {
|
||||
var scope = compile('<ul><li ng:repeat="item in items" ' +
|
||||
'ng:bind="item + \':\' + $position + \'|\'"></li></ul>');
|
||||
scope.items = ['misko', 'shyam', 'doug', 'frodo'];
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|');
|
||||
});
|
||||
|
||||
it('should expose iterator position as $position when iterating over objects', function() {
|
||||
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
|
||||
'ng:bind="key + \':\' + val + \':\' + $position + \'|\'"></li></ul>');
|
||||
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('misko:m:first|shyam:s:middle|doug:d:middle|frodo:f:last|');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('@ng:non-bindable', function() {
|
||||
|
||||
it('should prevent compilation of the owning element and its children', function(){
|
||||
var scope = compile('<div ng:non-bindable><span ng:bind="name"></span></div>');
|
||||
scope.$set('name', 'misko');
|
||||
scope.$eval();
|
||||
expect(element.text()).toEqual('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# <angular/> build config file
|
||||
---
|
||||
version: 0.9.2
|
||||
codename: faunal-mimicry
|
||||
version: 0.9.7
|
||||
codename: sonic-scream
|
||||
|
||||
+1
-1
@@ -3,6 +3,6 @@
|
||||
# run: watch watchr.rb
|
||||
# note: make sure that you have jstd server running (server.sh) and a browser captured
|
||||
|
||||
watch( '^src/' ) do
|
||||
watch( '^src/|^docs/' ) do
|
||||
%x{ echo "\n\ndoc run started @ `date`" > logs/docs.log; node docs/collect.js &> logs/docs.log & }
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user